import os.path
import json
import logging
-import tempfile
import multiprocessing
+import tempfile
+import functools
+import subprocess
import contextlib
import datetime
+
from libscanbuild import command_entry_point, compiler_wrapper, \
- wrapper_environment, run_build
+ wrapper_environment, run_build, run_command
from libscanbuild.arguments import parse_args_for_scan_build, \
parse_args_for_analyze_build
-from libscanbuild.runner import run
from libscanbuild.intercept import capture
from libscanbuild.report import document
-from libscanbuild.compilation import split_command
+from libscanbuild.compilation import split_command, classify_source, \
+ compiler_language
+from libscanbuild.clang import get_version, get_arguments
+from libscanbuild.shell import decode
__all__ = ['scan_build', 'analyze_build', 'analyze_compiler_wrapper']
exit_code = capture(args)
# Run the analyzer against the captured commands.
if need_analyzer(args.build):
- run_analyzer(args)
+ run_analyzer_parallel(args)
else:
# Run build command and analyzer with compiler wrappers.
environment = setup_environment(args)
# will re-assign the report directory as new output
with report_directory(args.output, args.keep_empty) as args.output:
# Run the analyzer against a compilation db.
- run_analyzer(args)
+ run_analyzer_parallel(args)
# Cover report generation and bug counting.
number_of_bugs = document(args)
# Set exit status as it was requested.
return len(args) and not re.search('configure|autogen', args[0])
-def run_analyzer(args):
+def run_analyzer_parallel(args):
""" Runs the analyzer against the given compilation database. """
def exclude(filename):
result.append('-analyzer-viz-egraph-ubigraph')
return prefix_with('-Xclang', result)
+
+
+def require(required):
+ """ Decorator for checking the required values in state.
+
+ It checks the required attributes in the passed state and stop when
+ any of those is missing. """
+
+ def decorator(function):
+ @functools.wraps(function)
+ def wrapper(*args, **kwargs):
+ for key in required:
+ if key not in args[0]:
+ raise KeyError('{0} not passed to {1}'.format(
+ key, function.__name__))
+
+ return function(*args, **kwargs)
+
+ return wrapper
+
+ return decorator
+
+
+@require(['command', # entry from compilation database
+ 'directory', # entry from compilation database
+ 'file', # entry from compilation database
+ 'clang', # clang executable name (and path)
+ 'direct_args', # arguments from command line
+ 'force_debug', # kill non debug macros
+ 'output_dir', # where generated report files shall go
+ 'output_format', # it's 'plist' or 'html' or both
+ 'output_failures']) # generate crash reports or not
+def run(opts):
+ """ Entry point to run (or not) static analyzer against a single entry
+ of the compilation database.
+
+ This complex task is decomposed into smaller methods which are calling
+ each other in chain. If the analyzis is not possibe the given method
+ just return and break the chain.
+
+ The passed parameter is a python dictionary. Each method first check
+ that the needed parameters received. (This is done by the 'require'
+ decorator. It's like an 'assert' to check the contract between the
+ caller and the called method.) """
+
+ try:
+ command = opts.pop('command')
+ command = command if isinstance(command, list) else decode(command)
+ logging.debug("Run analyzer against '%s'", command)
+ opts.update(classify_parameters(command))
+
+ return arch_check(opts)
+ except Exception:
+ logging.error("Problem occured during analyzis.", exc_info=1)
+ return None
+
+
+@require(['clang', 'directory', 'flags', 'file', 'output_dir', 'language',
+ 'error_output', 'exit_code'])
+def report_failure(opts):
+ """ Create report when analyzer failed.
+
+ The major report is the preprocessor output. The output filename generated
+ randomly. The compiler output also captured into '.stderr.txt' file.
+ And some more execution context also saved into '.info.txt' file. """
+
+ def extension():
+ """ Generate preprocessor file extension. """
+
+ mapping = {'objective-c++': '.mii', 'objective-c': '.mi', 'c++': '.ii'}
+ return mapping.get(opts['language'], '.i')
+
+ def destination():
+ """ Creates failures directory if not exits yet. """
+
+ failures_dir = os.path.join(opts['output_dir'], 'failures')
+ if not os.path.isdir(failures_dir):
+ os.makedirs(failures_dir)
+ return failures_dir
+
+ # Classify error type: when Clang terminated by a signal it's a 'Crash'.
+ # (python subprocess Popen.returncode is negative when child terminated
+ # by signal.) Everything else is 'Other Error'.
+ error = 'crash' if opts['exit_code'] < 0 else 'other_error'
+ # Create preprocessor output file name. (This is blindly following the
+ # Perl implementation.)
+ (handle, name) = tempfile.mkstemp(suffix=extension(),
+ prefix='clang_' + error + '_',
+ dir=destination())
+ os.close(handle)
+ # Execute Clang again, but run the syntax check only.
+ cwd = opts['directory']
+ cmd = get_arguments(
+ [opts['clang'], '-fsyntax-only', '-E'
+ ] + opts['flags'] + [opts['file'], '-o', name], cwd)
+ run_command(cmd, cwd=cwd)
+ # write general information about the crash
+ with open(name + '.info.txt', 'w') as handle:
+ handle.write(opts['file'] + os.linesep)
+ handle.write(error.title().replace('_', ' ') + os.linesep)
+ handle.write(' '.join(cmd) + os.linesep)
+ handle.write(' '.join(os.uname()) + os.linesep)
+ handle.write(get_version(opts['clang']))
+ handle.close()
+ # write the captured output too
+ with open(name + '.stderr.txt', 'w') as handle:
+ handle.writelines(opts['error_output'])
+ handle.close()
+
+
+@require(['clang', 'directory', 'flags', 'direct_args', 'file', 'output_dir',
+ 'output_format'])
+def run_analyzer(opts, continuation=report_failure):
+ """ It assembles the analysis command line and executes it. Capture the
+ output of the analysis and returns with it. If failure reports are
+ requested, it calls the continuation to generate it. """
+
+ def target():
+ """ Creates output file name for reports. """
+ if opts['output_format'] in {'plist', 'plist-html'}:
+ (handle, name) = tempfile.mkstemp(prefix='report-',
+ suffix='.plist',
+ dir=opts['output_dir'])
+ os.close(handle)
+ return name
+ return opts['output_dir']
+
+ try:
+ cwd = opts['directory']
+ cmd = get_arguments([opts['clang'], '--analyze'] +
+ opts['direct_args'] + opts['flags'] +
+ [opts['file'], '-o', target()],
+ cwd)
+ output = run_command(cmd, cwd=cwd)
+ return {'error_output': output, 'exit_code': 0}
+ except subprocess.CalledProcessError as ex:
+ result = {'error_output': ex.output, 'exit_code': ex.returncode}
+ if opts.get('output_failures', False):
+ opts.update(result)
+ continuation(opts)
+ return result
+
+
+@require(['flags', 'force_debug'])
+def filter_debug_flags(opts, continuation=run_analyzer):
+ """ Filter out nondebug macros when requested. """
+
+ if opts.pop('force_debug'):
+ # lazy implementation just append an undefine macro at the end
+ opts.update({'flags': opts['flags'] + ['-UNDEBUG']})
+
+ return continuation(opts)
+
+
+@require(['language', 'compiler', 'file', 'flags'])
+def language_check(opts, continuation=filter_debug_flags):
+ """ Find out the language from command line parameters or file name
+ extension. The decision also influenced by the compiler invocation. """
+
+ accepted = frozenset({
+ 'c', 'c++', 'objective-c', 'objective-c++', 'c-cpp-output',
+ 'c++-cpp-output', 'objective-c-cpp-output'
+ })
+
+ # language can be given as a parameter...
+ language = opts.pop('language')
+ compiler = opts.pop('compiler')
+ # ... or find out from source file extension
+ if language is None and compiler is not None:
+ language = classify_source(opts['file'], compiler == 'c')
+
+ if language is None:
+ logging.debug('skip analysis, language not known')
+ return None
+ elif language not in accepted:
+ logging.debug('skip analysis, language not supported')
+ return None
+ else:
+ logging.debug('analysis, language: %s', language)
+ opts.update({'language': language,
+ 'flags': ['-x', language] + opts['flags']})
+ return continuation(opts)
+
+
+@require(['arch_list', 'flags'])
+def arch_check(opts, continuation=language_check):
+ """ Do run analyzer through one of the given architectures. """
+
+ disabled = frozenset({'ppc', 'ppc64'})
+
+ received_list = opts.pop('arch_list')
+ if received_list:
+ # filter out disabled architectures and -arch switches
+ filtered_list = [a for a in received_list if a not in disabled]
+ if filtered_list:
+ # There should be only one arch given (or the same multiple
+ # times). If there are multiple arch are given and are not
+ # the same, those should not change the pre-processing step.
+ # But that's the only pass we have before run the analyzer.
+ current = filtered_list.pop()
+ logging.debug('analysis, on arch: %s', current)
+
+ opts.update({'flags': ['-arch', current] + opts['flags']})
+ return continuation(opts)
+ else:
+ logging.debug('skip analysis, found not supported arch')
+ return None
+ else:
+ logging.debug('analysis, on default arch')
+ return continuation(opts)
+
+# To have good results from static analyzer certain compiler options shall be
+# omitted. The compiler flag filtering only affects the static analyzer run.
+#
+# Keys are the option name, value number of options to skip
+IGNORED_FLAGS = {
+ '-c': 0, # compile option will be overwritten
+ '-fsyntax-only': 0, # static analyzer option will be overwritten
+ '-o': 1, # will set up own output file
+ # flags below are inherited from the perl implementation.
+ '-g': 0,
+ '-save-temps': 0,
+ '-install_name': 1,
+ '-exported_symbols_list': 1,
+ '-current_version': 1,
+ '-compatibility_version': 1,
+ '-init': 1,
+ '-e': 1,
+ '-seg1addr': 1,
+ '-bundle_loader': 1,
+ '-multiply_defined': 1,
+ '-sectorder': 3,
+ '--param': 1,
+ '--serialize-diagnostics': 1
+}
+
+
+def classify_parameters(command):
+ """ Prepare compiler flags (filters some and add others) and take out
+ language (-x) and architecture (-arch) flags for future processing. """
+
+ result = {
+ 'flags': [], # the filtered compiler flags
+ 'arch_list': [], # list of architecture flags
+ 'language': None, # compilation language, None, if not specified
+ 'compiler': compiler_language(command) # 'c' or 'c++'
+ }
+
+ # iterate on the compile options
+ args = iter(command[1:])
+ for arg in args:
+ # take arch flags into a separate basket
+ if arg == '-arch':
+ result['arch_list'].append(next(args))
+ # take language
+ elif arg == '-x':
+ result['language'] = next(args)
+ # parameters which looks source file are not flags
+ elif re.match(r'^[^-].+', arg) and classify_source(arg):
+ pass
+ # ignore some flags
+ elif arg in IGNORED_FLAGS:
+ count = IGNORED_FLAGS[arg]
+ for _ in range(count):
+ next(args)
+ # we don't care about extra warnings, but we should suppress ones
+ # that we don't want to see.
+ elif re.match(r'^-W.+', arg) and not re.match(r'^-Wno-.+', arg):
+ pass
+ # and consider everything else as compilation flag.
+ else:
+ result['flags'].append(arg)
+
+ return result
+++ /dev/null
-# -*- coding: utf-8 -*-
-# The LLVM Compiler Infrastructure
-#
-# This file is distributed under the University of Illinois Open Source
-# License. See LICENSE.TXT for details.
-""" This module is responsible to run the analyzer commands. """
-
-import re
-import os
-import os.path
-import tempfile
-import functools
-import subprocess
-import logging
-from libscanbuild import run_command
-from libscanbuild.compilation import classify_source, compiler_language
-from libscanbuild.clang import get_version, get_arguments
-from libscanbuild.shell import decode
-
-__all__ = ['run']
-
-# To have good results from static analyzer certain compiler options shall be
-# omitted. The compiler flag filtering only affects the static analyzer run.
-#
-# Keys are the option name, value number of options to skip
-IGNORED_FLAGS = {
- '-c': 0, # compile option will be overwritten
- '-fsyntax-only': 0, # static analyzer option will be overwritten
- '-o': 1, # will set up own output file
- # flags below are inherited from the perl implementation.
- '-g': 0,
- '-save-temps': 0,
- '-install_name': 1,
- '-exported_symbols_list': 1,
- '-current_version': 1,
- '-compatibility_version': 1,
- '-init': 1,
- '-e': 1,
- '-seg1addr': 1,
- '-bundle_loader': 1,
- '-multiply_defined': 1,
- '-sectorder': 3,
- '--param': 1,
- '--serialize-diagnostics': 1
-}
-
-
-def require(required):
- """ Decorator for checking the required values in state.
-
- It checks the required attributes in the passed state and stop when
- any of those is missing. """
-
- def decorator(function):
- @functools.wraps(function)
- def wrapper(*args, **kwargs):
- for key in required:
- if key not in args[0]:
- raise KeyError('{0} not passed to {1}'.format(
- key, function.__name__))
-
- return function(*args, **kwargs)
-
- return wrapper
-
- return decorator
-
-
-@require(['command', # entry from compilation database
- 'directory', # entry from compilation database
- 'file', # entry from compilation database
- 'clang', # clang executable name (and path)
- 'direct_args', # arguments from command line
- 'force_debug', # kill non debug macros
- 'output_dir', # where generated report files shall go
- 'output_format', # it's 'plist' or 'html' or both
- 'output_failures']) # generate crash reports or not
-def run(opts):
- """ Entry point to run (or not) static analyzer against a single entry
- of the compilation database.
-
- This complex task is decomposed into smaller methods which are calling
- each other in chain. If the analyzis is not possibe the given method
- just return and break the chain.
-
- The passed parameter is a python dictionary. Each method first check
- that the needed parameters received. (This is done by the 'require'
- decorator. It's like an 'assert' to check the contract between the
- caller and the called method.) """
-
- try:
- command = opts.pop('command')
- command = command if isinstance(command, list) else decode(command)
- logging.debug("Run analyzer against '%s'", command)
- opts.update(classify_parameters(command))
-
- return arch_check(opts)
- except Exception:
- logging.error("Problem occured during analyzis.", exc_info=1)
- return None
-
-
-@require(['clang', 'directory', 'flags', 'file', 'output_dir', 'language',
- 'error_output', 'exit_code'])
-def report_failure(opts):
- """ Create report when analyzer failed.
-
- The major report is the preprocessor output. The output filename generated
- randomly. The compiler output also captured into '.stderr.txt' file.
- And some more execution context also saved into '.info.txt' file. """
-
- def extension():
- """ Generate preprocessor file extension. """
-
- mapping = {'objective-c++': '.mii', 'objective-c': '.mi', 'c++': '.ii'}
- return mapping.get(opts['language'], '.i')
-
- def destination():
- """ Creates failures directory if not exits yet. """
-
- failures_dir = os.path.join(opts['output_dir'], 'failures')
- if not os.path.isdir(failures_dir):
- os.makedirs(failures_dir)
- return failures_dir
-
- # Classify error type: when Clang terminated by a signal it's a 'Crash'.
- # (python subprocess Popen.returncode is negative when child terminated
- # by signal.) Everything else is 'Other Error'.
- error = 'crash' if opts['exit_code'] < 0 else 'other_error'
- # Create preprocessor output file name. (This is blindly following the
- # Perl implementation.)
- (handle, name) = tempfile.mkstemp(suffix=extension(),
- prefix='clang_' + error + '_',
- dir=destination())
- os.close(handle)
- # Execute Clang again, but run the syntax check only.
- cwd = opts['directory']
- cmd = get_arguments(
- [opts['clang'], '-fsyntax-only', '-E'
- ] + opts['flags'] + [opts['file'], '-o', name], cwd)
- run_command(cmd, cwd=cwd)
- # write general information about the crash
- with open(name + '.info.txt', 'w') as handle:
- handle.write(opts['file'] + os.linesep)
- handle.write(error.title().replace('_', ' ') + os.linesep)
- handle.write(' '.join(cmd) + os.linesep)
- handle.write(' '.join(os.uname()) + os.linesep)
- handle.write(get_version(opts['clang']))
- handle.close()
- # write the captured output too
- with open(name + '.stderr.txt', 'w') as handle:
- handle.writelines(opts['error_output'])
- handle.close()
-
-
-@require(['clang', 'directory', 'flags', 'direct_args', 'file', 'output_dir',
- 'output_format'])
-def run_analyzer(opts, continuation=report_failure):
- """ It assembles the analysis command line and executes it. Capture the
- output of the analysis and returns with it. If failure reports are
- requested, it calls the continuation to generate it. """
-
- def target():
- """ Creates output file name for reports. """
- if opts['output_format'] in {'plist', 'plist-html'}:
- (handle, name) = tempfile.mkstemp(prefix='report-',
- suffix='.plist',
- dir=opts['output_dir'])
- os.close(handle)
- return name
- return opts['output_dir']
-
- try:
- cwd = opts['directory']
- cmd = get_arguments([opts['clang'], '--analyze'] +
- opts['direct_args'] + opts['flags'] +
- [opts['file'], '-o', target()],
- cwd)
- output = run_command(cmd, cwd=cwd)
- return {'error_output': output, 'exit_code': 0}
- except subprocess.CalledProcessError as ex:
- result = {'error_output': ex.output, 'exit_code': ex.returncode}
- if opts.get('output_failures', False):
- opts.update(result)
- continuation(opts)
- return result
-
-
-@require(['flags', 'force_debug'])
-def filter_debug_flags(opts, continuation=run_analyzer):
- """ Filter out nondebug macros when requested. """
-
- if opts.pop('force_debug'):
- # lazy implementation just append an undefine macro at the end
- opts.update({'flags': opts['flags'] + ['-UNDEBUG']})
-
- return continuation(opts)
-
-
-@require(['language', 'compiler', 'file', 'flags'])
-def language_check(opts, continuation=filter_debug_flags):
- """ Find out the language from command line parameters or file name
- extension. The decision also influenced by the compiler invocation. """
-
- accepted = frozenset({
- 'c', 'c++', 'objective-c', 'objective-c++', 'c-cpp-output',
- 'c++-cpp-output', 'objective-c-cpp-output'
- })
-
- # language can be given as a parameter...
- language = opts.pop('language')
- compiler = opts.pop('compiler')
- # ... or find out from source file extension
- if language is None and compiler is not None:
- language = classify_source(opts['file'], compiler == 'c')
-
- if language is None:
- logging.debug('skip analysis, language not known')
- return None
- elif language not in accepted:
- logging.debug('skip analysis, language not supported')
- return None
- else:
- logging.debug('analysis, language: %s', language)
- opts.update({'language': language,
- 'flags': ['-x', language] + opts['flags']})
- return continuation(opts)
-
-
-@require(['arch_list', 'flags'])
-def arch_check(opts, continuation=language_check):
- """ Do run analyzer through one of the given architectures. """
-
- disabled = frozenset({'ppc', 'ppc64'})
-
- received_list = opts.pop('arch_list')
- if received_list:
- # filter out disabled architectures and -arch switches
- filtered_list = [a for a in received_list if a not in disabled]
- if filtered_list:
- # There should be only one arch given (or the same multiple
- # times). If there are multiple arch are given and are not
- # the same, those should not change the pre-processing step.
- # But that's the only pass we have before run the analyzer.
- current = filtered_list.pop()
- logging.debug('analysis, on arch: %s', current)
-
- opts.update({'flags': ['-arch', current] + opts['flags']})
- return continuation(opts)
- else:
- logging.debug('skip analysis, found not supported arch')
- return None
- else:
- logging.debug('analysis, on default arch')
- return continuation(opts)
-
-
-def classify_parameters(command):
- """ Prepare compiler flags (filters some and add others) and take out
- language (-x) and architecture (-arch) flags for future processing. """
-
- result = {
- 'flags': [], # the filtered compiler flags
- 'arch_list': [], # list of architecture flags
- 'language': None, # compilation language, None, if not specified
- 'compiler': compiler_language(command) # 'c' or 'c++'
- }
-
- # iterate on the compile options
- args = iter(command[1:])
- for arg in args:
- # take arch flags into a separate basket
- if arg == '-arch':
- result['arch_list'].append(next(args))
- # take language
- elif arg == '-x':
- result['language'] = next(args)
- # parameters which looks source file are not flags
- elif re.match(r'^[^-].+', arg) and classify_source(arg):
- pass
- # ignore some flags
- elif arg in IGNORED_FLAGS:
- count = IGNORED_FLAGS[arg]
- for _ in range(count):
- next(args)
- # we don't care about extra warnings, but we should suppress ones
- # that we don't want to see.
- elif re.match(r'^-W.+', arg) and not re.match(r'^-Wno-.+', arg):
- pass
- # and consider everything else as compilation flag.
- else:
- result['flags'].append(arg)
-
- return result
from . import test_libear
from . import test_compilation
from . import test_clang
-from . import test_runner
from . import test_report
from . import test_analyze
from . import test_intercept
suite.addTests(loader.loadTestsFromModule(test_libear))
suite.addTests(loader.loadTestsFromModule(test_compilation))
suite.addTests(loader.loadTestsFromModule(test_clang))
- suite.addTests(loader.loadTestsFromModule(test_runner))
suite.addTests(loader.loadTestsFromModule(test_report))
suite.addTests(loader.loadTestsFromModule(test_analyze))
suite.addTests(loader.loadTestsFromModule(test_intercept))
import libear
import libscanbuild.analyze as sut
import unittest
+import re
+import os
+import os.path
+
class ReportDirectoryTest(unittest.TestCase):
sut.report_directory(tmpdir, False) as report_dir3:
self.assertLess(report_dir1, report_dir2)
self.assertLess(report_dir2, report_dir3)
+
+
+class FilteringFlagsTest(unittest.TestCase):
+
+ def test_language_captured(self):
+ def test(flags):
+ cmd = ['clang', '-c', 'source.c'] + flags
+ opts = sut.classify_parameters(cmd)
+ return opts['language']
+
+ self.assertEqual(None, test([]))
+ self.assertEqual('c', test(['-x', 'c']))
+ self.assertEqual('cpp', test(['-x', 'cpp']))
+
+ def test_arch(self):
+ def test(flags):
+ cmd = ['clang', '-c', 'source.c'] + flags
+ opts = sut.classify_parameters(cmd)
+ return opts['arch_list']
+
+ self.assertEqual([], test([]))
+ self.assertEqual(['mips'], test(['-arch', 'mips']))
+ self.assertEqual(['mips', 'i386'],
+ test(['-arch', 'mips', '-arch', 'i386']))
+
+ def assertFlagsChanged(self, expected, flags):
+ cmd = ['clang', '-c', 'source.c'] + flags
+ opts = sut.classify_parameters(cmd)
+ self.assertEqual(expected, opts['flags'])
+
+ def assertFlagsUnchanged(self, flags):
+ self.assertFlagsChanged(flags, flags)
+
+ def assertFlagsFiltered(self, flags):
+ self.assertFlagsChanged([], flags)
+
+ def test_optimalizations_pass(self):
+ self.assertFlagsUnchanged(['-O'])
+ self.assertFlagsUnchanged(['-O1'])
+ self.assertFlagsUnchanged(['-Os'])
+ self.assertFlagsUnchanged(['-O2'])
+ self.assertFlagsUnchanged(['-O3'])
+
+ def test_include_pass(self):
+ self.assertFlagsUnchanged([])
+ self.assertFlagsUnchanged(['-include', '/usr/local/include'])
+ self.assertFlagsUnchanged(['-I.'])
+ self.assertFlagsUnchanged(['-I', '.'])
+ self.assertFlagsUnchanged(['-I/usr/local/include'])
+ self.assertFlagsUnchanged(['-I', '/usr/local/include'])
+ self.assertFlagsUnchanged(['-I/opt', '-I', '/opt/otp/include'])
+ self.assertFlagsUnchanged(['-isystem', '/path'])
+ self.assertFlagsUnchanged(['-isystem=/path'])
+
+ def test_define_pass(self):
+ self.assertFlagsUnchanged(['-DNDEBUG'])
+ self.assertFlagsUnchanged(['-UNDEBUG'])
+ self.assertFlagsUnchanged(['-Dvar1=val1', '-Dvar2=val2'])
+ self.assertFlagsUnchanged(['-Dvar="val ues"'])
+
+ def test_output_filtered(self):
+ self.assertFlagsFiltered(['-o', 'source.o'])
+
+ def test_some_warning_filtered(self):
+ self.assertFlagsFiltered(['-Wall'])
+ self.assertFlagsFiltered(['-Wnoexcept'])
+ self.assertFlagsFiltered(['-Wreorder', '-Wunused', '-Wundef'])
+ self.assertFlagsUnchanged(['-Wno-reorder', '-Wno-unused'])
+
+ def test_compile_only_flags_pass(self):
+ self.assertFlagsUnchanged(['-std=C99'])
+ self.assertFlagsUnchanged(['-nostdinc'])
+ self.assertFlagsUnchanged(['-isystem', '/image/debian'])
+ self.assertFlagsUnchanged(['-iprefix', '/usr/local'])
+ self.assertFlagsUnchanged(['-iquote=me'])
+ self.assertFlagsUnchanged(['-iquote', 'me'])
+
+ def test_compile_and_link_flags_pass(self):
+ self.assertFlagsUnchanged(['-fsinged-char'])
+ self.assertFlagsUnchanged(['-fPIC'])
+ self.assertFlagsUnchanged(['-stdlib=libc++'])
+ self.assertFlagsUnchanged(['--sysroot', '/'])
+ self.assertFlagsUnchanged(['-isysroot', '/'])
+
+ def test_some_flags_filtered(self):
+ self.assertFlagsFiltered(['-g'])
+ self.assertFlagsFiltered(['-fsyntax-only'])
+ self.assertFlagsFiltered(['-save-temps'])
+ self.assertFlagsFiltered(['-init', 'my_init'])
+ self.assertFlagsFiltered(['-sectorder', 'a', 'b', 'c'])
+
+
+class Spy(object):
+ def __init__(self):
+ self.arg = None
+ self.success = 0
+
+ def call(self, params):
+ self.arg = params
+ return self.success
+
+
+class RunAnalyzerTest(unittest.TestCase):
+
+ @staticmethod
+ def run_analyzer(content, failures_report):
+ with libear.TemporaryDirectory() as tmpdir:
+ filename = os.path.join(tmpdir, 'test.cpp')
+ with open(filename, 'w') as handle:
+ handle.write(content)
+
+ opts = {
+ 'clang': 'clang',
+ 'directory': os.getcwd(),
+ 'flags': [],
+ 'direct_args': [],
+ 'file': filename,
+ 'output_dir': tmpdir,
+ 'output_format': 'plist',
+ 'output_failures': failures_report
+ }
+ spy = Spy()
+ result = sut.run_analyzer(opts, spy.call)
+ return (result, spy.arg)
+
+ def test_run_analyzer(self):
+ content = "int div(int n, int d) { return n / d; }"
+ (result, fwds) = RunAnalyzerTest.run_analyzer(content, False)
+ self.assertEqual(None, fwds)
+ self.assertEqual(0, result['exit_code'])
+
+ def test_run_analyzer_crash(self):
+ content = "int div(int n, int d) { return n / d }"
+ (result, fwds) = RunAnalyzerTest.run_analyzer(content, False)
+ self.assertEqual(None, fwds)
+ self.assertEqual(1, result['exit_code'])
+
+ def test_run_analyzer_crash_and_forwarded(self):
+ content = "int div(int n, int d) { return n / d }"
+ (_, fwds) = RunAnalyzerTest.run_analyzer(content, True)
+ self.assertEqual(1, fwds['exit_code'])
+ self.assertTrue(len(fwds['error_output']) > 0)
+
+
+class ReportFailureTest(unittest.TestCase):
+
+ def assertUnderFailures(self, path):
+ self.assertEqual('failures', os.path.basename(os.path.dirname(path)))
+
+ def test_report_failure_create_files(self):
+ with libear.TemporaryDirectory() as tmpdir:
+ # create input file
+ filename = os.path.join(tmpdir, 'test.c')
+ with open(filename, 'w') as handle:
+ handle.write('int main() { return 0')
+ uname_msg = ' '.join(os.uname()) + os.linesep
+ error_msg = 'this is my error output'
+ # execute test
+ opts = {
+ 'clang': 'clang',
+ 'directory': os.getcwd(),
+ 'flags': [],
+ 'file': filename,
+ 'output_dir': tmpdir,
+ 'language': 'c',
+ 'error_type': 'other_error',
+ 'error_output': error_msg,
+ 'exit_code': 13
+ }
+ sut.report_failure(opts)
+ # verify the result
+ result = dict()
+ pp_file = None
+ for root, _, files in os.walk(tmpdir):
+ keys = [os.path.join(root, name) for name in files]
+ for key in keys:
+ with open(key, 'r') as handle:
+ result[key] = handle.readlines()
+ if re.match(r'^(.*/)+clang(.*)\.i$', key):
+ pp_file = key
+
+ # prepocessor file generated
+ self.assertUnderFailures(pp_file)
+ # info file generated and content dumped
+ info_file = pp_file + '.info.txt'
+ self.assertTrue(info_file in result)
+ self.assertEqual('Other Error\n', result[info_file][1])
+ self.assertEqual(uname_msg, result[info_file][3])
+ # error file generated and content dumped
+ error_file = pp_file + '.stderr.txt'
+ self.assertTrue(error_file in result)
+ self.assertEqual([error_msg], result[error_file])
+
+
+class AnalyzerTest(unittest.TestCase):
+
+ def test_nodebug_macros_appended(self):
+ def test(flags):
+ spy = Spy()
+ opts = {'flags': flags, 'force_debug': True}
+ self.assertEqual(spy.success,
+ sut.filter_debug_flags(opts, spy.call))
+ return spy.arg['flags']
+
+ self.assertEqual(['-UNDEBUG'], test([]))
+ self.assertEqual(['-DNDEBUG', '-UNDEBUG'], test(['-DNDEBUG']))
+ self.assertEqual(['-DSomething', '-UNDEBUG'], test(['-DSomething']))
+
+ def test_set_language_fall_through(self):
+ def language(expected, input):
+ spy = Spy()
+ input.update({'compiler': 'c', 'file': 'test.c'})
+ self.assertEqual(spy.success, sut.language_check(input, spy.call))
+ self.assertEqual(expected, spy.arg['language'])
+
+ language('c', {'language': 'c', 'flags': []})
+ language('c++', {'language': 'c++', 'flags': []})
+
+ def test_set_language_stops_on_not_supported(self):
+ spy = Spy()
+ input = {
+ 'compiler': 'c',
+ 'flags': [],
+ 'file': 'test.java',
+ 'language': 'java'
+ }
+ self.assertIsNone(sut.language_check(input, spy.call))
+ self.assertIsNone(spy.arg)
+
+ def test_set_language_sets_flags(self):
+ def flags(expected, input):
+ spy = Spy()
+ input.update({'compiler': 'c', 'file': 'test.c'})
+ self.assertEqual(spy.success, sut.language_check(input, spy.call))
+ self.assertEqual(expected, spy.arg['flags'])
+
+ flags(['-x', 'c'], {'language': 'c', 'flags': []})
+ flags(['-x', 'c++'], {'language': 'c++', 'flags': []})
+
+ def test_set_language_from_filename(self):
+ def language(expected, input):
+ spy = Spy()
+ input.update({'language': None, 'flags': []})
+ self.assertEqual(spy.success, sut.language_check(input, spy.call))
+ self.assertEqual(expected, spy.arg['language'])
+
+ language('c', {'file': 'file.c', 'compiler': 'c'})
+ language('c++', {'file': 'file.c', 'compiler': 'c++'})
+ language('c++', {'file': 'file.cxx', 'compiler': 'c'})
+ language('c++', {'file': 'file.cxx', 'compiler': 'c++'})
+ language('c++', {'file': 'file.cpp', 'compiler': 'c++'})
+ language('c-cpp-output', {'file': 'file.i', 'compiler': 'c'})
+ language('c++-cpp-output', {'file': 'file.i', 'compiler': 'c++'})
+
+ def test_arch_loop_sets_flags(self):
+ def flags(archs):
+ spy = Spy()
+ input = {'flags': [], 'arch_list': archs}
+ sut.arch_check(input, spy.call)
+ return spy.arg['flags']
+
+ self.assertEqual([], flags([]))
+ self.assertEqual(['-arch', 'i386'], flags(['i386']))
+ self.assertEqual(['-arch', 'i386'], flags(['i386', 'ppc']))
+ self.assertEqual(['-arch', 'sparc'], flags(['i386', 'sparc']))
+
+ def test_arch_loop_stops_on_not_supported(self):
+ def stop(archs):
+ spy = Spy()
+ input = {'flags': [], 'arch_list': archs}
+ self.assertIsNone(sut.arch_check(input, spy.call))
+ self.assertIsNone(spy.arg)
+
+ stop(['ppc'])
+ stop(['ppc64'])
+
+
+@sut.require([])
+def method_without_expecteds(opts):
+ return 0
+
+
+@sut.require(['this', 'that'])
+def method_with_expecteds(opts):
+ return 0
+
+
+@sut.require([])
+def method_exception_from_inside(opts):
+ raise Exception('here is one')
+
+
+class RequireDecoratorTest(unittest.TestCase):
+
+ def test_method_without_expecteds(self):
+ self.assertEqual(method_without_expecteds(dict()), 0)
+ self.assertEqual(method_without_expecteds({}), 0)
+ self.assertEqual(method_without_expecteds({'this': 2}), 0)
+ self.assertEqual(method_without_expecteds({'that': 3}), 0)
+
+ def test_method_with_expecteds(self):
+ self.assertRaises(KeyError, method_with_expecteds, dict())
+ self.assertRaises(KeyError, method_with_expecteds, {})
+ self.assertRaises(KeyError, method_with_expecteds, {'this': 2})
+ self.assertRaises(KeyError, method_with_expecteds, {'that': 3})
+ self.assertEqual(method_with_expecteds({'this': 0, 'that': 3}), 0)
+
+ def test_method_exception_not_caught(self):
+ self.assertRaises(Exception, method_exception_from_inside, dict())
'file.i.stderr.txt')
def test_parse_real_crash(self):
- import libscanbuild.runner as sut2
+ import libscanbuild.analyze as sut2
import re
with libear.TemporaryDirectory() as tmpdir:
filename = os.path.join(tmpdir, 'test.c')
+++ /dev/null
-# -*- coding: utf-8 -*-
-# The LLVM Compiler Infrastructure
-#
-# This file is distributed under the University of Illinois Open Source
-# License. See LICENSE.TXT for details.
-
-import libear
-import libscanbuild.runner as sut
-import unittest
-import re
-import os
-import os.path
-
-
-class FilteringFlagsTest(unittest.TestCase):
-
- def test_language_captured(self):
- def test(flags):
- cmd = ['clang', '-c', 'source.c'] + flags
- opts = sut.classify_parameters(cmd)
- return opts['language']
-
- self.assertEqual(None, test([]))
- self.assertEqual('c', test(['-x', 'c']))
- self.assertEqual('cpp', test(['-x', 'cpp']))
-
- def test_arch(self):
- def test(flags):
- cmd = ['clang', '-c', 'source.c'] + flags
- opts = sut.classify_parameters(cmd)
- return opts['arch_list']
-
- self.assertEqual([], test([]))
- self.assertEqual(['mips'], test(['-arch', 'mips']))
- self.assertEqual(['mips', 'i386'],
- test(['-arch', 'mips', '-arch', 'i386']))
-
- def assertFlagsChanged(self, expected, flags):
- cmd = ['clang', '-c', 'source.c'] + flags
- opts = sut.classify_parameters(cmd)
- self.assertEqual(expected, opts['flags'])
-
- def assertFlagsUnchanged(self, flags):
- self.assertFlagsChanged(flags, flags)
-
- def assertFlagsFiltered(self, flags):
- self.assertFlagsChanged([], flags)
-
- def test_optimalizations_pass(self):
- self.assertFlagsUnchanged(['-O'])
- self.assertFlagsUnchanged(['-O1'])
- self.assertFlagsUnchanged(['-Os'])
- self.assertFlagsUnchanged(['-O2'])
- self.assertFlagsUnchanged(['-O3'])
-
- def test_include_pass(self):
- self.assertFlagsUnchanged([])
- self.assertFlagsUnchanged(['-include', '/usr/local/include'])
- self.assertFlagsUnchanged(['-I.'])
- self.assertFlagsUnchanged(['-I', '.'])
- self.assertFlagsUnchanged(['-I/usr/local/include'])
- self.assertFlagsUnchanged(['-I', '/usr/local/include'])
- self.assertFlagsUnchanged(['-I/opt', '-I', '/opt/otp/include'])
- self.assertFlagsUnchanged(['-isystem', '/path'])
- self.assertFlagsUnchanged(['-isystem=/path'])
-
- def test_define_pass(self):
- self.assertFlagsUnchanged(['-DNDEBUG'])
- self.assertFlagsUnchanged(['-UNDEBUG'])
- self.assertFlagsUnchanged(['-Dvar1=val1', '-Dvar2=val2'])
- self.assertFlagsUnchanged(['-Dvar="val ues"'])
-
- def test_output_filtered(self):
- self.assertFlagsFiltered(['-o', 'source.o'])
-
- def test_some_warning_filtered(self):
- self.assertFlagsFiltered(['-Wall'])
- self.assertFlagsFiltered(['-Wnoexcept'])
- self.assertFlagsFiltered(['-Wreorder', '-Wunused', '-Wundef'])
- self.assertFlagsUnchanged(['-Wno-reorder', '-Wno-unused'])
-
- def test_compile_only_flags_pass(self):
- self.assertFlagsUnchanged(['-std=C99'])
- self.assertFlagsUnchanged(['-nostdinc'])
- self.assertFlagsUnchanged(['-isystem', '/image/debian'])
- self.assertFlagsUnchanged(['-iprefix', '/usr/local'])
- self.assertFlagsUnchanged(['-iquote=me'])
- self.assertFlagsUnchanged(['-iquote', 'me'])
-
- def test_compile_and_link_flags_pass(self):
- self.assertFlagsUnchanged(['-fsinged-char'])
- self.assertFlagsUnchanged(['-fPIC'])
- self.assertFlagsUnchanged(['-stdlib=libc++'])
- self.assertFlagsUnchanged(['--sysroot', '/'])
- self.assertFlagsUnchanged(['-isysroot', '/'])
-
- def test_some_flags_filtered(self):
- self.assertFlagsFiltered(['-g'])
- self.assertFlagsFiltered(['-fsyntax-only'])
- self.assertFlagsFiltered(['-save-temps'])
- self.assertFlagsFiltered(['-init', 'my_init'])
- self.assertFlagsFiltered(['-sectorder', 'a', 'b', 'c'])
-
-
-class Spy(object):
- def __init__(self):
- self.arg = None
- self.success = 0
-
- def call(self, params):
- self.arg = params
- return self.success
-
-
-class RunAnalyzerTest(unittest.TestCase):
-
- @staticmethod
- def run_analyzer(content, failures_report):
- with libear.TemporaryDirectory() as tmpdir:
- filename = os.path.join(tmpdir, 'test.cpp')
- with open(filename, 'w') as handle:
- handle.write(content)
-
- opts = {
- 'clang': 'clang',
- 'directory': os.getcwd(),
- 'flags': [],
- 'direct_args': [],
- 'file': filename,
- 'output_dir': tmpdir,
- 'output_format': 'plist',
- 'output_failures': failures_report
- }
- spy = Spy()
- result = sut.run_analyzer(opts, spy.call)
- return (result, spy.arg)
-
- def test_run_analyzer(self):
- content = "int div(int n, int d) { return n / d; }"
- (result, fwds) = RunAnalyzerTest.run_analyzer(content, False)
- self.assertEqual(None, fwds)
- self.assertEqual(0, result['exit_code'])
-
- def test_run_analyzer_crash(self):
- content = "int div(int n, int d) { return n / d }"
- (result, fwds) = RunAnalyzerTest.run_analyzer(content, False)
- self.assertEqual(None, fwds)
- self.assertEqual(1, result['exit_code'])
-
- def test_run_analyzer_crash_and_forwarded(self):
- content = "int div(int n, int d) { return n / d }"
- (_, fwds) = RunAnalyzerTest.run_analyzer(content, True)
- self.assertEqual(1, fwds['exit_code'])
- self.assertTrue(len(fwds['error_output']) > 0)
-
-
-class ReportFailureTest(unittest.TestCase):
-
- def assertUnderFailures(self, path):
- self.assertEqual('failures', os.path.basename(os.path.dirname(path)))
-
- def test_report_failure_create_files(self):
- with libear.TemporaryDirectory() as tmpdir:
- # create input file
- filename = os.path.join(tmpdir, 'test.c')
- with open(filename, 'w') as handle:
- handle.write('int main() { return 0')
- uname_msg = ' '.join(os.uname()) + os.linesep
- error_msg = 'this is my error output'
- # execute test
- opts = {
- 'clang': 'clang',
- 'directory': os.getcwd(),
- 'flags': [],
- 'file': filename,
- 'output_dir': tmpdir,
- 'language': 'c',
- 'error_type': 'other_error',
- 'error_output': error_msg,
- 'exit_code': 13
- }
- sut.report_failure(opts)
- # verify the result
- result = dict()
- pp_file = None
- for root, _, files in os.walk(tmpdir):
- keys = [os.path.join(root, name) for name in files]
- for key in keys:
- with open(key, 'r') as handle:
- result[key] = handle.readlines()
- if re.match(r'^(.*/)+clang(.*)\.i$', key):
- pp_file = key
-
- # prepocessor file generated
- self.assertUnderFailures(pp_file)
- # info file generated and content dumped
- info_file = pp_file + '.info.txt'
- self.assertTrue(info_file in result)
- self.assertEqual('Other Error\n', result[info_file][1])
- self.assertEqual(uname_msg, result[info_file][3])
- # error file generated and content dumped
- error_file = pp_file + '.stderr.txt'
- self.assertTrue(error_file in result)
- self.assertEqual([error_msg], result[error_file])
-
-
-class AnalyzerTest(unittest.TestCase):
-
- def test_nodebug_macros_appended(self):
- def test(flags):
- spy = Spy()
- opts = {'flags': flags, 'force_debug': True}
- self.assertEqual(spy.success,
- sut.filter_debug_flags(opts, spy.call))
- return spy.arg['flags']
-
- self.assertEqual(['-UNDEBUG'], test([]))
- self.assertEqual(['-DNDEBUG', '-UNDEBUG'], test(['-DNDEBUG']))
- self.assertEqual(['-DSomething', '-UNDEBUG'], test(['-DSomething']))
-
- def test_set_language_fall_through(self):
- def language(expected, input):
- spy = Spy()
- input.update({'compiler': 'c', 'file': 'test.c'})
- self.assertEqual(spy.success, sut.language_check(input, spy.call))
- self.assertEqual(expected, spy.arg['language'])
-
- language('c', {'language': 'c', 'flags': []})
- language('c++', {'language': 'c++', 'flags': []})
-
- def test_set_language_stops_on_not_supported(self):
- spy = Spy()
- input = {
- 'compiler': 'c',
- 'flags': [],
- 'file': 'test.java',
- 'language': 'java'
- }
- self.assertIsNone(sut.language_check(input, spy.call))
- self.assertIsNone(spy.arg)
-
- def test_set_language_sets_flags(self):
- def flags(expected, input):
- spy = Spy()
- input.update({'compiler': 'c', 'file': 'test.c'})
- self.assertEqual(spy.success, sut.language_check(input, spy.call))
- self.assertEqual(expected, spy.arg['flags'])
-
- flags(['-x', 'c'], {'language': 'c', 'flags': []})
- flags(['-x', 'c++'], {'language': 'c++', 'flags': []})
-
- def test_set_language_from_filename(self):
- def language(expected, input):
- spy = Spy()
- input.update({'language': None, 'flags': []})
- self.assertEqual(spy.success, sut.language_check(input, spy.call))
- self.assertEqual(expected, spy.arg['language'])
-
- language('c', {'file': 'file.c', 'compiler': 'c'})
- language('c++', {'file': 'file.c', 'compiler': 'c++'})
- language('c++', {'file': 'file.cxx', 'compiler': 'c'})
- language('c++', {'file': 'file.cxx', 'compiler': 'c++'})
- language('c++', {'file': 'file.cpp', 'compiler': 'c++'})
- language('c-cpp-output', {'file': 'file.i', 'compiler': 'c'})
- language('c++-cpp-output', {'file': 'file.i', 'compiler': 'c++'})
-
- def test_arch_loop_sets_flags(self):
- def flags(archs):
- spy = Spy()
- input = {'flags': [], 'arch_list': archs}
- sut.arch_check(input, spy.call)
- return spy.arg['flags']
-
- self.assertEqual([], flags([]))
- self.assertEqual(['-arch', 'i386'], flags(['i386']))
- self.assertEqual(['-arch', 'i386'], flags(['i386', 'ppc']))
- self.assertEqual(['-arch', 'sparc'], flags(['i386', 'sparc']))
-
- def test_arch_loop_stops_on_not_supported(self):
- def stop(archs):
- spy = Spy()
- input = {'flags': [], 'arch_list': archs}
- self.assertIsNone(sut.arch_check(input, spy.call))
- self.assertIsNone(spy.arg)
-
- stop(['ppc'])
- stop(['ppc64'])
-
-
-@sut.require([])
-def method_without_expecteds(opts):
- return 0
-
-
-@sut.require(['this', 'that'])
-def method_with_expecteds(opts):
- return 0
-
-
-@sut.require([])
-def method_exception_from_inside(opts):
- raise Exception('here is one')
-
-
-class RequireDecoratorTest(unittest.TestCase):
-
- def test_method_without_expecteds(self):
- self.assertEqual(method_without_expecteds(dict()), 0)
- self.assertEqual(method_without_expecteds({}), 0)
- self.assertEqual(method_without_expecteds({'this': 2}), 0)
- self.assertEqual(method_without_expecteds({'that': 3}), 0)
-
- def test_method_with_expecteds(self):
- self.assertRaises(KeyError, method_with_expecteds, dict())
- self.assertRaises(KeyError, method_with_expecteds, {})
- self.assertRaises(KeyError, method_with_expecteds, {'this': 2})
- self.assertRaises(KeyError, method_with_expecteds, {'that': 3})
- self.assertEqual(method_with_expecteds({'this': 0, 'that': 3}), 0)
-
- def test_method_exception_not_caught(self):
- self.assertRaises(Exception, method_exception_from_inside, dict())