3 # Copyright (c) 2020 Orange and others.
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # pylint: disable=too-many-instance-attributes
12 """Define the parent classes of all Xtesting Features.
14 Feature is considered as TestCase offered by Third-party. It offers
15 helpers to run any python method or any bash command.
25 from lxml import etree
29 from xtesting.core import testcase
32 __author__ = ("Vincent Mahe <v.mahe@orange.com>, "
33 "Cedric Ollivier <cedric.ollivier@orange.com>")
36 class MTSLauncher(testcase.TestCase):
37 """Class designed to run MTS tests."""
39 __logger = logging.getLogger(__name__)
40 mts_install_dir = "/opt/mts"
42 def __init__(self, **kwargs):
43 super(MTSLauncher, self).__init__(**kwargs)
44 self.result_file = "{}/{}.log".format(self.res_dir, self.case_name)
45 # Location of the HTML report generated by MTS
46 self.mts_stats_dir = os.path.join(self.res_dir, 'mts_stats_report')
47 # Location of the log files generated by MTS for each test.
48 # Need to end path with a separator because of a bug in MTS.
49 self.mts_logs_dir = os.path.join(self.res_dir,
50 'mts_logs' + os.path.sep)
51 # The location of file named testPlan.csv
52 # that it always in $MTS_HOME/logs
53 self.mts_result_csv_file = self.mts_install_dir + os.path.sep
54 self.mts_result_csv_file += ("logs" + os.path.sep + "testPlan.csv")
62 def parse_results(self):
63 """Parse testPlan.csv containing the status of each testcase of the test file.
64 See sample file in `xtesting/samples/mts/output/testPlan.csv`
66 with open(self.mts_result_csv_file) as stream_:
67 self.__logger.info("Parsing file : %s", self.mts_result_csv_file)
68 reader = csv.reader(stream_, delimiter=';')
71 msg = prettytable.PrettyTable(
72 header_style='upper', padding_width=5,
73 field_names=['MTS test', 'MTS test case',
79 # If there's only one delimiter,
80 # it is the name of the <test> elt
83 _test_dict['parent'] = test_name
85 testcase_name = row[0].lstrip()
86 testcase_status = row[2]
88 if testcase_status == 'OK':
90 elif testcase_status == 'Failed':
92 elif testcase_status == '?':
94 _test_dict['status'] = testcase_status
95 _test_dict['name'] = testcase_name
99 _test_dict['status']])
101 _tests_data.append(_test_dict)
103 self.result = 100 * (
104 self.pass_tests / self.total_tests)
105 except ZeroDivisionError:
106 self.__logger.error("No test has been run")
107 self.__logger.info("MTS Test result:\n\n%s\n", msg.get_string())
109 self.details['description'] = "Execution of some MTS tests"
110 self.details['total_tests'] = self.total_tests
111 self.details['pass_tests'] = self.pass_tests
112 self.details['fail_tests'] = self.fail_tests
113 self.details['skip_tests'] = self.skip_tests
114 self.details['tests'] = _tests_data
116 def parse_xml_test_file(self, xml_test_file):
117 """Parse the XML file containing the test definition for MTS.
118 See sample file in `xtesting/samples/mts/test.xml`
122 "Parsing XML test file %s containing the MTS tests definitions.",
125 parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
126 self.__logger.info("XML test file %s successfully parsed.",
128 root = etree.parse(xml_test_file, parser=parser)
129 # Need to look at all child nodes because there may be
130 # some <for> elt between <test> and <testcase> elt
131 self.testcases = root.xpath('//test//testcase/@name')
132 nb_testcases = len(self.testcases)
133 if nb_testcases == 0:
134 self.__logger.warning("Found no MTS testcase !")
135 elif nb_testcases == 1:
136 self.__logger.info("Found only one MTS testcase: %s",
139 self.__logger.info("Found %d MTS testcases :", nb_testcases)
140 for mts_testcase in self.testcases:
141 self.__logger.info(" - %s", mts_testcase)
142 except etree.XMLSyntaxError as xml_err:
143 self.__logger.error("Error while parsing XML test file: %s",
147 def check_enabled_mts_test_cases(self, enabled_testcases):
148 """Make sure that all required MTS test cases exist
149 in the XML test file.
151 if len(enabled_testcases) > 0:
152 # Verify if the MTS test case exists in the whole list of test
153 # cases declared in the test XML file
154 for enabled_testcase in enabled_testcases:
155 if enabled_testcase not in self.testcases:
157 "The required MTS testcase named `%s` does not exist"
158 " !", enabled_testcase)
162 def execute(self, **kwargs): # pylint: disable=too-many-locals
163 """Execute the cmd passed as arg
166 kwargs: Arbitrary keyword arguments.
173 console = kwargs["console"] if "console" in kwargs else False
174 # Read specific parameters for MTS
175 test_file = kwargs["test_file"]
177 "log_level"] if "log_level" in kwargs else "INFO"
179 # For some MTS tests, we need to force stop after N sec
180 max_duration = kwargs[
181 "max_duration"] if "max_duration" in kwargs else None
182 store_method = kwargs[
183 "store_method"] if "store_method" in kwargs else "FILE"
184 # Must use the $HOME_MTS/bin as current working dir
185 cwd = self.mts_install_dir + os.path.sep + "bin"
187 # Get the list of enabled MTS testcases, if any
188 enabled_testcases = kwargs[
189 "testcases"] if "testcases" in kwargs else []
190 enabled_testcases_str = ''
191 if len(enabled_testcases) > 0:
192 enabled_testcases_str = ' '.join(enabled_testcases)
193 check_ok = self.check_enabled_mts_test_cases(enabled_testcases)
197 # Build command line to launch for MTS
198 cmd = ("cd {} && ./startCmd.sh {} {} -sequential -levelLog:{}"
200 " -config:stats.REPORT_DIRECTORY+{}"
201 " -config:logs.STORAGE_DIRECTORY+{}"
203 " -showRep:false").format(cwd,
205 enabled_testcases_str,
211 # Make sure to create the necessary output sub-folders for MTS
212 if not os.path.isdir(self.mts_stats_dir):
213 os.makedirs(self.mts_stats_dir)
214 if not os.path.isdir(self.mts_logs_dir):
215 os.makedirs(self.mts_logs_dir)
217 "MTS statistics output dir: %s ", self.mts_stats_dir)
218 self.__logger.info("MTS logs output dir: %s ", self.mts_logs_dir)
220 # Launch MTS as a sub-process
221 # and save its standard output to a file
222 with open(self.result_file, 'w') as f_stdout:
223 self.__logger.info("Calling %s", cmd)
224 process = subprocess.Popen(
225 cmd, shell=True, stdout=subprocess.PIPE,
226 stderr=subprocess.STDOUT)
227 for line in iter(process.stdout.readline, b''):
229 sys.stdout.write(line.decode("utf-8"))
230 f_stdout.write(line.decode("utf-8"))
233 process.wait(timeout=max_duration)
234 except subprocess.TimeoutExpired:
237 "Killing MTS process after %d second(s).",
242 with open(self.result_file, 'r') as f_stdin:
243 self.__logger.debug("$ %s\n%s", cmd, f_stdin.read().rstrip())
244 return process.returncode
246 self.__logger.error("Missing mandatory arg for MTS. kwargs: %s",
250 def run(self, **kwargs):
253 It allows executing any Python method by calling execute().
255 It sets the following attributes required to push the results
262 It doesn't fulfill details when pushing the results to the DB.
265 kwargs: Arbitrary keyword arguments.
268 TestCase.EX_OK if execute() returns 0,
269 TestCase.EX_RUN_ERROR otherwise.
271 self.start_time = time.time()
272 exit_code = testcase.TestCase.EX_RUN_ERROR
275 nb_testcases = self.parse_xml_test_file(kwargs["test_file"])
276 # Do something only if there are some MTS test cases in the test
279 if self.execute(**kwargs) == 0:
280 exit_code = testcase.TestCase.EX_OK
283 except Exception: # pylint: disable=broad-except
284 self.__logger.exception(
285 "Cannot parse result file "
286 "$MTS_HOME/logs/testPlan.csv")
287 exit_code = testcase.TestCase.EX_RUN_ERROR
288 except Exception: # pylint: disable=broad-except
289 self.__logger.exception("%s FAILED", self.project_name)
290 self.stop_time = time.time()