diff options
-rw-r--r-- | test/aarch64/test-assembler-aarch64.h | 10 | ||||
-rw-r--r-- | test/aarch64/test-simulator-aarch64.cc | 6 | ||||
-rw-r--r-- | test/test-api.cc | 10 | ||||
-rw-r--r-- | tools/known_test_failures.py | 16 | ||||
-rw-r--r-- | tools/printer.py | 14 | ||||
-rwxr-xr-x | tools/test.py | 5 | ||||
-rw-r--r-- | tools/thread_pool.py | 55 | ||||
-rw-r--r-- | tools/threaded_tests.py | 102 |
8 files changed, 162 insertions, 56 deletions
diff --git a/test/aarch64/test-assembler-aarch64.h b/test/aarch64/test-assembler-aarch64.h index b45c7ae8..cc4160b4 100644 --- a/test/aarch64/test-assembler-aarch64.h +++ b/test/aarch64/test-assembler-aarch64.h @@ -302,10 +302,12 @@ inline bool CanRun(const CPUFeatures& required, bool* queried_can_run) { if (!*queried_can_run) { *queried_can_run = true; CPUFeatures missing = required.Without(cpu); - std::cout << "Warning: This test cannot execute its generated code on this " - "CPU.\n"; - std::cout << " Required: { " << required << " }\n"; - std::cout << " Missing: { " << missing << " }\n"; + // Note: This message needs to match REGEXP_MISSING_FEATURES from + // tools/threaded_test.py. + std::cout << "SKIPPED: Missing features: { " << missing << " }\n"; + std::cout << "This test requires the following features to run its " + "generated code on this CPU: " + << required << "\n"; } return false; } diff --git a/test/aarch64/test-simulator-aarch64.cc b/test/aarch64/test-simulator-aarch64.cc index b145bb9a..257326f1 100644 --- a/test/aarch64/test-simulator-aarch64.cc +++ b/test/aarch64/test-simulator-aarch64.cc @@ -142,8 +142,10 @@ namespace aarch64 { *skipped = false; \ } else { \ std::stringstream os; \ - os << "Warning: skipping test due to missing CPU features.\n"; \ - os << " Missing: {" << requirements.Without(this_machine) << "}\n"; \ + /* Note: This message needs to match REGEXP_MISSING_FEATURES from */ \ + /* tools/threaded_test.py. */ \ + os << "SKIPPED: Missing features: { "; \ + os << requirements.Without(this_machine) << " }\n"; \ printf("%s", os.str().c_str()); \ *skipped = true; \ } \ diff --git a/test/test-api.cc b/test/test-api.cc index 53eaae38..5e0001e6 100644 --- a/test/test-api.cc +++ b/test/test-api.cc @@ -559,9 +559,13 @@ TEST(CPUFeatures_infer_from_id_registers) { VIXL_CHECK(os_auto.Has(os_with_id_regs)); VIXL_CHECK(os_with_id_regs.Has(os_auto)); } else { - printf( - "Warning: skipping test because ID register emulation is not " - "available.\n"); + // Note: This message needs to match REGEXP_MISSING_FEATURES from + // tools/threaded_test.py. + std::cout << "SKIPPED: Missing features: { " + << CPUFeatures::kIDRegisterEmulation << " }\n"; + std::cout << "This test requires the following features to run its " + "generated code on this CPU: " + << CPUFeatures::kIDRegisterEmulation << "\n"; } } diff --git a/tools/known_test_failures.py b/tools/known_test_failures.py index ad7ff1b5..262d6e62 100644 --- a/tools/known_test_failures.py +++ b/tools/known_test_failures.py @@ -45,8 +45,8 @@ def FilterKnownValgrindTestFailures(tests): if major > 3 or (major == 3 and minor > 10): return tests - # Valgrind versions before 3.11 have issues with fused multiply-add, - # so disable the affected tests. + reason = "Valgrind versions before 3.11 have issues with fused multiply-add, " \ + "so disable the affected tests." known_valgrind_test_failures = { 'AARCH64_SIM_fmadd_d', 'AARCH64_SIM_fmadd_s', @@ -76,13 +76,13 @@ def FilterKnownValgrindTestFailures(tests): 'AARCH64_SIM_frsqrts_D' } - for t in sorted(known_valgrind_test_failures): - print('Skipping ' + t + '...') - - return filter(lambda x: x not in known_valgrind_test_failures, tests) + filtered_list = filter(lambda x: x not in known_valgrind_test_failures, tests) + return (filtered_list, len(tests) - len(filtered_list), reason) def FilterKnownTestFailures(tests, **env): + skipped = [] if env.get('under_valgrind'): - tests = FilterKnownValgrindTestFailures(tests) + tests, n_tests_skipped, reason = FilterKnownValgrindTestFailures(tests) + skipped.append( (n_tests_skipped, reason) ) - return tests + return (tests, skipped) diff --git a/tools/printer.py b/tools/printer.py index 4a37a340..a28b433f 100644 --- a/tools/printer.py +++ b/tools/printer.py @@ -104,18 +104,22 @@ def PrintOverwritableLine(string, has_lock = False, type = LINE_TYPE_NONE): # Display the run progress: # prefix [time| progress|+ passed|- failed] name -def UpdateProgress(start_time, passed, failed, count, name, prefix = '', - prevent_next_overwrite = False, has_lock = False): +def UpdateProgress(start_time, passed, failed, count, skipped, known_failures, + name, prefix = '', prevent_next_overwrite = False, + has_lock = False): minutes, seconds = divmod(time.time() - start_time, 60) - progress = float(passed + failed) / count * 100 + progress = float(passed + failed + skipped) / count * 100 passed_colour = COLOUR_GREEN if passed != 0 else '' failed_colour = COLOUR_RED if failed != 0 else '' + skipped_colour = COLOUR_ORANGE if (skipped + known_failures) != 0 else '' indicator = '[%02d:%02d| %3d%%|' indicator += passed_colour + '+ %d' + NO_COLOUR + '|' - indicator += failed_colour + '- %d' + NO_COLOUR + ']' + indicator += failed_colour + '- %d' + NO_COLOUR + '|' + indicator += skipped_colour + '? %d' + NO_COLOUR + ']' progress_string = prefix - progress_string += indicator % (minutes, seconds, progress, passed, failed) + progress_string += indicator % (minutes, seconds, progress, passed, failed, + skipped + known_failures) progress_string += ' ' + name PrintOverwritableLine(progress_string, type = LINE_TYPE_PROGRESS, diff --git a/tools/test.py b/tools/test.py index 9f2b6539..0d6bcb82 100755 --- a/tools/test.py +++ b/tools/test.py @@ -34,7 +34,6 @@ import multiprocessing import os from os.path import join import platform -import re import subprocess import sys import time @@ -144,6 +143,8 @@ def BuildOptions(): help='''Don't actually build or run anything, but print the configurations that would be tested.''') + general_arguments.add_argument('--verbose', action='store_true', + help='''Print extra information.''') general_arguments.add_argument( '--jobs', '-j', metavar='N', type=int, nargs='?', default=multiprocessing.cpu_count(), @@ -429,7 +430,7 @@ if __name__ == '__main__': if not args.nobench: rc.Combine(RunBenchmarks(options, args)) - rc.Combine(tests.Run(args.jobs)) + rc.Combine(tests.Run(args.jobs, args.verbose)) if not args.dry_run: rc.PrintStatus() diff --git a/tools/thread_pool.py b/tools/thread_pool.py new file mode 100644 index 00000000..60abafa2 --- /dev/null +++ b/tools/thread_pool.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python2.7 + +# Copyright 2019, VIXL authors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of ARM Limited nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import multiprocessing +import signal +import sys + +# Catch SIGINT to gracefully exit when ctrl+C is pressed. +def SigIntHandler(signal, frame): + sys.exit(1) + +signal.signal(signal.SIGINT, SigIntHandler) + +# This function can't run in parallel due to constraints from the +# multiprocessing module. +__run_tests_lock__ = multiprocessing.Lock() +def Multithread(function, list_of_args, num_threads=1, init_function=None): + with __run_tests_lock__: + if init_function: + if not init_function(): + # Init failed: early exit + return + + pool = multiprocessing.Pool(num_threads) + # The '.get(9999999)' is a workaround to allow killing the test script with + # ctrl+C from the shell. This bug is documented at + # http://bugs.python.org/issue8296. + pool.map_async(function, list_of_args).get(9999999) + pool.close() + pool.join() diff --git a/tools/threaded_tests.py b/tools/threaded_tests.py index 035e96b7..a2655330 100644 --- a/tools/threaded_tests.py +++ b/tools/threaded_tests.py @@ -24,22 +24,18 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import collections import multiprocessing import re -import signal import subprocess -import sys import time from known_test_failures import FilterKnownTestFailures import printer +import thread_pool import util -# Catch SIGINT to gracefully exit when ctrl+C is pressed. -def SigIntHandler(signal, frame): - sys.exit(1) - -signal.signal(signal.SIGINT, SigIntHandler) +REGEXP_MISSING_FEATURES = "Missing features: { ([^,}]+(, [^,}]+)*) }" # Scan matching tests and return a test manifest. def GetTests(runner, filters = []): @@ -48,7 +44,6 @@ def GetTests(runner, filters = []): tests = output.split() for f in filters: - print f tests = filter(re.compile(f).search, tests) return tests @@ -61,7 +56,20 @@ def RunTest(test): rc = p.poll() if rc == 0: - with Test.n_tests_passed.get_lock(): Test.n_tests_passed.value += 1 + skipped = False + lines = p_out.split('\n') + skipped_id = "SKIPPED: " + for i in range(len(lines)): + if lines[i].startswith(skipped_id): + skipped = True + reason = lines[i][len(skipped_id):] + with Test.n_tests_skipped.get_lock(): + Test.n_tests_skipped.value += 1 + test.shared.tests_skipped.setdefault(reason, 0) + test.shared.tests_skipped[reason] += 1 + break + if not skipped: + with Test.n_tests_passed.get_lock(): Test.n_tests_passed.value += 1 else: with Test.n_tests_failed.get_lock(): Test.n_tests_failed.value += 1 @@ -71,6 +79,8 @@ def RunTest(test): Test.n_tests_passed.value, Test.n_tests_failed.value, test.shared.n_tests, + Test.n_tests_skipped.value, + test.shared.n_known_failures, test.name, prevent_next_overwrite = (rc != 0), has_lock = True, @@ -91,6 +101,8 @@ class Test(object): # or static, or no work is started. n_tests_passed = multiprocessing.Value('i', 0) n_tests_failed = multiprocessing.Value('i', 0) + n_tests_skipped = multiprocessing.Value('i', 0) + manager = multiprocessing.Manager() def __init__(self, test_runner, test, runtime_options, use_valgrind, shared): self.command = [test_runner, test] + runtime_options @@ -104,10 +116,18 @@ class TestQueue(object): self.progress_prefix = prefix self.under_valgrind = under_valgrind self.queue = [] + self.tests_skipped = Test.manager.dict() + self.n_known_failures = 0 + self.known_failures = collections.Counter() def Add(self, test_runner_command, filters, runtime_options): tests = GetTests(test_runner_command, filters) - tests = FilterKnownTestFailures(tests, under_valgrind = self.under_valgrind) + n_tests_total = len(tests) + tests, skipped = FilterKnownTestFailures(tests, under_valgrind = self.under_valgrind) + for n_tests, reason in skipped: + if n_tests > 0: + self.n_known_failures += n_tests + self.known_failures[reason] += n_tests if len(tests) == 0: printer.Print('No tests to run.') @@ -118,35 +138,53 @@ class TestQueue(object): runtime_options, self.under_valgrind, self)) # Run the specified tests. - # This function won't run in parallel due to constraints from the - # multiprocessing module. - __run_tests_lock__ = multiprocessing.Lock() - def Run(self, jobs): - with TestQueue.__run_tests_lock__: + def Run(self, jobs, verbose): + def InitGlobals(): # Initialisation. self.start_time = time.time() self.n_tests = len(self.queue) if self.n_tests == 0: printer.Print('No tests to run.') - return 0 + return False Test.n_tests_passed.value = 0 Test.n_tests_failed.value = 0 - - pool = multiprocessing.Pool(jobs) - # The '.get(9999999)' is a workaround to allow killing the test script with - # ctrl+C from the shell. This bug is documented at - # http://bugs.python.org/issue8296. - work = pool.map_async(RunTest, self.queue).get(9999999) - pool.close() - pool.join() - - printer.UpdateProgress(self.start_time, - Test.n_tests_passed.value, - Test.n_tests_failed.value, - self.n_tests, - '== Done ==', - prevent_next_overwrite = True, - prefix = self.progress_prefix) + Test.n_tests_skipped.value = 0 + self.tests_skipped.clear() + return True + + thread_pool.Multithread(RunTest, self.queue, jobs, InitGlobals) + + printer.UpdateProgress(self.start_time, + Test.n_tests_passed.value, + Test.n_tests_failed.value, + self.n_tests, + Test.n_tests_skipped.value, + self.n_known_failures, + '== Done ==', + prevent_next_overwrite = True, + prefix = self.progress_prefix) + n_tests_features = 0 + features = set() + for reason, n_tests in self.tests_skipped.items(): + m = re.match(REGEXP_MISSING_FEATURES, reason) + if m: + if verbose: + printer.Print("%d tests skipped because the following features are not " + "available '%s'" % (n_tests, m.group(1))) + else: + n_tests_features += n_tests + features.update(m.group(1).split(', ')) + else: + printer.Print("%d tests skipped because '%s'" % (n_tests, reason)) + + n_tests_other = 0 + if n_tests_features > 0 : + printer.Print("%d tests skipped because the CPU does not support " + "the following features: '%s'" % + (n_tests_features, ", ".join(features))) + + for reason, n_tests in self.known_failures.items(): + printer.Print("%d tests skipped because '%s'" % (n_tests, reason)) # Empty the queue now that the tests have been run. self.queue = [] |