text: #!/usr/bin/env python
  # Copyright (c) 2013 The Chromium Authors. All rights reserved.
  # Use of this source code is governed by a BSD-style license that can be
  # found in the LICENSE file.
  
  """Test harness for chromium clang tools."""
  
  import difflib
  import glob
  import json
  import os
  import os.path
  import subprocess
  import shutil
  import sys
  
  
  def _GenerateCompileCommands(files):
    """Returns a JSON string containing a compilation database for the input."""
    return json.dumps([{'directory': '.',
                        'command': 'clang++ -fsyntax-only -c %s' % f,
                        'file': f} for f in files], indent=2)
  
  
  def _NumberOfTestsToString(tests):
    """Returns an English describing the number of tests."""
    return "%d test%s" % (tests, 's' if tests != 1 else '')
  
  
  def main(argv):
    if len(argv) < 1:
      print 'Usage: test_tool.py <clang tool>'
      print '  <clang tool> is the clang tool to be tested.'
      sys.exit(1)
  
    tool_to_test = argv[0]
    tools_clang_scripts_directory = os.path.dirname(os.path.realpath(__file__))
    tools_clang_directory = os.path.dirname(tools_clang_scripts_directory)
    test_directory_for_tool = os.path.join(
        tools_clang_directory, tool_to_test, 'tests')
    compile_database = os.path.join(test_directory_for_tool,
                                    'compile_commands.json')
    source_files = glob.glob(os.path.join(test_directory_for_tool,
                                          '*-original.cc'))
    actual_files = ['-'.join([source_file.rsplit('-', 2)[0], 'actual.cc'])
                    for source_file in source_files]
    expected_files = ['-'.join([source_file.rsplit('-', 2)[0], 'expected.cc'])
                      for source_file in source_files]
  
    try:
      # Set up the test environment.
      for source, actual in zip(source_files, actual_files):
        shutil.copyfile(source, actual)
      # Stage the test files in the git index. If they aren't staged, then
      # run_tools.py will skip them when applying replacements.
      args = ['git', 'add']
      args.extend(actual_files)
      subprocess.check_call(args)
      # Generate a temporary compilation database to run the tool over.
      with open(compile_database, 'w') as f:
        f.write(_GenerateCompileCommands(actual_files))
  
      args = ['python',
              os.path.join(tools_clang_scripts_directory, 'run_tool.py'),
              tool_to_test,
              test_directory_for_tool]
      args.extend(actual_files)
      run_tool = subprocess.Popen(args, stdout=subprocess.PIPE)
      stdout, _ = run_tool.communicate()
      if run_tool.returncode != 0:
        print 'run_tool failed:\n%s' % stdout
        sys.exit(1)
  
      passed = 0
      failed = 0
      for expected, actual in zip(expected_files, actual_files):
        print '[ RUN      ] %s' % os.path.relpath(actual)
        expected_output = actual_output = None
        with open(expected, 'r') as f:
          expected_output = f.readlines()
        with open(actual, 'r') as f:
          actual_output = f.readlines()
        if actual_output != expected_output:
          print '[  FAILED  ] %s' % os.path.relpath(actual)
          failed += 1
          for line in difflib.unified_diff(expected_output, actual_output,
                                           fromfile=os.path.relpath(expected),
                                           tofile=os.path.relpath(actual)):
            sys.stdout.write(line)
          # Don't clean up the file on failure, so the results can be referenced
          # more easily.
          continue
        print '[       OK ] %s' % os.path.relpath(actual)
        passed += 1
        os.remove(actual)
  
      if failed == 0:
        os.remove(compile_database)
  
      print '[==========] %s ran.' % _NumberOfTestsToString(len(source_files))
      if passed > 0:
        print '[  PASSED  ] %s.' % _NumberOfTestsToString(passed)
      if failed > 0:
        print '[  FAILED  ] %s.' % _NumberOfTestsToString(failed)
    finally:
      # No matter what, unstage the git changes we made earlier to avoid polluting
      # the index.
      args = ['git', 'reset', '--quiet', 'HEAD']
      args.extend(actual_files)
      subprocess.call(args)
  
  
  if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
  