diff options
author | armvixl <vixl@arm.com> | 2015-07-21 11:37:10 +0100 |
---|---|---|
committer | armvixl <vixl@arm.com> | 2015-07-21 11:37:10 +0100 |
commit | db6443499376478f5281607a3923e6ffc4c8d8ec (patch) | |
tree | 7b735273ab6b530d3388dd61d0ff22f3565b47af /tools | |
parent | 6e2c8275d5f34a531fe1eef7a7aa877601be8558 (diff) |
VIXL Release 1.10
Refer to the README.md and LICENCE files for details.
Diffstat (limited to 'tools')
-rw-r--r-- | tools/config.py | 46 | ||||
-rw-r--r-- | tools/git.py | 4 | ||||
-rwxr-xr-x | tools/lint.py | 131 | ||||
-rwxr-xr-x | tools/make_instruction_doc.pl | 5 | ||||
-rwxr-xr-x | tools/presubmit.py | 309 | ||||
-rw-r--r-- | tools/printer.py | 106 | ||||
-rwxr-xr-x | tools/test.py | 536 | ||||
-rw-r--r-- | tools/threaded_tests.py | 153 | ||||
-rw-r--r-- | tools/util.py | 34 |
9 files changed, 695 insertions, 629 deletions
diff --git a/tools/config.py b/tools/config.py new file mode 100644 index 00000000..0787a91b --- /dev/null +++ b/tools/config.py @@ -0,0 +1,46 @@ +# Copyright 2015, ARM Limited +# 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 os + +# These paths describe the structure of the repository. +dir_tools = os.path.dirname(os.path.realpath(__file__)) +dir_root = os.path.abspath(os.path.join(dir_tools, '..')) +dir_build = os.path.join(dir_root, 'obj') +dir_build_latest = os.path.join(dir_build, 'latest') +dir_src_vixl = os.path.join(dir_root, 'src') +dir_benchmarks = os.path.join(dir_root, 'benchmarks') +dir_examples = os.path.join(dir_root, 'examples') + + +# The full list of available build modes. +build_options_modes = ['release', 'debug'] +# The list of C++ standard to test for. +tested_cpp_standards = ['c++98', 'c++11'] +# The list of compilers tested. +tested_compilers = ['g++', 'clang++'] + + diff --git a/tools/git.py b/tools/git.py index dddaddfc..2fca3edf 100644 --- a/tools/git.py +++ b/tools/git.py @@ -28,8 +28,8 @@ import re import util import os.path -def is_git_repository_root(): - return os.path.isdir('.git') +def is_git_repository_root(path): + return os.path.isdir(os.path.join(path, '.git')) def get_tracked_files(): command = 'git ls-tree HEAD -r --full-tree --name-only' diff --git a/tools/lint.py b/tools/lint.py index 73cb8fd4..36c5358b 100755 --- a/tools/lint.py +++ b/tools/lint.py @@ -29,68 +29,20 @@ import argparse import multiprocessing import re +import signal import subprocess import sys +import config import git import printer import util -# Google's cpplint.py from depot_tools is the linter used here. -# These are positive rules, added to the set of rules that the linter checks. -CPP_LINTER_RULES = ''' -build/class -build/deprecated -build/endif_comment -build/forward_decl -build/include_order -build/printf_format -build/storage_class -legal/copyright -readability/boost -readability/braces -readability/casting -readability/constructors -readability/fn_size -readability/function -readability/multiline_comment -readability/multiline_string -readability/streams -readability/utf8 -runtime/arrays -runtime/casting -runtime/deprecated_fn -runtime/explicit -runtime/int -runtime/memset -runtime/mutex -runtime/nonconf -runtime/printf -runtime/printf_format -runtime/references -runtime/rtti -runtime/sizeof -runtime/string -runtime/virtual -runtime/vlog -whitespace/blank_line -whitespace/braces -whitespace/comma -whitespace/comments -whitespace/end_of_line -whitespace/ending_newline -whitespace/indent -whitespace/labels -whitespace/line_length -whitespace/newline -whitespace/operators -whitespace/parens -whitespace/tab -whitespace/todo -'''.split() - - +# Catch SIGINT to gracefully exit when ctrl+C is pressed. +def sigint_handler(signal, frame): + sys.exit(1) +signal.signal(signal.SIGINT, sigint_handler) def BuildOptions(): result = argparse.ArgumentParser( @@ -104,8 +56,6 @@ def BuildOptions(): help='''Runs the tests using N jobs. If the option is set but no value is provided, the script will use as many jobs as it thinks useful.''') - result.add_argument('--verbose', action='store_true', - help='Print verbose output.') return result.parse_args() @@ -113,8 +63,8 @@ def BuildOptions(): __lint_results_lock__ = multiprocessing.Lock() # Returns the number of errors in the file linted. -def Lint(filename, lint_options, progress_prefix = '', verbose = False): - command = ['cpplint.py', lint_options, filename] +def Lint(filename, progress_prefix = ''): + command = ['cpplint.py', filename] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -133,10 +83,12 @@ def Lint(filename, lint_options, progress_prefix = '', verbose = False): output_line = progress_prefix + line.rstrip('\r\n') if LINT_ERROR_LINE_REGEXP.search(line): - printer.PrintOverwritableLine(output_line, verbose = verbose) + printer.PrintOverwritableLine(output_line, + type = printer.LINE_TYPE_LINTER) printer.EnsureNewLine() elif LINT_DONE_PROC_LINE_REGEXP.search(line): - printer.PrintOverwritableLine(output_line, verbose = verbose) + printer.PrintOverwritableLine(output_line, + type = printer.LINE_TYPE_LINTER) elif LINT_STATUS_LINE_REGEXP.search(line): status_line = line @@ -151,7 +103,7 @@ def Lint(filename, lint_options, progress_prefix = '', verbose = False): n_errors = int(n_errors_str) status_line = \ progress_prefix + 'Total errors found in %s : %d' % (filename, n_errors) - printer.PrintOverwritableLine(status_line, verbose = verbose) + printer.PrintOverwritableLine(status_line, type = printer.LINE_TYPE_LINTER) printer.EnsureNewLine() return n_errors @@ -159,19 +111,40 @@ def Lint(filename, lint_options, progress_prefix = '', verbose = False): # The multiprocessing map_async function does not allow passing multiple # arguments directly, so use a wrapper. def LintWrapper(args): - return Lint(*args) + # Run under a try-catch to avoid flooding the output when the script is + # interrupted from the keyboard with ctrl+C. + try: + return Lint(*args) + except: + sys.exit(1) # Returns the total number of errors found in the files linted. -def LintFiles(files, lint_args = CPP_LINTER_RULES, jobs = 1, verbose = False, - progress_prefix = ''): - lint_options = '--filter=-,+' + ',+'.join(lint_args) +def LintFiles(files, jobs = 1, progress_prefix = ''): + if not IsCppLintAvailable(): + print( + printer.COLOUR_RED + \ + ("cpplint.py not found. Please ensure the depot" + " tools are installed and in your PATH. See" + " http://dev.chromium.org/developers/how-tos/install-depot-tools for" + " details.") + \ + printer.NO_COLOUR) + return -1 + pool = multiprocessing.Pool(jobs) # The '.get(9999999)' is workaround to allow killing the test script with # ctrl+C from the shell. This bug is documented at # http://bugs.python.org/issue8296. - tasks = [(f, lint_options, progress_prefix, verbose) for f in files] - results = pool.map_async(LintWrapper, tasks).get(9999999) + tasks = [(f, progress_prefix) for f in files] + # Run under a try-catch to avoid flooding the output when the script is + # interrupted from the keyboard with ctrl+C. + try: + results = pool.map_async(LintWrapper, tasks).get(9999999) + pool.close() + pool.join() + except KeyboardInterrupt: + pool.terminate() + sys.exit(1) n_errors = sum(results) printer.PrintOverwritableLine( @@ -186,20 +159,28 @@ def IsCppLintAvailable(): CPP_EXT_REGEXP = re.compile('\.(cc|h)$') -SIM_TRACES_REGEXP = re.compile('trace-a64\.h$') def is_linter_input(filename): - # Don't lint the simulator traces file; it takes a very long time to check - # and it's (mostly) generated automatically anyway. - if SIM_TRACES_REGEXP.search(filename): return False - # Otherwise, lint all C++ files. + # lint all C++ files. return CPP_EXT_REGEXP.search(filename) != None -default_tracked_files = git.get_tracked_files().split() -default_tracked_files = filter(is_linter_input, default_tracked_files) + +def GetDefaultTrackedFiles(): + if git.is_git_repository_root(config.dir_root): + default_tracked_files = git.get_tracked_files().split() + default_tracked_files = filter(is_linter_input, default_tracked_files) + return 0, default_tracked_files + else: + printer.Print(printer.COLOUR_ORANGE + 'WARNING: This script is not run ' \ + 'from its Git repository. The linter will not run.' + \ + printer.NO_COLOUR) + return 1, [] if __name__ == '__main__': # Parse the arguments. args = BuildOptions() + retcode, default_tracked_files = GetDefaultTrackedFiles() + if retcode: + sys.exit(retcode) retcode = LintFiles(default_tracked_files, - jobs = args.jobs, verbose = args.verbose) + jobs = args.jobs) sys.exit(retcode) diff --git a/tools/make_instruction_doc.pl b/tools/make_instruction_doc.pl index 192de96a..8ecc8c49 100755 --- a/tools/make_instruction_doc.pl +++ b/tools/make_instruction_doc.pl @@ -27,7 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Assembler header file. -my $hfile = "src/a64/assembler-a64.h"; +my $hfile = "src/vixl/a64/assembler-a64.h"; # Extra pseudo instructions added to AArch64. my @extras = qw/bind debug dci dc32 dc64 place/; @@ -87,7 +87,8 @@ print describe_insts('Additional or pseudo instructions', 'pseudo'); sub inst_sort { $inst{$a}->{'mnemonic'} cmp $inst{$b}->{'mnemonic'} || - $inst{$a}->{'description'} cmp $inst{$b}->{'description'}; + $inst{$a}->{'description'} cmp $inst{$b}->{'description'} || + $a cmp $b; } # Return a Markdown formatted list of instructions of a particular type. diff --git a/tools/presubmit.py b/tools/presubmit.py deleted file mode 100755 index bd9f2b57..00000000 --- a/tools/presubmit.py +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2015, ARM Limited -# 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 os -import sys -import argparse -import re -import platform -import subprocess -import multiprocessing - -import git -import printer -import test -import util - - -SUPPORTED_COMPILERS = ['g++', 'clang++'] -OBJ_DIR = './obj' - - -def BuildOptions(): - result = argparse.ArgumentParser( - description='Run the linter and unit tests.', - # Print default values. - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - result.add_argument('--verbose', '-v', action='store_true', - help='Print all tests output at the end.') - result.add_argument('--notest', action='store_true', - help='Do not run tests. Run the linter only.') - result.add_argument('--nolint', action='store_true', - help='Do not run the linter. Run the tests only.') - result.add_argument('--noclean', action='store_true', - help='Do not clean before build.') - result.add_argument('--fast', action='store_true', - help='Only test with one toolchain') - result.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?', - default=1, const=multiprocessing.cpu_count(), - help='''Run the tests using N jobs. If the option is set - but no value is provided, the script will use as many jobs - as it thinks useful.''') - sim_default = 'off' if platform.machine() == 'aarch64' else 'on' - result.add_argument('--simulator', action='store', choices=['on', 'off'], - default=sim_default, - help='Explicitly enable or disable the simulator.') - return result.parse_args() - - -def check_supported(compiler, mode, std): - if compiler not in SUPPORTED_COMPILERS: - print 'Invalid compiler.' - sys.exit(1) - if mode not in ['release', 'debug']: - print 'Invalid mode.' - sys.exit(1) - if std not in ['c++98', 'c++11']: - print 'Invalid c++ standard.' - sys.exit(1) - - -def initalize_compiler_list(): - compiler_list = [] - for compiler in SUPPORTED_COMPILERS: - if util.has_compiler(compiler) and (len(compiler_list) == 0 or not args.fast): - compiler_list.append(compiler) - else: - # This warning suffices for args.fast too. - print 'WARNING: Skipping ' + compiler + ' tests.' - if len(compiler_list) == 0: - util.abort('Found no supported compilers') - return compiler_list - - -def CleanBuildSystem(compiler): - def clean(compiler, mode, std): - check_supported(compiler, mode, std) - os.environ['CXX'] = compiler - if args.verbose: - print 'Cleaning ' + compiler + ' ' + std + ' ' \ - + mode + ' mode test...' - command = 'scons mode=%s std=%s simulator=%s all --clean' % \ - (mode, std, args.simulator) - status, output = util.getstatusoutput(command) - if status != 0: - print(output) - util.abort('Failed cleaning test: ' + command) - - clean(compiler, 'debug', 'c++98') - clean(compiler, 'debug', 'c++11') - clean(compiler, 'release', 'c++98') - clean(compiler, 'release', 'c++11') - - -def BuildEverything(compiler): - def build(compiler, mode, std): - check_supported(compiler, mode, std) - os.environ['CXX'] = compiler - if args.verbose: - print 'Building ' + compiler + ' ' + std + ' ' \ - + mode + ' mode test...' - if args.jobs == 1: - print '- This may take a while. Pass `-j` to use multiple threads.' - command = 'scons mode=%s std=%s simulator=%s all -j%u' % \ - (mode, std, args.simulator, args.jobs) - status, output = util.getstatusoutput(command) - if status != 0: - print(output) - util.abort('Failed building test: ' + command) - - print 'Building ' + compiler + ' tests...' - build(compiler, 'debug', 'c++98') - build(compiler, 'debug', 'c++11') - build(compiler, 'release', 'c++98') - build(compiler, 'release', 'c++11') - - -NOT_RUN = 'NOT RUN' -PASSED = 'PASSED' -FAILED = 'FAILED' - -class Test: - def __init__(self, name): - self.name = name - self.status = NOT_RUN - - def name_prefix(self): - return '%-40s : ' % self.name - - -class Tester: - def __init__(self): - self.tests = [] - - def AddTest(self, test): - self.tests.append(test) - - def RunAll(self): - result = PASSED - for test in self.tests: - if args.verbose: print('Running ' + test.name + '...') - test.Run() - if test.status != PASSED: result = FAILED - print('Presubmit tests ' + result + '.') - - -class VIXLTest(Test): - def __init__(self, compiler, mode, std, simulator, debugger = False, verbose = False): - check_supported(compiler, mode, std) - self.verbose = verbose - self.debugger = debugger - self.compiler = compiler - self.mode = mode - self.std = std - - name = 'test ' + compiler + ' ' + std + ' ' + mode - if simulator: - name += ' (%s)' % ('debugger' if debugger else 'simulator') - Test.__init__(self, name) - - self.exe = 'test-runner' - if simulator: - self.exe += '_sim' - if mode == 'debug': - self.exe += '_g' - - def Run(self): - self.status = PASSED - command = os.path.join(OBJ_DIR, self.mode, self.compiler, - self.std, self.exe) - manifest = test.ReadManifest(command, [], self.debugger, False, self.verbose) - retcode = test.RunTests(manifest, jobs = args.jobs, - verbose = self.verbose, debugger = self.debugger, - progress_prefix = self.name_prefix()) - printer.EnsureNewLine() - if retcode != 0: - self.status = FAILED - - -class LintTest(Test): - def __init__(self): - name = 'cpp lint' - Test.__init__(self, name) - - def Run(self): - if not lint.IsCppLintAvailable(): - self.status = FAILED - print self.name_prefix() + FAILED + ''' -cpplint.py not found. Please ensure the depot tools are installed and in your -PATH. See http://dev.chromium.org/developers/how-tos/install-depot-tools for -details.''' - return - - n_errors = lint.LintFiles(lint.default_tracked_files, - jobs = args.jobs, verbose = args.verbose, - progress_prefix = self.name_prefix()) - self.status = PASSED if n_errors == 0 else FAILED - - -class BenchTest(Test): - def __init__(self, compiler, mode, std, simulator): - check_supported(compiler, mode, std) - self.compiler = compiler - self.mode = mode - self.std = std - - name = 'benchmarks ' + compiler + ' ' + std + ' ' + mode - Test.__init__(self, name) - self.exe_suffix = '' - if simulator: - self.exe_suffix += '_sim' - if mode == 'debug': - self.exe_suffix += '_g' - - def Run(self): - benchmarks = ['bench-dataop', 'bench-branch', 'bench-branch-link', - 'bench-branch-masm', 'bench-branch-link-masm'] - self.status = PASSED - for bench in benchmarks: - command = os.path.join(OBJ_DIR, self.mode, self.compiler, self.std, - bench + self.exe_suffix) - (rc, out) = util.getstatusoutput(command) - if rc != 0: - self.status = FAILED - print self.name_prefix() + 'Failed to run `' + command + '`' - print self.name_prefix() + self.status - - - -if __name__ == '__main__': - original_dir = os.path.abspath('.') - # $ROOT/tools/presubmit.py - root_dir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) - os.chdir(root_dir) - args = BuildOptions() - - if not args.nolint and not git.is_git_repository_root(): - print 'WARNING: This is not a Git repository. The linter will not run.' - args.nolint = True - - if not args.nolint: - import lint - LintTest().Run() - - if not args.notest: - tester = Tester() - compiler_list = initalize_compiler_list() - - for compiler in compiler_list: - if not args.noclean: - CleanBuildSystem(compiler) - BuildEverything(compiler) - - if args.simulator == 'on': - # mode, std, sim, debugger, verbose - tester.AddTest(VIXLTest(compiler, 'release', 'c++98', True, True, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'debug', 'c++98', True, True, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'release', 'c++98', True, False, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'debug', 'c++98', True, False, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'release', 'c++11', True, True, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'debug', 'c++11', True, True, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'release', 'c++11', True, False, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'debug', 'c++11', True, False, args.verbose)) - tester.AddTest(BenchTest(compiler,'release', 'c++98', True)) - tester.AddTest(BenchTest(compiler,'debug', 'c++98', True)) - tester.AddTest(BenchTest(compiler,'release', 'c++11', True)) - tester.AddTest(BenchTest(compiler,'debug', 'c++11', True)) - else: - tester.AddTest(VIXLTest(compiler, 'release', 'c++98', False, False, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'debug', 'c++98', False, False, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'release', 'c++11', False, False, args.verbose)) - tester.AddTest(VIXLTest(compiler, 'debug', 'c++11', False, False, args.verbose)) - tester.AddTest(BenchTest(compiler,'release', 'c++98', False)) - tester.AddTest(BenchTest(compiler,'debug', 'c++98', False)) - tester.AddTest(BenchTest(compiler,'release', 'c++11', False)) - tester.AddTest(BenchTest(compiler,'debug', 'c++11', False)) - - tester.RunAll() - - if git.is_git_repository_root(): - untracked_files = git.get_untracked_files() - if untracked_files: - print '\nWARNING: The following files are untracked:' - for f in untracked_files: - print f.lstrip('?') diff --git a/tools/printer.py b/tools/printer.py index 70826368..dc97a5bc 100644 --- a/tools/printer.py +++ b/tools/printer.py @@ -31,71 +31,95 @@ import sys import time import multiprocessing +output_redirected_to_file = not sys.stdout.isatty() + +def ColourCode(colour): + return '' if output_redirected_to_file else colour + +COLOUR_GREEN = ColourCode("\x1b[0;32m") +COLOUR_ORANGE = ColourCode("\x1b[0;33m") +COLOUR_RED = ColourCode("\x1b[0;31m") +NO_COLOUR = ColourCode("\x1b[0m") + +# Indicates the 'type' of the last printed line. +LINE_TYPE_NONE = 0 +# Any type below this one is overwritable. +LINE_TYPE_OVERWRITABLE = 1 +LINE_TYPE_PROGRESS = 2 +LINE_TYPE_LINTER = 3 -__need_newline__ = multiprocessing.Value('i', 0) -__last_overwritable_line_length__ = multiprocessing.Value('i', 0) __print_lock__ = multiprocessing.Lock() +__last_overwritable_line_length__ = multiprocessing.Value('i', 0) +__last_line_type__ = multiprocessing.Value('i', LINE_TYPE_NONE) -# If the last printed character was not a newline, print one. def EnsureNewLine(): - global __need_newline__ - - if __need_newline__.value: - __need_newline__.value = 0 + if __last_line_type__.value >= LINE_TYPE_OVERWRITABLE: sys.stdout.write('\n') + __last_line_type__.value = LINE_TYPE_NONE + +def PrintInternal(string): + sys.stdout.write(string) + spaces = __last_overwritable_line_length__.value - len(string) + if spaces > 0: + sys.stdout.write(' ' * spaces) -# Like print, but insert a newline if necessary to avoid corrupting the status -# display (provided by UpdateProgress). -def Print(string): - global __last_overwritable_line_length__ - EnsureNewLine() - print string +def Print(string, has_lock = False): + if not has_lock: __print_lock__.acquire() + + if __last_line_type__.value != LINE_TYPE_NONE: + sys.stdout.write('\n') + + PrintInternal(string) + sys.stdout.write('\n') __last_overwritable_line_length__.value = 0 + __last_line_type__.value = LINE_TYPE_NONE + if not has_lock: __print_lock__.release() -def PrintOverwritableLine(string, verbose = False): - global __need_newline__ - global __last_overwritable_line_length__ - with __print_lock__: - if verbose: - # In verbose mode we do not overwrite previous lines. - EnsureNewLine() - else: - # Otherwise, overwrite the previous line. - sys.stdout.write('\r') +# Lines of a specific type only overwrite and can only be overwritten by lines +# of the same type. +def PrintOverwritableLine(string, has_lock = False, type = LINE_TYPE_NONE): + if not has_lock: __print_lock__.acquire() - sys.stdout.write(string) + if (__last_line_type__.value != type) and \ + (__last_line_type__.value >= LINE_TYPE_OVERWRITABLE): + sys.stdout.write('\n') - # Append spaces to hide the previous line. - new_len = len(string) - spaces = __last_overwritable_line_length__.value - new_len - if spaces > 0: - sys.stdout.write(' ' * spaces) - __last_overwritable_line_length__.value = new_len + PrintInternal(string) + if not output_redirected_to_file: + sys.stdout.write('\r') + else: + sys.stdout.write('\n') + sys.stdout.flush() + + __last_overwritable_line_length__.value = len(string) + __last_line_type__.value = type - # We haven't printed a newline, so any subsequent print output (with verbose - # logs or error reports) will need to print one. - __need_newline__.value = 1 + if not has_lock: __print_lock__.release() # Display the run progress: -# [time| progress|+ passed|- failed] Name -def UpdateProgress(start_time, passed, failed, count, verbose, name, - prefix = ''): +# prefix [time| progress|+ passed|- failed] name +def UpdateProgress(start_time, passed, failed, count, name, prefix = '', + prevent_next_overwrite = False, has_lock = False): minutes, seconds = divmod(time.time() - start_time, 60) progress = float(passed + failed) / count * 100 - passed_colour = '\x1b[32m' if passed != 0 else '' - failed_colour = '\x1b[31m' if failed != 0 else '' + passed_colour = COLOUR_GREEN if passed != 0 else '' + failed_colour = COLOUR_RED if failed != 0 else '' indicator = '[%02d:%02d| %3d%%|' - indicator += passed_colour + '+ %d\x1b[0m|' - indicator += failed_colour + '- %d\x1b[0m]' + indicator += passed_colour + '+ %d' + NO_COLOUR + '|' + indicator += failed_colour + '- %d' + NO_COLOUR + ']' progress_string = prefix progress_string += indicator % (minutes, seconds, progress, passed, failed) progress_string += ' ' + name - PrintOverwritableLine(progress_string, verbose) + PrintOverwritableLine(progress_string, type = LINE_TYPE_PROGRESS, + has_lock = has_lock) + if prevent_next_overwrite: + sys.stdout.write('\n') + __last_line_type__.value = LINE_TYPE_NONE diff --git a/tools/test.py b/tools/test.py index 675c09a6..efc41ed0 100755 --- a/tools/test.py +++ b/tools/test.py @@ -1,6 +1,6 @@ #!/usr/bin/env python2.7 -# Copyright 2014, ARM Limited +# Copyright 2015, ARM Limited # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -26,221 +26,383 @@ # 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 os -import sys import argparse -import re +import fcntl +import git +import itertools +import multiprocessing +import os +from os.path import join import platform +import re import subprocess -import multiprocessing +import sys import time -import util +import config +import lint +import printer +import test +import threaded_tests +import util -from printer import EnsureNewLine, Print, UpdateProgress +dir_root = config.dir_root -def BuildOptions(): - result = argparse.ArgumentParser( - description = - '''This tool runs each test reported by $TEST --list (and filtered as - specified). A summary will be printed, and detailed test output will be - stored in log/$TEST.''', - # Print default values. - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - result.add_argument('filters', metavar='filter', nargs='*', - help='Run tests matching all of the (regexp) filters.') - result.add_argument('--runner', action='store', required=True, - help='The test executable to run.') - result.add_argument('--coloured_trace', action='store_true', - help='''Pass --coloured_trace to the test runner. This - will put colour codes in the log files. The - coloured output can be viewed by "less -R", for - example.''') - result.add_argument('--debugger', action='store_true', - help='''Pass --debugger to test, so that the debugger is - used instead of the simulator. This has no effect - when running natively.''') - result.add_argument('--verbose', action='store_true', - help='Print verbose output.') - result.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?', - default=1, const=multiprocessing.cpu_count(), - help='''Runs the tests using N jobs. If the option is set - but no value is provided, the script will use as many jobs - as it thinks useful.''') - sim_default = 'off' if platform.machine() == 'aarch64' else 'on' - result.add_argument('--simulator', action='store', choices=['on', 'off'], - default=sim_default, - help='Explicitly enable or disable the simulator.') - return result.parse_args() +def Optionify(name): + return '--' + name -def VerbosePrint(verbose, string): - if verbose: - Print(string) +# The options that can be tested are abstracted to provide an easy way to add +# new ones. +# Environment options influence the environment. They can be used for example to +# set the compiler used. +# Build options are options passed to scons, with a syntax like `scons opt=val` +# Runtime options are options passed to the test program. +# See the definition of `test_options` below. +# 'all' is a special value for the options. If specified, all other values of +# the option are tested. +class TestOption(object): + type_environment = 'type_environment' + type_build = 'type_build' + type_run = 'type_run' -# A class representing an individual test. -class Test: - def __init__(self, name, runner, debugger, coloured_trace, verbose): + def __init__(self, option_type, name, help, + val_test_choices, val_test_default = None, + # If unset, the user can pass any value. + strict_choices = True): self.name = name - self.runner = runner - self.debugger = debugger - self.coloured_trace = coloured_trace - self.verbose = verbose - self.logpath = os.path.join('log', os.path.basename(self.runner)) - if self.debugger: - basename = name + '_debugger' + self.option_type = option_type + self.help = help + self.val_test_choices = val_test_choices + self.strict_choices = strict_choices + if val_test_default is not None: + self.val_test_default = val_test_default else: - basename = name - self.logout = os.path.join(self.logpath, basename + '.stdout') - self.logerr = os.path.join(self.logpath, basename + '.stderr') - if not os.path.exists(self.logpath): os.makedirs(self.logpath) - - # Run the test. - # Use a thread to be able to control the test. - def Run(self): - command = \ - [self.runner, '--trace_sim', '--trace_reg', '--trace_write', self.name] - if self.coloured_trace: - command.append('--coloured_trace') - if self.debugger: - command.append('--debugger') - - VerbosePrint(self.verbose, '==== Running ' + self.name + '... ====') - sys.stdout.flush() - - process = subprocess.Popen(command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - # Get the output and return status of the test. - stdout, stderr = process.communicate() - retcode = process.poll() - - # Write stdout and stderr to the log. - with open(self.logout, 'w') as f: f.write(stdout) - with open(self.logerr, 'w') as f: f.write(stderr) - - if retcode == 0: - # Success. - # We normally only print the command on failure, but with --verbose we - # should also print it on success. - VerbosePrint(self.verbose, 'COMMAND: ' + ' '.join(command)) - VerbosePrint(self.verbose, 'LOG (stdout): ' + self.logout) - VerbosePrint(self.verbose, 'LOG (stderr): ' + self.logerr + '\n') + self.val_test_default = val_test_choices[0] + + def ArgList(self, to_test): + res = [] + if to_test == 'all': + for value in self.val_test_choices: + if value != 'all': + res.append(self.GetOptionString(value)) else: - # Failure. - Print('--- FAILED ' + self.name + ' ---') - Print('COMMAND: ' + ' '.join(command)) - Print('LOG (stdout): ' + self.logout) - Print('LOG (stderr): ' + self.logerr + '\n') - - return retcode - - -# Scan matching tests and return a test manifest. -def ReadManifest(runner, filters = [], - debugger = False, coloured_trace = False, verbose = False): - status, output = util.getstatusoutput(runner + ' --list') - if status != 0: util.abort('Failed to list all tests') - - names = output.split() - for f in filters: - names = filter(re.compile(f).search, names) - - return map(lambda x: - Test(x, runner, debugger, coloured_trace, verbose), names) - - -# Shared state for multiprocessing. Ideally the context should be passed with -# arguments, but constraints from the multiprocessing module prevent us from -# doing so: the shared variables (multiprocessing.Value) must be global, or no -# work is started. So we abstract some additional state into global variables to -# simplify the implementation. -# Read-write variables for the workers. -n_tests_passed = multiprocessing.Value('i', 0) -n_tests_failed = multiprocessing.Value('i', 0) -# Read-only for workers. -n_tests = None -start_time = None -verbose_test_run = None -test_suite_name = '' - -def RunTest(test): - UpdateProgress(start_time, n_tests_passed.value, n_tests_failed.value, - n_tests, verbose_test_run, test.name, test_suite_name) - # Run the test and update the statistics. - retcode = test.Run() - if retcode == 0: - with n_tests_passed.get_lock(): n_tests_passed.value += 1 - else: - with n_tests_failed.get_lock(): n_tests_failed.value += 1 + for value in to_test: + res.append(self.GetOptionString(value)) + return res + +class EnvironmentOption(TestOption): + option_type = TestOption.type_environment + def __init__(self, name, environment_variable_name, help, + val_test_choices, val_test_default = None, + strict_choices = True): + super(EnvironmentOption, self).__init__(EnvironmentOption.option_type, + name, + help, + val_test_choices, + val_test_default, + strict_choices = strict_choices) + self.environment_variable_name = environment_variable_name + + def GetOptionString(self, value): + return self.environment_variable_name + '=' + value + + +class BuildOption(TestOption): + option_type = TestOption.type_build + def __init__(self, name, help, + val_test_choices, val_test_default = None, + strict_choices = True): + super(BuildOption, self).__init__(BuildOption.option_type, + name, + help, + val_test_choices, + val_test_default, + strict_choices = strict_choices) + def GetOptionString(self, value): + return self.name + '=' + value + + +class RuntimeOption(TestOption): + option_type = TestOption.type_run + def __init__(self, name, help, + val_test_choices, val_test_default = None): + super(RuntimeOption, self).__init__(RuntimeOption.option_type, + name, + help, + val_test_choices, + val_test_default) + def GetOptionString(self, value): + if value == 'on': + return Optionify(self.name) + else: + return None + + + +environment_option_compiler = \ + EnvironmentOption('compiler', 'CXX', 'Test for the specified compilers.', + val_test_choices=['all'] + config.tested_compilers, + strict_choices = False) +test_environment_options = [ + environment_option_compiler +] + +build_option_mode = \ + BuildOption('mode', 'Test with the specified build modes.', + val_test_choices=['all'] + config.build_options_modes) +build_option_standard = \ + BuildOption('std', 'Test with the specified C++ standard.', + val_test_choices=['all'] + config.tested_cpp_standards, + strict_choices = False) +test_build_options = [ + build_option_mode, + build_option_standard +] + +runtime_option_debugger = \ + RuntimeOption('debugger', + '''Test with the specified configurations for the debugger. + Note that this is only tested if we are using the simulator.''', + val_test_choices=['all', 'on', 'off']) +test_runtime_options = [ + runtime_option_debugger +] + +test_options = \ + test_environment_options + test_build_options + test_runtime_options + +def BuildOptions(): + args = argparse.ArgumentParser( + description = + '''This tool runs all tests matching the speficied filters for multiple + environment, build options, and runtime options configurations.''', + # Print default values. + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + args.add_argument('filters', metavar='filter', nargs='*', + help='Run tests matching all of the (regexp) filters.') + + # We automatically build the script options from the options to be tested. + test_arguments = args.add_argument_group( + 'Test options', + 'These options indicate what should be tested') + for option in test_options: + choices = option.val_test_choices if option.strict_choices else None + help = option.help + if not option.strict_choices: + help += ' Supported values: {' + ','.join(option.val_test_choices) + '}' + test_arguments.add_argument(Optionify(option.name), + nargs='+', + choices=choices, + default=option.val_test_default, + help=help, + action='store') + + general_arguments = args.add_argument_group('General options') + general_arguments.add_argument('--fast', action='store_true', + help='''Skip the lint tests, and run only with + one compiler, in one mode, with one C++ + standard, and with an appropriate default for + runtime options. The compiler, mode, and C++ + standard used are the first ones provided to + the script or in the default arguments.''') + general_arguments.add_argument( + '--jobs', '-j', metavar='N', type=int, nargs='?', + default=multiprocessing.cpu_count(), + const=multiprocessing.cpu_count(), + help='''Runs the tests using N jobs. If the option is set but no value is + provided, the script will use as many jobs as it thinks useful.''') + general_arguments.add_argument('--nobench', action='store_true', + help='Do not run benchmarks.') + general_arguments.add_argument('--nolint', action='store_true', + help='Do not run the linter.') + general_arguments.add_argument('--notest', action='store_true', + help='Do not run tests.') + sim_default = 'off' if platform.machine() == 'aarch64' else 'on' + general_arguments.add_argument( + '--simulator', action='store', choices=['on', 'off'], + default=sim_default, + help='Explicitly enable or disable the simulator.') + return args.parse_args() + + +def RunCommand(command, environment_options = None): + # Create a copy of the environment. We do not want to pollute the environment + # of future commands run. + environment = os.environ + # Configure the environment. + # TODO: We currently pass the options as strings, so we need to parse them. We + # should instead pass them as a data structure and build the string option + # later. `environment_options` looks like `['CXX=compiler', 'OPT=val']`. + if environment_options: + for option in environment_options: + opt, val = option.split('=') + environment[opt] = val + + printable_command = '' + if environment_options: + printable_command += ' '.join(environment_options) + ' ' + printable_command += ' '.join(command) + + printable_command_orange = \ + printer.COLOUR_ORANGE + printable_command + printer.NO_COLOUR + printer.PrintOverwritableLine(printable_command_orange) + sys.stdout.flush() + + # Start a process for the command. + # Interleave `stderr` and `stdout`. + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=environment) + + # We want to be able to display a continuously updated 'work indicator' while + # the process is running. Since the process can hang if the `stdout` pipe is + # full, we need to pull from it regularly. We cannot do so via the + # `readline()` function because it is blocking, and would thus cause the + # indicator to not be updated properly. So use file control mechanisms + # instead. + indicator = ' (still working: %d seconds elapsed)' + + # Mark the process output as non-blocking. + flags = fcntl.fcntl(p.stdout, fcntl.F_GETFL) + fcntl.fcntl(p.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + t_start = time.time() + t_last_indication = t_start + process_output = '' + + # Keep looping as long as the process is running. + while p.poll() is None: + # Avoid polling too often. + time.sleep(0.1) + # Update the progress indicator. + t_current = time.time() + if (t_current - t_start >= 2) and (t_current - t_last_indication >= 1): + printer.PrintOverwritableLine( + printable_command_orange + indicator % int(t_current - t_start)) + sys.stdout.flush() + t_last_indication = t_current + # Pull from the process output. + while True: + try: + line = os.read(p.stdout.fileno(), 1024) + except OSError: + line = '' + break + if line == '': break + process_output += line + + # The process has exited. Don't forget to retrieve the rest of its output. + out, err = p.communicate() + rc = p.poll() + process_output += out + + if rc == 0: + printer.Print(printer.COLOUR_GREEN + printable_command + printer.NO_COLOUR) + else: + printer.Print(printer.COLOUR_RED + printable_command + printer.NO_COLOUR) + printer.Print(process_output) + return rc -# Run all tests in the manifest. -# This function won't run in parallel due to constraints from the -# multiprocessing module. -__run_tests_lock__ = multiprocessing.Lock() -def RunTests(manifest, jobs = 1, verbose = False, debugger = False, - progress_prefix = ''): - global n_tests - global start_time - global verbose_test_run - global test_suite_name - with __run_tests_lock__: +def RunLinter(): + rc, default_tracked_files = lint.GetDefaultTrackedFiles() + if rc: + return rc + return lint.LintFiles(map(lambda x: join(dir_root, x), default_tracked_files), + jobs = args.jobs, progress_prefix = 'cpp lint: ') - # Reset the counters. - n_tests_passed.value = 0 - n_tests_failed.value = 0 - verbose_test_run = verbose - test_suite_name = progress_prefix - n_tests = len(manifest) +def BuildAll(build_options, jobs): + scons_command = ["scons", "-C", dir_root, 'all', '-j', str(jobs)] + scons_command += list(build_options) + return RunCommand(scons_command, list(environment_options)) - if n_tests == 0: - Print('No tests to run.') - return 0 - VerbosePrint(verbose, 'Running %d tests...' % (n_tests)) - start_time = time.time() +def RunBenchmarks(): + rc = 0 + benchmark_names = util.ListCCFilesWithoutExt(config.dir_benchmarks) + for bench in benchmark_names: + rc |= RunCommand( + [os.path.realpath(join(config.dir_build_latest, 'benchmarks', bench))]) + return rc - pool = multiprocessing.Pool(jobs) - # The '.get(9999999)' is 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, manifest).get(9999999) - done_message = '== Done ==' - UpdateProgress(start_time, n_tests_passed.value, n_tests_failed.value, - n_tests, verbose, done_message, progress_prefix) +def PrintStatus(success): + printer.Print('\n$ ' + ' '.join(sys.argv)) + if success: + printer.Print('SUCCESS') + else: + printer.Print('FAILURE') - return n_tests_failed.value # 0 indicates success if __name__ == '__main__': - # $ROOT/tools/test.py - root_dir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) + util.require_program('scons') + rc = 0 - # Parse the arguments. args = BuildOptions() - # Find a valid path to args.runner (in case it doesn't begin with './'). - args.runner = os.path.join('.', args.runner) - - if not os.access(args.runner, os.X_OK): - print "'" + args.test + "' is not executable or does not exist." - sys.exit(1) - - # List all matching tests. - manifest = ReadManifest(args.runner, args.filters, - args.debugger, args.coloured_trace, args.verbose) - - # Run the tests. - status = RunTests(manifest, jobs=args.jobs, - verbose=args.verbose, debugger=args.debugger) - EnsureNewLine() - - sys.exit(status) - + if args.fast: + def SetFast(option, specified, default): + option.val_test_choices = \ + [default[0] if specified == 'all' else specified[0]] + SetFast(environment_option_compiler, args.compiler, config.tested_compilers) + SetFast(build_option_mode, args.mode, config.build_options_modes) + SetFast(build_option_standard, args.std, config.tested_cpp_standards) + SetFast(runtime_option_debugger, args.debugger, ['on', 'off']) + + if not args.nolint and not args.fast: + rc |= RunLinter() + + # Don't try to test the debugger if we are not running with the simulator. + if not args.simulator: + test_runtime_options = \ + filter(lambda x: x.name != 'debugger', test_runtime_options) + + # List all combinations of options that will be tested. + def ListCombinations(args, options): + opts_list = map(lambda opt : opt.ArgList(args.__dict__[opt.name]), options) + return list(itertools.product(*opts_list)) + test_env_combinations = ListCombinations(args, test_environment_options) + test_build_combinations = ListCombinations(args, test_build_options) + test_runtime_combinations = ListCombinations(args, test_runtime_options) + + for environment_options in test_env_combinations: + for build_options in test_build_combinations: + # Avoid going through the build stage if we are not using the build + # result. + if not (args.notest and args.nobench): + build_rc = BuildAll(build_options, args.jobs) + # Don't run the tests for this configuration if the build failed. + if build_rc != 0: + rc |= build_rc + continue + + # Use the realpath of the test executable so that the commands printed + # can be copy-pasted and run. + test_executable = os.path.realpath( + join(config.dir_build_latest, 'test', 'test-runner')) + + if not args.notest: + printer.Print(test_executable) + + for runtime_options in test_runtime_combinations: + if not args.notest: + runtime_options = [x for x in runtime_options if x is not None] + prefix = ' ' + ' '.join(runtime_options) + ' ' + rc |= threaded_tests.RunTests(test_executable, + args.filters, + list(runtime_options), + jobs = args.jobs, prefix = prefix) + + if not args.nobench: + rc |= RunBenchmarks() + + PrintStatus(rc == 0) diff --git a/tools/threaded_tests.py b/tools/threaded_tests.py new file mode 100644 index 00000000..dcb17cbc --- /dev/null +++ b/tools/threaded_tests.py @@ -0,0 +1,153 @@ +# Copyright 2015, ARM Limited +# 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 re +import signal +import subprocess +import sys +import time + +import printer +import util + +# Catch SIGINT to gracefully exit when ctrl+C is pressed. +def SigIntHandler(signal, frame): + sys.exit(1) + +signal.signal(signal.SIGINT, SigIntHandler) + + +# Scan matching tests and return a test manifest. +def GetTests(runner, filters = []): + rc, output = util.getstatusoutput(runner + ' --list') + if rc != 0: util.abort('Failed to list all tests') + + tests = output.split() + for f in filters: + print f + tests = filter(re.compile(f).search, tests) + + return tests + + +# Shared state for multiprocessing. Ideally the context should be passed with +# arguments, but constraints from the multiprocessing module prevent us from +# doing so: the shared variables (multiprocessing.Value) must be global, or no +# work is started. So we abstract some additional state into global variables to +# simplify the implementation. +# Read-write variables for the workers. +n_tests_passed = multiprocessing.Value('i', 0) +n_tests_failed = multiprocessing.Value('i', 0) +# Read-only for workers. +test_runner = None +test_runner_runtime_options = None +n_tests = None +start_time = None +progress_prefix = None + + +def RunTest(test): + command = [test_runner, test] + test_runner_runtime_options + + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + p_out, p_err = p.communicate() + rc = p.poll() + + if rc == 0: + with n_tests_passed.get_lock(): n_tests_passed.value += 1 + else: + with n_tests_failed.get_lock(): n_tests_failed.value += 1 + + printer.__print_lock__.acquire() + + printer.UpdateProgress(start_time, + n_tests_passed.value, + n_tests_failed.value, + n_tests, + test, + prevent_next_overwrite = (rc != 0), + has_lock = True, + prefix = progress_prefix) + + if rc != 0: + printer.Print('FAILED: ' + test, has_lock = True) + printer.Print(printer.COLOUR_RED + ' '.join(command) + printer.NO_COLOUR, + has_lock = True) + printer.Print(p_out, has_lock = True) + + printer.__print_lock__.release() + + +# Run the specified tests. +# This function won't run in parallel due to constraints from the +# multiprocessing module. +__run_tests_lock__ = multiprocessing.Lock() +def RunTests(test_runner_command, filters, runtime_options, + jobs = 1, prefix = ''): + global test_runner + global test_runner_runtime_options + global n_tests + global start_time + global progress_prefix + + tests = GetTests(test_runner_command, filters) + + if n_tests == 0: + printer.Print('No tests to run.') + return 0 + + with __run_tests_lock__: + + # Initialisation. + start_time = time.time() + test_runner = test_runner_command + test_runner_runtime_options = runtime_options + n_tests = len(tests) + n_tests_passed.value = 0 + n_tests_failed.value = 0 + progress_prefix = prefix + + 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, tests).get(9999999) + pool.close() + pool.join() + + printer.UpdateProgress(start_time, + n_tests_passed.value, + n_tests_failed.value, + n_tests, + '== Done ==', + prevent_next_overwrite = True, + prefix = progress_prefix) + + # `0` indicates success + return n_tests_failed.value diff --git a/tools/util.py b/tools/util.py index 1c127de4..6862b891 100644 --- a/tools/util.py +++ b/tools/util.py @@ -1,4 +1,4 @@ -# Copyright 2014, ARM Limited +# Copyright 2015, ARM Limited # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -24,11 +24,17 @@ # 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 glob import os -import sys -import subprocess -import shlex import re +import shlex +import subprocess +import sys + + +def ListCCFilesWithoutExt(path): + return map(lambda x : os.path.splitext(os.path.basename(x))[0], + glob.glob(os.path.join(path, '*.cc'))) def abort(message): @@ -37,21 +43,23 @@ def abort(message): # Emulate python3 subprocess.getstatusoutput. -def getstatusoutput(command): +def getstatusoutput(command, shell=False): try: args = shlex.split(command) - output = subprocess.check_output(args, stderr=subprocess.STDOUT) + output = subprocess.check_output(args, stderr=subprocess.STDOUT, shell=shell) return 0, output.rstrip('\n') except subprocess.CalledProcessError as e: return e.returncode, e.output.rstrip('\n') -def last_line(text): - lines = text.split('\n') - last = lines[-1].split('\r') - return last[-1] +def ensure_dir(path_name): + if not os.path.exists(path_name): + os.makedirs(path_name) -def has_compiler(compiler): - status, output = getstatusoutput('which ' + compiler) - return status == 0 +# Check that the specified program is available. +def require_program(program_name): + rc, out = getstatusoutput('which %s' % program_name) + if rc != 0: + print('ERROR: The required program %s was not found.' % program_name) + sys.exit(rc) |