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
28 from xtesting.core import testcase
31 __author__ = ("Vincent Mahe <v.mahe@orange.com>, "
32 "Cedric Ollivier <cedric.ollivier@orange.com>")
35 class MTSLauncher(testcase.TestCase):
36 """Class designed to run MTS tests."""
38 __logger = logging.getLogger(__name__)
39 mts_install_dir = "/opt/mts"
41 def __init__(self, **kwargs):
42 super(MTSLauncher, self).__init__(**kwargs)
43 self.result_file = "{}/{}.log".format(self.res_dir, self.case_name)
44 # Location of the HTML report generated by MTS
45 self.mts_stats_dir = os.path.join(self.res_dir, 'mts_stats_report')
46 # Location of the log files generated by MTS for each test.
47 # Need to end path with a separator because of a bug in MTS.
48 self.mts_logs_dir = os.path.join(self.res_dir,
49 'mts_logs' + os.path.sep)
50 # The location of file named testPlan.csv
51 # that it always in $MTS_HOME/logs
52 self.mts_result_csv_file = self.mts_install_dir + os.path.sep
53 self.mts_result_csv_file += ("logs" + os.path.sep + "testPlan.csv")
61 def parse_results(self):
62 """Parse testPlan.csv containing the status of each testcase of the test file.
63 See sample file in `xtesting/samples/mts/output/testPlan.csv`
65 with open(self.mts_result_csv_file) as stream_:
66 self.__logger.info("Parsing file : %s", self.mts_result_csv_file)
67 reader = csv.reader(stream_, delimiter=';')
70 msg = prettytable.PrettyTable(
71 header_style='upper', padding_width=5,
72 field_names=['MTS test', 'MTS test case',
78 # If there's only one delimiter,
79 # it is the name of the <test> elt
82 _test_dict['parent'] = test_name
84 testcase_name = row[0].lstrip()
85 testcase_status = row[2]
87 if testcase_status == 'OK':
89 elif testcase_status == 'Failed':
91 elif testcase_status == '?':
93 _test_dict['status'] = testcase_status
94 _test_dict['name'] = testcase_name
98 _test_dict['status']])
100 _tests_data.append(_test_dict)
102 self.result = 100 * (
103 self.pass_tests / self.total_tests)
104 except ZeroDivisionError:
105 self.__logger.error("No test has been run")
106 self.__logger.info("MTS Test result:\n\n%s\n", msg.get_string())
108 self.details['description'] = "Execution of some MTS tests"
109 self.details['total_tests'] = self.total_tests
110 self.details['pass_tests'] = self.pass_tests
111 self.details['fail_tests'] = self.fail_tests
112 self.details['skip_tests'] = self.skip_tests
113 self.details['tests'] = _tests_data
115 def parse_xml_test_file(self, xml_test_file):
116 """Parse the XML file containing the test definition for MTS.
117 See sample file in `xtesting/samples/mts/test.xml`
121 "Parsing XML test file %s containing the MTS tests definitions.",
124 parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
125 self.__logger.info("XML test file %s successfully parsed.",
127 root = etree.parse(xml_test_file, parser=parser)
128 # Need to look at all child nodes because there may be
129 # some <for> elt between <test> and <testcase> elt
130 self.testcases = root.xpath('//test//testcase/@name')
131 nb_testcases = len(self.testcases)
132 if nb_testcases == 0:
133 self.__logger.warning("Found no MTS testcase !")
134 elif nb_testcases == 1:
135 self.__logger.info("Found only one MTS testcase: %s",
138 self.__logger.info("Found %d MTS testcases :", nb_testcases)
139 for mts_testcase in self.testcases:
140 self.__logger.info(" - %s", mts_testcase)
141 except etree.XMLSyntaxError as xml_err:
142 self.__logger.error("Error while parsing XML test file: %s",
146 def check_enabled_mts_test_cases(self, enabled_testcases):
147 """Make sure that all required MTS test cases exist
148 in the XML test file.
150 if len(enabled_testcases) > 0:
151 # Verify if the MTS test case exists in the whole list of test
152 # cases declared in the test XML file
153 for enabled_testcase in enabled_testcases:
154 if enabled_testcase not in self.testcases:
156 "The required MTS testcase named `%s` does not exist"
157 " !", enabled_testcase)
161 def execute(self, **kwargs): # pylint: disable=too-many-locals
162 """Execute the cmd passed as arg
165 kwargs: Arbitrary keyword arguments.
172 console = kwargs["console"] if "console" in kwargs else False
173 # Read specific parameters for MTS
174 test_file = kwargs["test_file"]
176 "log_level"] if "log_level" in kwargs else "INFO"
178 # For some MTS tests, we need to force stop after N sec
179 max_duration = kwargs[
180 "max_duration"] if "max_duration" in kwargs else None
181 store_method = kwargs[
182 "store_method"] if "store_method" in kwargs else "FILE"
183 # Must use the $HOME_MTS/bin as current working dir
184 cwd = self.mts_install_dir + os.path.sep + "bin"
186 # Get the list of enabled MTS testcases, if any
187 enabled_testcases = kwargs[
188 "testcases"] if "testcases" in kwargs else []
189 enabled_testcases_str = ''
190 if len(enabled_testcases) > 0:
191 enabled_testcases_str = ' '.join(enabled_testcases)
192 check_ok = self.check_enabled_mts_test_cases(enabled_testcases)
196 # Build command line to launch for MTS
197 cmd = ("cd {} && ./startCmd.sh {} {} -sequential -levelLog:{}"
199 " -config:stats.REPORT_DIRECTORY+{}"
200 " -config:logs.STORAGE_DIRECTORY+{}"
202 " -showRep:false").format(cwd,
204 enabled_testcases_str,
210 # Make sure to create the necessary output sub-folders for MTS
211 if not os.path.isdir(self.mts_stats_dir):
212 os.makedirs(self.mts_stats_dir)
213 if not os.path.isdir(self.mts_logs_dir):
214 os.makedirs(self.mts_logs_dir)
216 "MTS statistics output dir: %s ", self.mts_stats_dir)
217 self.__logger.info("MTS logs output dir: %s ", self.mts_logs_dir)
219 # Launch MTS as a sub-process
220 # and save its standard output to a file
221 with open(self.result_file, 'w') as f_stdout:
222 self.__logger.info("Calling %s", cmd)
223 process = subprocess.Popen(
224 cmd, shell=True, stdout=subprocess.PIPE,
225 stderr=subprocess.STDOUT)
226 for line in iter(process.stdout.readline, b''):
228 sys.stdout.write(line.decode("utf-8"))
229 f_stdout.write(line.decode("utf-8"))
231 process.wait(timeout=max_duration)
232 except subprocess.TimeoutExpired:
235 "Killing MTS process after %d second(s).",
238 with open(self.result_file, 'r') as f_stdin:
239 self.__logger.debug("$ %s\n%s", cmd, f_stdin.read().rstrip())
240 return process.returncode
242 self.__logger.error("Missing mandatory arg for MTS. kwargs: %s",
246 def run(self, **kwargs):
249 It allows executing any Python method by calling execute().
251 It sets the following attributes required to push the results
258 It doesn't fulfill details when pushing the results to the DB.
261 kwargs: Arbitrary keyword arguments.
264 TestCase.EX_OK if execute() returns 0,
265 TestCase.EX_RUN_ERROR otherwise.
267 self.start_time = time.time()
268 exit_code = testcase.TestCase.EX_RUN_ERROR
271 nb_testcases = self.parse_xml_test_file(kwargs["test_file"])
272 # Do something only if there are some MTS test cases in the test
275 if self.execute(**kwargs) == 0:
276 exit_code = testcase.TestCase.EX_OK
279 except Exception: # pylint: disable=broad-except
280 self.__logger.exception(
281 "Cannot parse result file "
282 "$MTS_HOME/logs/testPlan.csv")
283 exit_code = testcase.TestCase.EX_RUN_ERROR
284 except Exception: # pylint: disable=broad-except
285 self.__logger.exception("%s FAILED", self.project_name)
286 self.stop_time = time.time()