1#!/usr/bin/env python2.7
2
3# Copyright 2013, 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
34
35import util
36import git
37
38# Google's cpplint.py from depot_tools is the linter used here.
39CPP_LINTER_RULES = '''
40build/class
41build/deprecated
42build/endif_comment
43build/forward_decl
44build/include_order
45build/printf_format
46build/storage_class
47legal/copyright
48readability/boost
49readability/braces
50readability/casting
51readability/constructors
52readability/fn_size
53readability/function
54readability/multiline_comment
55readability/multiline_string
56readability/streams
57readability/utf8
58runtime/arrays
59runtime/casting
60runtime/deprecated_fn
61runtime/explicit
62runtime/int
63runtime/memset
64runtime/mutex
65runtime/nonconf
66runtime/printf
67runtime/printf_format
68runtime/references
69runtime/rtti
70runtime/sizeof
71runtime/string
72runtime/virtual
73runtime/vlog
74whitespace/blank_line
75whitespace/braces
76whitespace/comma
77whitespace/comments
78whitespace/end_of_line
79whitespace/ending_newline
80whitespace/indent
81whitespace/labels
82whitespace/line_length
83whitespace/newline
84whitespace/operators
85whitespace/parens
86whitespace/tab
87whitespace/todo
88'''.split()
89
90
91def BuildOptions():
92  result = argparse.ArgumentParser(description='Run the linter and unit tests.')
93  result.add_argument('--verbose', '-v', action='store_true',
94                      help='Print all tests output at the end.')
95  result.add_argument('--notest', action='store_true',
96                      help='Do not run tests. Run the linter only.')
97  result.add_argument('--nolint', action='store_true',
98                      help='Do not run the linter. Run the tests only.')
99  result.add_argument('--noclean', action='store_true',
100                      help='Do not clean before build.')
101  result.add_argument('--jobs', '-j', metavar='N', type=int, default=1,
102                      help='Allow N jobs at once.')
103  sim_default = 'off' if platform.machine() == 'aarch64' else 'on'
104  result.add_argument('--simulator', action='store', choices=['on', 'off'],
105                      default=sim_default,
106                      help='''Explicitly enable or disable the simulator. On
107                      this system, the default is "''' + sim_default + '".')
108  return result.parse_args()
109
110
111def CleanBuildSystem():
112  def clean(mode):
113    if args.verbose: print('Cleaning ' + mode + ' mode cctest...')
114    command = 'scons mode=%s simulator=%s target=cctest --clean' % \
115              (mode, args.simulator)
116    status, output = util.getstatusoutput(command)
117    if status != 0:
118      print(output)
119      util.abort('Failed cleaning cctest: ' + command)
120  clean('debug')
121  clean('release')
122
123
124def BuildRequiredObjects():
125  def build(mode):
126    if args.verbose: print('Building ' + mode + ' mode cctest...')
127    command = 'scons mode=%s simulator=%s target=cctest -j%u' % \
128              (mode, args.simulator, args.jobs)
129    status, output = util.getstatusoutput(command)
130    if status != 0:
131      print(output)
132      util.abort('Failed building cctest: ' + command)
133  build('debug')
134  build('release')
135
136
137NOT_RUN = 'NOT RUN'
138PASSED = 'PASSED'
139FAILED = 'FAILED'
140
141
142class Test:
143  def __init__(self, name, command, get_summary = util.last_line):
144    self.name = name
145    self.command = command
146    self.get_summary = get_summary
147    self.output = NOT_RUN
148    self.status = NOT_RUN
149    self.summary = NOT_RUN
150
151  def Run(self):
152    if args.verbose: print('Running ' + self.name + '...')
153    retcode, self.output = util.getstatusoutput(self.command)
154    self.status = PASSED if retcode == 0 else FAILED
155    self.summary = self.get_summary(self.output)
156
157  def PrintOutcome(self):
158    print(('%-26s : %s') % (self.name, self.summary))
159
160  def PrintOutput(self):
161    print('\n\n=== OUTPUT of: ' + self.command + '\n')
162    print(self.output)
163
164
165class Tester:
166  def __init__(self):
167    self.tests = []
168
169  def AddTest(self, test):
170    self.tests.append(test)
171
172  def RunAll(self):
173    result = PASSED
174    for test in self.tests:
175      test.Run()
176      if test.status != PASSED: result = FAILED
177      test.PrintOutcome()
178    print('Presubmit tests ' + result + '.')
179
180  def PrintFailedTestOutput(self):
181    for test in self.tests:
182      if test.status == FAILED:
183        test.PrintOutput();
184
185
186if __name__ == '__main__':
187  original_dir = os.path.abspath('.')
188  # $ROOT/tools/presubmit.py
189  root_dir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
190  os.chdir(root_dir)
191  args = BuildOptions()
192
193  if not args.nolint and not git.is_git_repository_root():
194    print 'WARNING: This is not a Git repository. The linter will not run.'
195    args.nolint = True
196
197  tester = Tester()
198  if not args.nolint:
199    CPP_EXT_REGEXP = re.compile('\.(cc|h)$')
200    SIM_TRACES_REGEXP = re.compile('test-simulator-traces-a64\.h$')
201    def is_linter_input(filename):
202      # Don't lint the simulator traces file; it takes a very long time to check
203      # and it's (mostly) generated automatically anyway.
204      if SIM_TRACES_REGEXP.search(filename): return False
205      # Otherwise, lint all C++ files.
206      return CPP_EXT_REGEXP.search(filename) != None
207
208    lint_args = '--filter=-,+' + ',+'.join(CPP_LINTER_RULES) + ' '
209    tracked_files = git.get_tracked_files().split()
210    tracked_files = filter(is_linter_input, tracked_files)
211    tracked_files = ' '.join(tracked_files)
212    lint = Test('cpp lint', 'cpplint.py ' + lint_args + tracked_files)
213    tester.AddTest(lint)
214
215  if not args.notest:
216    if not args.noclean:
217      CleanBuildSystem()
218    BuildRequiredObjects()
219
220    def command(*test_args):
221      if args.verbose:
222        return 'tools/test.py --verbose ' + ' '.join(test_args)
223      else:
224        return 'tools/test.py ' + ' '.join(test_args)
225
226    if args.simulator == 'on':
227      tester.AddTest(Test('cctest release (debugger)',
228                          command('--cctest=cctest_sim', '--debugger')))
229      tester.AddTest(Test('cctest debug (debugger)',
230                          command('--cctest=cctest_sim_g', '--debugger')))
231      tester.AddTest(Test('cctest release (simulator)',
232                          command('--cctest=cctest_sim')))
233      tester.AddTest(Test('cctest debug (simulator)',
234                          command('--cctest=cctest_sim_g')))
235    else:
236      tester.AddTest(Test('cctest release', command('--cctest=cctest')))
237      tester.AddTest(Test('cctest debug', command('--cctest=cctest_g')))
238
239  tester.RunAll()
240
241  # If tests failed, print their output.
242  tester.PrintFailedTestOutput()
243
244  if git.is_git_repository_root():
245    untracked_files = git.get_untracked_files()
246    if untracked_files:
247      print '\nWARNING: The following files are untracked:'
248      for f in untracked_files:
249        print f.lstrip('?')
250