blob: b4e9da9d548a2df39b0852d70127434528897ecb [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
43import lint
44import printer
45import test
46import threaded_tests
armvixlad96eda2013-06-14 11:42:37 +010047import util
48
49
armvixldb644342015-07-21 11:37:10 +010050dir_root = config.dir_root
51
52def Optionify(name):
53 return '--' + name
54
55
56# The options that can be tested are abstracted to provide an easy way to add
57# new ones.
58# Environment options influence the environment. They can be used for example to
59# set the compiler used.
60# Build options are options passed to scons, with a syntax like `scons opt=val`
61# Runtime options are options passed to the test program.
62# See the definition of `test_options` below.
63
64# 'all' is a special value for the options. If specified, all other values of
65# the option are tested.
66class TestOption(object):
67 type_environment = 'type_environment'
68 type_build = 'type_build'
69 type_run = 'type_run'
70
71 def __init__(self, option_type, name, help,
72 val_test_choices, val_test_default = None,
73 # If unset, the user can pass any value.
74 strict_choices = True):
75 self.name = name
76 self.option_type = option_type
77 self.help = help
78 self.val_test_choices = val_test_choices
79 self.strict_choices = strict_choices
80 if val_test_default is not None:
81 self.val_test_default = val_test_default
82 else:
83 self.val_test_default = val_test_choices[0]
84
85 def ArgList(self, to_test):
86 res = []
87 if to_test == 'all':
88 for value in self.val_test_choices:
89 if value != 'all':
90 res.append(self.GetOptionString(value))
91 else:
92 for value in to_test:
93 res.append(self.GetOptionString(value))
94 return res
95
96class EnvironmentOption(TestOption):
97 option_type = TestOption.type_environment
98 def __init__(self, name, environment_variable_name, help,
99 val_test_choices, val_test_default = None,
100 strict_choices = True):
101 super(EnvironmentOption, self).__init__(EnvironmentOption.option_type,
102 name,
103 help,
104 val_test_choices,
105 val_test_default,
106 strict_choices = strict_choices)
107 self.environment_variable_name = environment_variable_name
108
109 def GetOptionString(self, value):
110 return self.environment_variable_name + '=' + value
111
112
113class BuildOption(TestOption):
114 option_type = TestOption.type_build
115 def __init__(self, name, help,
116 val_test_choices, val_test_default = None,
117 strict_choices = True):
118 super(BuildOption, self).__init__(BuildOption.option_type,
119 name,
120 help,
121 val_test_choices,
122 val_test_default,
123 strict_choices = strict_choices)
124 def GetOptionString(self, value):
125 return self.name + '=' + value
126
127
128class RuntimeOption(TestOption):
129 option_type = TestOption.type_run
130 def __init__(self, name, help,
131 val_test_choices, val_test_default = None):
132 super(RuntimeOption, self).__init__(RuntimeOption.option_type,
133 name,
134 help,
135 val_test_choices,
136 val_test_default)
137 def GetOptionString(self, value):
138 if value == 'on':
139 return Optionify(self.name)
140 else:
141 return None
142
143
144
145environment_option_compiler = \
146 EnvironmentOption('compiler', 'CXX', 'Test for the specified compilers.',
147 val_test_choices=['all'] + config.tested_compilers,
148 strict_choices = False)
149test_environment_options = [
150 environment_option_compiler
151]
152
153build_option_mode = \
154 BuildOption('mode', 'Test with the specified build modes.',
155 val_test_choices=['all'] + config.build_options_modes)
156build_option_standard = \
157 BuildOption('std', 'Test with the specified C++ standard.',
158 val_test_choices=['all'] + config.tested_cpp_standards,
159 strict_choices = False)
160test_build_options = [
161 build_option_mode,
162 build_option_standard
163]
164
165runtime_option_debugger = \
166 RuntimeOption('debugger',
167 '''Test with the specified configurations for the debugger.
168 Note that this is only tested if we are using the simulator.''',
169 val_test_choices=['all', 'on', 'off'])
170test_runtime_options = [
171 runtime_option_debugger
172]
173
174test_options = \
175 test_environment_options + test_build_options + test_runtime_options
armvixl5799d6c2014-05-01 11:05:00 +0100176
177
armvixlad96eda2013-06-14 11:42:37 +0100178def BuildOptions():
armvixldb644342015-07-21 11:37:10 +0100179 args = argparse.ArgumentParser(
180 description =
181 '''This tool runs all tests matching the speficied filters for multiple
182 environment, build options, and runtime options configurations.''',
183 # Print default values.
184 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
185
186 args.add_argument('filters', metavar='filter', nargs='*',
187 help='Run tests matching all of the (regexp) filters.')
188
189 # We automatically build the script options from the options to be tested.
190 test_arguments = args.add_argument_group(
191 'Test options',
192 'These options indicate what should be tested')
193 for option in test_options:
194 choices = option.val_test_choices if option.strict_choices else None
195 help = option.help
196 if not option.strict_choices:
197 help += ' Supported values: {' + ','.join(option.val_test_choices) + '}'
198 test_arguments.add_argument(Optionify(option.name),
199 nargs='+',
200 choices=choices,
201 default=option.val_test_default,
202 help=help,
203 action='store')
204
205 general_arguments = args.add_argument_group('General options')
206 general_arguments.add_argument('--fast', action='store_true',
207 help='''Skip the lint tests, and run only with
208 one compiler, in one mode, with one C++
209 standard, and with an appropriate default for
210 runtime options. The compiler, mode, and C++
211 standard used are the first ones provided to
212 the script or in the default arguments.''')
213 general_arguments.add_argument(
214 '--jobs', '-j', metavar='N', type=int, nargs='?',
215 default=multiprocessing.cpu_count(),
216 const=multiprocessing.cpu_count(),
217 help='''Runs the tests using N jobs. If the option is set but no value is
218 provided, the script will use as many jobs as it thinks useful.''')
219 general_arguments.add_argument('--nobench', action='store_true',
220 help='Do not run benchmarks.')
221 general_arguments.add_argument('--nolint', action='store_true',
222 help='Do not run the linter.')
223 general_arguments.add_argument('--notest', action='store_true',
224 help='Do not run tests.')
armvixlc68cb642014-09-25 18:49:30 +0100225 sim_default = 'off' if platform.machine() == 'aarch64' else 'on'
armvixldb644342015-07-21 11:37:10 +0100226 general_arguments.add_argument(
227 '--simulator', action='store', choices=['on', 'off'],
228 default=sim_default,
229 help='Explicitly enable or disable the simulator.')
armvixl684cd2a2015-10-23 13:38:33 +0100230 general_arguments.add_argument(
231 '--under_valgrind', action='store_true',
232 help='''Run the test-runner commands under Valgrind.
233 Note that a few tests are known to fail because of
234 issues in Valgrind''')
armvixldb644342015-07-21 11:37:10 +0100235 return args.parse_args()
armvixlad96eda2013-06-14 11:42:37 +0100236
237
armvixldb644342015-07-21 11:37:10 +0100238def RunCommand(command, environment_options = None):
239 # Create a copy of the environment. We do not want to pollute the environment
240 # of future commands run.
241 environment = os.environ
242 # Configure the environment.
243 # TODO: We currently pass the options as strings, so we need to parse them. We
244 # should instead pass them as a data structure and build the string option
245 # later. `environment_options` looks like `['CXX=compiler', 'OPT=val']`.
246 if environment_options:
247 for option in environment_options:
248 opt, val = option.split('=')
249 environment[opt] = val
armvixlad96eda2013-06-14 11:42:37 +0100250
armvixldb644342015-07-21 11:37:10 +0100251 printable_command = ''
252 if environment_options:
253 printable_command += ' '.join(environment_options) + ' '
254 printable_command += ' '.join(command)
armvixlad96eda2013-06-14 11:42:37 +0100255
armvixldb644342015-07-21 11:37:10 +0100256 printable_command_orange = \
257 printer.COLOUR_ORANGE + printable_command + printer.NO_COLOUR
258 printer.PrintOverwritableLine(printable_command_orange)
259 sys.stdout.flush()
armvixlad96eda2013-06-14 11:42:37 +0100260
armvixldb644342015-07-21 11:37:10 +0100261 # Start a process for the command.
262 # Interleave `stderr` and `stdout`.
263 p = subprocess.Popen(command,
264 stdout=subprocess.PIPE,
265 stderr=subprocess.STDOUT,
266 env=environment)
armvixlad96eda2013-06-14 11:42:37 +0100267
armvixldb644342015-07-21 11:37:10 +0100268 # We want to be able to display a continuously updated 'work indicator' while
269 # the process is running. Since the process can hang if the `stdout` pipe is
270 # full, we need to pull from it regularly. We cannot do so via the
271 # `readline()` function because it is blocking, and would thus cause the
272 # indicator to not be updated properly. So use file control mechanisms
273 # instead.
274 indicator = ' (still working: %d seconds elapsed)'
armvixl5799d6c2014-05-01 11:05:00 +0100275
armvixldb644342015-07-21 11:37:10 +0100276 # Mark the process output as non-blocking.
277 flags = fcntl.fcntl(p.stdout, fcntl.F_GETFL)
278 fcntl.fcntl(p.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
armvixl5799d6c2014-05-01 11:05:00 +0100279
armvixldb644342015-07-21 11:37:10 +0100280 t_start = time.time()
281 t_last_indication = t_start
282 process_output = ''
armvixl5799d6c2014-05-01 11:05:00 +0100283
armvixldb644342015-07-21 11:37:10 +0100284 # Keep looping as long as the process is running.
285 while p.poll() is None:
286 # Avoid polling too often.
287 time.sleep(0.1)
288 # Update the progress indicator.
289 t_current = time.time()
290 if (t_current - t_start >= 2) and (t_current - t_last_indication >= 1):
291 printer.PrintOverwritableLine(
292 printable_command_orange + indicator % int(t_current - t_start))
293 sys.stdout.flush()
294 t_last_indication = t_current
295 # Pull from the process output.
296 while True:
297 try:
298 line = os.read(p.stdout.fileno(), 1024)
299 except OSError:
300 line = ''
301 break
302 if line == '': break
303 process_output += line
armvixlad96eda2013-06-14 11:42:37 +0100304
armvixldb644342015-07-21 11:37:10 +0100305 # The process has exited. Don't forget to retrieve the rest of its output.
306 out, err = p.communicate()
307 rc = p.poll()
308 process_output += out
armvixlad96eda2013-06-14 11:42:37 +0100309
armvixldb644342015-07-21 11:37:10 +0100310 if rc == 0:
311 printer.Print(printer.COLOUR_GREEN + printable_command + printer.NO_COLOUR)
armvixl4a102ba2014-07-14 09:02:40 +0100312 else:
armvixldb644342015-07-21 11:37:10 +0100313 printer.Print(printer.COLOUR_RED + printable_command + printer.NO_COLOUR)
314 printer.Print(process_output)
315 return rc
armvixl5799d6c2014-05-01 11:05:00 +0100316
317
armvixldb644342015-07-21 11:37:10 +0100318def RunLinter():
319 rc, default_tracked_files = lint.GetDefaultTrackedFiles()
320 if rc:
321 return rc
322 return lint.LintFiles(map(lambda x: join(dir_root, x), default_tracked_files),
323 jobs = args.jobs, progress_prefix = 'cpp lint: ')
armvixlad96eda2013-06-14 11:42:37 +0100324
armvixlad96eda2013-06-14 11:42:37 +0100325
armvixlad96eda2013-06-14 11:42:37 +0100326
armvixldb644342015-07-21 11:37:10 +0100327def BuildAll(build_options, jobs):
328 scons_command = ["scons", "-C", dir_root, 'all', '-j', str(jobs)]
329 scons_command += list(build_options)
330 return RunCommand(scons_command, list(environment_options))
armvixlad96eda2013-06-14 11:42:37 +0100331
armvixl4a102ba2014-07-14 09:02:40 +0100332
armvixldb644342015-07-21 11:37:10 +0100333def RunBenchmarks():
334 rc = 0
335 benchmark_names = util.ListCCFilesWithoutExt(config.dir_benchmarks)
336 for bench in benchmark_names:
337 rc |= RunCommand(
338 [os.path.realpath(join(config.dir_build_latest, 'benchmarks', bench))])
339 return rc
armvixl4a102ba2014-07-14 09:02:40 +0100340
armvixl4a102ba2014-07-14 09:02:40 +0100341
armvixldb644342015-07-21 11:37:10 +0100342def PrintStatus(success):
343 printer.Print('\n$ ' + ' '.join(sys.argv))
344 if success:
345 printer.Print('SUCCESS')
346 else:
347 printer.Print('FAILURE')
armvixl4a102ba2014-07-14 09:02:40 +0100348
armvixlad96eda2013-06-14 11:42:37 +0100349
350
351if __name__ == '__main__':
armvixldb644342015-07-21 11:37:10 +0100352 util.require_program('scons')
353 rc = 0
armvixlad96eda2013-06-14 11:42:37 +0100354
armvixlad96eda2013-06-14 11:42:37 +0100355 args = BuildOptions()
armvixlad96eda2013-06-14 11:42:37 +0100356
armvixl684cd2a2015-10-23 13:38:33 +0100357 if args.under_valgrind:
358 util.require_program('valgrind')
359
armvixldb644342015-07-21 11:37:10 +0100360 if args.fast:
361 def SetFast(option, specified, default):
362 option.val_test_choices = \
363 [default[0] if specified == 'all' else specified[0]]
364 SetFast(environment_option_compiler, args.compiler, config.tested_compilers)
365 SetFast(build_option_mode, args.mode, config.build_options_modes)
366 SetFast(build_option_standard, args.std, config.tested_cpp_standards)
367 SetFast(runtime_option_debugger, args.debugger, ['on', 'off'])
armvixlad96eda2013-06-14 11:42:37 +0100368
armvixldb644342015-07-21 11:37:10 +0100369 if not args.nolint and not args.fast:
370 rc |= RunLinter()
armvixl5799d6c2014-05-01 11:05:00 +0100371
armvixldb644342015-07-21 11:37:10 +0100372 # Don't try to test the debugger if we are not running with the simulator.
373 if not args.simulator:
374 test_runtime_options = \
375 filter(lambda x: x.name != 'debugger', test_runtime_options)
armvixlad96eda2013-06-14 11:42:37 +0100376
armvixldb644342015-07-21 11:37:10 +0100377 # List all combinations of options that will be tested.
378 def ListCombinations(args, options):
379 opts_list = map(lambda opt : opt.ArgList(args.__dict__[opt.name]), options)
380 return list(itertools.product(*opts_list))
381 test_env_combinations = ListCombinations(args, test_environment_options)
382 test_build_combinations = ListCombinations(args, test_build_options)
383 test_runtime_combinations = ListCombinations(args, test_runtime_options)
armvixlad96eda2013-06-14 11:42:37 +0100384
armvixldb644342015-07-21 11:37:10 +0100385 for environment_options in test_env_combinations:
386 for build_options in test_build_combinations:
387 # Avoid going through the build stage if we are not using the build
388 # result.
389 if not (args.notest and args.nobench):
390 build_rc = BuildAll(build_options, args.jobs)
391 # Don't run the tests for this configuration if the build failed.
392 if build_rc != 0:
393 rc |= build_rc
394 continue
armvixlad96eda2013-06-14 11:42:37 +0100395
armvixldb644342015-07-21 11:37:10 +0100396 # Use the realpath of the test executable so that the commands printed
397 # can be copy-pasted and run.
398 test_executable = os.path.realpath(
399 join(config.dir_build_latest, 'test', 'test-runner'))
400
401 if not args.notest:
402 printer.Print(test_executable)
403
404 for runtime_options in test_runtime_combinations:
405 if not args.notest:
406 runtime_options = [x for x in runtime_options if x is not None]
407 prefix = ' ' + ' '.join(runtime_options) + ' '
408 rc |= threaded_tests.RunTests(test_executable,
409 args.filters,
410 list(runtime_options),
armvixl684cd2a2015-10-23 13:38:33 +0100411 args.under_valgrind,
armvixldb644342015-07-21 11:37:10 +0100412 jobs = args.jobs, prefix = prefix)
413
414 if not args.nobench:
415 rc |= RunBenchmarks()
416
417 PrintStatus(rc == 0)