blob: 1b344ca886dbd8ecb1ae344ccf7abf9cf08797cd [file] [log] [blame]
armvixlad96eda2013-06-14 11:42:37 +01001#!/usr/bin/env python2.7
2
armvixldb644342015-07-21 11:37:10 +01003# Copyright 2015, ARM Limited
armvixlad96eda2013-06-14 11:42:37 +01004# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9# * Redistributions of source code must retain the above copyright notice,
10# this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright notice,
12# this list of conditions and the following disclaimer in the documentation
13# and/or other materials provided with the distribution.
14# * Neither the name of ARM Limited nor the names of its contributors may be
15# used to endorse or promote products derived from this software without
16# specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
19# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
armvixlad96eda2013-06-14 11:42:37 +010029import argparse
armvixldb644342015-07-21 11:37:10 +010030import fcntl
31import git
32import itertools
armvixl4a102ba2014-07-14 09:02:40 +010033import multiprocessing
armvixldb644342015-07-21 11:37:10 +010034import os
35from os.path import join
36import platform
37import re
38import subprocess
39import sys
armvixlad96eda2013-06-14 11:42:37 +010040import time
armvixldb644342015-07-21 11:37:10 +010041
42import config
armvixl0f35e362016-05-10 13:57:58 +010043import clang_format
armvixldb644342015-07-21 11:37:10 +010044import lint
45import printer
46import test
47import threaded_tests
armvixlad96eda2013-06-14 11:42:37 +010048import util
49
50
armvixldb644342015-07-21 11:37:10 +010051dir_root = config.dir_root
52
53def Optionify(name):
54 return '--' + name
55
56
57# The options that can be tested are abstracted to provide an easy way to add
58# new ones.
59# Environment options influence the environment. They can be used for example to
60# set the compiler used.
61# Build options are options passed to scons, with a syntax like `scons opt=val`
62# Runtime options are options passed to the test program.
63# See the definition of `test_options` below.
64
65# 'all' is a special value for the options. If specified, all other values of
66# the option are tested.
67class TestOption(object):
68 type_environment = 'type_environment'
69 type_build = 'type_build'
70 type_run = 'type_run'
71
72 def __init__(self, option_type, name, help,
73 val_test_choices, val_test_default = None,
74 # If unset, the user can pass any value.
75 strict_choices = True):
76 self.name = name
77 self.option_type = option_type
78 self.help = help
79 self.val_test_choices = val_test_choices
80 self.strict_choices = strict_choices
81 if val_test_default is not None:
82 self.val_test_default = val_test_default
83 else:
84 self.val_test_default = val_test_choices[0]
85
86 def ArgList(self, to_test):
87 res = []
88 if to_test == 'all':
89 for value in self.val_test_choices:
90 if value != 'all':
91 res.append(self.GetOptionString(value))
92 else:
93 for value in to_test:
94 res.append(self.GetOptionString(value))
95 return res
96
97class EnvironmentOption(TestOption):
98 option_type = TestOption.type_environment
99 def __init__(self, name, environment_variable_name, help,
100 val_test_choices, val_test_default = None,
101 strict_choices = True):
102 super(EnvironmentOption, self).__init__(EnvironmentOption.option_type,
103 name,
104 help,
105 val_test_choices,
106 val_test_default,
107 strict_choices = strict_choices)
108 self.environment_variable_name = environment_variable_name
109
110 def GetOptionString(self, value):
111 return self.environment_variable_name + '=' + value
112
113
114class BuildOption(TestOption):
115 option_type = TestOption.type_build
116 def __init__(self, name, help,
117 val_test_choices, val_test_default = None,
118 strict_choices = True):
119 super(BuildOption, self).__init__(BuildOption.option_type,
120 name,
121 help,
122 val_test_choices,
123 val_test_default,
124 strict_choices = strict_choices)
125 def GetOptionString(self, value):
126 return self.name + '=' + value
127
128
129class RuntimeOption(TestOption):
130 option_type = TestOption.type_run
131 def __init__(self, name, help,
132 val_test_choices, val_test_default = None):
133 super(RuntimeOption, self).__init__(RuntimeOption.option_type,
134 name,
135 help,
136 val_test_choices,
137 val_test_default)
138 def GetOptionString(self, value):
139 if value == 'on':
140 return Optionify(self.name)
141 else:
142 return None
143
144
145
146environment_option_compiler = \
147 EnvironmentOption('compiler', 'CXX', 'Test for the specified compilers.',
148 val_test_choices=['all'] + config.tested_compilers,
149 strict_choices = False)
150test_environment_options = [
151 environment_option_compiler
152]
153
154build_option_mode = \
155 BuildOption('mode', 'Test with the specified build modes.',
156 val_test_choices=['all'] + config.build_options_modes)
157build_option_standard = \
158 BuildOption('std', 'Test with the specified C++ standard.',
159 val_test_choices=['all'] + config.tested_cpp_standards,
160 strict_choices = False)
161test_build_options = [
162 build_option_mode,
163 build_option_standard
164]
165
166runtime_option_debugger = \
167 RuntimeOption('debugger',
168 '''Test with the specified configurations for the debugger.
169 Note that this is only tested if we are using the simulator.''',
170 val_test_choices=['all', 'on', 'off'])
171test_runtime_options = [
172 runtime_option_debugger
173]
174
175test_options = \
176 test_environment_options + test_build_options + test_runtime_options
armvixl5799d6c2014-05-01 11:05:00 +0100177
178
armvixlad96eda2013-06-14 11:42:37 +0100179def BuildOptions():
armvixldb644342015-07-21 11:37:10 +0100180 args = argparse.ArgumentParser(
181 description =
armvixl0f35e362016-05-10 13:57:58 +0100182 '''This tool runs all tests matching the specified filters for multiple
armvixldb644342015-07-21 11:37:10 +0100183 environment, build options, and runtime options configurations.''',
184 # Print default values.
185 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
186
187 args.add_argument('filters', metavar='filter', nargs='*',
188 help='Run tests matching all of the (regexp) filters.')
189
190 # We automatically build the script options from the options to be tested.
191 test_arguments = args.add_argument_group(
192 'Test options',
193 'These options indicate what should be tested')
194 for option in test_options:
195 choices = option.val_test_choices if option.strict_choices else None
196 help = option.help
197 if not option.strict_choices:
198 help += ' Supported values: {' + ','.join(option.val_test_choices) + '}'
199 test_arguments.add_argument(Optionify(option.name),
200 nargs='+',
201 choices=choices,
202 default=option.val_test_default,
203 help=help,
204 action='store')
205
206 general_arguments = args.add_argument_group('General options')
207 general_arguments.add_argument('--fast', action='store_true',
armvixl0f35e362016-05-10 13:57:58 +0100208 help='''Skip the lint and clang-format tests,
209 and run only with one compiler, in one mode,
210 with one C++ standard, and with an appropriate
211 default for runtime options. The compiler,
212 mode, and C++ standard used are the first ones
213 provided to the script or in the default
214 arguments.''')
armvixldb644342015-07-21 11:37:10 +0100215 general_arguments.add_argument(
216 '--jobs', '-j', metavar='N', type=int, nargs='?',
217 default=multiprocessing.cpu_count(),
218 const=multiprocessing.cpu_count(),
219 help='''Runs the tests using N jobs. If the option is set but no value is
220 provided, the script will use as many jobs as it thinks useful.''')
221 general_arguments.add_argument('--nobench', action='store_true',
222 help='Do not run benchmarks.')
223 general_arguments.add_argument('--nolint', action='store_true',
224 help='Do not run the linter.')
armvixl0f35e362016-05-10 13:57:58 +0100225 general_arguments.add_argument('--noclang-format', action='store_true',
226 help='Do not run clang-format.')
armvixldb644342015-07-21 11:37:10 +0100227 general_arguments.add_argument('--notest', action='store_true',
228 help='Do not run tests.')
armvixlc68cb642014-09-25 18:49:30 +0100229 sim_default = 'off' if platform.machine() == 'aarch64' else 'on'
armvixldb644342015-07-21 11:37:10 +0100230 general_arguments.add_argument(
231 '--simulator', action='store', choices=['on', 'off'],
232 default=sim_default,
233 help='Explicitly enable or disable the simulator.')
armvixl684cd2a2015-10-23 13:38:33 +0100234 general_arguments.add_argument(
235 '--under_valgrind', action='store_true',
236 help='''Run the test-runner commands under Valgrind.
237 Note that a few tests are known to fail because of
238 issues in Valgrind''')
armvixldb644342015-07-21 11:37:10 +0100239 return args.parse_args()
armvixlad96eda2013-06-14 11:42:37 +0100240
241
armvixldb644342015-07-21 11:37:10 +0100242def RunCommand(command, environment_options = None):
243 # Create a copy of the environment. We do not want to pollute the environment
244 # of future commands run.
245 environment = os.environ
246 # Configure the environment.
247 # TODO: We currently pass the options as strings, so we need to parse them. We
248 # should instead pass them as a data structure and build the string option
249 # later. `environment_options` looks like `['CXX=compiler', 'OPT=val']`.
250 if environment_options:
251 for option in environment_options:
252 opt, val = option.split('=')
253 environment[opt] = val
armvixlad96eda2013-06-14 11:42:37 +0100254
armvixldb644342015-07-21 11:37:10 +0100255 printable_command = ''
256 if environment_options:
257 printable_command += ' '.join(environment_options) + ' '
258 printable_command += ' '.join(command)
armvixlad96eda2013-06-14 11:42:37 +0100259
armvixldb644342015-07-21 11:37:10 +0100260 printable_command_orange = \
261 printer.COLOUR_ORANGE + printable_command + printer.NO_COLOUR
262 printer.PrintOverwritableLine(printable_command_orange)
263 sys.stdout.flush()
armvixlad96eda2013-06-14 11:42:37 +0100264
armvixldb644342015-07-21 11:37:10 +0100265 # Start a process for the command.
266 # Interleave `stderr` and `stdout`.
267 p = subprocess.Popen(command,
268 stdout=subprocess.PIPE,
269 stderr=subprocess.STDOUT,
270 env=environment)
armvixlad96eda2013-06-14 11:42:37 +0100271
armvixldb644342015-07-21 11:37:10 +0100272 # We want to be able to display a continuously updated 'work indicator' while
273 # the process is running. Since the process can hang if the `stdout` pipe is
274 # full, we need to pull from it regularly. We cannot do so via the
275 # `readline()` function because it is blocking, and would thus cause the
276 # indicator to not be updated properly. So use file control mechanisms
277 # instead.
278 indicator = ' (still working: %d seconds elapsed)'
armvixl5799d6c2014-05-01 11:05:00 +0100279
armvixldb644342015-07-21 11:37:10 +0100280 # Mark the process output as non-blocking.
281 flags = fcntl.fcntl(p.stdout, fcntl.F_GETFL)
282 fcntl.fcntl(p.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
armvixl5799d6c2014-05-01 11:05:00 +0100283
armvixldb644342015-07-21 11:37:10 +0100284 t_start = time.time()
285 t_last_indication = t_start
286 process_output = ''
armvixl5799d6c2014-05-01 11:05:00 +0100287
armvixldb644342015-07-21 11:37:10 +0100288 # Keep looping as long as the process is running.
289 while p.poll() is None:
290 # Avoid polling too often.
291 time.sleep(0.1)
292 # Update the progress indicator.
293 t_current = time.time()
294 if (t_current - t_start >= 2) and (t_current - t_last_indication >= 1):
295 printer.PrintOverwritableLine(
296 printable_command_orange + indicator % int(t_current - t_start))
297 sys.stdout.flush()
298 t_last_indication = t_current
299 # Pull from the process output.
300 while True:
301 try:
302 line = os.read(p.stdout.fileno(), 1024)
303 except OSError:
304 line = ''
305 break
306 if line == '': break
307 process_output += line
armvixlad96eda2013-06-14 11:42:37 +0100308
armvixldb644342015-07-21 11:37:10 +0100309 # The process has exited. Don't forget to retrieve the rest of its output.
310 out, err = p.communicate()
311 rc = p.poll()
312 process_output += out
armvixlad96eda2013-06-14 11:42:37 +0100313
armvixldb644342015-07-21 11:37:10 +0100314 if rc == 0:
315 printer.Print(printer.COLOUR_GREEN + printable_command + printer.NO_COLOUR)
armvixl4a102ba2014-07-14 09:02:40 +0100316 else:
armvixldb644342015-07-21 11:37:10 +0100317 printer.Print(printer.COLOUR_RED + printable_command + printer.NO_COLOUR)
318 printer.Print(process_output)
319 return rc
armvixl5799d6c2014-05-01 11:05:00 +0100320
321
armvixldb644342015-07-21 11:37:10 +0100322def RunLinter():
323 rc, default_tracked_files = lint.GetDefaultTrackedFiles()
324 if rc:
325 return rc
326 return lint.LintFiles(map(lambda x: join(dir_root, x), default_tracked_files),
327 jobs = args.jobs, progress_prefix = 'cpp lint: ')
armvixlad96eda2013-06-14 11:42:37 +0100328
armvixlad96eda2013-06-14 11:42:37 +0100329
armvixl0f35e362016-05-10 13:57:58 +0100330def RunClangFormat():
331 return clang_format.ClangFormatFiles(clang_format.GetCppSourceFilesToFormat(),
332 jobs = args.jobs,
333 progress_prefix = 'clang-format: ')
334
335
armvixlad96eda2013-06-14 11:42:37 +0100336
armvixldb644342015-07-21 11:37:10 +0100337def BuildAll(build_options, jobs):
338 scons_command = ["scons", "-C", dir_root, 'all', '-j', str(jobs)]
339 scons_command += list(build_options)
340 return RunCommand(scons_command, list(environment_options))
armvixlad96eda2013-06-14 11:42:37 +0100341
armvixl4a102ba2014-07-14 09:02:40 +0100342
armvixldb644342015-07-21 11:37:10 +0100343def RunBenchmarks():
344 rc = 0
345 benchmark_names = util.ListCCFilesWithoutExt(config.dir_benchmarks)
346 for bench in benchmark_names:
347 rc |= RunCommand(
348 [os.path.realpath(join(config.dir_build_latest, 'benchmarks', bench))])
349 return rc
armvixl4a102ba2014-07-14 09:02:40 +0100350
armvixl4a102ba2014-07-14 09:02:40 +0100351
armvixldb644342015-07-21 11:37:10 +0100352def PrintStatus(success):
353 printer.Print('\n$ ' + ' '.join(sys.argv))
354 if success:
355 printer.Print('SUCCESS')
356 else:
357 printer.Print('FAILURE')
armvixl4a102ba2014-07-14 09:02:40 +0100358
armvixlad96eda2013-06-14 11:42:37 +0100359
360
361if __name__ == '__main__':
armvixldb644342015-07-21 11:37:10 +0100362 util.require_program('scons')
363 rc = 0
armvixlad96eda2013-06-14 11:42:37 +0100364
armvixlad96eda2013-06-14 11:42:37 +0100365 args = BuildOptions()
armvixlad96eda2013-06-14 11:42:37 +0100366
armvixl684cd2a2015-10-23 13:38:33 +0100367 if args.under_valgrind:
368 util.require_program('valgrind')
369
armvixldb644342015-07-21 11:37:10 +0100370 if args.fast:
371 def SetFast(option, specified, default):
372 option.val_test_choices = \
373 [default[0] if specified == 'all' else specified[0]]
374 SetFast(environment_option_compiler, args.compiler, config.tested_compilers)
375 SetFast(build_option_mode, args.mode, config.build_options_modes)
376 SetFast(build_option_standard, args.std, config.tested_cpp_standards)
377 SetFast(runtime_option_debugger, args.debugger, ['on', 'off'])
armvixlad96eda2013-06-14 11:42:37 +0100378
armvixldb644342015-07-21 11:37:10 +0100379 if not args.nolint and not args.fast:
380 rc |= RunLinter()
armvixl0f35e362016-05-10 13:57:58 +0100381 if not args.noclang_format and not args.fast:
382 rc |= RunClangFormat()
armvixl5799d6c2014-05-01 11:05:00 +0100383
armvixldb644342015-07-21 11:37:10 +0100384 # Don't try to test the debugger if we are not running with the simulator.
385 if not args.simulator:
386 test_runtime_options = \
387 filter(lambda x: x.name != 'debugger', test_runtime_options)
armvixlad96eda2013-06-14 11:42:37 +0100388
armvixldb644342015-07-21 11:37:10 +0100389 # List all combinations of options that will be tested.
390 def ListCombinations(args, options):
391 opts_list = map(lambda opt : opt.ArgList(args.__dict__[opt.name]), options)
392 return list(itertools.product(*opts_list))
393 test_env_combinations = ListCombinations(args, test_environment_options)
394 test_build_combinations = ListCombinations(args, test_build_options)
395 test_runtime_combinations = ListCombinations(args, test_runtime_options)
armvixlad96eda2013-06-14 11:42:37 +0100396
armvixldb644342015-07-21 11:37:10 +0100397 for environment_options in test_env_combinations:
398 for build_options in test_build_combinations:
399 # Avoid going through the build stage if we are not using the build
400 # result.
401 if not (args.notest and args.nobench):
402 build_rc = BuildAll(build_options, args.jobs)
403 # Don't run the tests for this configuration if the build failed.
404 if build_rc != 0:
405 rc |= build_rc
406 continue
armvixlad96eda2013-06-14 11:42:37 +0100407
armvixldb644342015-07-21 11:37:10 +0100408 # Use the realpath of the test executable so that the commands printed
409 # can be copy-pasted and run.
410 test_executable = os.path.realpath(
411 join(config.dir_build_latest, 'test', 'test-runner'))
412
413 if not args.notest:
414 printer.Print(test_executable)
415
416 for runtime_options in test_runtime_combinations:
417 if not args.notest:
418 runtime_options = [x for x in runtime_options if x is not None]
419 prefix = ' ' + ' '.join(runtime_options) + ' '
420 rc |= threaded_tests.RunTests(test_executable,
421 args.filters,
422 list(runtime_options),
armvixl684cd2a2015-10-23 13:38:33 +0100423 args.under_valgrind,
armvixldb644342015-07-21 11:37:10 +0100424 jobs = args.jobs, prefix = prefix)
425
426 if not args.nobench:
427 rc |= RunBenchmarks()
428
429 PrintStatus(rc == 0)
Alexandre Rames7c0ea8b2016-05-18 13:47:42 +0100430
431 sys.exit(rc)