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
26 # Compatibility python 2/3 for input function
27 # input stays input for python 3 and input = raw_input for python 2
36 from hashlib import sha1
38 from sha import sha as sha1
41 import src.ElementTree as etree
42 from src.xmlManager import add_simple_node
44 # Define all possible option for the test command : sat test <options>
45 parser = src.options.Options()
52 """Optional: The name of the test base to use."
53 This name has to be registered in your application and in a project.
54 A path to a test base can also be used."""
63 """Optional: Specify the path to a SALOME launcher
64 used to launch the test scripts of the test base."""
72 _("Optional: Which grid(s) to test (subdirectory of the test base)."),
79 _("Optional: Which session(s) to test (subdirectory of the grid)."),
87 """Optional: Set the display where to launch SALOME.
88 If value is NO then option --show-desktop=0 will be used to launch SALOME."""
96 _("Optional: keep temporary big tests directories."),
101 """method that is called when salomeTools is called with --help option.
103 :return: The text to display for the test command description.
107 "The test command runs a test base on a SALOME installation.\n\n"
108 "example:\nsat test SALOME-master --grid GEOM --session light"
112 def parse_option_old(args, config):
113 """Parse the options and do some verifications about it
115 :param args List: The list of arguments of the command
116 :param config Config: The global configuration
117 :return: the options of the current command launch and the full arguments
118 :rtype: Tuple (options, args)
120 (options, args) = parser.parse_args(args)
122 if not options.launcher:
123 options.launcher = ""
124 elif not os.path.isabs(options.launcher):
125 if not src.config_has_application(config):
127 "An application is required to use a relative path with option --appli"
129 raise src.SatException(msg)
130 options.launcher = os.path.join(config.APPLICATION.workdir, options.launcher)
132 if not os.path.exists(options.launcher):
133 raise src.SatException(_("Launcher not found: %s") % options.launcher)
135 return (options, args)
138 def parse_option(args, config):
139 """Parse the options and do some verifications about it
141 :param args List: The list of arguments of the command
142 :param config Config: The global configuration
143 :return: the options of the current command launch and the full arguments
144 :rtype: Tuple (options, args)
146 (options, args) = parser.parse_args(args)
148 if not options.launcher:
149 options.launcher = ""
150 return (options, args)
152 if not os.path.isabs(options.launcher):
153 if not src.config_has_application(config):
155 "An application is required to use a relative path with option --appli"
157 raise src.SatException(msg)
159 options.launcher = os.path.join(
160 config.APPLICATION.workdir, options.launcher
162 if not os.path.exists(options.launcher):
163 raise src.SatException(_("Launcher not found: %s") % options.launcher)
166 launcher = os.path.realpath(os.path.expandvars(options.launcher))
167 if os.path.exists(launcher):
168 options.launcher = launcher
169 return (options, args)
171 raise src.SatException(_("Launcher not found: %s") % options.launcher)
176 path = input("enter a path where to save the result: ")
179 "the result will be not save. Are you sure to " "continue ? [y/n] "
186 elif os.path.exists(path):
188 "Warning, the content of %s will be deleted. Are you"
189 " sure to continue ? [y/n] " % path
199 def save_file(filename, base):
200 f = open(filename, "r")
204 objectname = sha1(content).hexdigest()
206 f = gzip.open(os.path.join(base, ".objects", objectname), "w")
212 def move_test_results(in_dir, what, out_dir, logger):
213 if out_dir == in_dir:
220 # create test results directory if necessary
221 # logger.write("FINAL = %s\n" % finalPath, 5)
222 if not os.access(finalPath, os.F_OK):
223 # shutil.rmtree(finalPath)
224 os.makedirs(finalPath)
227 logger.error(_("%s cannot be created.") % finalPath)
228 finalPath = ask_a_path()
231 os.makedirs(os.path.join(finalPath, what, "BASES"))
233 # check if .objects directory exists
234 if not os.access(os.path.join(finalPath, ".objects"), os.F_OK):
235 os.makedirs(os.path.join(finalPath, ".objects"))
237 logger.write(_("copy tests results to %s ... ") % finalPath, 3)
239 # logger.write("\n", 5)
243 os.path.join(in_dir, what, "env_info.py"),
244 os.path.join(finalPath, what, "env_info.py"),
247 # for all sub directory (ie testbase) in the BASES directory
248 for testbase in os.listdir(os.path.join(in_dir, what, "BASES")):
249 outtestbase = os.path.join(finalPath, what, "BASES", testbase)
250 intestbase = os.path.join(in_dir, what, "BASES", testbase)
252 # ignore files in root dir
253 if not os.path.isdir(intestbase):
256 os.makedirs(outtestbase)
257 # logger.write(" copy testbase %s\n" % testbase, 5)
261 for m in os.listdir(intestbase)
262 if os.path.isdir(os.path.join(intestbase, m))
264 # ignore source configuration directories
265 if grid_[:4] == ".git" or grid_ == "CVS":
268 outgrid = os.path.join(outtestbase, grid_)
269 ingrid = os.path.join(intestbase, grid_)
271 # logger.write(" copy grid %s\n" % grid_, 5)
273 if grid_ == "RESSOURCES":
274 for file_name in os.listdir(ingrid):
275 if not os.path.isfile(os.path.join(ingrid, file_name)):
277 f = open(os.path.join(outgrid, file_name), "w")
278 f.write(save_file(os.path.join(ingrid, file_name), finalPath))
281 for session_name in [
283 for t in os.listdir(ingrid)
284 if os.path.isdir(os.path.join(ingrid, t))
286 outsession = os.path.join(outgrid, session_name)
287 insession = os.path.join(ingrid, session_name)
288 os.makedirs(outsession)
290 for file_name in os.listdir(insession):
291 if not os.path.isfile(os.path.join(insession, file_name)):
293 if file_name.endswith("result.py"):
295 os.path.join(insession, file_name),
296 os.path.join(outsession, file_name),
299 f = open(os.path.join(outsession, file_name), "w")
302 os.path.join(insession, file_name), finalPath
307 logger.write(src.printcolors.printc("OK"), 3, False)
308 logger.write("\n", 3, False)
311 def check_remote_machine(machine_name, logger):
312 logger.write(_("\ncheck the display on %s\n" % machine_name), 4)
313 ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s "ls"' % machine_name
314 logger.write(_("Executing the command : %s " % ssh_cmd), 4)
315 p = subprocess.Popen(
318 stdin=subprocess.PIPE,
319 stdout=subprocess.PIPE,
320 stderr=subprocess.PIPE,
323 if p.returncode != 0:
324 logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 1)
325 logger.write(" " + src.printcolors.printcError(p.stderr.read()), 2)
327 src.printcolors.printcWarning(("No ssh access to the display machine.")), 1
330 logger.write(src.printcolors.printcSuccess(src.OK_STATUS) + "\n\n", 4)
333 def findOrCreateNode(parentNode, nameNodeToFind):
334 found = parentNode.find(nameNodeToFind)
336 created = add_simple_node(parentNode, nameNodeToFind)
342 def purgeEmptyNodes(root):
344 recursive remove node.text and node.tail if empty node
345 as nothing else than whitespace(s) and RCLF(s)
348 | 1) pretty print file xml -> creates indentation(s) in text and tail
349 | 2) and reload parse file xml
351 # print("root", root.tag, root.text)
355 if text.replace(" ", "").replace("\n", "") == "":
356 # print("purgeEmptyNodes text %s" % root.tag)
359 if tail.replace(" ", "").replace("\n", "") == "":
360 # print("purgeEmptyNodes tail %s" % root.tag)
363 purgeEmptyNodes(node)
368 # Creates the XML report for a product.
369 def create_test_report(config, xml_history_path, dest_path, retcode, xmlname=""):
370 # get the date and hour of the launching of the command, in order to keep
372 date_hour = config.VARS.datehour
374 # Get some information to put in the xml file
375 application_name = config.VARS.application
376 withappli = src.config_has_application(config)
379 if not os.path.exists(xml_history_path):
380 print("Log file creation %s" % xml_history_path)
382 root = etree.Element("salome")
383 prod_node = etree.Element("product", name=application_name, build=xmlname)
384 root.append(prod_node)
386 print("Log file modification %s" % xml_history_path)
387 root = etree.parse(xml_history_path).getroot()
388 purgeEmptyNodes(root)
389 prod_node = root.find("product")
391 prod_node.attrib["history_file"] = os.path.basename(xml_history_path)
392 prod_node.attrib["global_res"] = str(retcode)
396 for node in prod_node.findall("version_to_download") + prod_node.findall(
399 prod_node.remove(node)
401 add_simple_node(prod_node, "version_to_download", config.APPLICATION.name)
402 add_simple_node(prod_node, "out_dir", config.APPLICATION.workdir)
406 for node in prod_node.findall("exec"):
407 prod_node.remove(node)
409 exec_node = add_simple_node(prod_node, "exec")
410 exec_node.append(etree.Element("env", name="Host", value=config.VARS.node))
411 exec_node.append(etree.Element("env", name="Architecture", value=config.VARS.dist))
414 "env", name="Number of processors", value=str(config.VARS.nb_proc)
418 etree.Element("env", name="Begin date", value=src.parse_date(date_hour))
420 exec_node.append(etree.Element("env", name="Command", value=config.VARS.command))
422 etree.Element("env", name="sat version", value=config.INTERNAL.sat_version)
425 if "TESTS" in config:
426 tests = findOrCreateNode(prod_node, "tests")
427 known_errors = findOrCreateNode(prod_node, "known_errors")
428 new_errors = findOrCreateNode(prod_node, "new_errors")
429 amend = findOrCreateNode(prod_node, "amend")
432 for test in config.TESTS:
433 if not test.testbase in tt:
434 tt[test.testbase] = [test]
436 tt[test.testbase].append(test)
438 for testbase in tt.keys():
440 print("---- create_test_report %s %s" % (testbase, first_time))
441 gn = findOrCreateNode(tests, "testbase")
443 # initialize all grids and session to "not executed"
444 for mn in gn.findall("grid"):
445 mn.attrib["executed_last_time"] = "no"
446 for tyn in mn.findall("session"):
447 tyn.attrib["executed_last_time"] = "no"
448 for test_node in tyn.findall("test"):
449 for node in test_node.getchildren():
450 if node.tag != "history":
451 test_node.remove(node)
454 for attribute in test_node.attrib:
455 if attribute != "script" and attribute != "res":
456 attribs_to_pop.append(attribute)
457 for attribute in attribs_to_pop:
458 test_node.attrib.pop(attribute)
460 gn.attrib["name"] = testbase
461 nb, nb_pass, nb_failed, nb_timeout, nb_not_run = 0, 0, 0, 0, 0
464 for test in tt[testbase]:
465 if not (test.grid in grids):
467 mn = add_simple_node(gn, "grid")
468 mn.attrib["name"] = test.grid
470 l_mn = gn.findall("grid")
472 for grid_node in l_mn:
473 if grid_node.attrib["name"] == test.grid:
477 mn = add_simple_node(gn, "grid")
478 mn.attrib["name"] = test.grid
480 grids[test.grid] = mn
482 mn.attrib["executed_last_time"] = "yes"
484 if not "%s/%s" % (test.grid, test.session) in sessions:
486 tyn = add_simple_node(mn, "session")
487 tyn.attrib["name"] = test.session
489 l_tyn = mn.findall("session")
491 for session_node in l_tyn:
492 if session_node.attrib["name"] == test.session:
496 tyn = add_simple_node(mn, "session")
497 tyn.attrib["name"] = test.session
499 sessions["%s/%s" % (test.grid, test.session)] = tyn
501 tyn.attrib["executed_last_time"] = "yes"
503 for script in test.script:
505 tn = add_simple_node(
506 sessions["%s/%s" % (test.grid, test.session)], "test"
508 tn.attrib["session"] = test.session
509 tn.attrib["script"] = script.name
510 hn = add_simple_node(tn, "history")
512 l_tn = sessions["%s/%s" % (test.grid, test.session)].findall(
516 for test_node in l_tn:
517 if test_node.attrib["script"] == script["name"]:
522 tn = add_simple_node(
523 sessions["%s/%s" % (test.grid, test.session)], "test"
525 tn.attrib["session"] = test.session
526 tn.attrib["script"] = script.name
527 hn = add_simple_node(tn, "history")
529 # Get or create the history node for the current test
530 if len(tn.findall("history")) == 0:
531 hn = add_simple_node(tn, "history")
533 hn = tn.find("history")
534 # Put the last test data into the history
535 if "res" in tn.attrib:
537 "date_hour": date_hour,
538 "res": tn.attrib["res"],
540 add_simple_node(hn, "previous_test", attrib=attributes)
542 if node.tag != "history":
545 if "callback" in script:
547 cnode = add_simple_node(tn, "callback")
550 ): # bug xml mal forme colorisation src.architecture.is_windows():
554 lambda x: x in string.printable, script.callback
557 cnode.text = script.callback.decode("string_escape")
558 except UnicodeDecodeError as exc:
560 script.callback[: exc.start]
562 + script.callback[exc.end - 2 :]
564 cnode = add_simple_node(tn, "callback")
565 cnode.text = zz.decode("UTF-8")
567 # Add the script content
568 cnode = add_simple_node(tn, "content")
569 cnode.text = script.content
571 # Add the script execution log
572 cnode = add_simple_node(tn, "out")
573 cnode.text = script.out
575 if "amend" in script:
576 cnode = add_simple_node(tn, "amend")
577 cnode.text = script.amend.decode("UTF-8")
580 tn.attrib["exec_time"] = "?"
582 tn.attrib["exec_time"] = "%.3f" % script.time
583 tn.attrib["res"] = script.res
585 if "amend" in script:
586 amend_test = add_simple_node(amend, "atest")
587 amend_test.attrib["name"] = os.path.join(
588 test.grid, test.session, script.name
590 amend_test.attrib["reason"] = script.amend.decode("UTF-8")
594 if script.res == src.OK_STATUS:
596 elif script.res == src.TIMEOUT_STATUS:
598 elif script.res == src.KO_STATUS:
603 if "known_error" in script:
604 kf_script = add_simple_node(known_errors, "error")
605 kf_script.attrib["name"] = os.path.join(
606 test.grid, test.session, script.name
608 kf_script.attrib["date"] = script.known_error.date
609 kf_script.attrib["expected"] = script.known_error.expected
610 kf_script.attrib["comment"] = script.known_error.comment.decode(
613 kf_script.attrib["fixed"] = str(script.known_error.fixed)
615 datetime.datetime.today().strftime("%Y-%m-" "%d")
616 > script.known_error.expected
619 kf_script.attrib["overdue"] = str(overdue)
621 elif script.res == src.KO_STATUS:
622 new_err = add_simple_node(new_errors, "new_error")
623 script_path = os.path.join(test.grid, test.session, script.name)
624 new_err.attrib["name"] = script_path
627 ] = "sat testerror %s -s %s -c 'my" " comment' -p %s" % (
633 gn.attrib["total"] = str(nb)
634 gn.attrib["pass"] = str(nb_pass)
635 gn.attrib["failed"] = str(nb_failed)
636 gn.attrib["timeout"] = str(nb_timeout)
637 gn.attrib["not_run"] = str(nb_not_run)
639 # Remove the res attribute of all tests that were not launched
641 for mn in gn.findall("grid"):
642 if mn.attrib["executed_last_time"] == "no":
643 for tyn in mn.findall("session"):
644 if tyn.attrib["executed_last_time"] == "no":
645 for test_node in tyn.findall("test"):
646 if "res" in test_node.attrib:
647 test_node.attrib.pop("res")
649 if len(xmlname) == 0:
650 xmlname = application_name
651 if not xmlname.endswith(".xml"):
654 src.xmlManager.write_report(os.path.join(dest_path, xmlname), root, "test.xsl")
655 src.xmlManager.write_report(xml_history_path, root, "test_history.xsl")
659 def generate_history_xml_path(config, test_base):
660 """Generate the name of the xml file that contain the history of the tests
661 on the machine with the current APPLICATION and the current test base.
663 :param config Config: The global configuration
664 :param test_base Str: The test base name (or path)
665 :return: the full path of the history xml file
668 history_xml_name = ""
669 if "APPLICATION" in config:
670 history_xml_name += config.APPLICATION.name
671 history_xml_name += "-"
672 history_xml_name += config.VARS.dist
673 history_xml_name += "-"
674 test_base_name = test_base
675 if os.path.exists(test_base):
676 test_base_name = os.path.basename(test_base)
677 history_xml_name += test_base_name
678 history_xml_name += ".xml"
679 log_dir = src.get_log_path(config)
680 return os.path.join(log_dir, "TEST", history_xml_name)
683 def run(args, runner, logger):
684 """method that is called when salomeTools is called with test parameter."""
685 (options, args) = parse_option(args, runner.cfg)
687 # the test base is specified either by the application, or by the --base option
688 with_application = False
689 if runner.cfg.VARS.application != "None":
691 _("Running tests on application %s\n")
692 % src.printcolors.printcLabel(runner.cfg.VARS.application),
695 with_application = True
696 elif not options.base:
697 raise src.SatException(_("A test base is required. Use the --base " "option"))
699 # the launcher is specified either by the application, or by the --launcher option
701 # check if environment is loaded
702 if "KERNEL_ROOT_DIR" in os.environ:
704 src.printcolors.printcWarning(
705 _("WARNING: " "SALOME environment already sourced")
711 elif options.launcher:
713 src.printcolors.printcWarning(_("Running SALOME " "application.")) + "\n\n",
718 "Impossible to find any launcher.\nPlease specify an "
719 "application or a launcher"
721 logger.write(src.printcolors.printcError(msg))
726 show_desktop = options.display and options.display.upper() == "NO"
727 if options.display and options.display != "NO":
728 remote_name = options.display.split(":")[0]
729 if remote_name != "" and (not src.architecture.is_windows()):
730 check_remote_machine(remote_name, logger)
731 # if explicitly set use user choice
732 os.environ["DISPLAY"] = options.display
733 elif "DISPLAY" not in os.environ:
736 "test" in runner.cfg.LOCAL
737 and "display" in runner.cfg.LOCAL.test
738 and len(runner.cfg.LOCAL.test.display) > 0
740 # use default value for test tool
741 os.environ["DISPLAY"] = runner.cfg.LOCAL.test.display
743 os.environ["DISPLAY"] = "localhost:0.0"
748 tmp_dir = os.path.join(
749 runner.cfg.VARS.tmp_root, runner.cfg.APPLICATION.name, "test"
752 tmp_dir = os.path.join(runner.cfg.VARS.tmp_root, "test")
754 # remove previous tmp dir
755 if os.access(tmp_dir, os.F_OK):
757 shutil.rmtree(tmp_dir)
759 logger.error(_("error removing TT_TMP_RESULT %s\n") % tmp_dir)
762 lines.append("date = '%s'" % runner.cfg.VARS.date)
763 lines.append("hour = '%s'" % runner.cfg.VARS.hour)
764 lines.append("node = '%s'" % runner.cfg.VARS.node)
765 lines.append("arch = '%s'" % runner.cfg.VARS.dist)
767 if "APPLICATION" in runner.cfg:
768 lines.append("application_info = {}")
769 lines.append("application_info['name'] = '%s'" % runner.cfg.APPLICATION.name)
770 lines.append("application_info['tag'] = '%s'" % runner.cfg.APPLICATION.tag)
772 "application_info['products'] = %s" % str(runner.cfg.APPLICATION.products)
775 content = "\n".join(lines)
777 # create hash from context information
778 # CVW TODO or not dirname = datetime.datetime.now().strftime("%y%m%d_%H%M%S_") + sha1(content.encode()).hexdigest()[0:8]
779 dirname = sha1(content.encode()).hexdigest()[0:8] # only 8 firsts probably good
780 base_dir = os.path.join(tmp_dir, dirname)
781 os.makedirs(base_dir)
782 os.environ["TT_TMP_RESULT"] = base_dir
784 # create env_info file
785 with open(os.path.join(base_dir, "env_info.py"), "w") as f:
788 # create working dir and bases dir
789 working_dir = os.path.join(base_dir, "WORK")
790 os.makedirs(working_dir)
791 os.makedirs(os.path.join(base_dir, "BASES"))
792 os.chdir(working_dir)
794 if "PYTHONPATH" not in os.environ:
795 os.environ["PYTHONPATH"] = ""
797 for var in os.environ["PYTHONPATH"].split(":"):
798 if var not in sys.path:
801 # launch of the tests
802 #####################
805 test_base = options.base
806 elif with_application and "test_base" in runner.cfg.APPLICATION:
807 test_base = runner.cfg.APPLICATION.test_base.name
809 src.printcolors.print_value(logger, _("Display"), os.environ["DISPLAY"], 2)
810 src.printcolors.print_value(
811 logger, _("Timeout"), src.test_module.DEFAULT_TIMEOUT, 2
813 src.printcolors.print_value(logger, _("Working dir"), base_dir, 3)
815 # create the test object
816 test_runner = src.test_module.Test(
822 sessions=options.sessions,
823 launcher=options.launcher,
824 show_desktop=show_desktop,
827 if not test_runner.test_base_found:
832 logger.allowPrintLevel = False
833 retcode = test_runner.run_all_tests()
834 logger.allowPrintLevel = True
836 logger.write(_("Tests finished"), 1)
837 logger.write("\n", 2, False)
839 logger.write(_("\nGenerate the specific test log\n"), 5)
840 log_dir = src.get_log_path(runner.cfg)
841 out_dir = os.path.join(log_dir, "TEST")
842 src.ensure_path_exists(out_dir)
843 name_xml_board = logger.logFileName.split(".")[0] + "_board.xml"
844 historic_xml_path = generate_history_xml_path(runner.cfg, test_base)
847 runner.cfg, historic_xml_path, out_dir, retcode, xmlname=name_xml_board
849 xml_board_path = os.path.join(out_dir, name_xml_board)
851 logger.l_logFiles.append(xml_board_path)
853 os.path.join("TEST", name_xml_board),
856 "Click on the link to get the detailed test results",
858 logger.write("\nTests board file %s\n" % xml_board_path, 1)
860 # Add the historic files into the log files list of the command
861 logger.l_logFiles.append(historic_xml_path)
863 if not options.keeptempdir:
865 "Removing the temporary directory: rm -rf %s\n"
866 % test_runner.tmp_working_dir,
869 if os.path.exists(test_runner.tmp_working_dir):
870 shutil.rmtree(test_runner.tmp_working_dir)
873 "NOT Removing the temporary directory: rm -rf %s\n"
874 % test_runner.tmp_working_dir,