more concise API for creating build integration tests

Summary:
This removes some boilerplate and duplicated code and makes it easier to add
more tests.

Reviewed By: martinoluca, jeremydubreil

Differential Revision: D3365807

fbshipit-source-id: 9a2e0e5
master
Jules Villard 9 years ago committed by Facebook Github Bot 9
parent bb6a91e6e3
commit 1dc636a971

@ -107,11 +107,7 @@ def save_report(reports, filename):
separators=(',', ': '), sort_keys=True) separators=(',', ': '), sort_keys=True)
def run_analysis(root, clean_cmds, build_cmds, analyzer, env=None): def run_analysis(clean_cmds, build_cmds, analyzer, env=None):
if not os.path.exists(root):
os.makedirs(root)
os.chdir(root)
for clean_cmd in clean_cmds: for clean_cmd in clean_cmds:
subprocess.check_call(clean_cmd, env=env) subprocess.check_call(clean_cmd, env=env)
@ -182,7 +178,8 @@ def check_results(errors, patterns):
def is_tool_available(cmd): def is_tool_available(cmd):
try: try:
subprocess.call(cmd) with open(os.devnull, 'w') as devnull:
subprocess.call(cmd, stdout=devnull)
except OSError as e: except OSError as e:
if e.errno == os.errno.ENOENT: if e.errno == os.errno.ENOENT:
return False return False
@ -205,170 +202,168 @@ def make_paths_relative_in_report(root, errors):
# remove "root/" from each file name # remove "root/" from each file name
rel_fname = error[issues.JSON_INDEX_FILENAME][len(root) + 1:] rel_fname = error[issues.JSON_INDEX_FILENAME][len(root) + 1:]
error[issues.JSON_INDEX_FILENAME] = rel_fname error[issues.JSON_INDEX_FILENAME] = rel_fname
return errors
def test(name,
readable_name,
root,
compile_commands,
clean_commands=[],
env=None,
available=lambda: True,
enabled=None,
report_fname=None,
preprocess=lambda: None,
postprocess=lambda errors: errors):
"""Run a test.
Arguments:
- [name] is used to test if the test is enabled by default (but
see [enabled])
- [root] the directory from which to run the test
- [compile_commands] the commands to be captured by Infer
- [clean_commands] commands to setup the build directory prior to
running Infer
- [env] the environment in which to run all the commands
- [available] a test to determine whether the test can be run
- [enabled] whether the test should attempt to run. By default it
is enabled if [[name] in [to_test]]
- [report_fname] where to find the expected Infer results
- [preprocess] a function to run before the clean and compile
commands. If the function returns something non-None, use that as
the compile commands.
- [postprocess] a function that takes in an Infer report and can
modify them. It must return an Infer report.
Returns [True] if the test ran, [False] otherwise.
"""
# python can't into using values of arguments in the default
# values of other arguments
if enabled is None:
enabled = name in to_test
if report_fname is None:
report_fname = '%s_report.json' % name
if not (enabled and available()):
print('Skipping %s integration test' % readable_name)
return False
print('\nRunning %s integration test' % readable_name)
if not os.path.exists(root):
os.makedirs(root)
os.chdir(root)
pre = preprocess()
if pre is not None:
compile_commands = pre
# rerun this in case preprocess() deleted the current directory
if not os.path.exists(root):
os.makedirs(root)
os.chdir(root)
errors = run_analysis(
clean_commands,
compile_commands,
INFER_EXECUTABLE,
env=env)
original = os.path.join(EXPECTED_OUTPUTS_DIR, report_fname)
do_test(postprocess(errors), original)
return True
class BuildIntegrationTest(unittest.TestCase): class BuildIntegrationTest(unittest.TestCase):
def test_ant_integration(self): def test_ant_integration(self):
if not ('ant' in to_test and is_tool_available(['ant', '-version'])): test('ant', 'Ant',
print('\nSkipping Ant integration test') os.path.join(SCRIPT_DIR, os.pardir),
return [['ant', 'compile']],
clean_commands=[['ant', 'clean']],
print('\nRunning Ant integration test') available=lambda: is_tool_available(['ant', '-version']))
root = os.path.join(SCRIPT_DIR, os.pardir)
errors = run_analysis(
root,
[['ant', 'clean']],
[['ant', 'compile']],
INFER_EXECUTABLE)
original = os.path.join(EXPECTED_OUTPUTS_DIR, 'ant_report.json')
do_test(errors, original)
def test_javac_integration( def test_javac_integration(
self, self,
enabled=None, enabled=None,
root=os.path.join(ROOT_DIR, 'examples'), root=os.path.join(ROOT_DIR, 'examples'),
report_name='javac_report.json'): report_fname='javac_report.json'):
if enabled is None: test('javac', 'javac',
enabled = 'javac' in to_test root,
if not enabled: [['javac', 'Hello.java']],
print('\nSkipping javac integration test') enabled=enabled,
return report_fname=report_fname)
print('\nRunning javac integration test')
errors = run_analysis(
root,
[],
[['javac', 'Hello.java']],
INFER_EXECUTABLE)
original = os.path.join(EXPECTED_OUTPUTS_DIR, report_name)
do_test(errors, original)
def test_gradle_integration( def test_gradle_integration(
self, self,
enabled=None, enabled=None,
root=os.path.join(ROOT_DIR, 'examples', 'java_hello'), root=os.path.join(ROOT_DIR, 'examples', 'java_hello'),
report_name='gradle_report.json'): report_fname='gradle_report.json'):
if enabled is None: env = os.environ.copy()
enabled = 'gradle' in to_test
if not enabled:
print('\nSkipping Gradle integration test')
return
print('\nRunning Gradle integration test using mock gradle')
env = os.environ
env['PATH'] = '{}:{}'.format( env['PATH'] = '{}:{}'.format(
os.path.join(SCRIPT_DIR, 'mock'), os.path.join(SCRIPT_DIR, 'mock'),
os.getenv('PATH'), os.getenv('PATH'),
) )
errors = run_analysis( test('gradle', 'Gradle',
root, root,
[], [['gradle', 'build']],
[['gradle', 'build']], enabled=enabled,
INFER_EXECUTABLE, report_fname=report_fname,
env=env) env=env)
original = os.path.join(EXPECTED_OUTPUTS_DIR, report_name)
do_test(errors, original)
def test_buck_integration(self): def test_buck_integration(self):
if not ('buck' in to_test and test('buck', 'Buck',
is_tool_available(['buck', '--version'])): ROOT_DIR,
print('\nSkipping Buck integration test') [['buck', 'build', 'infer']],
return clean_commands=[['buck', 'clean']],
available=lambda: is_tool_available(['buck', '--version']))
print('\nRunning Buck integration test')
errors = run_analysis(
ROOT_DIR,
[['buck', 'clean']],
[['buck', 'build', 'infer']],
INFER_EXECUTABLE)
original = os.path.join(EXPECTED_OUTPUTS_DIR, 'buck_report.json')
do_test(errors, original)
def test_make_integration( def test_make_integration(
self, self,
enabled=None, enabled=None,
root=os.path.join(CODETOANALYZE_DIR, 'make'), root=os.path.join(CODETOANALYZE_DIR, 'make'),
report_name='make_report.json'): report_fname='make_report.json'):
if enabled is None: test('make', 'make',
enabled = 'make' in to_test root,
if not enabled: [['make', 'all']],
print('\nSkipping make integration test') clean_commands=[['make', 'clean']],
return enabled=enabled,
report_fname=report_fname)
print('\nRunning make integration test')
errors = run_analysis(
root,
[['make', 'clean']],
[['make', 'all']],
INFER_EXECUTABLE)
original = os.path.join(EXPECTED_OUTPUTS_DIR, report_name)
do_test(errors, original)
def test_wonky_locale_integration(self): def test_wonky_locale_integration(self):
if 'locale' not in to_test: env = os.environ.copy()
print('\nSkipping wonky locale integration test')
return
print('\nRunning wonky locale integration test')
root = os.path.join(CODETOANALYZE_DIR, 'make')
env = os.environ
env['LC_ALL'] = 'C' env['LC_ALL'] = 'C'
# check that we are able to remove the previous results by test('locale', 'wonky locale',
# running the analysis twice os.path.join(CODETOANALYZE_DIR, 'make'),
errors = run_analysis( [['clang', '-c', 'utf8_in_function_names.c'],
root, ['clang', '-c', 'utf8_in_function_names.c']],
[], env=env)
[['clang', '-c', 'utf8_in_function_names.c'],
['clang', '-c', 'utf8_in_function_names.c']],
INFER_EXECUTABLE,
env=env)
original = os.path.join(EXPECTED_OUTPUTS_DIR, 'locale_report.json')
do_test(errors, original)
def test_waf_integration(self): def test_waf_integration(self):
if 'waf' not in to_test: test('waf', 'waf',
print('\nSkipping waf integration test') os.path.join(CODETOANALYZE_DIR, 'make'),
return [['./waf', 'build']],
clean_commands=[['make', 'clean']])
print('\nRunning waf integration test')
root = os.path.join(CODETOANALYZE_DIR, 'make')
errors = run_analysis(
root,
[['make', 'clean']],
[['./waf', 'build']],
INFER_EXECUTABLE)
original = os.path.join(EXPECTED_OUTPUTS_DIR, 'waf_report.json')
do_test(errors, original)
def test_cmake_integration( def test_cmake_integration(
self, self,
enabled=None, enabled=None,
root=os.path.join(CODETOANALYZE_DIR, 'cmake'), root=os.path.join(CODETOANALYZE_DIR, 'cmake'),
report_name='cmake_report.json'): report_fname='cmake_report.json'):
if enabled is None: build_root = os.path.join(root, 'build')
enabled = 'cmake' in to_test if test('cmake', 'CMake',
if not (enabled and build_root,
is_tool_available(['cmake', '--version'])): [['cmake', '..'], ['make', 'clean', 'all']],
print('\nSkipping cmake integration test') available=lambda: is_tool_available(['cmake', '--version']),
return enabled=enabled,
# remove build/ directory just in case
preprocess=lambda: shutil.rmtree(build_root, True),
# cmake produces absolute paths using the real path
postprocess=(lambda errors:
make_paths_relative_in_report(
os.path.realpath(root), errors))):
# remove build/ directory
shutil.rmtree(build_root)
print('\nRunning cmake integration test')
orig_root = root
root = os.path.join(root, 'build')
# remove build/ directory just in case
shutil.rmtree(root, True)
errors = run_analysis(
root,
[],
[['cmake', '..'], ['make', 'clean', 'all']],
INFER_EXECUTABLE)
# remove build/ directory
shutil.rmtree(root)
original = os.path.join(EXPECTED_OUTPUTS_DIR, report_name)
# cmake produces absolute paths using the real path
make_paths_relative_in_report(os.path.realpath(orig_root), errors)
do_test(errors, original)
def test_utf8_in_pwd_integration(self): def test_utf8_in_pwd_integration(self):
if not 'utf8_in_pwd' in to_test: if not 'utf8_in_pwd' in to_test:
@ -386,36 +381,25 @@ class BuildIntegrationTest(unittest.TestCase):
self.test_cmake_integration( self.test_cmake_integration(
enabled=True, enabled=True,
root=os.path.join(utf8_in_pwd_path, 'cmake'), root=os.path.join(utf8_in_pwd_path, 'cmake'),
report_name='utf8_in_pwd_cmake_report.json') report_fname='utf8_in_pwd_cmake_report.json')
self.test_gradle_integration( self.test_gradle_integration(
enabled=True, enabled=True,
root=os.path.join(utf8_in_pwd_path, 'gradle'), root=os.path.join(utf8_in_pwd_path, 'gradle'),
report_name='utf8_in_pwd_gradle_report.json') report_fname='utf8_in_pwd_gradle_report.json')
self.test_javac_integration( self.test_javac_integration(
enabled=True, enabled=True,
root=os.path.join(utf8_in_pwd_path), root=os.path.join(utf8_in_pwd_path),
report_name='utf8_in_pwd_javac_report.json') report_fname='utf8_in_pwd_javac_report.json')
self.test_make_integration( self.test_make_integration(
enabled=True, enabled=True,
root=os.path.join(utf8_in_pwd_path, 'make'), root=os.path.join(utf8_in_pwd_path, 'make'),
report_name='utf8_in_pwd_make_report.json') report_fname='utf8_in_pwd_make_report.json')
shutil.rmtree(utf8_in_pwd_path, True) # remove copied dir shutil.rmtree(utf8_in_pwd_path, True) # remove copied dir
def test_unknown_extension(self): def test_unknown_extension(self):
if 'unknown_ext' not in to_test: test('unknown_ext', 'unknown extension',
print('\nSkipping unknown extension integration test') CODETOANALYZE_DIR,
return [['clang', '-x', 'c', '-c', 'hello.unknown_ext']])
print('\nRunning unknown extension integration test')
root = CODETOANALYZE_DIR
errors = run_analysis(
root,
[],
[['clang', '-x', 'c', '-c', 'hello.unknown_ext']],
INFER_EXECUTABLE)
original = os.path.join(EXPECTED_OUTPUTS_DIR,
'unknown_ext_report.json')
do_test(errors, original)
if __name__ == '__main__': if __name__ == '__main__':

Loading…
Cancel
Save