Salome HOME
style: black format
[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 # Compatibility python 2/3 for input function
27 # input stays input for python 3 and input = raw_input for python 2
28 try:
29     input = raw_input
30 except NameError:
31     pass
32
33 verbose = False
34
35 try:
36     from hashlib import sha1
37 except ImportError:
38     from sha import sha as sha1
39
40 import src
41 import src.ElementTree as etree
42 from src.xmlManager import add_simple_node
43
44 # Define all possible option for the test command :  sat test <options>
45 parser = src.options.Options()
46 parser.add_option(
47     "b",
48     "base",
49     "string",
50     "base",
51     _(
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."""
55     ),
56 )
57 parser.add_option(
58     "l",
59     "launcher",
60     "string",
61     "launcher",
62     _(
63         """Optional: Specify the path to a SALOME launcher
64           used to launch the test scripts of the test base."""
65     ),
66 )
67 parser.add_option(
68     "g",
69     "grid",
70     "list",
71     "grids",
72     _("Optional: Which grid(s) to test (subdirectory of the test base)."),
73 )
74 parser.add_option(
75     "s",
76     "session",
77     "list2",
78     "sessions",
79     _("Optional: Which session(s) to test (subdirectory of the grid)."),
80 )
81 parser.add_option(
82     "",
83     "display",
84     "string",
85     "display",
86     _(
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."""
89     ),
90 )
91 parser.add_option(
92     "",
93     "keep",
94     "boolean",
95     "keeptempdir",
96     _("Optional: keep temporary big tests directories."),
97 )
98
99
100 def description():
101     """method that is called when salomeTools is called with --help option.
102
103     :return: The text to display for the test command description.
104     :rtype: str
105     """
106     return _(
107         "The test command runs a test base on a SALOME installation.\n\n"
108         "example:\nsat test SALOME-master --grid GEOM --session light"
109     )
110
111
112 def parse_option_old(args, config):
113     """Parse the options and do some verifications about it
114
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)
119     """
120     (options, args) = parser.parse_args(args)
121
122     if not options.launcher:
123         options.launcher = ""
124     elif not os.path.isabs(options.launcher):
125         if not src.config_has_application(config):
126             msg = _(
127                 "An application is required to use a relative path with option --appli"
128             )
129             raise src.SatException(msg)
130         options.launcher = os.path.join(config.APPLICATION.workdir, options.launcher)
131
132         if not os.path.exists(options.launcher):
133             raise src.SatException(_("Launcher not found: %s") % options.launcher)
134
135     return (options, args)
136
137
138 def parse_option(args, config):
139     """Parse the options and do some verifications about it
140
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)
145     """
146     (options, args) = parser.parse_args(args)
147
148     if not options.launcher:
149         options.launcher = ""
150         return (options, args)
151
152     if not os.path.isabs(options.launcher):
153         if not src.config_has_application(config):
154             msg = _(
155                 "An application is required to use a relative path with option --appli"
156             )
157             raise src.SatException(msg)
158         else:
159             options.launcher = os.path.join(
160                 config.APPLICATION.workdir, options.launcher
161             )
162             if not os.path.exists(options.launcher):
163                 raise src.SatException(_("Launcher not found: %s") % options.launcher)
164
165     # absolute path
166     launcher = os.path.realpath(os.path.expandvars(options.launcher))
167     if os.path.exists(launcher):
168         options.launcher = launcher
169         return (options, args)
170
171     raise src.SatException(_("Launcher not found: %s") % options.launcher)
172
173
174 def ask_a_path():
175     """ """
176     path = input("enter a path where to save the result: ")
177     if path == "":
178         result = input(
179             "the result will be not save. Are you sure to " "continue ? [y/n] "
180         )
181         if result == "y":
182             return path
183         else:
184             return ask_a_path()
185
186     elif os.path.exists(path):
187         result = input(
188             "Warning, the content of %s will be deleted. Are you"
189             " sure to continue ? [y/n] " % path
190         )
191         if result == "y":
192             return path
193         else:
194             return ask_a_path()
195     else:
196         return path
197
198
199 def save_file(filename, base):
200     f = open(filename, "r")
201     content = f.read()
202     f.close()
203
204     objectname = sha1(content).hexdigest()
205
206     f = gzip.open(os.path.join(base, ".objects", objectname), "w")
207     f.write(content)
208     f.close()
209     return objectname
210
211
212 def move_test_results(in_dir, what, out_dir, logger):
213     if out_dir == in_dir:
214         return
215
216     finalPath = out_dir
217     pathIsOk = False
218     while not pathIsOk:
219         try:
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)
225             pathIsOk = True
226         except:
227             logger.error(_("%s cannot be created.") % finalPath)
228             finalPath = ask_a_path()
229
230     if finalPath != "":
231         os.makedirs(os.path.join(finalPath, what, "BASES"))
232
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"))
236
237         logger.write(_("copy tests results to %s ... ") % finalPath, 3)
238         logger.flush()
239         # logger.write("\n", 5)
240
241         # copy env_info.py
242         shutil.copy2(
243             os.path.join(in_dir, what, "env_info.py"),
244             os.path.join(finalPath, what, "env_info.py"),
245         )
246
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)
251
252             # ignore files in root dir
253             if not os.path.isdir(intestbase):
254                 continue
255
256             os.makedirs(outtestbase)
257             # logger.write("  copy testbase %s\n" % testbase, 5)
258
259             for grid_ in [
260                 m
261                 for m in os.listdir(intestbase)
262                 if os.path.isdir(os.path.join(intestbase, m))
263             ]:
264                 # ignore source configuration directories
265                 if grid_[:4] == ".git" or grid_ == "CVS":
266                     continue
267
268                 outgrid = os.path.join(outtestbase, grid_)
269                 ingrid = os.path.join(intestbase, grid_)
270                 os.makedirs(outgrid)
271                 # logger.write("    copy grid %s\n" % grid_, 5)
272
273                 if grid_ == "RESSOURCES":
274                     for file_name in os.listdir(ingrid):
275                         if not os.path.isfile(os.path.join(ingrid, file_name)):
276                             continue
277                         f = open(os.path.join(outgrid, file_name), "w")
278                         f.write(save_file(os.path.join(ingrid, file_name), finalPath))
279                         f.close()
280                 else:
281                     for session_name in [
282                         t
283                         for t in os.listdir(ingrid)
284                         if os.path.isdir(os.path.join(ingrid, t))
285                     ]:
286                         outsession = os.path.join(outgrid, session_name)
287                         insession = os.path.join(ingrid, session_name)
288                         os.makedirs(outsession)
289
290                         for file_name in os.listdir(insession):
291                             if not os.path.isfile(os.path.join(insession, file_name)):
292                                 continue
293                             if file_name.endswith("result.py"):
294                                 shutil.copy2(
295                                     os.path.join(insession, file_name),
296                                     os.path.join(outsession, file_name),
297                                 )
298                             else:
299                                 f = open(os.path.join(outsession, file_name), "w")
300                                 f.write(
301                                     save_file(
302                                         os.path.join(insession, file_name), finalPath
303                                     )
304                                 )
305                                 f.close()
306
307     logger.write(src.printcolors.printc("OK"), 3, False)
308     logger.write("\n", 3, False)
309
310
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(
316         ssh_cmd,
317         shell=True,
318         stdin=subprocess.PIPE,
319         stdout=subprocess.PIPE,
320         stderr=subprocess.PIPE,
321     )
322     p.wait()
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)
326         logger.write(
327             src.printcolors.printcWarning(("No ssh access to the display machine.")), 1
328         )
329     else:
330         logger.write(src.printcolors.printcSuccess(src.OK_STATUS) + "\n\n", 4)
331
332
333 def findOrCreateNode(parentNode, nameNodeToFind):
334     found = parentNode.find(nameNodeToFind)
335     if found is None:
336         created = add_simple_node(parentNode, nameNodeToFind)
337         return created
338     else:
339         return found
340
341
342 def purgeEmptyNodes(root):
343     """
344     recursive remove node.text and node.tail if empty node
345     as nothing else than whitespace(s) and RCLF(s)
346
347     | this is comes from
348     | 1) pretty print file xml -> creates indentation(s) in text and tail
349     | 2) and reload parse file xml
350     """
351     # print("root", root.tag, root.text)
352     text = root.text
353     tail = root.tail
354     if text is not None:
355         if text.replace(" ", "").replace("\n", "") == "":
356             # print("purgeEmptyNodes text %s" % root.tag)
357             root.text = None
358     if tail is not None:
359         if tail.replace(" ", "").replace("\n", "") == "":
360             # print("purgeEmptyNodes tail %s" % root.tag)
361             root.tail = None
362     for node in root:
363         purgeEmptyNodes(node)
364     return
365
366
367 ##
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
371     # history
372     date_hour = config.VARS.datehour
373
374     # Get some information to put in the xml file
375     application_name = config.VARS.application
376     withappli = src.config_has_application(config)
377
378     first_time = False
379     if not os.path.exists(xml_history_path):
380         print("Log file creation %s" % xml_history_path)
381         first_time = True
382         root = etree.Element("salome")
383         prod_node = etree.Element("product", name=application_name, build=xmlname)
384         root.append(prod_node)
385     else:
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")
390
391     prod_node.attrib["history_file"] = os.path.basename(xml_history_path)
392     prod_node.attrib["global_res"] = str(retcode)
393
394     if withappli:
395         if not first_time:
396             for node in prod_node.findall("version_to_download") + prod_node.findall(
397                 "out_dir"
398             ):
399                 prod_node.remove(node)
400
401         add_simple_node(prod_node, "version_to_download", config.APPLICATION.name)
402         add_simple_node(prod_node, "out_dir", config.APPLICATION.workdir)
403
404     # add environment
405     if not first_time:
406         for node in prod_node.findall("exec"):
407             prod_node.remove(node)
408
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))
412     exec_node.append(
413         etree.Element(
414             "env", name="Number of processors", value=str(config.VARS.nb_proc)
415         )
416     )
417     exec_node.append(
418         etree.Element("env", name="Begin date", value=src.parse_date(date_hour))
419     )
420     exec_node.append(etree.Element("env", name="Command", value=config.VARS.command))
421     exec_node.append(
422         etree.Element("env", name="sat version", value=config.INTERNAL.sat_version)
423     )
424
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")
430
431         tt = {}
432         for test in config.TESTS:
433             if not test.testbase in tt:
434                 tt[test.testbase] = [test]
435             else:
436                 tt[test.testbase].append(test)
437
438         for testbase in tt.keys():
439             if verbose:
440                 print("---- create_test_report %s %s" % (testbase, first_time))
441             gn = findOrCreateNode(tests, "testbase")
442
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)
452
453                         attribs_to_pop = []
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)
459
460             gn.attrib["name"] = testbase
461             nb, nb_pass, nb_failed, nb_timeout, nb_not_run = 0, 0, 0, 0, 0
462             grids = {}
463             sessions = {}
464             for test in tt[testbase]:
465                 if not (test.grid in grids):
466                     if first_time:
467                         mn = add_simple_node(gn, "grid")
468                         mn.attrib["name"] = test.grid
469                     else:
470                         l_mn = gn.findall("grid")
471                         mn = None
472                         for grid_node in l_mn:
473                             if grid_node.attrib["name"] == test.grid:
474                                 mn = grid_node
475                                 break
476                         if mn == None:
477                             mn = add_simple_node(gn, "grid")
478                             mn.attrib["name"] = test.grid
479
480                     grids[test.grid] = mn
481
482                 mn.attrib["executed_last_time"] = "yes"
483
484                 if not "%s/%s" % (test.grid, test.session) in sessions:
485                     if first_time:
486                         tyn = add_simple_node(mn, "session")
487                         tyn.attrib["name"] = test.session
488                     else:
489                         l_tyn = mn.findall("session")
490                         tyn = None
491                         for session_node in l_tyn:
492                             if session_node.attrib["name"] == test.session:
493                                 tyn = session_node
494                                 break
495                         if tyn == None:
496                             tyn = add_simple_node(mn, "session")
497                             tyn.attrib["name"] = test.session
498
499                     sessions["%s/%s" % (test.grid, test.session)] = tyn
500
501                 tyn.attrib["executed_last_time"] = "yes"
502
503                 for script in test.script:
504                     if first_time:
505                         tn = add_simple_node(
506                             sessions["%s/%s" % (test.grid, test.session)], "test"
507                         )
508                         tn.attrib["session"] = test.session
509                         tn.attrib["script"] = script.name
510                         hn = add_simple_node(tn, "history")
511                     else:
512                         l_tn = sessions["%s/%s" % (test.grid, test.session)].findall(
513                             "test"
514                         )
515                         tn = None
516                         for test_node in l_tn:
517                             if test_node.attrib["script"] == script["name"]:
518                                 tn = test_node
519                                 break
520
521                         if tn == None:
522                             tn = add_simple_node(
523                                 sessions["%s/%s" % (test.grid, test.session)], "test"
524                             )
525                             tn.attrib["session"] = test.session
526                             tn.attrib["script"] = script.name
527                             hn = add_simple_node(tn, "history")
528                         else:
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")
532                             else:
533                                 hn = tn.find("history")
534                             # Put the last test data into the history
535                             if "res" in tn.attrib:
536                                 attributes = {
537                                     "date_hour": date_hour,
538                                     "res": tn.attrib["res"],
539                                 }
540                                 add_simple_node(hn, "previous_test", attrib=attributes)
541                             for node in tn:
542                                 if node.tag != "history":
543                                     tn.remove(node)
544
545                     if "callback" in script:
546                         try:
547                             cnode = add_simple_node(tn, "callback")
548                             if (
549                                 True
550                             ):  # bug xml mal forme colorisation  src.architecture.is_windows():
551                                 import string
552
553                                 cnode.text = filter(
554                                     lambda x: x in string.printable, script.callback
555                                 )
556                             else:
557                                 cnode.text = script.callback.decode("string_escape")
558                         except UnicodeDecodeError as exc:
559                             zz = (
560                                 script.callback[: exc.start]
561                                 + "?"
562                                 + script.callback[exc.end - 2 :]
563                             )
564                             cnode = add_simple_node(tn, "callback")
565                             cnode.text = zz.decode("UTF-8")
566
567                     # Add the script content
568                     cnode = add_simple_node(tn, "content")
569                     cnode.text = script.content
570
571                     # Add the script execution log
572                     cnode = add_simple_node(tn, "out")
573                     cnode.text = script.out
574
575                     if "amend" in script:
576                         cnode = add_simple_node(tn, "amend")
577                         cnode.text = script.amend.decode("UTF-8")
578
579                     if script.time < 0:
580                         tn.attrib["exec_time"] = "?"
581                     else:
582                         tn.attrib["exec_time"] = "%.3f" % script.time
583                     tn.attrib["res"] = script.res
584
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
589                         )
590                         amend_test.attrib["reason"] = script.amend.decode("UTF-8")
591
592                     # calculate status
593                     nb += 1
594                     if script.res == src.OK_STATUS:
595                         nb_pass += 1
596                     elif script.res == src.TIMEOUT_STATUS:
597                         nb_timeout += 1
598                     elif script.res == src.KO_STATUS:
599                         nb_failed += 1
600                     else:
601                         nb_not_run += 1
602
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
607                         )
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(
611                             "UTF-8"
612                         )
613                         kf_script.attrib["fixed"] = str(script.known_error.fixed)
614                         overdue = (
615                             datetime.datetime.today().strftime("%Y-%m-" "%d")
616                             > script.known_error.expected
617                         )
618                         if overdue:
619                             kf_script.attrib["overdue"] = str(overdue)
620
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
625                         new_err.attrib[
626                             "cmd"
627                         ] = "sat testerror %s -s %s -c 'my" " comment' -p %s" % (
628                             application_name,
629                             script_path,
630                             config.VARS.dist,
631                         )
632
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)
638
639             # Remove the res attribute of all tests that were not launched
640             # this time
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")
648
649     if len(xmlname) == 0:
650         xmlname = application_name
651     if not xmlname.endswith(".xml"):
652         xmlname += ".xml"
653
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")
656     return src.OK_STATUS
657
658
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.
662
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
666     :rtype: Str
667     """
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)
681
682
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)
686
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":
690         logger.write(
691             _("Running tests on application %s\n")
692             % src.printcolors.printcLabel(runner.cfg.VARS.application),
693             1,
694         )
695         with_application = True
696     elif not options.base:
697         raise src.SatException(_("A test base is required. Use the --base " "option"))
698
699     # the launcher is specified either by the application, or by the --launcher option
700     if with_application:
701         # check if environment is loaded
702         if "KERNEL_ROOT_DIR" in os.environ:
703             logger.write(
704                 src.printcolors.printcWarning(
705                     _("WARNING: " "SALOME environment already sourced")
706                 )
707                 + "\n",
708                 1,
709             )
710
711     elif options.launcher:
712         logger.write(
713             src.printcolors.printcWarning(_("Running SALOME " "application.")) + "\n\n",
714             1,
715         )
716     else:
717         msg = _(
718             "Impossible to find any launcher.\nPlease specify an "
719             "application or a launcher"
720         )
721         logger.write(src.printcolors.printcError(msg))
722         logger.write("\n")
723         return 1
724
725     # set the display
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:
734         # if no display set
735         if (
736             "test" in runner.cfg.LOCAL
737             and "display" in runner.cfg.LOCAL.test
738             and len(runner.cfg.LOCAL.test.display) > 0
739         ):
740             # use default value for test tool
741             os.environ["DISPLAY"] = runner.cfg.LOCAL.test.display
742         else:
743             os.environ["DISPLAY"] = "localhost:0.0"
744
745     # initialization
746     #################
747     if with_application:
748         tmp_dir = os.path.join(
749             runner.cfg.VARS.tmp_root, runner.cfg.APPLICATION.name, "test"
750         )
751     else:
752         tmp_dir = os.path.join(runner.cfg.VARS.tmp_root, "test")
753
754     # remove previous tmp dir
755     if os.access(tmp_dir, os.F_OK):
756         try:
757             shutil.rmtree(tmp_dir)
758         except:
759             logger.error(_("error removing TT_TMP_RESULT %s\n") % tmp_dir)
760
761     lines = []
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)
766
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)
771         lines.append(
772             "application_info['products'] = %s" % str(runner.cfg.APPLICATION.products)
773         )
774
775     content = "\n".join(lines)
776
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
783
784     # create env_info file
785     with open(os.path.join(base_dir, "env_info.py"), "w") as f:
786         f.write(content)
787
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)
793
794     if "PYTHONPATH" not in os.environ:
795         os.environ["PYTHONPATH"] = ""
796     else:
797         for var in os.environ["PYTHONPATH"].split(":"):
798             if var not in sys.path:
799                 sys.path.append(var)
800
801     # launch of the tests
802     #####################
803     test_base = ""
804     if options.base:
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
808
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
812     )
813     src.printcolors.print_value(logger, _("Working dir"), base_dir, 3)
814
815     # create the test object
816     test_runner = src.test_module.Test(
817         runner.cfg,
818         logger,
819         base_dir,
820         testbase=test_base,
821         grids=options.grids,
822         sessions=options.sessions,
823         launcher=options.launcher,
824         show_desktop=show_desktop,
825     )
826
827     if not test_runner.test_base_found:
828         # Fail
829         return 1
830
831     # run the test
832     logger.allowPrintLevel = False
833     retcode = test_runner.run_all_tests()
834     logger.allowPrintLevel = True
835
836     logger.write(_("Tests finished"), 1)
837     logger.write("\n", 2, False)
838
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)
845
846     create_test_report(
847         runner.cfg, historic_xml_path, out_dir, retcode, xmlname=name_xml_board
848     )
849     xml_board_path = os.path.join(out_dir, name_xml_board)
850
851     logger.l_logFiles.append(xml_board_path)
852     logger.add_link(
853         os.path.join("TEST", name_xml_board),
854         "board",
855         retcode,
856         "Click on the link to get the detailed test results",
857     )
858     logger.write("\nTests board file %s\n" % xml_board_path, 1)
859
860     # Add the historic files into the log files list of the command
861     logger.l_logFiles.append(historic_xml_path)
862
863     if not options.keeptempdir:
864         logger.write(
865             "Removing the temporary directory: rm -rf %s\n"
866             % test_runner.tmp_working_dir,
867             5,
868         )
869         if os.path.exists(test_runner.tmp_working_dir):
870             shutil.rmtree(test_runner.tmp_working_dir)
871     else:
872         logger.write(
873             "NOT Removing the temporary directory: rm -rf %s\n"
874             % test_runner.tmp_working_dir,
875             5,
876         )
877
878     return retcode