3 # Copyright (C) 2010-2012 CEA/DEN
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License.
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 from hashlib import sha1
31 from sha import sha as sha1
34 import src.ElementTree as etree
35 from src.xmlManager import add_simple_node
37 # Define all possible option for the test command : sat test <options>
38 parser = src.options.Options()
39 parser.add_option('b', 'base', 'string', 'base',
40 _("Optional: Indicate the name of the test base to use.\n\tThis name has to"
41 " be registered in your application and in a project.\n\tA path to a "
42 "test base can also be used."))
43 parser.add_option('l', 'launcher', 'string', 'launcher',
44 _("Optional: Use this option to specify the path to a SALOME launcher to "
45 "use to launch the test scripts of the test base."))
46 parser.add_option('g', 'grid', 'list', 'grids',
47 _('Optional: Indicate which grid(s) to test (subdirectory of the test '
49 parser.add_option('s', 'session', 'list2', 'sessions',
50 _('Optional: indicate which session(s) to test (subdirectory of the '
52 parser.add_option('', 'display', 'string', 'display',
53 _("Optional: set the display where to launch SALOME.\n"
54 "\tIf value is NO then option --show-desktop=0 will be used to launch SALOME."))
57 '''method that is called when salomeTools is called with --help option.
59 :return: The text to display for the test command description.
62 return _("The test command runs a test base on a SALOME installation.\n\n"
63 "example:\nsat test SALOME-master --grid GEOM --session light")
65 def parse_option(args, config):
66 """ Parse the options and do some verifications about it
68 :param args List: The list of arguments of the command
69 :param config Config: The global configuration
70 :return: the options of the current command launch and the full arguments
71 :rtype: Tuple (options, args)
73 (options, args) = parser.parse_args(args)
75 if not options.launcher:
77 elif not os.path.isabs(options.launcher):
78 if not src.config_has_application(config):
79 raise src.SatException(_("An application is required to use a "
80 "relative path with option --appli"))
81 options.launcher = os.path.join(config.APPLICATION.workdir,
84 if not os.path.exists(options.launcher):
85 raise src.SatException(_("Launcher not found: %s") %
88 return (options, args)
93 path = raw_input("enter a path where to save the result: ")
95 result = raw_input("the result will be not save. Are you sure to "
102 elif os.path.exists(path):
103 result = raw_input("Warning, the content of %s will be deleted. Are you"
104 " sure to continue ? [y/n] " % path)
112 def save_file(filename, base):
113 f = open(filename, 'r')
117 objectname = sha1(content).hexdigest()
119 f = gzip.open(os.path.join(base, '.objects', objectname), 'w')
124 def move_test_results(in_dir, what, out_dir, logger):
125 if out_dir == in_dir:
132 # create test results directory if necessary
133 #logger.write("FINAL = %s\n" % finalPath, 5)
134 if not os.access(finalPath, os.F_OK):
135 #shutil.rmtree(finalPath)
136 os.makedirs(finalPath)
139 logger.error(_("%s cannot be created.") % finalPath)
140 finalPath = ask_a_path()
143 os.makedirs(os.path.join(finalPath, what, 'BASES'))
145 # check if .objects directory exists
146 if not os.access(os.path.join(finalPath, '.objects'), os.F_OK):
147 os.makedirs(os.path.join(finalPath, '.objects'))
149 logger.write(_('copy tests results to %s ... ') % finalPath, 3)
151 #logger.write("\n", 5)
154 shutil.copy2(os.path.join(in_dir, what, 'env_info.py'),
155 os.path.join(finalPath, what, 'env_info.py'))
157 # for all sub directory (ie testbase) in the BASES directory
158 for testbase in os.listdir(os.path.join(in_dir, what, 'BASES')):
159 outtestbase = os.path.join(finalPath, what, 'BASES', testbase)
160 intestbase = os.path.join(in_dir, what, 'BASES', testbase)
162 # ignore files in root dir
163 if not os.path.isdir(intestbase):
166 os.makedirs(outtestbase)
167 #logger.write(" copy testbase %s\n" % testbase, 5)
169 for grid_ in [m for m in os.listdir(intestbase) if os.path.isdir(
170 os.path.join(intestbase, m))]:
171 # ignore source configuration directories
172 if grid_[:4] == '.git' or grid_ == 'CVS':
175 outgrid = os.path.join(outtestbase, grid_)
176 ingrid = os.path.join(intestbase, grid_)
178 #logger.write(" copy grid %s\n" % grid_, 5)
180 if grid_ == 'RESSOURCES':
181 for file_name in os.listdir(ingrid):
182 if not os.path.isfile(os.path.join(ingrid,
185 f = open(os.path.join(outgrid, file_name), "w")
186 f.write(save_file(os.path.join(ingrid, file_name),
190 for session_name in [t for t in os.listdir(ingrid) if
191 os.path.isdir(os.path.join(ingrid, t))]:
192 outsession = os.path.join(outgrid, session_name)
193 insession = os.path.join(ingrid, session_name)
194 os.makedirs(outsession)
196 for file_name in os.listdir(insession):
197 if not os.path.isfile(os.path.join(insession,
200 if file_name.endswith('result.py'):
201 shutil.copy2(os.path.join(insession, file_name),
202 os.path.join(outsession, file_name))
204 f = open(os.path.join(outsession, file_name), "w")
205 f.write(save_file(os.path.join(insession,
210 logger.write(src.printcolors.printc("OK"), 3, False)
211 logger.write("\n", 3, False)
213 def check_remote_machine(machine_name, logger):
214 logger.write(_("\ncheck the display on %s\n" % machine_name), 4)
215 ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s "ls"' % machine_name
216 logger.write(_("Executing the command : %s " % ssh_cmd), 4)
217 p = subprocess.Popen(ssh_cmd,
219 stdin =subprocess.PIPE,
220 stdout=subprocess.PIPE,
221 stderr=subprocess.PIPE)
223 if p.returncode != 0:
224 logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 1)
225 logger.write(" " + src.printcolors.printcError(p.stderr.read()), 2)
226 logger.write(src.printcolors.printcWarning((
227 "No ssh access to the display machine.")),1)
229 logger.write(src.printcolors.printcSuccess(src.OK_STATUS) + "\n\n", 4)
232 # Creates the XML report for a product.
233 def create_test_report(config,
238 # get the date and hour of the launching of the command, in order to keep
240 date_hour = config.VARS.datehour
242 # Get some information to put in the xml file
243 application_name = config.VARS.application
244 withappli = src.config_has_application(config)
247 if not os.path.exists(xml_history_path):
248 if verbose: print("first_time as NOT existing '%s'" % xml_history_path) # cvw TODO
250 root = etree.Element("salome")
251 prod_node = etree.Element("product", name=application_name, build=xmlname)
252 root.append(prod_node)
254 if verbose: print("NOT first_time as existing '%s'" % xml_history_path) # cvw TODO
255 root = etree.parse(xml_history_path).getroot()
256 prod_node = root.find("product")
258 prod_node.attrib["history_file"] = os.path.basename(xml_history_path)
259 prod_node.attrib["global_res"] = retcode
261 # OP 14/11/2017 Ajout de traces pour essayer de decouvrir le pb
262 # de remontee de log des tests
263 #print "TRACES OP - test.py/create_test_report() : xml_history_path = '#%s#'" %xml_history_path
267 for node in (prod_node.findall("version_to_download") +
268 prod_node.findall("out_dir")):
269 prod_node.remove(node)
271 add_simple_node(prod_node, "version_to_download",
272 config.APPLICATION.name)
274 add_simple_node(prod_node, "out_dir", config.APPLICATION.workdir)
278 for node in prod_node.findall("exec"):
279 prod_node.remove(node)
281 exec_node = add_simple_node(prod_node, "exec")
282 exec_node.append(etree.Element("env", name="Host", value=config.VARS.node))
283 exec_node.append(etree.Element("env", name="Architecture",
284 value=config.VARS.dist))
285 exec_node.append(etree.Element("env", name="Number of processors",
286 value=str(config.VARS.nb_proc)))
287 exec_node.append(etree.Element("env", name="Begin date",
288 value=src.parse_date(date_hour)))
289 exec_node.append(etree.Element("env", name="Command",
290 value=config.VARS.command))
291 exec_node.append(etree.Element("env", name="sat version",
292 value=config.INTERNAL.sat_version))
294 if 'TESTS' in config:
296 tests = add_simple_node(prod_node, "tests")
297 known_errors = add_simple_node(prod_node, "known_errors")
298 new_errors = add_simple_node(prod_node, "new_errors")
299 amend = add_simple_node(prod_node, "amend")
301 tests = prod_node.find("tests")
302 known_errors = prod_node.find("known_errors")
303 new_errors = prod_node.find("new_errors")
304 amend = prod_node.find("amend")
307 for test in config.TESTS:
308 if not tt.has_key(test.testbase):
309 tt[test.testbase] = [test]
311 tt[test.testbase].append(test)
313 for testbase in tt.keys():
314 if verbose: print("---- create_test_report %s %s" % (testbase, first_time))
316 gn = add_simple_node(tests, "testbase")
318 gn = tests.find("testbase")
319 # initialize all grids and session to "not executed"
320 for mn in gn.findall("grid"):
321 mn.attrib["executed_last_time"] = "no"
322 for tyn in mn.findall("session"):
323 tyn.attrib["executed_last_time"] = "no"
324 for test_node in tyn.findall('test'):
325 for node in test_node.getchildren():
326 if node.tag != "history":
327 test_node.remove(node)
330 for attribute in test_node.attrib:
331 if (attribute != "script" and
333 attribs_to_pop.append(attribute)
334 for attribute in attribs_to_pop:
335 test_node.attrib.pop(attribute)
337 gn.attrib['name'] = testbase
338 nb, nb_pass, nb_failed, nb_timeout, nb_not_run = 0, 0, 0, 0, 0
341 for test in tt[testbase]:
342 if not grids.has_key(test.grid):
344 mn = add_simple_node(gn, "grid")
345 mn.attrib['name'] = test.grid
347 l_mn = gn.findall("grid")
349 for grid_node in l_mn:
350 if grid_node.attrib['name'] == test.grid:
354 mn = add_simple_node(gn, "grid")
355 mn.attrib['name'] = test.grid
357 grids[test.grid] = mn
359 mn.attrib["executed_last_time"] = "yes"
361 if not sessions.has_key("%s/%s" % (test.grid, test.session)):
363 tyn = add_simple_node(mn, "session")
364 tyn.attrib['name'] = test.session
366 l_tyn = mn.findall("session")
368 for session_node in l_tyn:
369 if session_node.attrib['name'] == test.session:
373 tyn = add_simple_node(mn, "session")
374 tyn.attrib['name'] = test.session
376 sessions["%s/%s" % (test.grid, test.session)] = tyn
378 tyn.attrib["executed_last_time"] = "yes"
380 for script in test.script:
382 tn = add_simple_node(sessions[
383 "%s/%s" % (test.grid, test.session)],
385 tn.attrib['session'] = test.session
386 tn.attrib['script'] = script.name
387 hn = add_simple_node(tn, "history")
389 l_tn = sessions["%s/%s" % (test.grid, test.session)].findall(
392 for test_node in l_tn:
393 if test_node.attrib['script'] == script['name']:
398 tn = add_simple_node(sessions[
399 "%s/%s" % (test.grid, test.session)],
401 tn.attrib['session'] = test.session
402 tn.attrib['script'] = script.name
403 hn = add_simple_node(tn, "history")
405 # Get or create the history node for the current test
406 if len(tn.findall("history")) == 0:
407 hn = add_simple_node(tn, "history")
409 hn = tn.find("history")
410 # Put the last test data into the history
411 if 'res' in tn.attrib:
412 attributes = {"date_hour" : date_hour,
413 "res" : tn.attrib['res'] }
418 if node.tag != "history":
421 if 'callback' in script:
423 cnode = add_simple_node(tn, "callback")
424 if src.architecture.is_windows():
427 lambda x: x in string.printable,
430 cnode.text = script.callback.decode(
432 except UnicodeDecodeError as exc:
433 zz = (script.callback[:exc.start] +
435 script.callback[exc.end-2:])
436 cnode = add_simple_node(tn, "callback")
437 cnode.text = zz.decode("UTF-8")
439 # Add the script content
440 cnode = add_simple_node(tn, "content")
441 cnode.text = script.content
443 # Add the script execution log
444 cnode = add_simple_node(tn, "out")
445 cnode.text = script.out
447 if 'amend' in script:
448 cnode = add_simple_node(tn, "amend")
449 cnode.text = script.amend.decode("UTF-8")
452 tn.attrib['exec_time'] = "?"
454 tn.attrib['exec_time'] = "%.3f" % script.time
455 tn.attrib['res'] = script.res
457 if "amend" in script:
458 amend_test = add_simple_node(amend, "atest")
459 amend_test.attrib['name'] = os.path.join(test.grid,
462 amend_test.attrib['reason'] = script.amend.decode(
467 if script.res == src.OK_STATUS: nb_pass += 1
468 elif script.res == src.TIMEOUT_STATUS: nb_timeout += 1
469 elif script.res == src.KO_STATUS: nb_failed += 1
470 else: nb_not_run += 1
472 if "known_error" in script:
473 kf_script = add_simple_node(known_errors, "error")
474 kf_script.attrib['name'] = os.path.join(test.grid,
477 kf_script.attrib['date'] = script.known_error.date
479 'expected'] = script.known_error.expected
481 'comment'] = script.known_error.comment.decode("UTF-8")
482 kf_script.attrib['fixed'] = str(
483 script.known_error.fixed)
484 overdue = datetime.datetime.today().strftime("%Y-%m-"
485 "%d") > script.known_error.expected
487 kf_script.attrib['overdue'] = str(overdue)
489 elif script.res == src.KO_STATUS:
490 new_err = add_simple_node(new_errors, "new_error")
491 script_path = os.path.join(test.grid,
492 test.session, script.name)
493 new_err.attrib['name'] = script_path
494 new_err.attrib['cmd'] = ("sat testerror %s -s %s -c 'my"
495 " comment' -p %s" % \
496 (application_name, script_path, config.VARS.dist))
499 gn.attrib['total'] = str(nb)
500 gn.attrib['pass'] = str(nb_pass)
501 gn.attrib['failed'] = str(nb_failed)
502 gn.attrib['timeout'] = str(nb_timeout)
503 gn.attrib['not_run'] = str(nb_not_run)
505 # Remove the res attribute of all tests that were not launched
507 for mn in gn.findall("grid"):
508 if mn.attrib["executed_last_time"] == "no":
509 for tyn in mn.findall("session"):
510 if tyn.attrib["executed_last_time"] == "no":
511 for test_node in tyn.findall('test'):
512 if "res" in test_node.attrib:
513 test_node.attrib.pop("res")
515 if len(xmlname) == 0:
516 xmlname = application_name
517 if not xmlname.endswith(".xml"):
520 src.xmlManager.write_report(os.path.join(dest_path, xmlname),
523 src.xmlManager.write_report(xml_history_path,
528 def generate_history_xml_path(config, test_base):
529 """Generate the name of the xml file that contain the history of the tests
530 on the machine with the current APPLICATION and the current test base.
532 :param config Config: The global configuration
533 :param test_base Str: The test base name (or path)
534 :return: the full path of the history xml file
537 history_xml_name = ""
538 if "APPLICATION" in config:
539 history_xml_name += config.APPLICATION.name
540 history_xml_name += "-"
541 history_xml_name += config.VARS.dist
542 history_xml_name += "-"
543 test_base_name = test_base
544 if os.path.exists(test_base):
545 test_base_name = os.path.basename(test_base)
546 history_xml_name += test_base_name
547 history_xml_name += ".xml"
548 log_dir = src.get_log_path(config)
549 return os.path.join(log_dir, "TEST", history_xml_name)
551 def run(args, runner, logger):
552 '''method that is called when salomeTools is called with test parameter.
554 (options, args) = parse_option(args, runner.cfg)
556 # the test base is specified either by the application, or by the --base option
557 with_application = False
558 if runner.cfg.VARS.application != 'None':
559 logger.write(_('Running tests on application %s\n') %
560 src.printcolors.printcLabel(
561 runner.cfg.VARS.application), 1)
562 with_application = True
563 elif not options.base:
564 raise src.SatException(_('A test base is required. Use the --base '
567 # the launcher is specified either by the application, or by the --launcher option
569 # check if environment is loaded
570 if 'KERNEL_ROOT_DIR' in os.environ:
571 logger.write(src.printcolors.printcWarning(_("WARNING: "
572 "SALOME environment already sourced")) + "\n", 1)
575 elif options.launcher:
576 logger.write(src.printcolors.printcWarning(_("Running SALOME "
577 "application.")) + "\n\n", 1)
579 msg = _("Impossible to find any launcher.\nPlease specify an "
580 "application or a launcher")
581 logger.write(src.printcolors.printcError(msg))
586 show_desktop = (options.display and options.display.upper() == "NO")
587 if options.display and options.display != "NO":
588 remote_name = options.display.split(':')[0]
589 if remote_name != "":
590 check_remote_machine(remote_name, logger)
591 # if explicitly set use user choice
592 os.environ['DISPLAY'] = options.display
593 elif 'DISPLAY' not in os.environ:
595 if ('test' in runner.cfg.LOCAL and
596 'display' in runner.cfg.LOCAL.test and
597 len(runner.cfg.LOCAL.test.display) > 0):
598 # use default value for test tool
599 os.environ['DISPLAY'] = runner.cfg.LOCAL.test.display
601 os.environ['DISPLAY'] = "localhost:0.0"
606 tmp_dir = os.path.join(runner.cfg.VARS.tmp_root,
607 runner.cfg.APPLICATION.name,
610 tmp_dir = os.path.join(runner.cfg.VARS.tmp_root,
613 # remove previous tmp dir
614 if os.access(tmp_dir, os.F_OK):
616 shutil.rmtree(tmp_dir)
618 logger.error(_("error removing TT_TMP_RESULT %s\n")
622 lines.append("date = '%s'" % runner.cfg.VARS.date)
623 lines.append("hour = '%s'" % runner.cfg.VARS.hour)
624 lines.append("node = '%s'" % runner.cfg.VARS.node)
625 lines.append("arch = '%s'" % runner.cfg.VARS.dist)
627 if 'APPLICATION' in runner.cfg:
628 lines.append("application_info = {}")
629 lines.append("application_info['name'] = '%s'" %
630 runner.cfg.APPLICATION.name)
631 lines.append("application_info['tag'] = '%s'" %
632 runner.cfg.APPLICATION.tag)
633 lines.append("application_info['products'] = %s" %
634 str(runner.cfg.APPLICATION.products))
636 content = "\n".join(lines)
638 # create hash from context information
639 dirname = datetime.datetime.now().strftime("%y%m%d_%H%M%S_") + sha1(content.encode()).hexdigest()[0:6]
640 base_dir = os.path.join(tmp_dir, dirname)
641 os.makedirs(base_dir)
642 os.environ['TT_TMP_RESULT'] = base_dir
644 # create env_info file
645 f = open(os.path.join(base_dir, 'env_info.py'), "w")
649 # create working dir and bases dir
650 working_dir = os.path.join(base_dir, 'WORK')
651 os.makedirs(working_dir)
652 os.makedirs(os.path.join(base_dir, 'BASES'))
653 os.chdir(working_dir)
655 if 'PYTHONPATH' not in os.environ:
656 os.environ['PYTHONPATH'] = ''
658 for var in os.environ['PYTHONPATH'].split(':'):
659 if var not in sys.path:
662 # launch of the tests
663 #####################
666 test_base = options.base
667 elif with_application and "test_base" in runner.cfg.APPLICATION:
668 test_base = runner.cfg.APPLICATION.test_base.name
670 src.printcolors.print_value(logger, _('Display'), os.environ['DISPLAY'], 2)
671 src.printcolors.print_value(logger, _('Timeout'),
672 src.test_module.DEFAULT_TIMEOUT, 2)
673 src.printcolors.print_value(logger, _("Working dir"), base_dir, 3)
675 # create the test object
676 test_runner = src.test_module.Test(runner.cfg,
681 sessions=options.sessions,
682 launcher=options.launcher,
683 show_desktop=show_desktop)
685 if not test_runner.test_base_found:
690 logger.allowPrintLevel = False
691 retcode = test_runner.run_all_tests()
692 logger.allowPrintLevel = True
694 logger.write(_("Tests finished"), 1)
695 logger.write("\n", 2, False)
697 logger.write(_("\nGenerate the specific test log\n"), 5)
698 log_dir = src.get_log_path(runner.cfg)
699 out_dir = os.path.join(log_dir, "TEST")
700 src.ensure_path_exists(out_dir)
701 name_xml_board = logger.logFileName.split(".")[0] + "board" + ".xml"
702 historic_xml_path = generate_history_xml_path(runner.cfg, test_base)
704 create_test_report(runner.cfg,
708 xmlname = name_xml_board)
709 xml_board_path = os.path.join(out_dir, name_xml_board)
711 # OP 14/11/2017 Ajout de traces pour essayer de decouvrir le pb
712 # de remontee de log des tests
713 #print "TRACES OP - test.py/run() : historic_xml_path = '#%s#'" %historic_xml_path
714 #print "TRACES OP - test.py/run() : log_dir = '#%s#'" %log_dir
715 #print "TRACES OP - test.py/run() : name_xml_board = '#%s#'" %name_xml_board
717 logger.l_logFiles.append(xml_board_path)
718 logger.add_link(os.path.join("TEST", name_xml_board),
721 "Click on the link to get the detailed test results")
722 logger.write("\nTests board is file %s\n" % xml_board_path, 1)
724 # Add the historic files into the log files list of the command
725 logger.l_logFiles.append(historic_xml_path)
727 logger.write(_("Removing the temporary directory: "
728 "rm -rf %s\n" % test_runner.tmp_working_dir), 5)
729 if os.path.exists(test_runner.tmp_working_dir):
730 shutil.rmtree(test_runner.tmp_working_dir)