1# Copyright 2015, VIXL authors
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7#   * Redistributions of source code must retain the above copyright notice,
8#     this list of conditions and the following disclaimer.
9#   * Redistributions in binary form must reproduce the above copyright notice,
10#     this list of conditions and the following disclaimer in the documentation
11#     and/or other materials provided with the distribution.
12#   * Neither the name of ARM Limited nor the names of its contributors may be
13#     used to endorse or promote products derived from this software without
14#     specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27import multiprocessing
28import re
29import signal
30import subprocess
31import sys
32import time
33
34from known_test_failures import FilterKnownTestFailures
35import printer
36import util
37
38# Catch SIGINT to gracefully exit when ctrl+C is pressed.
39def SigIntHandler(signal, frame):
40  sys.exit(1)
41
42signal.signal(signal.SIGINT, SigIntHandler)
43
44
45# Scan matching tests and return a test manifest.
46def GetTests(runner, filters = []):
47  rc, output = util.getstatusoutput(runner +  ' --list')
48  if rc != 0: util.abort('Failed to list all tests')
49
50  tests = output.split()
51  for f in filters:
52    print f
53    tests = filter(re.compile(f).search, tests)
54
55  return tests
56
57
58# Shared state for multiprocessing. Ideally the context should be passed with
59# arguments, but constraints from the multiprocessing module prevent us from
60# doing so: the shared variables (multiprocessing.Value) must be global, or no
61# work is started. So we abstract some additional state into global variables to
62# simplify the implementation.
63# Read-write variables for the workers.
64n_tests_passed = multiprocessing.Value('i', 0)
65n_tests_failed = multiprocessing.Value('i', 0)
66# Read-only for workers.
67test_runner = None
68test_runner_runtime_options = None
69test_runner_under_valgrind = False
70n_tests = None
71start_time = None
72progress_prefix = None
73
74
75def RunTest(test):
76  command = [test_runner, test] + test_runner_runtime_options
77  if test_runner_under_valgrind:
78    command = ['valgrind'] + command
79
80  p = subprocess.Popen(command,
81                       stdout=subprocess.PIPE,
82                       stderr=subprocess.STDOUT)
83  p_out, p_err = p.communicate()
84  rc = p.poll()
85
86  if rc == 0:
87    with n_tests_passed.get_lock(): n_tests_passed.value += 1
88  else:
89    with n_tests_failed.get_lock(): n_tests_failed.value += 1
90
91  printer.__print_lock__.acquire()
92
93  printer.UpdateProgress(start_time,
94                         n_tests_passed.value,
95                         n_tests_failed.value,
96                         n_tests,
97                         test,
98                         prevent_next_overwrite = (rc != 0),
99                         has_lock = True,
100                         prefix = progress_prefix)
101
102  if rc != 0:
103    printer.Print('FAILED: ' + test, has_lock = True)
104    printer.Print(printer.COLOUR_RED + ' '.join(command) + printer.NO_COLOUR,
105                  has_lock = True)
106    printer.Print(p_out, has_lock = True)
107
108  printer.__print_lock__.release()
109
110
111# Run the specified tests.
112# This function won't run in parallel due to constraints from the
113# multiprocessing module.
114__run_tests_lock__ = multiprocessing.Lock()
115def RunTests(test_runner_command, filters, runtime_options,
116             under_valgrind = False,
117             jobs = 1, prefix = ''):
118  global test_runner
119  global test_runner_runtime_options
120  global test_runner_under_valgrind
121  global n_tests
122  global start_time
123  global progress_prefix
124
125  tests = GetTests(test_runner_command, filters)
126  tests = FilterKnownTestFailures(tests, under_valgrind=under_valgrind)
127
128  if n_tests == 0:
129    printer.Print('No tests to run.')
130    return 0
131
132  with __run_tests_lock__:
133
134    # Initialisation.
135    start_time = time.time()
136    test_runner = test_runner_command
137    test_runner_runtime_options = runtime_options
138    test_runner_under_valgrind = under_valgrind
139    n_tests = len(tests)
140    n_tests_passed.value = 0
141    n_tests_failed.value = 0
142    progress_prefix = prefix
143
144    pool = multiprocessing.Pool(jobs)
145    # The '.get(9999999)' is a workaround to allow killing the test script with
146    # ctrl+C from the shell. This bug is documented at
147    # http://bugs.python.org/issue8296.
148    work = pool.map_async(RunTest, tests).get(9999999)
149    pool.close()
150    pool.join()
151
152    printer.UpdateProgress(start_time,
153                           n_tests_passed.value,
154                           n_tests_failed.value,
155                           n_tests,
156                           '== Done ==',
157                           prevent_next_overwrite = True,
158                           prefix = progress_prefix)
159
160  # `0` indicates success
161  return n_tests_failed.value
162