aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorarmvixl <vixl@arm.com>2015-07-21 11:37:10 +0100
committerarmvixl <vixl@arm.com>2015-07-21 11:37:10 +0100
commitdb6443499376478f5281607a3923e6ffc4c8d8ec (patch)
tree7b735273ab6b530d3388dd61d0ff22f3565b47af /tools
parent6e2c8275d5f34a531fe1eef7a7aa877601be8558 (diff)
VIXL Release 1.10
Refer to the README.md and LICENCE files for details.
Diffstat (limited to 'tools')
-rw-r--r--tools/config.py46
-rw-r--r--tools/git.py4
-rwxr-xr-xtools/lint.py131
-rwxr-xr-xtools/make_instruction_doc.pl5
-rwxr-xr-xtools/presubmit.py309
-rw-r--r--tools/printer.py106
-rwxr-xr-xtools/test.py536
-rw-r--r--tools/threaded_tests.py153
-rw-r--r--tools/util.py34
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)