Home | History | Annotate | Download | only in tools
      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 
     29 import os
     30 import sys
     31 import argparse
     32 import re
     33 import platform
     34 
     35 import util
     36 import git
     37 
     38 # Google's cpplint.py from depot_tools is the linter used here.
     39 CPP_LINTER_RULES = '''
     40 build/class
     41 build/deprecated
     42 build/endif_comment
     43 build/forward_decl
     44 build/include_order
     45 build/printf_format
     46 build/storage_class
     47 legal/copyright
     48 readability/boost
     49 readability/braces
     50 readability/casting
     51 readability/constructors
     52 readability/fn_size
     53 readability/function
     54 readability/multiline_comment
     55 readability/multiline_string
     56 readability/streams
     57 readability/utf8
     58 runtime/arrays
     59 runtime/casting
     60 runtime/deprecated_fn
     61 runtime/explicit
     62 runtime/int
     63 runtime/memset
     64 runtime/mutex
     65 runtime/nonconf
     66 runtime/printf
     67 runtime/printf_format
     68 runtime/references
     69 runtime/rtti
     70 runtime/sizeof
     71 runtime/string
     72 runtime/virtual
     73 runtime/vlog
     74 whitespace/blank_line
     75 whitespace/braces
     76 whitespace/comma
     77 whitespace/comments
     78 whitespace/end_of_line
     79 whitespace/ending_newline
     80 whitespace/indent
     81 whitespace/labels
     82 whitespace/line_length
     83 whitespace/newline
     84 whitespace/operators
     85 whitespace/parens
     86 whitespace/tab
     87 whitespace/todo
     88 '''.split()
     89 
     90 
     91 def 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 
    111 def 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 
    124 def 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 
    137 NOT_RUN = 'NOT RUN'
    138 PASSED = 'PASSED'
    139 FAILED = 'FAILED'
    140 
    141 
    142 class 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 
    165 class 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 
    186 if __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