forked from OSchip/llvm-project
D17487: [analyzer][scan-build-py] flag filter modification for compilation database creation
llvm-svn: 266726
This commit is contained in:
parent
79b25d5ea4
commit
8bd63e5518
|
@ -25,8 +25,7 @@ from libscanbuild.runner import run
|
|||
from libscanbuild.intercept import capture
|
||||
from libscanbuild.report import report_directory, document
|
||||
from libscanbuild.clang import get_checkers
|
||||
from libscanbuild.runner import action_check
|
||||
from libscanbuild.command import classify_parameters, classify_source
|
||||
from libscanbuild.compilation import split_command
|
||||
|
||||
__all__ = ['analyze_build_main', 'analyze_build_wrapper']
|
||||
|
||||
|
@ -107,7 +106,7 @@ def run_analyzer(args, output_dir):
|
|||
'output_format': args.output_format,
|
||||
'output_failures': args.output_failures,
|
||||
'direct_args': analyzer_params(args),
|
||||
'force_analyze_debug_code' : args.force_analyze_debug_code
|
||||
'force_debug': args.force_debug
|
||||
}
|
||||
|
||||
logging.debug('run analyzer against compilation database')
|
||||
|
@ -140,8 +139,7 @@ def setup_environment(args, destination, bin_dir):
|
|||
'ANALYZE_BUILD_REPORT_FORMAT': args.output_format,
|
||||
'ANALYZE_BUILD_REPORT_FAILURES': 'yes' if args.output_failures else '',
|
||||
'ANALYZE_BUILD_PARAMETERS': ' '.join(analyzer_params(args)),
|
||||
'ANALYZE_BUILD_FORCE_ANALYZE_DEBUG_CODE'
|
||||
: 'yes' if args.force_analyze_debug_code else ''
|
||||
'ANALYZE_BUILD_FORCE_DEBUG': 'yes' if args.force_debug else ''
|
||||
})
|
||||
return environment
|
||||
|
||||
|
@ -163,32 +161,34 @@ def analyze_build_wrapper(cplusplus):
|
|||
return result
|
||||
# ... and run the analyzer if all went well.
|
||||
try:
|
||||
# check is it a compilation
|
||||
compilation = split_command(sys.argv)
|
||||
if compilation is None:
|
||||
return result
|
||||
# collect the needed parameters from environment, crash when missing
|
||||
consts = {
|
||||
parameters = {
|
||||
'clang': os.getenv('ANALYZE_BUILD_CLANG'),
|
||||
'output_dir': os.getenv('ANALYZE_BUILD_REPORT_DIR'),
|
||||
'output_format': os.getenv('ANALYZE_BUILD_REPORT_FORMAT'),
|
||||
'output_failures': os.getenv('ANALYZE_BUILD_REPORT_FAILURES'),
|
||||
'direct_args': os.getenv('ANALYZE_BUILD_PARAMETERS',
|
||||
'').split(' '),
|
||||
'force_analyze_debug_code':
|
||||
os.getenv('ANALYZE_BUILD_FORCE_ANALYZE_DEBUG_CODE'),
|
||||
'force_debug': os.getenv('ANALYZE_BUILD_FORCE_DEBUG'),
|
||||
'directory': os.getcwd(),
|
||||
'command': [sys.argv[0], '-c'] + compilation.flags
|
||||
}
|
||||
# get relevant parameters from command line arguments
|
||||
args = classify_parameters(sys.argv)
|
||||
filenames = args.pop('files', [])
|
||||
for filename in (name for name in filenames if classify_source(name)):
|
||||
parameters = dict(args, file=filename, **consts)
|
||||
# call static analyzer against the compilation
|
||||
for source in compilation.files:
|
||||
parameters.update({'file': source})
|
||||
logging.debug('analyzer parameters %s', parameters)
|
||||
current = action_check(parameters)
|
||||
current = run(parameters)
|
||||
# display error message from the static analyzer
|
||||
if current is not None:
|
||||
for line in current['error_output']:
|
||||
logging.info(line.rstrip())
|
||||
except Exception:
|
||||
logging.exception("run analyzer inside compiler wrapper failed.")
|
||||
return 0
|
||||
return result
|
||||
|
||||
|
||||
def analyzer_params(args):
|
||||
|
@ -208,8 +208,8 @@ def analyzer_params(args):
|
|||
if args.store_model:
|
||||
result.append('-analyzer-store={0}'.format(args.store_model))
|
||||
if args.constraints_model:
|
||||
result.append(
|
||||
'-analyzer-constraints={0}'.format(args.constraints_model))
|
||||
result.append('-analyzer-constraints={0}'.format(
|
||||
args.constraints_model))
|
||||
if args.internal_stats:
|
||||
result.append('-analyzer-stats')
|
||||
if args.analyze_headers:
|
||||
|
@ -457,11 +457,10 @@ def create_parser(from_build_command):
|
|||
the compilation database.""")
|
||||
advanced.add_argument(
|
||||
'--force-analyze-debug-code',
|
||||
dest='force_analyze_debug_code',
|
||||
dest='force_debug',
|
||||
action='store_true',
|
||||
help="""Tells analyzer to enable assertions in code even if they were
|
||||
disabled during compilation, enabling more precise
|
||||
results.""")
|
||||
disabled during compilation, enabling more precise results.""")
|
||||
|
||||
plugins = parser.add_argument_group('checker options')
|
||||
plugins.add_argument(
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
# -*- 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 for to parse a compiler invocation. """
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
__all__ = ['Action', 'classify_parameters', 'classify_source']
|
||||
|
||||
|
||||
class Action(object):
|
||||
""" Enumeration class for compiler action. """
|
||||
|
||||
Link, Compile, Ignored = range(3)
|
||||
|
||||
|
||||
def classify_parameters(command):
|
||||
""" Parses the command line arguments of the given invocation. """
|
||||
|
||||
# result value of this method.
|
||||
# some value are preset, some will be set only when found.
|
||||
result = {
|
||||
'action': Action.Link,
|
||||
'files': [],
|
||||
'output': None,
|
||||
'compile_options': [],
|
||||
'c++': is_cplusplus_compiler(command[0])
|
||||
# archs_seen
|
||||
# language
|
||||
}
|
||||
|
||||
# data structure to ignore compiler parameters.
|
||||
# key: parameter name, value: number of parameters to ignore afterwards.
|
||||
ignored = {
|
||||
'-g': 0,
|
||||
'-fsyntax-only': 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
|
||||
}
|
||||
|
||||
args = iter(command[1:])
|
||||
for arg in args:
|
||||
# compiler action parameters are the most important ones...
|
||||
if arg in {'-E', '-S', '-cc1', '-M', '-MM', '-###'}:
|
||||
result.update({'action': Action.Ignored})
|
||||
elif arg == '-c':
|
||||
result.update({'action': max(result['action'], Action.Compile)})
|
||||
# arch flags are taken...
|
||||
elif arg == '-arch':
|
||||
archs = result.get('archs_seen', [])
|
||||
result.update({'archs_seen': archs + [next(args)]})
|
||||
# explicit language option taken...
|
||||
elif arg == '-x':
|
||||
result.update({'language': next(args)})
|
||||
# output flag taken...
|
||||
elif arg == '-o':
|
||||
result.update({'output': next(args)})
|
||||
# warning disable options are taken...
|
||||
elif re.match(r'^-Wno-', arg):
|
||||
result['compile_options'].append(arg)
|
||||
# warning options are ignored...
|
||||
elif re.match(r'^-[mW].+', arg):
|
||||
pass
|
||||
# some preprocessor parameters are ignored...
|
||||
elif arg in {'-MD', '-MMD', '-MG', '-MP'}:
|
||||
pass
|
||||
elif arg in {'-MF', '-MT', '-MQ'}:
|
||||
next(args)
|
||||
# linker options are ignored...
|
||||
elif arg in {'-static', '-shared', '-s', '-rdynamic'} or \
|
||||
re.match(r'^-[lL].+', arg):
|
||||
pass
|
||||
elif arg in {'-l', '-L', '-u', '-z', '-T', '-Xlinker'}:
|
||||
next(args)
|
||||
# some other options are ignored...
|
||||
elif arg in ignored.keys():
|
||||
for _ in range(ignored[arg]):
|
||||
next(args)
|
||||
# parameters which looks source file are taken...
|
||||
elif re.match(r'^[^-].+', arg) and classify_source(arg):
|
||||
result['files'].append(arg)
|
||||
# and consider everything else as compile option.
|
||||
else:
|
||||
result['compile_options'].append(arg)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def classify_source(filename, cplusplus=False):
|
||||
""" Return the language from file name extension. """
|
||||
|
||||
mapping = {
|
||||
'.c': 'c++' if cplusplus else 'c',
|
||||
'.i': 'c++-cpp-output' if cplusplus else 'c-cpp-output',
|
||||
'.ii': 'c++-cpp-output',
|
||||
'.m': 'objective-c',
|
||||
'.mi': 'objective-c-cpp-output',
|
||||
'.mm': 'objective-c++',
|
||||
'.mii': 'objective-c++-cpp-output',
|
||||
'.C': 'c++',
|
||||
'.cc': 'c++',
|
||||
'.CC': 'c++',
|
||||
'.cp': 'c++',
|
||||
'.cpp': 'c++',
|
||||
'.cxx': 'c++',
|
||||
'.c++': 'c++',
|
||||
'.C++': 'c++',
|
||||
'.txx': 'c++'
|
||||
}
|
||||
|
||||
__, extension = os.path.splitext(os.path.basename(filename))
|
||||
return mapping.get(extension)
|
||||
|
||||
|
||||
def is_cplusplus_compiler(name):
|
||||
""" Returns true when the compiler name refer to a C++ compiler. """
|
||||
|
||||
match = re.match(r'^([^/]*/)*(\w*-)*(\w+\+\+)(-(\d+(\.\d+){0,3}))?$', name)
|
||||
return False if match is None else True
|
|
@ -0,0 +1,141 @@
|
|||
# -*- 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 for to parse a compiler invocation. """
|
||||
|
||||
import re
|
||||
import os
|
||||
import collections
|
||||
|
||||
__all__ = ['split_command', 'classify_source', 'compiler_language']
|
||||
|
||||
# Ignored compiler options map for compilation database creation.
|
||||
# The map is used in `split_command` method. (Which does ignore and classify
|
||||
# parameters.) Please note, that these are not the only parameters which
|
||||
# might be ignored.
|
||||
#
|
||||
# Keys are the option name, value number of options to skip
|
||||
IGNORED_FLAGS = {
|
||||
# compiling only flag, ignored because the creator of compilation
|
||||
# database will explicitly set it.
|
||||
'-c': 0,
|
||||
# preprocessor macros, ignored because would cause duplicate entries in
|
||||
# the output (the only difference would be these flags). this is actual
|
||||
# finding from users, who suffered longer execution time caused by the
|
||||
# duplicates.
|
||||
'-MD': 0,
|
||||
'-MMD': 0,
|
||||
'-MG': 0,
|
||||
'-MP': 0,
|
||||
'-MF': 1,
|
||||
'-MT': 1,
|
||||
'-MQ': 1,
|
||||
# linker options, ignored because for compilation database will contain
|
||||
# compilation commands only. so, the compiler would ignore these flags
|
||||
# anyway. the benefit to get rid of them is to make the output more
|
||||
# readable.
|
||||
'-static': 0,
|
||||
'-shared': 0,
|
||||
'-s': 0,
|
||||
'-rdynamic': 0,
|
||||
'-l': 1,
|
||||
'-L': 1,
|
||||
'-u': 1,
|
||||
'-z': 1,
|
||||
'-T': 1,
|
||||
'-Xlinker': 1
|
||||
}
|
||||
|
||||
# Known C/C++ compiler executable name patterns
|
||||
COMPILER_PATTERNS = frozenset([
|
||||
re.compile(r'^(intercept-|analyze-|)c(c|\+\+)$'),
|
||||
re.compile(r'^([^-]*-)*[mg](cc|\+\+)(-\d+(\.\d+){0,2})?$'),
|
||||
re.compile(r'^([^-]*-)*clang(\+\+)?(-\d+(\.\d+){0,2})?$'),
|
||||
re.compile(r'^llvm-g(cc|\+\+)$'),
|
||||
])
|
||||
|
||||
|
||||
def split_command(command):
|
||||
""" Returns a value when the command is a compilation, None otherwise.
|
||||
|
||||
The value on success is a named tuple with the following attributes:
|
||||
|
||||
files: list of source files
|
||||
flags: list of compile options
|
||||
compiler: string value of 'c' or 'c++' """
|
||||
|
||||
# the result of this method
|
||||
result = collections.namedtuple('Compilation',
|
||||
['compiler', 'flags', 'files'])
|
||||
result.compiler = compiler_language(command)
|
||||
result.flags = []
|
||||
result.files = []
|
||||
# quit right now, if the program was not a C/C++ compiler
|
||||
if not result.compiler:
|
||||
return None
|
||||
# iterate on the compile options
|
||||
args = iter(command[1:])
|
||||
for arg in args:
|
||||
# quit when compilation pass is not involved
|
||||
if arg in {'-E', '-S', '-cc1', '-M', '-MM', '-###'}:
|
||||
return None
|
||||
# ignore some flags
|
||||
elif arg in IGNORED_FLAGS:
|
||||
count = IGNORED_FLAGS[arg]
|
||||
for _ in range(count):
|
||||
next(args)
|
||||
elif re.match(r'^-(l|L|Wl,).+', arg):
|
||||
pass
|
||||
# some parameters could look like filename, take as compile option
|
||||
elif arg in {'-D', '-I'}:
|
||||
result.flags.extend([arg, next(args)])
|
||||
# parameter which looks source file is taken...
|
||||
elif re.match(r'^[^-].+', arg) and classify_source(arg):
|
||||
result.files.append(arg)
|
||||
# and consider everything else as compile option.
|
||||
else:
|
||||
result.flags.append(arg)
|
||||
# do extra check on number of source files
|
||||
return result if result.files else None
|
||||
|
||||
|
||||
def classify_source(filename, c_compiler=True):
|
||||
""" Return the language from file name extension. """
|
||||
|
||||
mapping = {
|
||||
'.c': 'c' if c_compiler else 'c++',
|
||||
'.i': 'c-cpp-output' if c_compiler else 'c++-cpp-output',
|
||||
'.ii': 'c++-cpp-output',
|
||||
'.m': 'objective-c',
|
||||
'.mi': 'objective-c-cpp-output',
|
||||
'.mm': 'objective-c++',
|
||||
'.mii': 'objective-c++-cpp-output',
|
||||
'.C': 'c++',
|
||||
'.cc': 'c++',
|
||||
'.CC': 'c++',
|
||||
'.cp': 'c++',
|
||||
'.cpp': 'c++',
|
||||
'.cxx': 'c++',
|
||||
'.c++': 'c++',
|
||||
'.C++': 'c++',
|
||||
'.txx': 'c++'
|
||||
}
|
||||
|
||||
__, extension = os.path.splitext(os.path.basename(filename))
|
||||
return mapping.get(extension)
|
||||
|
||||
|
||||
def compiler_language(command):
|
||||
""" A predicate to decide the command is a compiler call or not.
|
||||
|
||||
Returns 'c' or 'c++' when it match. None otherwise. """
|
||||
|
||||
cplusplus = re.compile(r'^(.+)(\+\+)(-.+|)$')
|
||||
|
||||
if command:
|
||||
executable = os.path.basename(command[0])
|
||||
if any(pattern.match(executable) for pattern in COMPILER_PATTERNS):
|
||||
return 'c++' if cplusplus.match(executable) else 'c'
|
||||
return None
|
|
@ -31,9 +31,9 @@ import argparse
|
|||
import logging
|
||||
import subprocess
|
||||
from libear import build_libear, TemporaryDirectory
|
||||
from libscanbuild import duplicate_check, tempdir, initialize_logging
|
||||
from libscanbuild import command_entry_point
|
||||
from libscanbuild.command import Action, classify_parameters
|
||||
from libscanbuild import duplicate_check, tempdir, initialize_logging
|
||||
from libscanbuild.compilation import split_command
|
||||
from libscanbuild.shell import encode, decode
|
||||
|
||||
__all__ = ['capture', 'intercept_build_main', 'intercept_build_wrapper']
|
||||
|
@ -72,23 +72,23 @@ def capture(args, bin_dir):
|
|||
from the arguments. And do shell escaping on the command.
|
||||
|
||||
To support incremental builds, it is desired to read elements from
|
||||
an existing compilation database from a previous run. These elemets
|
||||
an existing compilation database from a previous run. These elements
|
||||
shall be merged with the new elements. """
|
||||
|
||||
# create entries from the current run
|
||||
current = itertools.chain.from_iterable(
|
||||
# creates a sequence of entry generators from an exec,
|
||||
# but filter out non compiler calls before.
|
||||
(format_entry(x) for x in commands if is_compiler_call(x)))
|
||||
format_entry(command) for command in commands)
|
||||
# read entries from previous run
|
||||
if 'append' in args and args.append and os.path.exists(args.cdb):
|
||||
if 'append' in args and args.append and os.path.isfile(args.cdb):
|
||||
with open(args.cdb) as handle:
|
||||
previous = iter(json.load(handle))
|
||||
else:
|
||||
previous = iter([])
|
||||
# filter out duplicate entries from both
|
||||
duplicate = duplicate_check(entry_hash)
|
||||
return (entry for entry in itertools.chain(previous, current)
|
||||
return (entry
|
||||
for entry in itertools.chain(previous, current)
|
||||
if os.path.exists(entry['file']) and not duplicate(entry))
|
||||
|
||||
with TemporaryDirectory(prefix='intercept-', dir=tempdir()) as tmp_dir:
|
||||
|
@ -98,14 +98,14 @@ def capture(args, bin_dir):
|
|||
exit_code = subprocess.call(args.build, env=environment)
|
||||
logging.info('build finished with exit code: %d', exit_code)
|
||||
# read the intercepted exec calls
|
||||
commands = itertools.chain.from_iterable(
|
||||
exec_traces = itertools.chain.from_iterable(
|
||||
parse_exec_trace(os.path.join(tmp_dir, filename))
|
||||
for filename in sorted(glob.iglob(os.path.join(tmp_dir, '*.cmd'))))
|
||||
# do post processing only if that was requested
|
||||
if 'raw_entries' not in args or not args.raw_entries:
|
||||
entries = post_processing(commands)
|
||||
entries = post_processing(exec_traces)
|
||||
else:
|
||||
entries = commands
|
||||
entries = exec_traces
|
||||
# dump the compilation database
|
||||
with open(args.cdb, 'w+') as handle:
|
||||
json.dump(list(entries), handle, sort_keys=True, indent=4)
|
||||
|
@ -209,7 +209,7 @@ def parse_exec_trace(filename):
|
|||
}
|
||||
|
||||
|
||||
def format_entry(entry):
|
||||
def format_entry(exec_trace):
|
||||
""" Generate the desired fields for compilation database entries. """
|
||||
|
||||
def abspath(cwd, name):
|
||||
|
@ -217,40 +217,20 @@ def format_entry(entry):
|
|||
fullname = name if os.path.isabs(name) else os.path.join(cwd, name)
|
||||
return os.path.normpath(fullname)
|
||||
|
||||
logging.debug('format this command: %s', entry['command'])
|
||||
atoms = classify_parameters(entry['command'])
|
||||
if atoms['action'] <= Action.Compile:
|
||||
for source in atoms['files']:
|
||||
compiler = 'c++' if atoms['c++'] else 'cc'
|
||||
flags = atoms['compile_options']
|
||||
flags += ['-o', atoms['output']] if atoms['output'] else []
|
||||
flags += ['-x', atoms['language']] if 'language' in atoms else []
|
||||
flags += [elem
|
||||
for arch in atoms.get('archs_seen', [])
|
||||
for elem in ['-arch', arch]]
|
||||
command = [compiler, '-c'] + flags + [source]
|
||||
logging.debug('format this command: %s', exec_trace['command'])
|
||||
compilation = split_command(exec_trace['command'])
|
||||
if compilation:
|
||||
for source in compilation.files:
|
||||
compiler = 'c++' if compilation.compiler == 'c++' else 'cc'
|
||||
command = [compiler, '-c'] + compilation.flags + [source]
|
||||
logging.debug('formated as: %s', command)
|
||||
yield {
|
||||
'directory': entry['directory'],
|
||||
'directory': exec_trace['directory'],
|
||||
'command': encode(command),
|
||||
'file': abspath(entry['directory'], source)
|
||||
'file': abspath(exec_trace['directory'], source)
|
||||
}
|
||||
|
||||
|
||||
def is_compiler_call(entry):
|
||||
""" A predicate to decide the entry is a compiler call or not. """
|
||||
|
||||
patterns = [
|
||||
re.compile(r'^([^/]*/)*intercept-c(c|\+\+)$'),
|
||||
re.compile(r'^([^/]*/)*c(c|\+\+)$'),
|
||||
re.compile(r'^([^/]*/)*([^-]*-)*[mg](cc|\+\+)(-\d+(\.\d+){0,2})?$'),
|
||||
re.compile(r'^([^/]*/)*([^-]*-)*clang(\+\+)?(-\d+(\.\d+){0,2})?$'),
|
||||
re.compile(r'^([^/]*/)*llvm-g(cc|\+\+)$'),
|
||||
]
|
||||
executable = entry['command'][0]
|
||||
return any((pattern.match(executable) for pattern in patterns))
|
||||
|
||||
|
||||
def is_preload_disabled(platform):
|
||||
""" Library-based interposition will fail silently if SIP is enabled,
|
||||
so this should be detected. You can detect whether SIP is enabled on
|
||||
|
|
|
@ -5,18 +5,44 @@
|
|||
# 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.command import classify_parameters, Action, classify_source
|
||||
from libscanbuild.clang import get_arguments, get_version
|
||||
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.
|
||||
|
@ -29,8 +55,8 @@ def require(required):
|
|||
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__))
|
||||
raise KeyError('{0} not passed to {1}'.format(
|
||||
key, function.__name__))
|
||||
|
||||
return function(*args, **kwargs)
|
||||
|
||||
|
@ -39,10 +65,15 @@ def require(required):
|
|||
return decorator
|
||||
|
||||
|
||||
@require(['command', 'directory', 'file', # an entry from compilation database
|
||||
'clang', 'direct_args', # compiler name, and arguments from command
|
||||
'force_analyze_debug_code', # preprocessing options
|
||||
'output_dir', 'output_format', 'output_failures'])
|
||||
@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.
|
||||
|
@ -58,16 +89,17 @@ def run(opts):
|
|||
|
||||
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(decode(command)))
|
||||
opts.update(classify_parameters(command))
|
||||
|
||||
return action_check(opts)
|
||||
return arch_check(opts)
|
||||
except Exception:
|
||||
logging.error("Problem occured during analyzis.", exc_info=1)
|
||||
return None
|
||||
|
||||
|
||||
@require(['report', 'directory', 'clang', 'output_dir', 'language', 'file',
|
||||
@require(['clang', 'directory', 'flags', 'file', 'output_dir', 'language',
|
||||
'error_type', 'error_output', 'exit_code'])
|
||||
def report_failure(opts):
|
||||
""" Create report when analyzer failed.
|
||||
|
@ -96,36 +128,49 @@ def report_failure(opts):
|
|||
dir=destination(opts))
|
||||
os.close(handle)
|
||||
cwd = opts['directory']
|
||||
cmd = get_arguments([opts['clang']] + opts['report'] + ['-o', name], cwd)
|
||||
cmd = get_arguments([opts['clang'], '-fsyntax-only', '-E'] +
|
||||
opts['flags'] + [opts['file'], '-o', name], cwd)
|
||||
logging.debug('exec command in %s: %s', cwd, ' '.join(cmd))
|
||||
subprocess.call(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(cmd[0]))
|
||||
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()
|
||||
|
||||
# return with the previous step exit code and output
|
||||
return {
|
||||
'error_output': opts['error_output'],
|
||||
'exit_code': opts['exit_code']
|
||||
}
|
||||
|
||||
|
||||
@require(['clang', 'analyze', 'directory', 'output'])
|
||||
@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 output():
|
||||
""" 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']
|
||||
|
||||
cwd = opts['directory']
|
||||
cmd = get_arguments([opts['clang']] + opts['analyze'] + opts['output'],
|
||||
cmd = get_arguments([opts['clang'], '--analyze'] + opts['direct_args'] +
|
||||
opts['flags'] + [opts['file'], '-o', output()],
|
||||
cwd)
|
||||
logging.debug('exec command in %s: %s', cwd, ' '.join(cmd))
|
||||
child = subprocess.Popen(cmd,
|
||||
|
@ -145,119 +190,124 @@ def run_analyzer(opts, continuation=report_failure):
|
|||
'exit_code': child.returncode
|
||||
})
|
||||
return continuation(opts)
|
||||
# return the output for logging and exit code for testing
|
||||
return {'error_output': output, 'exit_code': child.returncode}
|
||||
|
||||
|
||||
@require(['output_dir'])
|
||||
def set_analyzer_output(opts, continuation=run_analyzer):
|
||||
""" Create output file if was requested.
|
||||
@require(['flags', 'force_debug'])
|
||||
def filter_debug_flags(opts, continuation=run_analyzer):
|
||||
""" Filter out nondebug macros when requested. """
|
||||
|
||||
This plays a role only if .plist files are requested. """
|
||||
|
||||
if opts.get('output_format') in {'plist', 'plist-html'}:
|
||||
with tempfile.NamedTemporaryFile(prefix='report-',
|
||||
suffix='.plist',
|
||||
delete=False,
|
||||
dir=opts['output_dir']) as output:
|
||||
opts.update({'output': ['-o', output.name]})
|
||||
return continuation(opts)
|
||||
else:
|
||||
opts.update({'output': ['-o', opts['output_dir']]})
|
||||
return continuation(opts)
|
||||
|
||||
def force_analyze_debug_code(cmd):
|
||||
""" Enable assert()'s by undefining NDEBUG. """
|
||||
cmd.append('-UNDEBUG')
|
||||
|
||||
@require(['file', 'directory', 'clang', 'direct_args',
|
||||
'force_analyze_debug_code', 'language', 'output_dir',
|
||||
'output_format', 'output_failures'])
|
||||
def create_commands(opts, continuation=set_analyzer_output):
|
||||
""" Create command to run analyzer or failure report generation.
|
||||
|
||||
It generates commands (from compilation database entries) which contains
|
||||
enough information to run the analyzer (and the crash report generation
|
||||
if that was requested). """
|
||||
|
||||
common = []
|
||||
if 'arch' in opts:
|
||||
common.extend(['-arch', opts.pop('arch')])
|
||||
common.extend(opts.pop('compile_options', []))
|
||||
if opts['force_analyze_debug_code']:
|
||||
force_analyze_debug_code(common)
|
||||
common.extend(['-x', opts['language']])
|
||||
common.append(os.path.relpath(opts['file'], opts['directory']))
|
||||
|
||||
opts.update({
|
||||
'analyze': ['--analyze'] + opts['direct_args'] + common,
|
||||
'report': ['-fsyntax-only', '-E'] + common
|
||||
})
|
||||
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(['file', 'c++'])
|
||||
def language_check(opts, continuation=create_commands):
|
||||
@require(['file', 'directory'])
|
||||
def set_file_path_relative(opts, continuation=filter_debug_flags):
|
||||
""" Set source file path to relative to the working directory.
|
||||
|
||||
The only purpose of this function is to pass the SATestBuild.py tests. """
|
||||
|
||||
opts.update({'file': os.path.relpath(opts['file'], opts['directory'])})
|
||||
|
||||
return continuation(opts)
|
||||
|
||||
|
||||
@require(['language', 'compiler', 'file', 'flags'])
|
||||
def language_check(opts, continuation=set_file_path_relative):
|
||||
""" Find out the language from command line parameters or file name
|
||||
extension. The decision also influenced by the compiler invocation. """
|
||||
|
||||
accepteds = {
|
||||
accepted = frozenset({
|
||||
'c', 'c++', 'objective-c', 'objective-c++', 'c-cpp-output',
|
||||
'c++-cpp-output', 'objective-c-cpp-output'
|
||||
}
|
||||
})
|
||||
|
||||
key = 'language'
|
||||
language = opts[key] if key in opts else \
|
||||
classify_source(opts['file'], opts['c++'])
|
||||
# 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 accepteds:
|
||||
elif language not in accepted:
|
||||
logging.debug('skip analysis, language not supported')
|
||||
return None
|
||||
else:
|
||||
logging.debug('analysis, language: %s', language)
|
||||
opts.update({key: language})
|
||||
opts.update({'language': language,
|
||||
'flags': ['-x', language] + opts['flags']})
|
||||
return continuation(opts)
|
||||
|
||||
|
||||
@require([])
|
||||
@require(['arch_list', 'flags'])
|
||||
def arch_check(opts, continuation=language_check):
|
||||
""" Do run analyzer through one of the given architectures. """
|
||||
|
||||
disableds = {'ppc', 'ppc64'}
|
||||
disabled = frozenset({'ppc', 'ppc64'})
|
||||
|
||||
key = 'archs_seen'
|
||||
if key in opts:
|
||||
received_list = opts.pop('arch_list')
|
||||
if received_list:
|
||||
# filter out disabled architectures and -arch switches
|
||||
archs = [a for a in opts[key] if a not in disableds]
|
||||
|
||||
if not archs:
|
||||
logging.debug('skip analysis, found not supported arch')
|
||||
return None
|
||||
else:
|
||||
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.
|
||||
arch = archs.pop()
|
||||
logging.debug('analysis, on arch: %s', arch)
|
||||
current = filtered_list.pop()
|
||||
logging.debug('analysis, on arch: %s', current)
|
||||
|
||||
opts.update({'arch': arch})
|
||||
del opts[key]
|
||||
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)
|
||||
|
||||
|
||||
@require(['action'])
|
||||
def action_check(opts, continuation=arch_check):
|
||||
""" Continue analysis only if it compilation or link. """
|
||||
def classify_parameters(command):
|
||||
""" Prepare compiler flags (filters some and add others) and take out
|
||||
language (-x) and architecture (-arch) flags for future processing. """
|
||||
|
||||
if opts.pop('action') <= Action.Compile:
|
||||
return continuation(opts)
|
||||
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:
|
||||
logging.debug('skip analysis, not compilation nor link')
|
||||
return None
|
||||
result['flags'].append(arg)
|
||||
|
||||
return result
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
|
||||
from ...unit import fixtures
|
||||
import libear
|
||||
from . import make_args, silent_check_call, silent_call, create_empty_file
|
||||
import unittest
|
||||
|
||||
|
@ -28,13 +28,13 @@ class CompilationDatabaseTest(unittest.TestCase):
|
|||
return len(content)
|
||||
|
||||
def test_successful_build(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
result = self.run_intercept(tmpdir, ['build_regular'])
|
||||
self.assertTrue(os.path.isfile(result))
|
||||
self.assertEqual(5, self.count_entries(result))
|
||||
|
||||
def test_successful_build_with_wrapper(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
result = os.path.join(tmpdir, 'cdb.json')
|
||||
make = make_args(tmpdir) + ['build_regular']
|
||||
silent_check_call(['intercept-build', '--cdb', result,
|
||||
|
@ -44,14 +44,14 @@ class CompilationDatabaseTest(unittest.TestCase):
|
|||
|
||||
@unittest.skipIf(os.getenv('TRAVIS'), 'ubuntu make return -11')
|
||||
def test_successful_build_parallel(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
result = self.run_intercept(tmpdir, ['-j', '4', 'build_regular'])
|
||||
self.assertTrue(os.path.isfile(result))
|
||||
self.assertEqual(5, self.count_entries(result))
|
||||
|
||||
@unittest.skipIf(os.getenv('TRAVIS'), 'ubuntu env remove clang from path')
|
||||
def test_successful_build_on_empty_env(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
result = os.path.join(tmpdir, 'cdb.json')
|
||||
make = make_args(tmpdir) + ['CC=clang', 'build_regular']
|
||||
silent_check_call(['intercept-build', '--cdb', result,
|
||||
|
@ -60,13 +60,13 @@ class CompilationDatabaseTest(unittest.TestCase):
|
|||
self.assertEqual(5, self.count_entries(result))
|
||||
|
||||
def test_successful_build_all_in_one(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
result = self.run_intercept(tmpdir, ['build_all_in_one'])
|
||||
self.assertTrue(os.path.isfile(result))
|
||||
self.assertEqual(5, self.count_entries(result))
|
||||
|
||||
def test_not_successful_build(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
result = os.path.join(tmpdir, 'cdb.json')
|
||||
make = make_args(tmpdir) + ['build_broken']
|
||||
silent_call(
|
||||
|
@ -84,12 +84,12 @@ class ExitCodeTest(unittest.TestCase):
|
|||
['intercept-build', '--cdb', result] + make)
|
||||
|
||||
def test_successful_build(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
exitcode = self.run_intercept(tmpdir, 'build_clean')
|
||||
self.assertFalse(exitcode)
|
||||
|
||||
def test_not_successful_build(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
exitcode = self.run_intercept(tmpdir, 'build_broken')
|
||||
self.assertTrue(exitcode)
|
||||
|
||||
|
@ -110,7 +110,7 @@ class ResumeFeatureTest(unittest.TestCase):
|
|||
return len(content)
|
||||
|
||||
def test_overwrite_existing_cdb(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
result = self.run_intercept(tmpdir, 'build_clean', [])
|
||||
self.assertTrue(os.path.isfile(result))
|
||||
result = self.run_intercept(tmpdir, 'build_regular', [])
|
||||
|
@ -118,7 +118,7 @@ class ResumeFeatureTest(unittest.TestCase):
|
|||
self.assertEqual(2, self.count_entries(result))
|
||||
|
||||
def test_append_to_existing_cdb(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
result = self.run_intercept(tmpdir, 'build_clean', [])
|
||||
self.assertTrue(os.path.isfile(result))
|
||||
result = self.run_intercept(tmpdir, 'build_regular', ['--append'])
|
||||
|
@ -138,7 +138,7 @@ class ResultFormatingTest(unittest.TestCase):
|
|||
return content
|
||||
|
||||
def assert_creates_number_of_entries(self, command, count):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
filename = os.path.join(tmpdir, 'test.c')
|
||||
create_empty_file(filename)
|
||||
command.append(filename)
|
||||
|
@ -153,7 +153,7 @@ class ResultFormatingTest(unittest.TestCase):
|
|||
self.assert_creates_number_of_entries(['cc', '-c', '-MM'], 0)
|
||||
|
||||
def assert_command_creates_entry(self, command, expected):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
filename = os.path.join(tmpdir, command[-1])
|
||||
create_empty_file(filename)
|
||||
cmd = ['sh', '-c', ' '.join(command)]
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
|
||||
from ...unit import fixtures
|
||||
import libear
|
||||
import unittest
|
||||
|
||||
import os.path
|
||||
|
@ -45,6 +45,6 @@ class ExecAnatomyTest(unittest.TestCase):
|
|||
def test_all_exec_calls(self):
|
||||
this_dir, _ = os.path.split(__file__)
|
||||
source_dir = os.path.normpath(os.path.join(this_dir, '..', 'exec'))
|
||||
with fixtures.TempDir() as tmp_dir:
|
||||
with libear.TemporaryDirectory() as tmp_dir:
|
||||
expected, result = run(source_dir, tmp_dir)
|
||||
self.assertEqualJson(expected, result)
|
||||
|
|
|
@ -4,13 +4,12 @@
|
|||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
|
||||
from ...unit import fixtures
|
||||
import libear
|
||||
from . import call_and_report
|
||||
import unittest
|
||||
|
||||
import os.path
|
||||
import string
|
||||
import subprocess
|
||||
import glob
|
||||
|
||||
|
||||
|
@ -37,19 +36,19 @@ def run_analyzer(directory, cdb, args):
|
|||
|
||||
class OutputDirectoryTest(unittest.TestCase):
|
||||
def test_regular_keeps_report_dir(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('regular', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(tmpdir, cdb, [])
|
||||
self.assertTrue(os.path.isdir(reportdir))
|
||||
|
||||
def test_clear_deletes_report_dir(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('clean', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(tmpdir, cdb, [])
|
||||
self.assertFalse(os.path.isdir(reportdir))
|
||||
|
||||
def test_clear_keeps_report_dir_when_asked(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('clean', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(tmpdir, cdb, ['--keep-empty'])
|
||||
self.assertTrue(os.path.isdir(reportdir))
|
||||
|
@ -57,38 +56,38 @@ class OutputDirectoryTest(unittest.TestCase):
|
|||
|
||||
class ExitCodeTest(unittest.TestCase):
|
||||
def test_regular_does_not_set_exit_code(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('regular', tmpdir)
|
||||
exit_code, __ = run_analyzer(tmpdir, cdb, [])
|
||||
self.assertFalse(exit_code)
|
||||
|
||||
def test_clear_does_not_set_exit_code(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('clean', tmpdir)
|
||||
exit_code, __ = run_analyzer(tmpdir, cdb, [])
|
||||
self.assertFalse(exit_code)
|
||||
|
||||
def test_regular_sets_exit_code_if_asked(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('regular', tmpdir)
|
||||
exit_code, __ = run_analyzer(tmpdir, cdb, ['--status-bugs'])
|
||||
self.assertTrue(exit_code)
|
||||
|
||||
def test_clear_does_not_set_exit_code_if_asked(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('clean', tmpdir)
|
||||
exit_code, __ = run_analyzer(tmpdir, cdb, ['--status-bugs'])
|
||||
self.assertFalse(exit_code)
|
||||
|
||||
def test_regular_sets_exit_code_if_asked_from_plist(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('regular', tmpdir)
|
||||
exit_code, __ = run_analyzer(
|
||||
tmpdir, cdb, ['--status-bugs', '--plist'])
|
||||
self.assertTrue(exit_code)
|
||||
|
||||
def test_clear_does_not_set_exit_code_if_asked_from_plist(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('clean', tmpdir)
|
||||
exit_code, __ = run_analyzer(
|
||||
tmpdir, cdb, ['--status-bugs', '--plist'])
|
||||
|
@ -105,7 +104,7 @@ class OutputFormatTest(unittest.TestCase):
|
|||
return len(glob.glob(os.path.join(directory, 'report-*.plist')))
|
||||
|
||||
def test_default_creates_html_report(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('regular', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(tmpdir, cdb, [])
|
||||
self.assertTrue(
|
||||
|
@ -114,7 +113,7 @@ class OutputFormatTest(unittest.TestCase):
|
|||
self.assertEqual(self.get_plist_count(reportdir), 0)
|
||||
|
||||
def test_plist_and_html_creates_html_report(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('regular', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(tmpdir, cdb, ['--plist-html'])
|
||||
self.assertTrue(
|
||||
|
@ -123,7 +122,7 @@ class OutputFormatTest(unittest.TestCase):
|
|||
self.assertEqual(self.get_plist_count(reportdir), 5)
|
||||
|
||||
def test_plist_does_not_creates_html_report(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('regular', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(tmpdir, cdb, ['--plist'])
|
||||
self.assertFalse(
|
||||
|
@ -134,14 +133,14 @@ class OutputFormatTest(unittest.TestCase):
|
|||
|
||||
class FailureReportTest(unittest.TestCase):
|
||||
def test_broken_creates_failure_reports(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('broken', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(tmpdir, cdb, [])
|
||||
self.assertTrue(
|
||||
os.path.isdir(os.path.join(reportdir, 'failures')))
|
||||
|
||||
def test_broken_does_not_creates_failure_reports(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('broken', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(
|
||||
tmpdir, cdb, ['--no-failure-reports'])
|
||||
|
@ -170,13 +169,13 @@ class TitleTest(unittest.TestCase):
|
|||
self.assertEqual(result['page'], expected)
|
||||
|
||||
def test_default_title_in_report(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('broken', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(tmpdir, cdb, [])
|
||||
self.assertTitleEqual(reportdir, 'src - analyzer results')
|
||||
|
||||
def test_given_title_in_report(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
cdb = prepare_cdb('broken', tmpdir)
|
||||
exit_code, reportdir = run_analyzer(
|
||||
tmpdir, cdb, ['--html-title', 'this is the title'])
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
|
||||
from ...unit import fixtures
|
||||
import libear
|
||||
from . import make_args, check_call_and_report, create_empty_file
|
||||
import unittest
|
||||
|
||||
|
@ -22,19 +22,19 @@ class OutputDirectoryTest(unittest.TestCase):
|
|||
cmd)
|
||||
|
||||
def test_regular_keeps_report_dir(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
make = make_args(tmpdir) + ['build_regular']
|
||||
outdir = self.run_analyzer(tmpdir, [], make)
|
||||
self.assertTrue(os.path.isdir(outdir))
|
||||
|
||||
def test_clear_deletes_report_dir(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
make = make_args(tmpdir) + ['build_clean']
|
||||
outdir = self.run_analyzer(tmpdir, [], make)
|
||||
self.assertFalse(os.path.isdir(outdir))
|
||||
|
||||
def test_clear_keeps_report_dir_when_asked(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
make = make_args(tmpdir) + ['build_clean']
|
||||
outdir = self.run_analyzer(tmpdir, ['--keep-empty'], make)
|
||||
self.assertTrue(os.path.isdir(outdir))
|
||||
|
@ -47,7 +47,7 @@ class RunAnalyzerTest(unittest.TestCase):
|
|||
return len(glob.glob(os.path.join(directory, 'report-*.plist')))
|
||||
|
||||
def test_interposition_works(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
make = make_args(tmpdir) + ['build_regular']
|
||||
outdir = check_call_and_report(
|
||||
['scan-build', '--plist', '-o', tmpdir, '--override-compiler'],
|
||||
|
@ -57,7 +57,7 @@ class RunAnalyzerTest(unittest.TestCase):
|
|||
self.assertEqual(self.get_plist_count(outdir), 5)
|
||||
|
||||
def test_intercept_wrapper_works(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
make = make_args(tmpdir) + ['build_regular']
|
||||
outdir = check_call_and_report(
|
||||
['scan-build', '--plist', '-o', tmpdir, '--intercept-first',
|
||||
|
@ -68,7 +68,7 @@ class RunAnalyzerTest(unittest.TestCase):
|
|||
self.assertEqual(self.get_plist_count(outdir), 5)
|
||||
|
||||
def test_intercept_library_works(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
make = make_args(tmpdir) + ['build_regular']
|
||||
outdir = check_call_and_report(
|
||||
['scan-build', '--plist', '-o', tmpdir, '--intercept-first'],
|
||||
|
@ -88,21 +88,21 @@ class RunAnalyzerTest(unittest.TestCase):
|
|||
return ['sh', '-c', command]
|
||||
|
||||
def test_interposition_cc_works(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
outdir = check_call_and_report(
|
||||
['scan-build', '--plist', '-o', tmpdir, '--override-compiler'],
|
||||
self.compile_empty_source_file(tmpdir, False))
|
||||
self.assertEqual(self.get_plist_count(outdir), 1)
|
||||
|
||||
def test_interposition_cxx_works(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
outdir = check_call_and_report(
|
||||
['scan-build', '--plist', '-o', tmpdir, '--override-compiler'],
|
||||
self.compile_empty_source_file(tmpdir, True))
|
||||
self.assertEqual(self.get_plist_count(outdir), 1)
|
||||
|
||||
def test_intercept_cc_works(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
outdir = check_call_and_report(
|
||||
['scan-build', '--plist', '-o', tmpdir, '--override-compiler',
|
||||
'--intercept-first'],
|
||||
|
@ -110,7 +110,7 @@ class RunAnalyzerTest(unittest.TestCase):
|
|||
self.assertEqual(self.get_plist_count(outdir), 1)
|
||||
|
||||
def test_intercept_cxx_works(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
outdir = check_call_and_report(
|
||||
['scan-build', '--plist', '-o', tmpdir, '--override-compiler',
|
||||
'--intercept-first'],
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
SRCDIR := ..
|
||||
OBJDIR := .
|
||||
|
||||
CFLAGS = -Wall -DDEBUG -Dvariable="value with space" -I $(SRCDIR)/include
|
||||
LDFLAGS =
|
||||
PROGRAM = $(OBJDIR)/prg
|
||||
|
||||
$(OBJDIR)/main.o: $(SRCDIR)/main.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/main.c
|
||||
|
||||
$(OBJDIR)/clean-one.o: $(SRCDIR)/clean-one.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/clean-one.c
|
||||
|
||||
$(OBJDIR)/clean-two.o: $(SRCDIR)/clean-two.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/clean-two.c
|
||||
|
||||
$(OBJDIR)/emit-one.o: $(SRCDIR)/emit-one.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/emit-one.c
|
||||
|
||||
$(OBJDIR)/emit-two.o: $(SRCDIR)/emit-two.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/emit-two.c
|
||||
|
||||
$(OBJDIR)/broken-one.o: $(SRCDIR)/broken-one.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/broken-one.c
|
||||
|
||||
$(OBJDIR)/broken-two.o: $(SRCDIR)/broken-two.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/broken-two.c
|
||||
|
||||
$(PROGRAM): $(OBJDIR)/main.o $(OBJDIR)/clean-one.o $(OBJDIR)/clean-two.o $(OBJDIR)/emit-one.o $(OBJDIR)/emit-two.o
|
||||
$(CC) $(LDFLAGS) -o $@ $(OBJDIR)/main.o $(OBJDIR)/clean-one.o $(OBJDIR)/clean-two.o $(OBJDIR)/emit-one.o $(OBJDIR)/emit-two.o
|
||||
|
||||
build_regular: $(PROGRAM)
|
||||
|
||||
build_clean: $(OBJDIR)/main.o $(OBJDIR)/clean-one.o $(OBJDIR)/clean-two.o
|
||||
|
||||
build_broken: $(OBJDIR)/main.o $(OBJDIR)/broken-one.o $(OBJDIR)/broken-two.o
|
||||
|
||||
build_all_in_one: $(SRCDIR)/main.c $(SRCDIR)/clean-one.c $(SRCDIR)/clean-two.c $(SRCDIR)/emit-one.c $(SRCDIR)/emit-two.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $(PROGRAM) $(SRCDIR)/main.c $(SRCDIR)/clean-one.c $(SRCDIR)/clean-two.c $(SRCDIR)/emit-one.c $(SRCDIR)/emit-two.c
|
||||
|
||||
clean:
|
||||
rm -f $(PROGRAM) $(OBJDIR)/*.o
|
|
@ -4,7 +4,8 @@
|
|||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
|
||||
from . import test_command
|
||||
from . import test_libear
|
||||
from . import test_compilation
|
||||
from . import test_clang
|
||||
from . import test_runner
|
||||
from . import test_report
|
||||
|
@ -13,8 +14,9 @@ from . import test_intercept
|
|||
from . import test_shell
|
||||
|
||||
|
||||
def load_tests(loader, suite, pattern):
|
||||
suite.addTests(loader.loadTestsFromModule(test_command))
|
||||
def load_tests(loader, suite, _):
|
||||
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))
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
# -*- 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 contextlib
|
||||
import tempfile
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
|
||||
class Spy(object):
|
||||
def __init__(self):
|
||||
self.arg = None
|
||||
self.success = 0
|
||||
|
||||
def call(self, params):
|
||||
self.arg = params
|
||||
return self.success
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def TempDir():
|
||||
name = tempfile.mkdtemp(prefix='scan-build-test-')
|
||||
try:
|
||||
yield name
|
||||
finally:
|
||||
shutil.rmtree(name)
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
def assertIn(self, element, collection):
|
||||
found = False
|
||||
for it in collection:
|
||||
if element == it:
|
||||
found = True
|
||||
|
||||
self.assertTrue(found, '{0} does not have {1}'.format(collection,
|
||||
element))
|
|
@ -5,4 +5,3 @@
|
|||
# License. See LICENSE.TXT for details.
|
||||
|
||||
import libscanbuild.analyze as sut
|
||||
from . import fixtures
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
|
||||
import libear
|
||||
import libscanbuild.clang as sut
|
||||
from . import fixtures
|
||||
import unittest
|
||||
import os.path
|
||||
|
||||
|
||||
class GetClangArgumentsTest(fixtures.TestCase):
|
||||
class GetClangArgumentsTest(unittest.TestCase):
|
||||
def test_get_clang_arguments(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
filename = os.path.join(tmpdir, 'test.c')
|
||||
with open(filename, 'w') as handle:
|
||||
handle.write('')
|
||||
|
@ -20,8 +21,8 @@ class GetClangArgumentsTest(fixtures.TestCase):
|
|||
['clang', '-c', filename, '-DNDEBUG', '-Dvar="this is it"'],
|
||||
tmpdir)
|
||||
|
||||
self.assertIn('NDEBUG', result)
|
||||
self.assertIn('var="this is it"', result)
|
||||
self.assertTrue('NDEBUG' in result)
|
||||
self.assertTrue('var="this is it"' in result)
|
||||
|
||||
def test_get_clang_arguments_fails(self):
|
||||
self.assertRaises(
|
||||
|
@ -29,7 +30,7 @@ class GetClangArgumentsTest(fixtures.TestCase):
|
|||
['clang', '-###', '-fsyntax-only', '-x', 'c', 'notexist.c'], '.')
|
||||
|
||||
|
||||
class GetCheckersTest(fixtures.TestCase):
|
||||
class GetCheckersTest(unittest.TestCase):
|
||||
def test_get_checkers(self):
|
||||
# this test is only to see is not crashing
|
||||
result = sut.get_checkers('clang', [])
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
# -*- 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 libscanbuild.command as sut
|
||||
from . import fixtures
|
||||
import unittest
|
||||
|
||||
|
||||
class ParseTest(unittest.TestCase):
|
||||
|
||||
def test_action(self):
|
||||
def test(expected, cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
self.assertEqual(expected, opts['action'])
|
||||
|
||||
Link = sut.Action.Link
|
||||
test(Link, ['clang', 'source.c'])
|
||||
|
||||
Compile = sut.Action.Compile
|
||||
test(Compile, ['clang', '-c', 'source.c'])
|
||||
test(Compile, ['clang', '-c', 'source.c', '-MF', 'source.d'])
|
||||
|
||||
Preprocess = sut.Action.Ignored
|
||||
test(Preprocess, ['clang', '-E', 'source.c'])
|
||||
test(Preprocess, ['clang', '-c', '-E', 'source.c'])
|
||||
test(Preprocess, ['clang', '-c', '-M', 'source.c'])
|
||||
test(Preprocess, ['clang', '-c', '-MM', 'source.c'])
|
||||
|
||||
def test_optimalizations(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('compile_options', [])
|
||||
|
||||
self.assertEqual(['-O'], test(['clang', '-c', 'source.c', '-O']))
|
||||
self.assertEqual(['-O1'], test(['clang', '-c', 'source.c', '-O1']))
|
||||
self.assertEqual(['-Os'], test(['clang', '-c', 'source.c', '-Os']))
|
||||
self.assertEqual(['-O2'], test(['clang', '-c', 'source.c', '-O2']))
|
||||
self.assertEqual(['-O3'], test(['clang', '-c', 'source.c', '-O3']))
|
||||
|
||||
def test_language(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('language')
|
||||
|
||||
self.assertEqual(None, test(['clang', '-c', 'source.c']))
|
||||
self.assertEqual('c', test(['clang', '-c', 'source.c', '-x', 'c']))
|
||||
self.assertEqual('cpp', test(['clang', '-c', 'source.c', '-x', 'cpp']))
|
||||
|
||||
def test_output(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('output')
|
||||
|
||||
self.assertEqual(None, test(['clang', '-c', 'source.c']))
|
||||
self.assertEqual('source.o',
|
||||
test(['clang', '-c', '-o', 'source.o', 'source.c']))
|
||||
|
||||
def test_arch(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('archs_seen', [])
|
||||
|
||||
eq = self.assertEqual
|
||||
|
||||
eq([], test(['clang', '-c', 'source.c']))
|
||||
eq(['mips'],
|
||||
test(['clang', '-c', 'source.c', '-arch', 'mips']))
|
||||
eq(['mips', 'i386'],
|
||||
test(['clang', '-c', 'source.c', '-arch', 'mips', '-arch', 'i386']))
|
||||
|
||||
def test_input_file(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('files', [])
|
||||
|
||||
eq = self.assertEqual
|
||||
|
||||
eq(['src.c'], test(['clang', 'src.c']))
|
||||
eq(['src.c'], test(['clang', '-c', 'src.c']))
|
||||
eq(['s1.c', 's2.c'], test(['clang', '-c', 's1.c', 's2.c']))
|
||||
|
||||
def test_include(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('compile_options', [])
|
||||
|
||||
eq = self.assertEqual
|
||||
|
||||
eq([], test(['clang', '-c', 'src.c']))
|
||||
eq(['-include', '/usr/local/include'],
|
||||
test(['clang', '-c', 'src.c', '-include', '/usr/local/include']))
|
||||
eq(['-I.'],
|
||||
test(['clang', '-c', 'src.c', '-I.']))
|
||||
eq(['-I', '.'],
|
||||
test(['clang', '-c', 'src.c', '-I', '.']))
|
||||
eq(['-I/usr/local/include'],
|
||||
test(['clang', '-c', 'src.c', '-I/usr/local/include']))
|
||||
eq(['-I', '/usr/local/include'],
|
||||
test(['clang', '-c', 'src.c', '-I', '/usr/local/include']))
|
||||
eq(['-I/opt', '-I', '/opt/otp/include'],
|
||||
test(['clang', '-c', 'src.c', '-I/opt', '-I', '/opt/otp/include']))
|
||||
eq(['-isystem', '/path'],
|
||||
test(['clang', '-c', 'src.c', '-isystem', '/path']))
|
||||
eq(['-isystem=/path'],
|
||||
test(['clang', '-c', 'src.c', '-isystem=/path']))
|
||||
|
||||
def test_define(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('compile_options', [])
|
||||
|
||||
eq = self.assertEqual
|
||||
|
||||
eq([], test(['clang', '-c', 'src.c']))
|
||||
eq(['-DNDEBUG'],
|
||||
test(['clang', '-c', 'src.c', '-DNDEBUG']))
|
||||
eq(['-UNDEBUG'],
|
||||
test(['clang', '-c', 'src.c', '-UNDEBUG']))
|
||||
eq(['-Dvar1=val1', '-Dvar2=val2'],
|
||||
test(['clang', '-c', 'src.c', '-Dvar1=val1', '-Dvar2=val2']))
|
||||
eq(['-Dvar="val ues"'],
|
||||
test(['clang', '-c', 'src.c', '-Dvar="val ues"']))
|
||||
|
||||
def test_ignored_flags(self):
|
||||
def test(flags):
|
||||
cmd = ['clang', 'src.o']
|
||||
opts = sut.classify_parameters(cmd + flags)
|
||||
self.assertEqual(['src.o'], opts.get('compile_options'))
|
||||
|
||||
test([])
|
||||
test(['-lrt', '-L/opt/company/lib'])
|
||||
test(['-static'])
|
||||
test(['-Wnoexcept', '-Wall'])
|
||||
test(['-mtune=i386', '-mcpu=i386'])
|
||||
|
||||
def test_compile_only_flags(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('compile_options', [])
|
||||
|
||||
eq = self.assertEqual
|
||||
|
||||
eq(['-std=C99'],
|
||||
test(['clang', '-c', 'src.c', '-std=C99']))
|
||||
eq(['-nostdinc'],
|
||||
test(['clang', '-c', 'src.c', '-nostdinc']))
|
||||
eq(['-isystem', '/image/debian'],
|
||||
test(['clang', '-c', 'src.c', '-isystem', '/image/debian']))
|
||||
eq(['-iprefix', '/usr/local'],
|
||||
test(['clang', '-c', 'src.c', '-iprefix', '/usr/local']))
|
||||
eq(['-iquote=me'],
|
||||
test(['clang', '-c', 'src.c', '-iquote=me']))
|
||||
eq(['-iquote', 'me'],
|
||||
test(['clang', '-c', 'src.c', '-iquote', 'me']))
|
||||
|
||||
def test_compile_and_link_flags(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('compile_options', [])
|
||||
|
||||
eq = self.assertEqual
|
||||
|
||||
eq(['-fsinged-char'],
|
||||
test(['clang', '-c', 'src.c', '-fsinged-char']))
|
||||
eq(['-fPIC'],
|
||||
test(['clang', '-c', 'src.c', '-fPIC']))
|
||||
eq(['-stdlib=libc++'],
|
||||
test(['clang', '-c', 'src.c', '-stdlib=libc++']))
|
||||
eq(['--sysroot', '/'],
|
||||
test(['clang', '-c', 'src.c', '--sysroot', '/']))
|
||||
eq(['-isysroot', '/'],
|
||||
test(['clang', '-c', 'src.c', '-isysroot', '/']))
|
||||
eq([],
|
||||
test(['clang', '-c', 'src.c', '-fsyntax-only']))
|
||||
eq([],
|
||||
test(['clang', '-c', 'src.c', '-sectorder', 'a', 'b', 'c']))
|
||||
|
||||
def test_detect_cxx_from_compiler_name(self):
|
||||
def test(cmd):
|
||||
opts = sut.classify_parameters(cmd)
|
||||
return opts.get('c++')
|
||||
|
||||
eq = self.assertEqual
|
||||
|
||||
eq(False, test(['cc', '-c', 'src.c']))
|
||||
eq(True, test(['c++', '-c', 'src.c']))
|
||||
eq(False, test(['clang', '-c', 'src.c']))
|
||||
eq(True, test(['clang++', '-c', 'src.c']))
|
||||
eq(False, test(['gcc', '-c', 'src.c']))
|
||||
eq(True, test(['g++', '-c', 'src.c']))
|
|
@ -0,0 +1,122 @@
|
|||
# -*- 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 libscanbuild.compilation as sut
|
||||
import unittest
|
||||
|
||||
|
||||
class CompilerTest(unittest.TestCase):
|
||||
|
||||
def test_is_compiler_call(self):
|
||||
self.assertIsNotNone(sut.compiler_language(['clang']))
|
||||
self.assertIsNotNone(sut.compiler_language(['clang-3.6']))
|
||||
self.assertIsNotNone(sut.compiler_language(['clang++']))
|
||||
self.assertIsNotNone(sut.compiler_language(['clang++-3.5.1']))
|
||||
self.assertIsNotNone(sut.compiler_language(['cc']))
|
||||
self.assertIsNotNone(sut.compiler_language(['c++']))
|
||||
self.assertIsNotNone(sut.compiler_language(['gcc']))
|
||||
self.assertIsNotNone(sut.compiler_language(['g++']))
|
||||
self.assertIsNotNone(sut.compiler_language(['/usr/local/bin/gcc']))
|
||||
self.assertIsNotNone(sut.compiler_language(['/usr/local/bin/g++']))
|
||||
self.assertIsNotNone(sut.compiler_language(['/usr/local/bin/clang']))
|
||||
self.assertIsNotNone(
|
||||
sut.compiler_language(['armv7_neno-linux-gnueabi-g++']))
|
||||
|
||||
self.assertIsNone(sut.compiler_language([]))
|
||||
self.assertIsNone(sut.compiler_language(['']))
|
||||
self.assertIsNone(sut.compiler_language(['ld']))
|
||||
self.assertIsNone(sut.compiler_language(['as']))
|
||||
self.assertIsNone(sut.compiler_language(['/usr/local/bin/compiler']))
|
||||
|
||||
|
||||
class SplitTest(unittest.TestCase):
|
||||
|
||||
def test_detect_cxx_from_compiler_name(self):
|
||||
def test(cmd):
|
||||
result = sut.split_command([cmd, '-c', 'src.c'])
|
||||
self.assertIsNotNone(result, "wrong input for test")
|
||||
return result.compiler == 'c++'
|
||||
|
||||
self.assertFalse(test('cc'))
|
||||
self.assertFalse(test('gcc'))
|
||||
self.assertFalse(test('clang'))
|
||||
|
||||
self.assertTrue(test('c++'))
|
||||
self.assertTrue(test('g++'))
|
||||
self.assertTrue(test('g++-5.3.1'))
|
||||
self.assertTrue(test('clang++'))
|
||||
self.assertTrue(test('clang++-3.7.1'))
|
||||
self.assertTrue(test('armv7_neno-linux-gnueabi-g++'))
|
||||
|
||||
def test_action(self):
|
||||
self.assertIsNotNone(sut.split_command(['clang', 'source.c']))
|
||||
self.assertIsNotNone(sut.split_command(['clang', '-c', 'source.c']))
|
||||
self.assertIsNotNone(sut.split_command(['clang', '-c', 'source.c',
|
||||
'-MF', 'a.d']))
|
||||
|
||||
self.assertIsNone(sut.split_command(['clang', '-E', 'source.c']))
|
||||
self.assertIsNone(sut.split_command(['clang', '-c', '-E', 'source.c']))
|
||||
self.assertIsNone(sut.split_command(['clang', '-c', '-M', 'source.c']))
|
||||
self.assertIsNone(
|
||||
sut.split_command(['clang', '-c', '-MM', 'source.c']))
|
||||
|
||||
def test_source_file(self):
|
||||
def test(expected, cmd):
|
||||
self.assertEqual(expected, sut.split_command(cmd).files)
|
||||
|
||||
test(['src.c'], ['clang', 'src.c'])
|
||||
test(['src.c'], ['clang', '-c', 'src.c'])
|
||||
test(['src.C'], ['clang', '-x', 'c', 'src.C'])
|
||||
test(['src.cpp'], ['clang++', '-c', 'src.cpp'])
|
||||
test(['s1.c', 's2.c'], ['clang', '-c', 's1.c', 's2.c'])
|
||||
test(['s1.c', 's2.c'], ['cc', 's1.c', 's2.c', '-ldep', '-o', 'a.out'])
|
||||
test(['src.c'], ['clang', '-c', '-I', './include', 'src.c'])
|
||||
test(['src.c'], ['clang', '-c', '-I', '/opt/me/include', 'src.c'])
|
||||
test(['src.c'], ['clang', '-c', '-D', 'config=file.c', 'src.c'])
|
||||
|
||||
self.assertIsNone(
|
||||
sut.split_command(['cc', 'this.o', 'that.o', '-o', 'a.out']))
|
||||
self.assertIsNone(
|
||||
sut.split_command(['cc', 'this.o', '-lthat', '-o', 'a.out']))
|
||||
|
||||
def test_filter_flags(self):
|
||||
def test(expected, flags):
|
||||
command = ['clang', '-c', 'src.c'] + flags
|
||||
self.assertEqual(expected, sut.split_command(command).flags)
|
||||
|
||||
def same(expected):
|
||||
test(expected, expected)
|
||||
|
||||
def filtered(flags):
|
||||
test([], flags)
|
||||
|
||||
same([])
|
||||
same(['-I', '/opt/me/include', '-DNDEBUG', '-ULIMITS'])
|
||||
same(['-O', '-O2'])
|
||||
same(['-m32', '-mmms'])
|
||||
same(['-Wall', '-Wno-unused', '-g', '-funroll-loops'])
|
||||
|
||||
filtered([])
|
||||
filtered(['-lclien', '-L/opt/me/lib', '-L', '/opt/you/lib'])
|
||||
filtered(['-static'])
|
||||
filtered(['-MD', '-MT', 'something'])
|
||||
filtered(['-MMD', '-MF', 'something'])
|
||||
|
||||
|
||||
class SourceClassifierTest(unittest.TestCase):
|
||||
|
||||
def test_sources(self):
|
||||
self.assertIsNone(sut.classify_source('file.o'))
|
||||
self.assertIsNone(sut.classify_source('file.exe'))
|
||||
self.assertIsNone(sut.classify_source('/path/file.o'))
|
||||
self.assertIsNone(sut.classify_source('clang'))
|
||||
|
||||
self.assertEqual('c', sut.classify_source('file.c'))
|
||||
self.assertEqual('c', sut.classify_source('./file.c'))
|
||||
self.assertEqual('c', sut.classify_source('/path/file.c'))
|
||||
self.assertEqual('c++', sut.classify_source('file.c', False))
|
||||
self.assertEqual('c++', sut.classify_source('./file.c', False))
|
||||
self.assertEqual('c++', sut.classify_source('/path/file.c', False))
|
|
@ -4,62 +4,37 @@
|
|||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
|
||||
import libear
|
||||
import libscanbuild.intercept as sut
|
||||
from . import fixtures
|
||||
import unittest
|
||||
import os.path
|
||||
|
||||
|
||||
class InterceptUtilTest(fixtures.TestCase):
|
||||
|
||||
def test_is_compiler_call_filter(self):
|
||||
def test(command):
|
||||
return sut.is_compiler_call({'command': [command]})
|
||||
|
||||
self.assertTrue(test('clang'))
|
||||
self.assertTrue(test('clang-3.6'))
|
||||
self.assertTrue(test('clang++'))
|
||||
self.assertTrue(test('clang++-3.5.1'))
|
||||
self.assertTrue(test('cc'))
|
||||
self.assertTrue(test('c++'))
|
||||
self.assertTrue(test('gcc'))
|
||||
self.assertTrue(test('g++'))
|
||||
self.assertTrue(test('/usr/local/bin/gcc'))
|
||||
self.assertTrue(test('/usr/local/bin/g++'))
|
||||
self.assertTrue(test('/usr/local/bin/clang'))
|
||||
self.assertTrue(test('armv7_neno-linux-gnueabi-g++'))
|
||||
|
||||
self.assertFalse(test(''))
|
||||
self.assertFalse(test('ld'))
|
||||
self.assertFalse(test('as'))
|
||||
self.assertFalse(test('/usr/local/bin/compiler'))
|
||||
class InterceptUtilTest(unittest.TestCase):
|
||||
|
||||
def test_format_entry_filters_action(self):
|
||||
def test(command):
|
||||
return list(sut.format_entry(
|
||||
{'command': command, 'directory': '/opt/src/project'}))
|
||||
trace = {'command': command, 'directory': '/opt/src/project'}
|
||||
return list(sut.format_entry(trace))
|
||||
|
||||
self.assertTrue(test(['cc', '-c', 'file.c', '-o', 'file.o']))
|
||||
self.assertFalse(test(['cc', '-E', 'file.c']))
|
||||
self.assertFalse(test(['cc', '-MM', 'file.c']))
|
||||
self.assertFalse(test(['cc', 'this.o', 'that.o', '-o', 'a.out']))
|
||||
self.assertFalse(test(['cc', '-print-prog-name']))
|
||||
|
||||
def test_format_entry_normalize_filename(self):
|
||||
directory = os.path.join(os.sep, 'home', 'me', 'project')
|
||||
parent = os.path.join(os.sep, 'home', 'me')
|
||||
current = os.path.join(parent, 'project')
|
||||
|
||||
def test(command):
|
||||
result = list(sut.format_entry(
|
||||
{'command': command, 'directory': directory}))
|
||||
return result[0]['file']
|
||||
def test(filename):
|
||||
trace = {'directory': current, 'command': ['cc', '-c', filename]}
|
||||
return list(sut.format_entry(trace))[0]['file']
|
||||
|
||||
self.assertEqual(test(['cc', '-c', 'file.c']),
|
||||
os.path.join(directory, 'file.c'))
|
||||
self.assertEqual(test(['cc', '-c', './file.c']),
|
||||
os.path.join(directory, 'file.c'))
|
||||
self.assertEqual(test(['cc', '-c', '../file.c']),
|
||||
os.path.join(os.path.dirname(directory), 'file.c'))
|
||||
self.assertEqual(test(['cc', '-c', '/opt/file.c']),
|
||||
'/opt/file.c')
|
||||
self.assertEqual(os.path.join(current, 'file.c'), test('file.c'))
|
||||
self.assertEqual(os.path.join(current, 'file.c'), test('./file.c'))
|
||||
self.assertEqual(os.path.join(parent, 'file.c'), test('../file.c'))
|
||||
self.assertEqual(os.path.join(current, 'file.c'),
|
||||
test(os.path.join(current, 'file.c')))
|
||||
|
||||
def test_sip(self):
|
||||
def create_status_report(filename, message):
|
||||
|
@ -92,7 +67,7 @@ class InterceptUtilTest(fixtures.TestCase):
|
|||
OSX = 'darwin'
|
||||
LINUX = 'linux'
|
||||
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
try:
|
||||
saved = os.environ['PATH']
|
||||
os.environ['PATH'] = tmpdir + ':' + saved
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# -*- 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 as sut
|
||||
import unittest
|
||||
import os.path
|
||||
|
||||
|
||||
class TemporaryDirectoryTest(unittest.TestCase):
|
||||
def test_creates_directory(self):
|
||||
dirname = None
|
||||
with sut.TemporaryDirectory() as tmpdir:
|
||||
self.assertTrue(os.path.isdir(tmpdir))
|
||||
dirname = tmpdir
|
||||
self.assertIsNotNone(dirname)
|
||||
self.assertFalse(os.path.exists(dirname))
|
||||
|
||||
def test_removes_directory_when_exception(self):
|
||||
dirname = None
|
||||
try:
|
||||
with sut.TemporaryDirectory() as tmpdir:
|
||||
self.assertTrue(os.path.isdir(tmpdir))
|
||||
dirname = tmpdir
|
||||
raise RuntimeError('message')
|
||||
except:
|
||||
self.assertIsNotNone(dirname)
|
||||
self.assertFalse(os.path.exists(dirname))
|
|
@ -4,15 +4,15 @@
|
|||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
|
||||
import libear
|
||||
import libscanbuild.report as sut
|
||||
from . import fixtures
|
||||
import unittest
|
||||
import os
|
||||
import os.path
|
||||
|
||||
|
||||
def run_bug_parse(content):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
file_name = os.path.join(tmpdir, 'test.html')
|
||||
with open(file_name, 'w') as handle:
|
||||
handle.writelines(content)
|
||||
|
@ -21,7 +21,7 @@ def run_bug_parse(content):
|
|||
|
||||
|
||||
def run_crash_parse(content, preproc):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
file_name = os.path.join(tmpdir, preproc + '.info.txt')
|
||||
with open(file_name, 'w') as handle:
|
||||
handle.writelines(content)
|
||||
|
@ -77,20 +77,22 @@ class ParseFileTest(unittest.TestCase):
|
|||
def test_parse_real_crash(self):
|
||||
import libscanbuild.runner as sut2
|
||||
import re
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
filename = os.path.join(tmpdir, 'test.c')
|
||||
with open(filename, 'w') as handle:
|
||||
handle.write('int main() { return 0')
|
||||
# produce failure report
|
||||
opts = {'directory': os.getcwd(),
|
||||
opts = {
|
||||
'clang': 'clang',
|
||||
'directory': os.getcwd(),
|
||||
'flags': [],
|
||||
'file': filename,
|
||||
'report': ['-fsyntax-only', '-E', filename],
|
||||
'language': 'c',
|
||||
'output_dir': tmpdir,
|
||||
'language': 'c',
|
||||
'error_type': 'other_error',
|
||||
'error_output': 'some output',
|
||||
'exit_code': 13}
|
||||
'exit_code': 13
|
||||
}
|
||||
sut2.report_failure(opts)
|
||||
# find the info file
|
||||
pp_file = None
|
||||
|
@ -123,7 +125,7 @@ class ReportMethodTest(unittest.TestCase):
|
|||
'/prefix/src/file'))
|
||||
|
||||
|
||||
class GetPrefixFromCompilationDatabaseTest(fixtures.TestCase):
|
||||
class GetPrefixFromCompilationDatabaseTest(unittest.TestCase):
|
||||
|
||||
def test_with_different_filenames(self):
|
||||
self.assertEqual(
|
||||
|
|
|
@ -4,96 +4,164 @@
|
|||
# This file is distributed under the University of Illinois Open Source
|
||||
# License. See LICENSE.TXT for details.
|
||||
|
||||
import libear
|
||||
import libscanbuild.runner as sut
|
||||
from . import fixtures
|
||||
import unittest
|
||||
import re
|
||||
import os
|
||||
import os.path
|
||||
|
||||
|
||||
def run_analyzer(content, opts):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
filename = os.path.join(tmpdir, 'test.cpp')
|
||||
with open(filename, 'w') as handle:
|
||||
handle.write(content)
|
||||
class FilteringFlagsTest(unittest.TestCase):
|
||||
|
||||
opts.update({
|
||||
'directory': os.getcwd(),
|
||||
'clang': 'clang',
|
||||
'file': filename,
|
||||
'language': 'c++',
|
||||
'analyze': ['--analyze', '-x', 'c++', filename],
|
||||
'output': ['-o', tmpdir]})
|
||||
spy = fixtures.Spy()
|
||||
result = sut.run_analyzer(opts, spy.call)
|
||||
return (result, spy.arg)
|
||||
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) = run_analyzer(content, dict())
|
||||
(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) = run_analyzer(content, dict())
|
||||
(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) = run_analyzer(content, {'output_failures': True})
|
||||
(_, fwds) = RunAnalyzerTest.run_analyzer(content, True)
|
||||
self.assertEqual('crash', fwds['error_type'])
|
||||
self.assertEqual(1, fwds['exit_code'])
|
||||
self.assertTrue(len(fwds['error_output']) > 0)
|
||||
|
||||
|
||||
class SetAnalyzerOutputTest(fixtures.TestCase):
|
||||
|
||||
def test_not_defined(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
opts = {'output_dir': tmpdir}
|
||||
spy = fixtures.Spy()
|
||||
sut.set_analyzer_output(opts, spy.call)
|
||||
self.assertTrue(os.path.exists(spy.arg['output'][1]))
|
||||
self.assertTrue(os.path.isdir(spy.arg['output'][1]))
|
||||
|
||||
def test_html(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
opts = {'output_dir': tmpdir, 'output_format': 'html'}
|
||||
spy = fixtures.Spy()
|
||||
sut.set_analyzer_output(opts, spy.call)
|
||||
self.assertTrue(os.path.exists(spy.arg['output'][1]))
|
||||
self.assertTrue(os.path.isdir(spy.arg['output'][1]))
|
||||
|
||||
def test_plist_html(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
opts = {'output_dir': tmpdir, 'output_format': 'plist-html'}
|
||||
spy = fixtures.Spy()
|
||||
sut.set_analyzer_output(opts, spy.call)
|
||||
self.assertTrue(os.path.exists(spy.arg['output'][1]))
|
||||
self.assertTrue(os.path.isfile(spy.arg['output'][1]))
|
||||
|
||||
def test_plist(self):
|
||||
with fixtures.TempDir() as tmpdir:
|
||||
opts = {'output_dir': tmpdir, 'output_format': 'plist'}
|
||||
spy = fixtures.Spy()
|
||||
sut.set_analyzer_output(opts, spy.call)
|
||||
self.assertTrue(os.path.exists(spy.arg['output'][1]))
|
||||
self.assertTrue(os.path.isfile(spy.arg['output'][1]))
|
||||
|
||||
|
||||
class ReportFailureTest(fixtures.TestCase):
|
||||
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 fixtures.TempDir() as tmpdir:
|
||||
with libear.TemporaryDirectory() as tmpdir:
|
||||
# create input file
|
||||
filename = os.path.join(tmpdir, 'test.c')
|
||||
with open(filename, 'w') as handle:
|
||||
|
@ -101,15 +169,17 @@ class ReportFailureTest(fixtures.TestCase):
|
|||
uname_msg = ' '.join(os.uname()) + os.linesep
|
||||
error_msg = 'this is my error output'
|
||||
# execute test
|
||||
opts = {'directory': os.getcwd(),
|
||||
opts = {
|
||||
'clang': 'clang',
|
||||
'directory': os.getcwd(),
|
||||
'flags': [],
|
||||
'file': filename,
|
||||
'report': ['-fsyntax-only', '-E', filename],
|
||||
'language': 'c',
|
||||
'output_dir': tmpdir,
|
||||
'language': 'c',
|
||||
'error_type': 'other_error',
|
||||
'error_output': error_msg,
|
||||
'exit_code': 13}
|
||||
'exit_code': 13
|
||||
}
|
||||
sut.report_failure(opts)
|
||||
# verify the result
|
||||
result = dict()
|
||||
|
@ -126,57 +196,110 @@ class ReportFailureTest(fixtures.TestCase):
|
|||
self.assertUnderFailures(pp_file)
|
||||
# info file generated and content dumped
|
||||
info_file = pp_file + '.info.txt'
|
||||
self.assertIn(info_file, result)
|
||||
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.assertIn(error_file, result)
|
||||
self.assertTrue(error_file in result)
|
||||
self.assertEqual([error_msg], result[error_file])
|
||||
|
||||
|
||||
class AnalyzerTest(unittest.TestCase):
|
||||
|
||||
def test_set_language(self):
|
||||
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_file_relative_path(self):
|
||||
def test(expected, input):
|
||||
spy = fixtures.Spy()
|
||||
spy = Spy()
|
||||
self.assertEqual(spy.success,
|
||||
sut.set_file_path_relative(input, spy.call))
|
||||
self.assertEqual(expected, spy.arg['file'])
|
||||
|
||||
test('source.c',
|
||||
{'file': '/home/me/source.c', 'directory': '/home/me'})
|
||||
test('me/source.c',
|
||||
{'file': '/home/me/source.c', 'directory': '/home'})
|
||||
test('../home/me/source.c',
|
||||
{'file': '/home/me/source.c', 'directory': '/tmp'})
|
||||
|
||||
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'])
|
||||
|
||||
l = 'language'
|
||||
f = 'file'
|
||||
i = 'c++'
|
||||
test('c', {f: 'file.c', l: 'c', i: False})
|
||||
test('c++', {f: 'file.c', l: 'c++', i: False})
|
||||
test('c++', {f: 'file.c', i: True})
|
||||
test('c', {f: 'file.c', i: False})
|
||||
test('c++', {f: 'file.cxx', i: False})
|
||||
test('c-cpp-output', {f: 'file.i', i: False})
|
||||
test('c++-cpp-output', {f: 'file.i', i: True})
|
||||
test('c-cpp-output', {f: 'f.i', l: 'c-cpp-output', i: True})
|
||||
language('c', {'language': 'c', 'flags': []})
|
||||
language('c++', {'language': 'c++', 'flags': []})
|
||||
|
||||
def test_arch_loop(self):
|
||||
def test(input):
|
||||
spy = fixtures.Spy()
|
||||
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
|
||||
return spy.arg['flags']
|
||||
|
||||
input = {'key': 'value'}
|
||||
self.assertEqual(input, test(input))
|
||||
self.assertEqual([], flags([]))
|
||||
self.assertEqual(['-arch', 'i386'], flags(['i386']))
|
||||
self.assertEqual(['-arch', 'i386'], flags(['i386', 'ppc']))
|
||||
self.assertEqual(['-arch', 'sparc'], flags(['i386', 'sparc']))
|
||||
|
||||
input = {'archs_seen': ['i386']}
|
||||
self.assertEqual({'arch': 'i386'}, test(input))
|
||||
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)
|
||||
|
||||
input = {'archs_seen': ['ppc']}
|
||||
self.assertEqual(None, test(input))
|
||||
|
||||
input = {'archs_seen': ['i386', 'ppc']}
|
||||
self.assertEqual({'arch': 'i386'}, test(input))
|
||||
|
||||
input = {'archs_seen': ['i386', 'sparc']}
|
||||
result = test(input)
|
||||
self.assertTrue(result == {'arch': 'i386'} or
|
||||
result == {'arch': 'sparc'})
|
||||
stop(['ppc'])
|
||||
stop(['ppc64'])
|
||||
|
||||
|
||||
@sut.require([])
|
||||
|
@ -211,14 +334,3 @@ class RequireDecoratorTest(unittest.TestCase):
|
|||
|
||||
def test_method_exception_not_caught(self):
|
||||
self.assertRaises(Exception, method_exception_from_inside, dict())
|
||||
|
||||
class ForceAnalyzeDebugTest(unittest.TestCase):
|
||||
|
||||
def test_force_analyze_debug_code(self):
|
||||
for a, b in [
|
||||
([], ['-UNDEBUG']),
|
||||
(['-O2'], ['-O2', '-UNDEBUG']),
|
||||
(['-Dkey=val'], ['-Dkey=val', '-UNDEBUG']),
|
||||
(['-D', 'NDEBUG'], ['-D', 'NDEBUG', '-UNDEBUG']) ]:
|
||||
sut.force_analyze_debug_code(a)
|
||||
self.assertEqual(a, b)
|
||||
|
|
Loading…
Reference in New Issue