]> SALOME platform Git repositories - tools/sat.git/blob - commands/test.py
Salome HOME
Remove the --grid option from the test command and replace it by the --base option
[tools/sat.git] / commands / test.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  CEA/DEN
4 #
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.
9 #
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.
14 #
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
18
19 import os
20 import sys
21 import shutil
22 import subprocess
23 import datetime
24 import gzip
25
26 try:
27     from hashlib import sha1
28 except ImportError:
29     from sha import sha as sha1
30
31 import src
32 import src.ElementTree as etree
33 from src.xmlManager import add_simple_node
34
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."))
51
52 def description():
53     '''method that is called when salomeTools is called with --help option.
54     
55     :return: The text to display for the test command description.
56     :rtype: str
57     '''
58     return _("The test command runs a test base on a SALOME installation.")     
59
60 def parse_option(args, config):
61     """ Parse the options and do some verifications about it
62     
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)
67     """
68     (options, args) = parser.parse_args(args)
69
70     if not options.launcher:
71         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, options.launcher)
77
78         if not os.path.exists(options.launcher):
79             raise src.SatException(_("Launcher not found: %s") % 
80                                    options.launcher)
81
82     return (options, args)
83
84 def ask_a_path():
85     """ 
86     """
87     path = raw_input("enter a path where to save the result: ")
88     if path == "":
89         result = raw_input("the result will be not save. Are you sure to "
90                            "continue ? [y/n] ")
91         if result == "y":
92             return path
93         else:
94             return ask_a_path()
95
96     elif os.path.exists(path):
97         result = raw_input("Warning, the content of %s will be deleted. Are you"
98                            " sure to continue ? [y/n] " % path)
99         if result == "y":
100             return path
101         else:
102             return ask_a_path()
103     else:
104         return path
105
106 def save_file(filename, base):
107     f = open(filename, 'r')
108     content = f.read()
109     f.close()
110
111     objectname = sha1(content).hexdigest()
112
113     f = gzip.open(os.path.join(base, '.objects', objectname), 'w')
114     f.write(content)
115     f.close()
116     return objectname
117
118 def move_test_results(in_dir, what, out_dir, logger):
119     if out_dir == in_dir:
120         return
121
122     finalPath = out_dir
123     pathIsOk = False
124     while not pathIsOk:
125         try:
126             # create test results directory if necessary
127             #logger.write("FINAL = %s\n" % finalPath, 5)
128             if not os.access(finalPath, os.F_OK):
129                 #shutil.rmtree(finalPath)
130                 os.makedirs(finalPath)
131             pathIsOk = True
132         except:
133             logger.error(_("%s cannot be created.") % finalPath)
134             finalPath = ask_a_path()
135
136     if finalPath != "":
137         os.makedirs(os.path.join(finalPath, what, 'BASES'))
138
139         # check if .objects directory exists
140         if not os.access(os.path.join(finalPath, '.objects'), os.F_OK):
141             os.makedirs(os.path.join(finalPath, '.objects'))
142
143         logger.write(_('copy tests results to %s ... ') % finalPath, 3)
144         logger.flush()
145         #logger.write("\n", 5)
146
147         # copy env_info.py
148         shutil.copy2(os.path.join(in_dir, what, 'env_info.py'),
149                      os.path.join(finalPath, what, 'env_info.py'))
150
151         # for all sub directory (ie testbase) in the BASES directory
152         for testbase in os.listdir(os.path.join(in_dir, what, 'BASES')):
153             outtestbase = os.path.join(finalPath, what, 'BASES', testbase)
154             intestbase = os.path.join(in_dir, what, 'BASES', testbase)
155
156             # ignore files in root dir
157             if not os.path.isdir(intestbase):
158                 continue
159
160             os.makedirs(outtestbase)
161             #logger.write("  copy testbase %s\n" % testbase, 5)
162
163             for module_ in [m for m in os.listdir(intestbase) if os.path.isdir(
164                                                     os.path.join(intestbase, m))]:
165                 # ignore source configuration directories
166                 if module_[:4] == '.git' or module_ == 'CVS':
167                     continue
168
169                 outmodule = os.path.join(outtestbase, module_)
170                 inmodule = os.path.join(intestbase, module_)
171                 os.makedirs(outmodule)
172                 #logger.write("    copy module %s\n" % module_, 5)
173
174                 if module_ == 'RESSOURCES':
175                     for file_name in os.listdir(inmodule):
176                         if not os.path.isfile(os.path.join(inmodule,
177                                                            file_name)):
178                             continue
179                         f = open(os.path.join(outmodule, file_name), "w")
180                         f.write(save_file(os.path.join(inmodule, file_name),
181                                           finalPath))
182                         f.close()
183                 else:
184                     for type_name in [t for t in os.listdir(inmodule) if 
185                                       os.path.isdir(os.path.join(inmodule, t))]:
186                         outtype = os.path.join(outmodule, type_name)
187                         intype = os.path.join(inmodule, type_name)
188                         os.makedirs(outtype)
189                         
190                         for file_name in os.listdir(intype):
191                             if not os.path.isfile(os.path.join(intype,
192                                                                file_name)):
193                                 continue
194                             if file_name.endswith('result.py'):
195                                 shutil.copy2(os.path.join(intype, file_name),
196                                              os.path.join(outtype, file_name))
197                             else:
198                                 f = open(os.path.join(outtype, file_name), "w")
199                                 f.write(save_file(os.path.join(intype,
200                                                                file_name),
201                                                   finalPath))
202                                 f.close()
203
204     logger.write(src.printcolors.printc("OK"), 3, False)
205     logger.write("\n", 3, False)
206
207 def check_remote_machine(machine_name, logger):
208     logger.write(_("\ncheck the display on %s\n" % machine_name), 4)
209     ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s "ls"' % machine_name
210     logger.write(_("Executing the command : %s " % ssh_cmd), 4)
211     p = subprocess.Popen(ssh_cmd, 
212                          shell=True,
213                          stdin =subprocess.PIPE,
214                          stdout=subprocess.PIPE,
215                          stderr=subprocess.PIPE)
216     p.wait()
217     if p.returncode != 0:
218         logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 1)
219         logger.write("    " + src.printcolors.printcError(p.stderr.read()), 2)
220         logger.write(src.printcolors.printcWarning((
221                                     "No ssh access to the display machine.")),1)
222     else:
223         logger.write(src.printcolors.printcSuccess(src.OK_STATUS) + "\n\n", 4)
224
225 ##
226 # Creates the XML report for a product.
227 def create_test_report(config, dest_path, stylesheet, xmlname=""):
228     application_name = config.VARS.application
229     withappli = src.config_has_application(config)
230
231     root = etree.Element("salome")
232     prod_node = etree.Element("product", name=application_name, build=xmlname)
233     root.append(prod_node)
234
235     if withappli:
236
237         add_simple_node(prod_node, "version_to_download",
238                         config.APPLICATION.name)
239         
240         add_simple_node(prod_node, "out_dir", config.APPLICATION.workdir)
241
242     # add environment
243     exec_node = add_simple_node(prod_node, "exec")
244     exec_node.append(etree.Element("env", name="Host", value=config.VARS.node))
245     exec_node.append(etree.Element("env", name="Architecture",
246                                    value=config.VARS.dist))
247     exec_node.append(etree.Element("env", name="Number of processors",
248                                    value=str(config.VARS.nb_proc)))    
249     exec_node.append(etree.Element("env", name="Begin date",
250                                    value=src.parse_date(config.VARS.datehour)))
251     exec_node.append(etree.Element("env", name="Command",
252                                    value=config.VARS.command))
253     exec_node.append(etree.Element("env", name="sat version",
254                                    value=config.INTERNAL.sat_version))
255
256     if 'TESTS' in config:
257         tests = add_simple_node(prod_node, "tests")
258         known_errors = add_simple_node(prod_node, "known_errors")
259         new_errors = add_simple_node(prod_node, "new_errors")
260         amend = add_simple_node(prod_node, "amend")
261         tt = {}
262         for test in config.TESTS:
263             if not tt.has_key(test.testbase):
264                 tt[test.testbase] = [test]
265             else:
266                 tt[test.testbase].append(test)
267
268         for testbase in tt.keys():
269             gn = add_simple_node(tests, "testbase")
270             gn.attrib['name'] = testbase
271             nb, nb_pass, nb_failed, nb_timeout, nb_not_run = 0, 0, 0, 0, 0
272             modules = {}
273             types = {}
274             for test in tt[testbase]:
275                 #print test.module
276                 if not modules.has_key(test.module):
277                     mn = add_simple_node(gn, "module")
278                     mn.attrib['name'] = test.module
279                     modules[test.module] = mn
280
281                 if not types.has_key("%s/%s" % (test.module, test.type)):
282                     tyn = add_simple_node(mn, "type")
283                     tyn.attrib['name'] = test.type
284                     types["%s/%s" % (test.module, test.type)] = tyn
285
286                 for script in test.script:
287                     tn = add_simple_node(types[
288                                     "%s/%s" % (test.module, test.type)], "test")
289                     tn.attrib['type'] = test.type
290                     tn.attrib['script'] = script.name
291                     if 'callback' in script:
292                         try:
293                             cnode = add_simple_node(tn, "callback")
294                             if src.architecture.is_windows():
295                                 import string
296                                 cnode.text = filter(
297                                                 lambda x: x in string.printable,
298                                                 script.callback)
299                             else:
300                                 cnode.text = script.callback.decode(
301                                                                 'string_escape')
302                         except UnicodeDecodeError as exc:
303                             zz = (script.callback[:exc.start] +
304                                   '?' +
305                                   script.callback[exc.end-2:])
306                             cnode = add_simple_node(tn, "callback")
307                             cnode.text = zz.decode("UTF-8")
308                     
309                     # Add the script content
310                     cnode = add_simple_node(tn, "content")
311                     cnode.text = script.content
312                     
313                     # Add the script execution log
314                     cnode = add_simple_node(tn, "out")
315                     cnode.text = script.out
316                     
317                     if 'amend' in script:
318                         cnode = add_simple_node(tn, "amend")
319                         cnode.text = script.amend.decode("UTF-8")
320
321                     if script.time < 0:
322                         tn.attrib['exec_time'] = "?"
323                     else:
324                         tn.attrib['exec_time'] = "%.3f" % script.time
325                     tn.attrib['res'] = script.res
326
327                     if "amend" in script:
328                         amend_test = add_simple_node(amend, "atest")
329                         amend_test.attrib['name'] = os.path.join(test.module,
330                                                                  test.type,
331                                                                  script.name)
332                         amend_test.attrib['reason'] = script.amend.decode(
333                                                                         "UTF-8")
334
335                     # calculate status
336                     nb += 1
337                     if script.res == src.OK_STATUS: nb_pass += 1
338                     elif script.res == src.TIMEOUT_STATUS: nb_timeout += 1
339                     elif script.res == src.KO_STATUS: nb_failed += 1
340                     else: nb_not_run += 1
341
342                     if "known_error" in script:
343                         kf_script = add_simple_node(known_errors, "error")
344                         kf_script.attrib['name'] = os.path.join(test.module,
345                                                                 test.type,
346                                                                 script.name)
347                         kf_script.attrib['date'] = script.known_error.date
348                         kf_script.attrib[
349                                     'expected'] = script.known_error.expected
350                         kf_script.attrib[
351                          'comment'] = script.known_error.comment.decode("UTF-8")
352                         kf_script.attrib['fixed'] = str(
353                                                        script.known_error.fixed)
354                         overdue = datetime.datetime.today().strftime("%Y-%m-"
355                                             "%d") > script.known_error.expected
356                         if overdue:
357                             kf_script.attrib['overdue'] = str(overdue)
358                         
359                     elif script.res == src.KO_STATUS:
360                         new_err = add_simple_node(new_errors, "new_error")
361                         script_path = os.path.join(test.module,
362                                                    test.type, script.name)
363                         new_err.attrib['name'] = script_path
364                         new_err.attrib['cmd'] = ("sat testerror %s -s %s -c 'my"
365                                                  " comment' -p %s" % \
366                             (application_name, script_path, config.VARS.dist))
367
368
369             gn.attrib['total'] = str(nb)
370             gn.attrib['pass'] = str(nb_pass)
371             gn.attrib['failed'] = str(nb_failed)
372             gn.attrib['timeout'] = str(nb_timeout)
373             gn.attrib['not_run'] = str(nb_not_run)
374
375     if len(xmlname) == 0:
376         xmlname = application_name
377     if not xmlname.endswith(".xml"):
378         xmlname += ".xml"
379
380     src.xmlManager.write_report(os.path.join(dest_path, xmlname),
381                                 root,
382                                 stylesheet)
383     return src.OK_STATUS
384
385 def run(args, runner, logger):
386     '''method that is called when salomeTools is called with test parameter.
387     '''
388     (options, args) = parse_option(args, runner.cfg)
389
390     with_application = False
391     if runner.cfg.VARS.application != 'None':
392         logger.write(_('Running tests on application %s\n') % 
393                             src.printcolors.printcLabel(
394                                                 runner.cfg.VARS.application), 1)
395         with_application = True
396     elif not options.base:
397         raise src.SatException(_('A test base is required. Use the --base '
398                                  'option'))
399
400     if with_application:
401         # check if environment is loaded
402         if 'KERNEL_ROOT_DIR' in os.environ:
403             logger.write(src.printcolors.printcWarning(_("WARNING: "
404                             "SALOME environment already sourced")) + "\n", 1)
405             
406         
407     elif options.launcher:
408         logger.write(src.printcolors.printcWarning(_("Running SALOME "
409                                                 "application.")) + "\n\n", 1)
410     else:
411         msg = _("Impossible to find any launcher.\nPlease specify an "
412                 "application or a launcher")
413         logger.write(src.printcolors.printcError(msg))
414         logger.write("\n")
415         return 1
416
417     # set the display
418     show_desktop = (options.display and options.display.upper() == "NO")
419     if options.display and options.display != "NO":
420         remote_name = options.display.split(':')[0]
421         if remote_name != "":
422             check_remote_machine(remote_name, logger)
423         # if explicitly set use user choice
424         os.environ['DISPLAY'] = options.display
425     elif 'DISPLAY' not in os.environ:
426         # if no display set
427         if ('display' in runner.cfg.SITE.test and 
428                                         len(runner.cfg.SITE.test.display) > 0):
429             # use default value for test tool
430             os.environ['DISPLAY'] = runner.cfg.SITE.test.display
431         else:
432             os.environ['DISPLAY'] = "localhost:0.0"
433
434     # initialization
435     #################
436     if with_application:
437         tmp_dir = runner.cfg.SITE.test.tmp_dir_with_application
438     else:
439         tmp_dir = runner.cfg.SITE.test.tmp_dir
440
441     # remove previous tmp dir
442     if os.access(tmp_dir, os.F_OK):
443         try:
444             shutil.rmtree(tmp_dir)
445         except:
446             logger.error(_("error removing TT_TMP_RESULT %s\n") 
447                                 % tmp_dir)
448
449     lines = []
450     lines.append("date = '%s'" % runner.cfg.VARS.date)
451     lines.append("hour = '%s'" % runner.cfg.VARS.hour)
452     lines.append("node = '%s'" % runner.cfg.VARS.node)
453     lines.append("arch = '%s'" % runner.cfg.VARS.dist)
454
455     if 'APPLICATION' in runner.cfg:
456         lines.append("application_info = {}")
457         lines.append("application_info['name'] = '%s'" % 
458                      runner.cfg.APPLICATION.name)
459         lines.append("application_info['tag'] = '%s'" % 
460                      runner.cfg.APPLICATION.tag)
461         lines.append("application_info['products'] = %s" % 
462                      str(runner.cfg.APPLICATION.products))
463
464     content = "\n".join(lines)
465
466     # create hash from session information
467     dirname = sha1(content.encode()).hexdigest()
468     base_dir = os.path.join(tmp_dir, dirname)
469     os.makedirs(base_dir)
470     os.environ['TT_TMP_RESULT'] = base_dir
471
472     # create env_info file
473     f = open(os.path.join(base_dir, 'env_info.py'), "w")
474     f.write(content)
475     f.close()
476
477     # create working dir and bases dir
478     working_dir = os.path.join(base_dir, 'WORK')
479     os.makedirs(working_dir)
480     os.makedirs(os.path.join(base_dir, 'BASES'))
481     os.chdir(working_dir)
482
483     if 'PYTHONPATH' not in os.environ:
484         os.environ['PYTHONPATH'] = ''
485     else:
486         for var in os.environ['PYTHONPATH'].split(':'):
487             if var not in sys.path:
488                 sys.path.append(var)
489
490     # launch of the tests
491     #####################
492     test_base = ""
493     if options.base:
494         test_base = options.base
495     elif with_application and "test_base" in runner.cfg.APPLICATION:
496         test_base = runner.cfg.APPLICATION.test_base.name
497
498     src.printcolors.print_value(logger, _('Display'), os.environ['DISPLAY'], 2)
499     src.printcolors.print_value(logger, _('Timeout'),
500                                 runner.cfg.SITE.test.timeout, 2)
501     src.printcolors.print_value(logger, _("Working dir"), base_dir, 3)
502
503     # create the test object
504     test_runner = src.test_module.Test(runner.cfg,
505                                   logger,
506                                   base_dir,
507                                   testbase=test_base,
508                                   modules=options.modules,
509                                   types=options.types,
510                                   launcher=options.launcher,
511                                   show_desktop=show_desktop)
512     
513     # run the test
514     logger.allowPrintLevel = False
515     retcode = test_runner.run_all_tests()
516     logger.allowPrintLevel = True
517
518     logger.write(_("Tests finished"), 1)
519     logger.write("\n", 2, False)
520     
521     logger.write(_("\nGenerate the specific test log\n"), 5)
522     out_dir = os.path.join(runner.cfg.SITE.log.log_dir, "TEST")
523     src.ensure_path_exists(out_dir)
524     name_xml_board = logger.logFileName.split(".")[0] + "board" + ".xml"
525     create_test_report(runner.cfg,
526                        out_dir,
527                        "test.xsl",
528                        xmlname = name_xml_board)
529     xml_board_path = os.path.join(out_dir, name_xml_board)
530     logger.l_logFiles.append(xml_board_path)
531     logger.add_link(os.path.join("TEST", name_xml_board),
532                     "board",
533                     retcode,
534                     "Click on the link to get the detailed test results")
535
536     return retcode
537