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
27 from hashlib import sha1
29 from sha import sha as sha1
32 import src.ElementTree as etree
33 from src.xmlManager import add_simple_node
35 # Define all possible option for the test command : sat test <options>
36 parser = src.options.Options()
37 parser.add_option('b', 'base', 'string', 'base',
38 _("Indicate the name of the test base to use.\n\tThis name has to be "
39 "registered in your application and in a project.\n\tA path to a test "
40 "base can also be used."))
41 parser.add_option('l', 'launcher', 'string', 'launcher',
42 _("Use this option to specify the path to a SALOME launcher to use to "
43 "launch the test scripts of the test base."))
44 parser.add_option('m', 'module', 'list', 'modules',
45 _('Indicate which module(s) to test (subdirectory of the test base).'))
46 parser.add_option('t', 'type', 'list', 'types',
47 _('Indicate which type(s) to test (subdirectory of the module).'))
48 parser.add_option('', 'display', 'string', 'display',
49 _("Set the display where to launch SALOME."
50 "\tIf value is NO then option --show-desktop=0 will be used to launch SALOME."))
53 '''method that is called when salomeTools is called with --help option.
55 :return: The text to display for the test command description.
58 return _("The test command runs a test base on a SALOME installation.")
60 def parse_option(args, config):
61 """ Parse the options and do some verifications about it
63 :param args List: The list of arguments of the command
64 :param config Config: The global configuration
65 :return: the options of the current command launch and the full arguments
66 :rtype: Tuple (options, args)
68 (options, args) = parser.parse_args(args)
70 if not options.launcher:
72 elif not os.path.isabs(options.launcher):
73 if not src.config_has_application(config):
74 raise src.SatException(_("An application is required to use a "
75 "relative path with option --appli"))
76 options.launcher = os.path.join(config.APPLICATION.workdir,
79 if not os.path.exists(options.launcher):
80 raise src.SatException(_("Launcher not found: %s") %
83 return (options, args)
88 path = raw_input("enter a path where to save the result: ")
90 result = raw_input("the result will be not save. Are you sure to "
97 elif os.path.exists(path):
98 result = raw_input("Warning, the content of %s will be deleted. Are you"
99 " sure to continue ? [y/n] " % path)
107 def save_file(filename, base):
108 f = open(filename, 'r')
112 objectname = sha1(content).hexdigest()
114 f = gzip.open(os.path.join(base, '.objects', objectname), 'w')
119 def move_test_results(in_dir, what, out_dir, logger):
120 if out_dir == in_dir:
127 # create test results directory if necessary
128 #logger.write("FINAL = %s\n" % finalPath, 5)
129 if not os.access(finalPath, os.F_OK):
130 #shutil.rmtree(finalPath)
131 os.makedirs(finalPath)
134 logger.error(_("%s cannot be created.") % finalPath)
135 finalPath = ask_a_path()
138 os.makedirs(os.path.join(finalPath, what, 'BASES'))
140 # check if .objects directory exists
141 if not os.access(os.path.join(finalPath, '.objects'), os.F_OK):
142 os.makedirs(os.path.join(finalPath, '.objects'))
144 logger.write(_('copy tests results to %s ... ') % finalPath, 3)
146 #logger.write("\n", 5)
149 shutil.copy2(os.path.join(in_dir, what, 'env_info.py'),
150 os.path.join(finalPath, what, 'env_info.py'))
152 # for all sub directory (ie testbase) in the BASES directory
153 for testbase in os.listdir(os.path.join(in_dir, what, 'BASES')):
154 outtestbase = os.path.join(finalPath, what, 'BASES', testbase)
155 intestbase = os.path.join(in_dir, what, 'BASES', testbase)
157 # ignore files in root dir
158 if not os.path.isdir(intestbase):
161 os.makedirs(outtestbase)
162 #logger.write(" copy testbase %s\n" % testbase, 5)
164 for module_ in [m for m in os.listdir(intestbase) if os.path.isdir(
165 os.path.join(intestbase, m))]:
166 # ignore source configuration directories
167 if module_[:4] == '.git' or module_ == 'CVS':
170 outmodule = os.path.join(outtestbase, module_)
171 inmodule = os.path.join(intestbase, module_)
172 os.makedirs(outmodule)
173 #logger.write(" copy module %s\n" % module_, 5)
175 if module_ == 'RESSOURCES':
176 for file_name in os.listdir(inmodule):
177 if not os.path.isfile(os.path.join(inmodule,
180 f = open(os.path.join(outmodule, file_name), "w")
181 f.write(save_file(os.path.join(inmodule, file_name),
185 for type_name in [t for t in os.listdir(inmodule) if
186 os.path.isdir(os.path.join(inmodule, t))]:
187 outtype = os.path.join(outmodule, type_name)
188 intype = os.path.join(inmodule, type_name)
191 for file_name in os.listdir(intype):
192 if not os.path.isfile(os.path.join(intype,
195 if file_name.endswith('result.py'):
196 shutil.copy2(os.path.join(intype, file_name),
197 os.path.join(outtype, file_name))
199 f = open(os.path.join(outtype, file_name), "w")
200 f.write(save_file(os.path.join(intype,
205 logger.write(src.printcolors.printc("OK"), 3, False)
206 logger.write("\n", 3, False)
208 def check_remote_machine(machine_name, logger):
209 logger.write(_("\ncheck the display on %s\n" % machine_name), 4)
210 ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s "ls"' % machine_name
211 logger.write(_("Executing the command : %s " % ssh_cmd), 4)
212 p = subprocess.Popen(ssh_cmd,
214 stdin =subprocess.PIPE,
215 stdout=subprocess.PIPE,
216 stderr=subprocess.PIPE)
218 if p.returncode != 0:
219 logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 1)
220 logger.write(" " + src.printcolors.printcError(p.stderr.read()), 2)
221 logger.write(src.printcolors.printcWarning((
222 "No ssh access to the display machine.")),1)
224 logger.write(src.printcolors.printcSuccess(src.OK_STATUS) + "\n\n", 4)
227 # Creates the XML report for a product.
228 def create_test_report(config, dest_path, stylesheet, xmlname=""):
229 application_name = config.VARS.application
230 withappli = src.config_has_application(config)
232 root = etree.Element("salome")
233 prod_node = etree.Element("product", name=application_name, build=xmlname)
234 root.append(prod_node)
238 add_simple_node(prod_node, "version_to_download",
239 config.APPLICATION.name)
241 add_simple_node(prod_node, "out_dir", config.APPLICATION.workdir)
244 exec_node = add_simple_node(prod_node, "exec")
245 exec_node.append(etree.Element("env", name="Host", value=config.VARS.node))
246 exec_node.append(etree.Element("env", name="Architecture",
247 value=config.VARS.dist))
248 exec_node.append(etree.Element("env", name="Number of processors",
249 value=str(config.VARS.nb_proc)))
250 exec_node.append(etree.Element("env", name="Begin date",
251 value=src.parse_date(config.VARS.datehour)))
252 exec_node.append(etree.Element("env", name="Command",
253 value=config.VARS.command))
254 exec_node.append(etree.Element("env", name="sat version",
255 value=config.INTERNAL.sat_version))
257 if 'TESTS' in config:
258 tests = add_simple_node(prod_node, "tests")
259 known_errors = add_simple_node(prod_node, "known_errors")
260 new_errors = add_simple_node(prod_node, "new_errors")
261 amend = add_simple_node(prod_node, "amend")
263 for test in config.TESTS:
264 if not tt.has_key(test.testbase):
265 tt[test.testbase] = [test]
267 tt[test.testbase].append(test)
269 for testbase in tt.keys():
270 gn = add_simple_node(tests, "testbase")
271 gn.attrib['name'] = testbase
272 nb, nb_pass, nb_failed, nb_timeout, nb_not_run = 0, 0, 0, 0, 0
275 for test in tt[testbase]:
277 if not modules.has_key(test.module):
278 mn = add_simple_node(gn, "module")
279 mn.attrib['name'] = test.module
280 modules[test.module] = mn
282 if not types.has_key("%s/%s" % (test.module, test.type)):
283 tyn = add_simple_node(mn, "type")
284 tyn.attrib['name'] = test.type
285 types["%s/%s" % (test.module, test.type)] = tyn
287 for script in test.script:
288 tn = add_simple_node(types[
289 "%s/%s" % (test.module, test.type)], "test")
290 tn.attrib['type'] = test.type
291 tn.attrib['script'] = script.name
292 if 'callback' in script:
294 cnode = add_simple_node(tn, "callback")
295 if src.architecture.is_windows():
298 lambda x: x in string.printable,
301 cnode.text = script.callback.decode(
303 except UnicodeDecodeError as exc:
304 zz = (script.callback[:exc.start] +
306 script.callback[exc.end-2:])
307 cnode = add_simple_node(tn, "callback")
308 cnode.text = zz.decode("UTF-8")
310 # Add the script content
311 cnode = add_simple_node(tn, "content")
312 cnode.text = script.content
314 # Add the script execution log
315 cnode = add_simple_node(tn, "out")
316 cnode.text = script.out
318 if 'amend' in script:
319 cnode = add_simple_node(tn, "amend")
320 cnode.text = script.amend.decode("UTF-8")
323 tn.attrib['exec_time'] = "?"
325 tn.attrib['exec_time'] = "%.3f" % script.time
326 tn.attrib['res'] = script.res
328 if "amend" in script:
329 amend_test = add_simple_node(amend, "atest")
330 amend_test.attrib['name'] = os.path.join(test.module,
333 amend_test.attrib['reason'] = script.amend.decode(
338 if script.res == src.OK_STATUS: nb_pass += 1
339 elif script.res == src.TIMEOUT_STATUS: nb_timeout += 1
340 elif script.res == src.KO_STATUS: nb_failed += 1
341 else: nb_not_run += 1
343 if "known_error" in script:
344 kf_script = add_simple_node(known_errors, "error")
345 kf_script.attrib['name'] = os.path.join(test.module,
348 kf_script.attrib['date'] = script.known_error.date
350 'expected'] = script.known_error.expected
352 'comment'] = script.known_error.comment.decode("UTF-8")
353 kf_script.attrib['fixed'] = str(
354 script.known_error.fixed)
355 overdue = datetime.datetime.today().strftime("%Y-%m-"
356 "%d") > script.known_error.expected
358 kf_script.attrib['overdue'] = str(overdue)
360 elif script.res == src.KO_STATUS:
361 new_err = add_simple_node(new_errors, "new_error")
362 script_path = os.path.join(test.module,
363 test.type, script.name)
364 new_err.attrib['name'] = script_path
365 new_err.attrib['cmd'] = ("sat testerror %s -s %s -c 'my"
366 " comment' -p %s" % \
367 (application_name, script_path, config.VARS.dist))
370 gn.attrib['total'] = str(nb)
371 gn.attrib['pass'] = str(nb_pass)
372 gn.attrib['failed'] = str(nb_failed)
373 gn.attrib['timeout'] = str(nb_timeout)
374 gn.attrib['not_run'] = str(nb_not_run)
376 if len(xmlname) == 0:
377 xmlname = application_name
378 if not xmlname.endswith(".xml"):
381 src.xmlManager.write_report(os.path.join(dest_path, xmlname),
386 def run(args, runner, logger):
387 '''method that is called when salomeTools is called with test parameter.
389 (options, args) = parse_option(args, runner.cfg)
391 with_application = False
392 if runner.cfg.VARS.application != 'None':
393 logger.write(_('Running tests on application %s\n') %
394 src.printcolors.printcLabel(
395 runner.cfg.VARS.application), 1)
396 with_application = True
397 elif not options.base:
398 raise src.SatException(_('A test base is required. Use the --base '
402 # check if environment is loaded
403 if 'KERNEL_ROOT_DIR' in os.environ:
404 logger.write(src.printcolors.printcWarning(_("WARNING: "
405 "SALOME environment already sourced")) + "\n", 1)
408 elif options.launcher:
409 logger.write(src.printcolors.printcWarning(_("Running SALOME "
410 "application.")) + "\n\n", 1)
412 msg = _("Impossible to find any launcher.\nPlease specify an "
413 "application or a launcher")
414 logger.write(src.printcolors.printcError(msg))
419 show_desktop = (options.display and options.display.upper() == "NO")
420 if options.display and options.display != "NO":
421 remote_name = options.display.split(':')[0]
422 if remote_name != "":
423 check_remote_machine(remote_name, logger)
424 # if explicitly set use user choice
425 os.environ['DISPLAY'] = options.display
426 elif 'DISPLAY' not in os.environ:
428 if ('display' in runner.cfg.SITE.test and
429 len(runner.cfg.SITE.test.display) > 0):
430 # use default value for test tool
431 os.environ['DISPLAY'] = runner.cfg.SITE.test.display
433 os.environ['DISPLAY'] = "localhost:0.0"
438 tmp_dir = runner.cfg.SITE.test.tmp_dir_with_application
440 tmp_dir = runner.cfg.SITE.test.tmp_dir
442 # remove previous tmp dir
443 if os.access(tmp_dir, os.F_OK):
445 shutil.rmtree(tmp_dir)
447 logger.error(_("error removing TT_TMP_RESULT %s\n")
451 lines.append("date = '%s'" % runner.cfg.VARS.date)
452 lines.append("hour = '%s'" % runner.cfg.VARS.hour)
453 lines.append("node = '%s'" % runner.cfg.VARS.node)
454 lines.append("arch = '%s'" % runner.cfg.VARS.dist)
456 if 'APPLICATION' in runner.cfg:
457 lines.append("application_info = {}")
458 lines.append("application_info['name'] = '%s'" %
459 runner.cfg.APPLICATION.name)
460 lines.append("application_info['tag'] = '%s'" %
461 runner.cfg.APPLICATION.tag)
462 lines.append("application_info['products'] = %s" %
463 str(runner.cfg.APPLICATION.products))
465 content = "\n".join(lines)
467 # create hash from context information
468 dirname = sha1(content.encode()).hexdigest()
469 base_dir = os.path.join(tmp_dir, dirname)
470 os.makedirs(base_dir)
471 os.environ['TT_TMP_RESULT'] = base_dir
473 # create env_info file
474 f = open(os.path.join(base_dir, 'env_info.py'), "w")
478 # create working dir and bases dir
479 working_dir = os.path.join(base_dir, 'WORK')
480 os.makedirs(working_dir)
481 os.makedirs(os.path.join(base_dir, 'BASES'))
482 os.chdir(working_dir)
484 if 'PYTHONPATH' not in os.environ:
485 os.environ['PYTHONPATH'] = ''
487 for var in os.environ['PYTHONPATH'].split(':'):
488 if var not in sys.path:
491 # launch of the tests
492 #####################
495 test_base = options.base
496 elif with_application and "test_base" in runner.cfg.APPLICATION:
497 test_base = runner.cfg.APPLICATION.test_base.name
499 src.printcolors.print_value(logger, _('Display'), os.environ['DISPLAY'], 2)
500 src.printcolors.print_value(logger, _('Timeout'),
501 runner.cfg.SITE.test.timeout, 2)
502 src.printcolors.print_value(logger, _("Working dir"), base_dir, 3)
504 # create the test object
505 test_runner = src.test_module.Test(runner.cfg,
509 modules=options.modules,
511 launcher=options.launcher,
512 show_desktop=show_desktop)
514 if not test_runner.test_base_found:
519 logger.allowPrintLevel = False
520 retcode = test_runner.run_all_tests()
521 logger.allowPrintLevel = True
523 logger.write(_("Tests finished"), 1)
524 logger.write("\n", 2, False)
526 logger.write(_("\nGenerate the specific test log\n"), 5)
527 out_dir = os.path.join(runner.cfg.SITE.log.log_dir, "TEST")
528 src.ensure_path_exists(out_dir)
529 name_xml_board = logger.logFileName.split(".")[0] + "board" + ".xml"
530 create_test_report(runner.cfg,
533 xmlname = name_xml_board)
534 xml_board_path = os.path.join(out_dir, name_xml_board)
535 logger.l_logFiles.append(xml_board_path)
536 logger.add_link(os.path.join("TEST", name_xml_board),
539 "Click on the link to get the detailed test results")