Home | History | Annotate | Download | only in clang
      1 #!/usr/bin/env python
      2 # Copyright 2015 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import glob
      7 import os
      8 import subprocess
      9 import sys
     10 
     11 
     12 class ClangPluginTest(object):
     13   """Test harness for clang plugins."""
     14 
     15   def __init__(self, test_base, clang_path, plugin_path, plugin_name,
     16                reset_results):
     17     """Constructor.
     18 
     19     Args:
     20       test_base: Path to the directory containing the tests.
     21       clang_path: Path to the clang binary.
     22       plugin_path: Optional path to the plugin binary. May be None, such as on
     23                    Windows, where the plugin is built directly into the clang
     24                    binary.
     25       plugin_name: Name of the plugin.
     26       reset_results: If true, resets expected results to the actual test output.
     27     """
     28     self._test_base = test_base
     29     self._clang_path = clang_path
     30     self._plugin_path = plugin_path
     31     self._plugin_name = plugin_name
     32     self._reset_results = reset_results
     33 
     34   def AddPluginArg(self, clang_cmd, plugin_arg):
     35     """Helper to add an argument for the tested plugin."""
     36     clang_cmd.extend(['-Xclang', '-plugin-arg-%s' % self._plugin_name,
     37                       '-Xclang', plugin_arg])
     38 
     39   def AdjustClangArguments(self, clang_cmd):
     40     """Tests can override this to customize the command line for clang."""
     41     pass
     42 
     43   def Run(self):
     44     """Runs the tests.
     45 
     46     The working directory is temporarily changed to self._test_base while
     47     running the tests.
     48 
     49     Returns: the number of failing tests.
     50     """
     51     print 'Using clang %s...' % self._clang_path
     52     print 'Using plugin %s...' % self._plugin_path
     53 
     54     os.chdir(self._test_base)
     55 
     56     clang_cmd = [self._clang_path, '-c', '-std=c++11']
     57     if self._plugin_path:
     58       clang_cmd.extend(['-Xclang', '-load', '-Xclang', self._plugin_path])
     59     clang_cmd.extend(['-Xclang', '-add-plugin', '-Xclang', self._plugin_name])
     60     self.AdjustClangArguments(clang_cmd)
     61 
     62     passing = []
     63     failing = []
     64     tests = glob.glob('*.cpp')
     65     for test in tests:
     66       sys.stdout.write('Testing %s... ' % test)
     67       test_name, _ = os.path.splitext(test)
     68 
     69       cmd = clang_cmd[:]
     70       try:
     71         # Some tests need to run with extra flags.
     72         cmd.extend(file('%s.flags' % test_name).read().split())
     73       except IOError:
     74         pass
     75       cmd.append(test)
     76 
     77       failure_message = self.RunOneTest(test_name, cmd)
     78       if failure_message:
     79         print 'failed: %s' % failure_message
     80         failing.append(test_name)
     81       else:
     82         print 'passed!'
     83         passing.append(test_name)
     84 
     85     print 'Ran %d tests: %d succeeded, %d failed' % (
     86         len(passing) + len(failing), len(passing), len(failing))
     87     for test in failing:
     88       print '    %s' % test
     89     return len(failing)
     90 
     91   def RunOneTest(self, test_name, cmd):
     92     try:
     93       actual = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
     94     except subprocess.CalledProcessError as e:
     95       # Some plugin tests intentionally trigger compile errors, so just ignore
     96       # an exit code that indicates failure.
     97       actual = e.output
     98     except Exception as e:
     99       return 'could not execute %s (%s)' % (cmd, e)
    100 
    101     return self.ProcessOneResult(test_name, actual)
    102 
    103   def ProcessOneResult(self, test_name, actual):
    104     """Tests can override this for custom result processing."""
    105     # On Windows, clang emits CRLF as the end of line marker. Normalize it to LF
    106     # to match posix systems.
    107     actual = actual.replace('\r\n', '\n')
    108 
    109     result_file = '%s.txt%s' % (test_name, '' if self._reset_results else
    110                                 '.actual')
    111     try:
    112       expected = open('%s.txt' % test_name).read()
    113     except IOError:
    114       open(result_file, 'w').write(actual)
    115       return 'no expected file found'
    116 
    117     if expected != actual:
    118       open(result_file, 'w').write(actual)
    119       error = 'expected and actual differed\n'
    120       error += 'Actual:\n' + actual
    121       error += 'Expected:\n' + expected
    122       return error
    123