2 # Copyright (c) 2013 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.
6 """Test harness for chromium clang tools."""
18 def _GenerateCompileCommands(files
, include_paths
):
19 """Returns a JSON string containing a compilation database for the input."""
20 include_path_flags
= ''.join('-I %s' % include_path
21 for include_path
in include_paths
)
22 return json
.dumps([{'directory': '.',
23 'command': 'clang++ -fsyntax-only %s -c %s' % (
24 include_path_flags
, f
),
25 'file': f
} for f
in files
], indent
=2)
28 def _NumberOfTestsToString(tests
):
29 """Returns an English describing the number of tests."""
30 return "%d test%s" % (tests
, 's' if tests
!= 1 else '')
35 print 'Usage: test_tool.py <clang tool>'
36 print ' <clang tool> is the clang tool to be tested.'
39 tool_to_test
= argv
[0]
40 tools_clang_scripts_directory
= os
.path
.dirname(os
.path
.realpath(__file__
))
41 tools_clang_directory
= os
.path
.dirname(tools_clang_scripts_directory
)
42 test_directory_for_tool
= os
.path
.join(
43 tools_clang_directory
, tool_to_test
, 'tests')
44 compile_database
= os
.path
.join(test_directory_for_tool
,
45 'compile_commands.json')
46 source_files
= glob
.glob(os
.path
.join(test_directory_for_tool
,
48 actual_files
= ['-'.join([source_file
.rsplit('-', 2)[0], 'actual.cc'])
49 for source_file
in source_files
]
50 expected_files
= ['-'.join([source_file
.rsplit('-', 2)[0], 'expected.cc'])
51 for source_file
in source_files
]
54 os
.path
.realpath(os
.path
.join(tools_clang_directory
, '../..')))
57 # Set up the test environment.
58 for source
, actual
in zip(source_files
, actual_files
):
59 shutil
.copyfile(source
, actual
)
60 # Stage the test files in the git index. If they aren't staged, then
61 # run_tools.py will skip them when applying replacements.
63 args
.extend(actual_files
)
64 subprocess
.check_call(args
)
65 # Generate a temporary compilation database to run the tool over.
66 with
open(compile_database
, 'w') as f
:
67 f
.write(_GenerateCompileCommands(actual_files
, include_paths
))
70 os
.path
.join(tools_clang_scripts_directory
, 'run_tool.py'),
72 test_directory_for_tool
]
73 args
.extend(actual_files
)
74 run_tool
= subprocess
.Popen(args
, stdout
=subprocess
.PIPE
)
75 stdout
, _
= run_tool
.communicate()
76 if run_tool
.returncode
!= 0:
77 print 'run_tool failed:\n%s' % stdout
82 for expected
, actual
in zip(expected_files
, actual_files
):
83 print '[ RUN ] %s' % os
.path
.relpath(actual
)
84 expected_output
= actual_output
= None
85 with
open(expected
, 'r') as f
:
86 expected_output
= f
.readlines()
87 with
open(actual
, 'r') as f
:
88 actual_output
= f
.readlines()
89 if actual_output
!= expected_output
:
90 print '[ FAILED ] %s' % os
.path
.relpath(actual
)
92 for line
in difflib
.unified_diff(expected_output
, actual_output
,
93 fromfile
=os
.path
.relpath(expected
),
94 tofile
=os
.path
.relpath(actual
)):
95 sys
.stdout
.write(line
)
96 # Don't clean up the file on failure, so the results can be referenced
99 print '[ OK ] %s' % os
.path
.relpath(actual
)
104 os
.remove(compile_database
)
106 print '[==========] %s ran.' % _NumberOfTestsToString(len(source_files
))
108 print '[ PASSED ] %s.' % _NumberOfTestsToString(passed
)
110 print '[ FAILED ] %s.' % _NumberOfTestsToString(failed
)
112 # No matter what, unstage the git changes we made earlier to avoid polluting
114 args
= ['git', 'reset', '--quiet', 'HEAD']
115 args
.extend(actual_files
)
116 subprocess
.call(args
)
119 if __name__
== '__main__':
120 sys
.exit(main(sys
.argv
[1:]))