1#!/usr/bin/env python2.7
2
3# Copyright 2015, ARM Limited
4# 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
29import os
30import sys
31import argparse
32import re
33import platform
34import subprocess
35import multiprocessing
36
37import git
38import printer
39import test
40import util
41
42
43SUPPORTED_COMPILERS = ['g++', 'clang++']
44OBJ_DIR = './obj'
45
46
47def BuildOptions():
48  result = argparse.ArgumentParser(
49          description='Run the linter and unit tests.',
50          # Print default values.
51          formatter_class=argparse.ArgumentDefaultsHelpFormatter)
52  result.add_argument('--verbose', '-v', action='store_true',
53                      help='Print all tests output at the end.')
54  result.add_argument('--notest', action='store_true',
55                      help='Do not run tests. Run the linter only.')
56  result.add_argument('--nolint', action='store_true',
57                      help='Do not run the linter. Run the tests only.')
58  result.add_argument('--noclean', action='store_true',
59                      help='Do not clean before build.')
60  result.add_argument('--fast', action='store_true',
61                      help='Only test with one toolchain')
62  result.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?',
63                      default=1, const=multiprocessing.cpu_count(),
64                      help='''Run the tests using N jobs. If the option is set
65                      but no value is provided, the script will use as many jobs
66                      as it thinks useful.''')
67  sim_default = 'off' if platform.machine() == 'aarch64' else 'on'
68  result.add_argument('--simulator', action='store', choices=['on', 'off'],
69                      default=sim_default,
70                      help='Explicitly enable or disable the simulator.')
71  return result.parse_args()
72
73
74def check_supported(compiler, mode, std):
75  if compiler not in SUPPORTED_COMPILERS:
76    print 'Invalid compiler.'
77    sys.exit(1)
78  if mode not in ['release', 'debug']:
79    print 'Invalid mode.'
80    sys.exit(1)
81  if std not in ['c++98', 'c++11']:
82    print 'Invalid c++ standard.'
83    sys.exit(1)
84
85
86def initalize_compiler_list():
87  compiler_list = []
88  for compiler in SUPPORTED_COMPILERS:
89    if util.has_compiler(compiler) and (len(compiler_list) == 0 or not args.fast):
90      compiler_list.append(compiler)
91    else:
92      # This warning suffices for args.fast too.
93      print 'WARNING: Skipping ' + compiler + ' tests.'
94  if len(compiler_list) == 0:
95    util.abort('Found no supported compilers')
96  return compiler_list
97
98
99def CleanBuildSystem(compiler):
100  def clean(compiler, mode, std):
101    check_supported(compiler, mode, std)
102    os.environ['CXX'] = compiler
103    if args.verbose:
104      print 'Cleaning ' + compiler + ' ' + std + ' ' \
105            + mode + ' mode test...'
106    command = 'scons mode=%s std=%s simulator=%s all --clean' % \
107              (mode, std, args.simulator)
108    status, output = util.getstatusoutput(command)
109    if status != 0:
110      print(output)
111      util.abort('Failed cleaning test: ' + command)
112
113  clean(compiler, 'debug',    'c++98')
114  clean(compiler, 'debug',    'c++11')
115  clean(compiler, 'release',  'c++98')
116  clean(compiler, 'release',  'c++11')
117
118
119def BuildEverything(compiler):
120  def build(compiler, mode, std):
121    check_supported(compiler, mode, std)
122    os.environ['CXX'] = compiler
123    if args.verbose:
124      print 'Building ' + compiler + ' ' +  std + ' ' \
125            + mode + ' mode test...'
126    if args.jobs == 1:
127      print '- This may take a while. Pass `-j` to use multiple threads.'
128    command = 'scons mode=%s std=%s simulator=%s all -j%u' % \
129              (mode, std, args.simulator, args.jobs)
130    status, output = util.getstatusoutput(command)
131    if status != 0:
132      print(output)
133      util.abort('Failed building test: ' + command)
134
135  print 'Building ' + compiler + ' tests...'
136  build(compiler, 'debug',    'c++98')
137  build(compiler, 'debug',    'c++11')
138  build(compiler, 'release',  'c++98')
139  build(compiler, 'release',  'c++11')
140
141
142NOT_RUN = 'NOT RUN'
143PASSED = 'PASSED'
144FAILED = 'FAILED'
145
146class Test:
147  def __init__(self, name):
148    self.name = name
149    self.status = NOT_RUN
150
151  def name_prefix(self):
152    return '%-40s : ' % self.name
153
154
155class Tester:
156  def __init__(self):
157    self.tests = []
158
159  def AddTest(self, test):
160    self.tests.append(test)
161
162  def RunAll(self):
163    result = PASSED
164    for test in self.tests:
165      if args.verbose: print('Running ' + test.name + '...')
166      test.Run()
167      if test.status != PASSED: result = FAILED
168    print('Presubmit tests ' + result + '.')
169
170
171class VIXLTest(Test):
172  def __init__(self, compiler, mode, std, simulator, debugger = False, verbose = False):
173    check_supported(compiler, mode, std)
174    self.verbose = verbose
175    self.debugger = debugger
176    self.compiler = compiler
177    self.mode = mode
178    self.std = std
179
180    name = 'test ' + compiler + ' ' + std + ' ' + mode
181    if simulator:
182      name += ' (%s)' % ('debugger' if debugger else 'simulator')
183    Test.__init__(self, name)
184
185    self.exe = 'test-runner'
186    if simulator:
187        self.exe += '_sim'
188    if mode == 'debug':
189      self.exe += '_g'
190
191  def Run(self):
192    self.status = PASSED
193    command = os.path.join(OBJ_DIR, self.mode, self.compiler,
194                           self.std, self.exe)
195    manifest = test.ReadManifest(command, [], self.debugger, False, self.verbose)
196    retcode = test.RunTests(manifest, jobs = args.jobs,
197                            verbose = self.verbose, debugger = self.debugger,
198                            progress_prefix = self.name_prefix())
199    printer.EnsureNewLine()
200    if retcode != 0:
201      self.status = FAILED
202
203
204class LintTest(Test):
205  def __init__(self):
206    name = 'cpp lint'
207    Test.__init__(self, name)
208
209  def Run(self):
210    if not lint.IsCppLintAvailable():
211      self.status = FAILED
212      print self.name_prefix() + FAILED + '''
213cpplint.py not found. Please ensure the depot tools are installed and in your
214PATH. See http://dev.chromium.org/developers/how-tos/install-depot-tools for
215details.'''
216      return
217
218    n_errors = lint.LintFiles(lint.default_tracked_files,
219                              jobs = args.jobs, verbose = args.verbose,
220                              progress_prefix = self.name_prefix())
221    self.status = PASSED if n_errors == 0 else FAILED
222
223
224class BenchTest(Test):
225  def __init__(self, compiler, mode, std, simulator):
226    check_supported(compiler, mode, std)
227    self.compiler = compiler
228    self.mode = mode
229    self.std = std
230
231    name = 'benchmarks ' + compiler + ' ' + std + ' ' + mode
232    Test.__init__(self, name)
233    self.exe_suffix = ''
234    if simulator:
235      self.exe_suffix += '_sim'
236    if mode == 'debug':
237      self.exe_suffix += '_g'
238
239  def Run(self):
240    benchmarks = ['bench-dataop', 'bench-branch', 'bench-branch-link',
241                  'bench-branch-masm', 'bench-branch-link-masm']
242    self.status = PASSED
243    for bench in benchmarks:
244      command = os.path.join(OBJ_DIR, self.mode, self.compiler, self.std,
245                             bench + self.exe_suffix)
246      (rc, out) = util.getstatusoutput(command)
247      if rc != 0:
248        self.status = FAILED
249        print self.name_prefix() + 'Failed to run `' + command + '`'
250    print self.name_prefix() + self.status
251
252
253
254if __name__ == '__main__':
255  original_dir = os.path.abspath('.')
256  # $ROOT/tools/presubmit.py
257  root_dir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
258  os.chdir(root_dir)
259  args = BuildOptions()
260
261  if not args.nolint and not git.is_git_repository_root():
262    print 'WARNING: This is not a Git repository. The linter will not run.'
263    args.nolint = True
264
265  if not args.nolint:
266    import lint
267    LintTest().Run()
268
269  if not args.notest:
270    tester = Tester()
271    compiler_list = initalize_compiler_list()
272
273    for compiler in compiler_list:
274      if not args.noclean:
275        CleanBuildSystem(compiler)
276      BuildEverything(compiler)
277
278      if args.simulator == 'on':
279        #                                 mode,       std,      sim,   debugger, verbose
280        tester.AddTest(VIXLTest(compiler, 'release',  'c++98',  True,  True,     args.verbose))
281        tester.AddTest(VIXLTest(compiler, 'debug',    'c++98',  True,  True,     args.verbose))
282        tester.AddTest(VIXLTest(compiler, 'release',  'c++98',  True,  False,    args.verbose))
283        tester.AddTest(VIXLTest(compiler, 'debug',    'c++98',  True,  False,    args.verbose))
284        tester.AddTest(VIXLTest(compiler, 'release',  'c++11',  True,  True,     args.verbose))
285        tester.AddTest(VIXLTest(compiler, 'debug',    'c++11',  True,  True,     args.verbose))
286        tester.AddTest(VIXLTest(compiler, 'release',  'c++11',  True,  False,    args.verbose))
287        tester.AddTest(VIXLTest(compiler, 'debug',    'c++11',  True,  False,    args.verbose))
288        tester.AddTest(BenchTest(compiler,'release',  'c++98',  True))
289        tester.AddTest(BenchTest(compiler,'debug',    'c++98',  True))
290        tester.AddTest(BenchTest(compiler,'release',  'c++11',  True))
291        tester.AddTest(BenchTest(compiler,'debug',    'c++11',  True))
292      else:
293        tester.AddTest(VIXLTest(compiler, 'release',  'c++98',  False, False,    args.verbose))
294        tester.AddTest(VIXLTest(compiler, 'debug',    'c++98',  False, False,    args.verbose))
295        tester.AddTest(VIXLTest(compiler, 'release',  'c++11',  False, False,    args.verbose))
296        tester.AddTest(VIXLTest(compiler, 'debug',    'c++11',  False, False,    args.verbose))
297        tester.AddTest(BenchTest(compiler,'release',  'c++98',  False))
298        tester.AddTest(BenchTest(compiler,'debug',    'c++98',  False))
299        tester.AddTest(BenchTest(compiler,'release',  'c++11',  False))
300        tester.AddTest(BenchTest(compiler,'debug',    'c++11',  False))
301
302    tester.RunAll()
303
304  if git.is_git_repository_root():
305    untracked_files = git.get_untracked_files()
306    if untracked_files:
307      print '\nWARNING: The following files are untracked:'
308      for f in untracked_files:
309        print f.lstrip('?')
310