• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
6import glob
7import os
8import subprocess
9import sys
10
11
12class 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