def parse_plist(pathOrFile, analyzerversion=None, sut=None, file_=None, stats=None): """ Given a .plist file emitted by clang-static-analyzer (e.g. via scan-build), parse it and return an Analysis instance """ plist = plistlib.readPlist(pathOrFile) # We now have the .plist file as a hierarchy of dicts, lists, etc # Handy debug dump: if 0: pprint(plist) # A list of filenames, apparently referenced by index within # diagnostics: files = plist['files'] generator = Generator(name='clang-analyzer', version=analyzerversion) metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) for diagnostic in plist['diagnostics']: if 0: pprint(diagnostic) cwe = None # TODO: we're not yet handling the following: # diagnostic['category'] # diagnostic['type'] message = Message(text=diagnostic['description']) loc = diagnostic['location'] location = Location( file=File(givenpath=files[loc.file], abspath=None), # FIXME: doesn't tell us function name # TODO: can we patch this upstream? function=None, point=Point(int(loc.line), int(loc.col))) notes = None trace = make_trace(files, diagnostic['path']) issue = Issue( cwe, None, # FIXME: can we get at the test id? location, message, notes, trace) analysis.results.append(issue) return analysis
def parse_file(data_file_name, findbugs_version=None, sut=None, file_=None, stats=None): """ :param data_file: str object containing findbugs scan result :type data_file: str :param findbugs_version: version of findbugs :type findbugs_version: str :return: Analysis instance """ data_file = open(data_file_name) generator = Generator(name="findbugs", version=findbugs_version) metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) for line in data_file.readlines(): issue = parse_line(line) if issue: analysis.results.append(issue) else: sys.stderr.write("fail to pass line=[%s]" % line) data_file.close() return analysis
def parse_file(fileobj, sut=None, file_=None, stats=None): tree = ET.parse(fileobj) root = tree.getroot() node_cppcheck = root.find('cppcheck') version = node_cppcheck.get('version') node_errors = root.find('errors') generator = Generator(name='cppcheck', version=node_cppcheck.get('version')) metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) for node_error in node_errors.findall('error'): # e.g.: # <error id="nullPointer" severity="error" msg="Possible null pointer dereference: end - otherwise it is redundant to check it against null." verbose="Possible null pointer dereference: end - otherwise it is redundant to check it against null."> # <location file="python-ethtool/ethtool.c" line="139"/> # <location file="python-ethtool/ethtool.c" line="141"/> # </error> testid = node_error.get('id') str_msg = node_error.get('msg') str_verbose = node_error.get('verbose') message = Message(text=str_msg) if str_verbose != str_msg: notes = Notes(str_verbose) else: notes = None location_nodes = list(node_error.findall('location')) for node_location in location_nodes: location = Location( file=File(node_location.get('file'), None), # FIXME: doesn't tell us function name # TODO: can we patch this upstream? function=None, # doesn't emit column point=Point(int(node_location.get('line')), 0)) # FIXME: bogus column issue = Issue(None, testid, location, message, notes, None, severity=node_error.get('severity')) analysis.results.append(issue) if not location_nodes: customfields = CustomFields() if str_verbose != str_msg: customfields['verbose'] = str_verbose failure = Failure(failureid=testid, location=None, message=message, customfields=customfields) analysis.results.append(failure) return analysis
class Pep8TestCase(unittest.TestCase): filepath = "tests/resources/python-firehose_0.3-1.dsc" firehose_results = Analysis( metadata=Metadata( generator=Generator( name='pep8' ), sut=None, file_=None, stats=None), results=[] ) @mock.patch('debile.slave.runners.pep8.run_command', return_value=('1.5.7', '', 0)) def test_pep8_version(self, mock): name, ver = version() self.assertEquals(name, 'pep8') self.assertEquals(ver, '1.5.7') @mock.patch('debile.slave.runners.pep8.run_command', return_value=('1.5.7', '', 1)) def test_pep8_version(self, mock): self.assertRaises(Exception, version) def test_pep8(self): pep8_analysis = pep8(self.filepath, self.firehose_results) content = pep8_analysis[1] self.assertTrue("missing whitespace around operator" in content) # It think it is safe to say that the string is not 4 chars long self.assertTrue(len(content) > 4) def test_pep8_wrappers(self): pep8_analysis = pep8(self.filepath, self.firehose_results) issues = parse_pep8(pep8_analysis[1].splitlines()) i = 0 for issue in issues: if issue.location.file.givenpath == "./firehose/model.py" and \ issue.location.point.line==96 and issue.location.point.column==1: found = issue i += 1 print found self.assertEquals(found.testid, "E302") self.assertEquals(found.location.file.givenpath, "./firehose/model.py") self.assertEquals(found.location.point.line, 96) self.assertEquals(found.location.point.column, 1) self.assertEquals(found.severity, "error") self.assertIsNone(found.notes) self.assertIsNone(found.customfields) self.assertTrue("E302 expected 2 blank lines, found 1" in found.message.text) self.assertTrue(i > 100)
def test_cppcheck_common(): firehorse_results = Analysis( metadata=Metadata(generator=Generator(name='cppcheck'), sut=None, file_=None, stats=None), results=[]) return cppcheck("tests/resources/libjsoncpp_0.6.0~rc2-3.1.dsc", firehorse_results)
class PylintTestCase(unittest.TestCase): filepath = "tests/resources/python-firehose_0.3-1.dsc" firehose_results = Analysis(metadata=Metadata( generator=Generator(name='pylint'), sut=None, file_=None, stats=None), results=[]) @mock.patch( 'debile.slave.runners.pylint.run_command', return_value=( 'pylint 1.4.3,\nastroid 1.3.6, common 0.62.0\nPython 2.7.10', '', 0)) def test_pylint_version(self, mock): name, ver = version() self.assertEquals(name, 'pylint') self.assertEquals(ver, '1.4.3') @mock.patch('debile.slave.runners.pylint.run_command', return_value=('', '', 1)) def test_pylint_version_with_error(self, mock): self.assertRaises(Exception, version) def test_pylint(self): pylint_analysis = pylint(self.filepath, self.firehose_results) content = pylint_analysis[1] self.assertTrue("Missing method docstring" in content) # It think it is safe to say that the string is not 4 chars long self.assertTrue(len(content) > 4) def test_pylint_wrappers(self): pylint_analysis = pylint(self.filepath, self.firehose_results) issues = parse_pylint(pylint_analysis[1].splitlines()) i = 0 for issue in issues: if issue.location.file.givenpath == \ "tests/parsers/test_clanganalyzer_parser.py" and \ issue.location.point.line==22 and issue.location.point.column==0: found = issue i += 1 print found self.assertEquals(found.testid, "W0611") self.assertEquals(found.location.file.givenpath, "tests/parsers/test_clanganalyzer_parser.py") self.assertEquals(found.location.point.line, 22) self.assertEquals(found.location.point.column, 0) self.assertEquals(found.severity, "warning") self.assertIsNone(found.notes) self.assertIsNone(found.customfields) self.assertTrue( "[unused-import]Unused Analysis imported from firehose.model" in found.message.text) self.assertTrue(i > 500)
def parse_json_v2(path): """ Given a JSON file emitted by: cov-format-errors --json-output-v2=<filename> parse it and return an Analysis instance """ with open(path) as f: js = json.load(f) if 0: pprint(js) generator = Generator(name='coverity') metadata = Metadata(generator, sut=None, file_=None, stats=None) analysis = Analysis(metadata, []) for issue in js['issues']: if 0: pprint(issue) cwe = None # Use checkerName (e.g. "RESOURCE_LEAK") for # the testid: testid = issue['checkerName'] # Use the eventDescription of the final event for the message: message = Message(text=issue['events'][-1]['eventDescription']) location = Location( file=File(givenpath=issue['mainEventFilePathname'], abspath=None), function=Function(name=issue['functionDisplayName']), point=Point(int(issue['mainEventLineNumber']), int(0))) notes = None trace = make_trace(issue) customfields = CustomFields() for key in ['mergeKey', 'subcategory', 'domain']: if key in issue: customfields[key] = issue[key] issue = Issue(cwe, testid, location, message, notes, trace, customfields=customfields) analysis.results.append(issue) return analysis
class Flake8TestCase(unittest.TestCase): filepath = "tests/resources/python-firehose_0.3-1.dsc" firehose_results = Analysis(metadata=Metadata( generator=Generator(name='flake8'), sut=None, file_=None, stats=None), results=[]) @mock.patch( 'debile.slave.runners.flake8.run_command', return_value= ('2.2.2 (pep8: 1.5.7, mccabe: 0.2.1, pyflakes: 0.8.1) CPython 2.7.10 on Linux', '', 0)) def test_flake8_version(self, mock): name, ver = version() self.assertEquals(name, 'flake8') self.assertEquals(ver, '2.2.2') @mock.patch('debile.slave.runners.flake8.run_command', return_value=('', '', 1)) def test_flake8_version_with_error(self, mock): self.assertRaises(Exception, version) def test_flake8(self): flake8_analysis = flake8(self.filepath, self.firehose_results) content = flake8_analysis[1] self.assertTrue("missing whitespace around operator" in content) # It think it is safe to say that the string is not 4 chars long self.assertTrue(len(content) > 4) def test_flake8_wrappers(self): flake8_analysis = flake8(self.filepath, self.firehose_results) issues = parse_flake8(flake8_analysis[1].splitlines()) i = 0 for issue in issues: if issue.location.file.givenpath == "./firehose/parsers/cppcheck.py" and \ issue.location.point.line==37 and issue.location.point.column==5: found = issue i += 1 print found self.assertEquals(found.testid, "F841") self.assertEquals(found.location.file.givenpath, "./firehose/parsers/cppcheck.py") self.assertEquals(found.location.point.line, 37) self.assertEquals(found.location.point.column, 5) self.assertEquals(found.severity, "error") self.assertIsNone(found.notes) self.assertIsNone(found.customfields) self.assertTrue( "F841 local variable 'version' is assigned to but never used" in found.message.text) self.assertTrue(i > 100)
def create_firehose(package, version_getter): logging.info("Initializing empty firehose report") sut = { "sources": generate_sut_from_source, "binaries": generate_sut_from_binary }[package['_type']](package) gname_, gversion = version_getter() gname = "ethel/%s" % gname_ return Analysis(metadata=Metadata( generator=Generator(name=gname, version=gversion), sut=sut, file_=None, stats=None), results=[])
def make_info(self): a = Analysis( metadata=Metadata(generator=Generator(name='an-invented-checker'), sut=None, file_=None, stats=None), results=[ Info(infoid='gimple-stats', location=Location(file=File('bar.c', None), function=Function('sample_function'), point=Point(10, 15)), message=Message('sample message'), customfields=CustomFields(num_stmts=57, num_basic_blocks=10)) ]) return a, a.results[0]
def parse_file(data_file_obj, findbugs_version=None, sut=None, file_=None, stats=None): """ :param data_file_obj: file object containing findbugs scan result in xml format, it can be generated using command: fb analyze -xml:withMessages [jar_file] :type data_file_obj: file object :param findbugs_version: version of findbugs :type findbugs_version: str :return: Analysis instance """ generator = Generator(name = "findbugs", version = findbugs_version) metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) def parse_BugInstance(bugInstance): message = Message(bugInstance.find("LongMessage").text) # findbugs has no column information sourceLine = bugInstance.find("SourceLine") point = Point(int(sourceLine.get("start")), 0) path = sourceLine.get("sourcepath") path = File(path, None) method = bugInstance.find("Method") if method: function = method.find("Message").text tmpIndex = function.rfind("In method ") + len("In method ") - 1 function = Function(function[tmpIndex+1:]) else: function = None location = Location(path, function, point) if DEBUG: print(str(location)+" "+str(message)) return Issue(None, None, location, message, None, None) tree = ET.parse(data_file_obj) root = tree.getroot() for bugInstance in root.findall("BugInstance"): issue=parse_BugInstance(bugInstance) if issue: analysis.results.append(issue) else: sys.stderr.write("fail to pass bugInstance=[%s]\n" % str(bugInstance)) return analysis
class LintianTestCase(unittest.TestCase): filepath = "tests/resources/libjsoncpp0_0.6.0~rc2-3.1_amd64.deb" firehose_results = Analysis(metadata=Metadata( generator=Generator(name='lintian'), sut=None, file_=None, stats=None), results=[]) @mock.patch('debile.slave.runners.lintian.run_command', return_value=('Lintian v2.5.31', '', 0)) def test_version(self, mock): name, ver = version() self.assertEquals(name, 'Lintian') self.assertEquals(ver, 'v2.5.31') @mock.patch('debile.slave.runners.lintian.run_command', return_value=('Lintian v2.5.31', '', 1)) def test_version_without_lintian(self, mock): self.assertRaises(Exception, version) def test_lintian(self): lintian_analysis = lintian(self.filepath, self.firehose_results) content = lintian_analysis[1] self.assertTrue("no-symbols-control-file" in content) # It think it is safe to say that the string is not 4 chars long self.assertTrue(len(content) > 4) def test_lintian_wrappers(self): lintian_analysis = lintian(self.filepath, self.firehose_results) issues = parse_lintian(lintian_analysis[1].splitlines(), self.filepath) i = 0 for issue in issues: if issue.testid == "no-symbols-control-file": found = issue i += 1 self.assertEquals(found.testid, "no-symbols-control-file") self.assertEquals( found.location.file.givenpath, "tests/resources/libjsoncpp0_0.6.0~rc2-3.1_amd64.deb") self.assertIsNone(found.location.point) self.assertEquals(found.severity, "info") self.assertIsNone(found.notes) self.assertIsNone(found.customfields) self.assertTrue( "libjsoncpp0: no-symbols-control-file usr/lib/libjsoncpp.so.0.6.0" in found.message.text) self.assertTrue(i > 1)
def make_complex_analysis(self): """ Construct a Analysis instance that uses all features """ a = Analysis( metadata=Metadata(generator=Generator(name='cpychecker', version='0.11'), sut=SourceRpm(name='python-ethtool', version='0.7', release='4.fc19', buildarch='x86_64'), file_=File(givenpath='foo.c', abspath='/home/david/coding/foo.c'), stats=Stats(wallclocktime=0.4)), results=[ Issue(cwe=681, testid='refcount-too-high', location=Location(file=File( givenpath='foo.c', abspath='/home/david/coding/foo.c'), function=Function('bar'), point=Point(10, 15)), message=Message(text='something bad involving pointers'), notes=Notes('here is some explanatory text'), trace=Trace([ State(location=Location(file=File('foo.c', None), function=Function('bar'), point=Point(7, 12)), notes=Notes('first we do this')), State(location=Location(file=File('foo.c', None), function=Function('bar'), point=Point(8, 10)), notes=Notes('then we do that')), State(location=Location(file=File('foo.c', None), function=Function('bar'), range_=Range( Point(10, 15), Point(10, 25))), notes=Notes('then it crashes here')) ]), severity='really bad', customfields=CustomFields(foo='bar')), ], customfields=CustomFields( gccinvocation='gcc -I/usr/include/python2.7 -c foo.c'), ) return a, a.results[0]
def parse_splint_csv(path): """ Parse a .csv file written by splint's "-csv FILENAME" option. Generate a list of Result instances. """ generator = Generator(name='splint') metadata = Metadata(generator, None, None, None) analysis = Analysis(metadata, []) with open(path, 'r') as f: reader = csv.reader(f) for raw_row in reader: # Skip the initial title row if raw_row[0] == 'Warning': continue rowobj = parse_row(raw_row) analysis.results.append(rowobj.to_issue()) return analysis
def make_failed_analysis(self): a = Analysis( metadata=Metadata(generator=Generator(name='yet-another-checker'), sut=None, file_=None, stats=None), results=[ Failure(failureid='out-of-memory', location=Location( file=File('foo.c', None), function=Function('something_complicated'), point=Point(10, 15)), message=Message('out of memory'), customfields=CustomFields(stdout='sample stdout', stderr='sample stderr', returncode=-9)) # (killed) ]) return a, a.results[0]
class FindbugsTestCase(unittest.TestCase): filepath = 'tests/resources/libjdom1-java_1.1.3-1_all.deb' firehose_results = Analysis( metadata=Metadata( generator=Generator( name='findbugs' ), sut=None, file_=None, stats=None), results=[] ) @mock.patch('debile.slave.runners.findbugs.run_command', return_value=('2.0.3', '', 0)) def test_version(self, mock): name, ver = version() self.assertEquals(name, 'findbugs') self.assertEquals(ver, '2.0.3') @mock.patch('debile.slave.runners.findbugs.run_command', return_value=('2.0.3', '', 1)) def test_version_without_findbugs(self, mock): self.assertRaises(Exception, version) def test_findbugs(self): findbugs_analysis = findbugs(self.filepath, self.firehose_results) content = findbugs_analysis[1] self.assertTrue("The following classes needed for analysis" in content) # It think it is safe to say that the string is not 4 chars long self.assertTrue(len(content) > 4) @mock.patch('debile.slave.runners.findbugs.run_command', return_value=(0, 'error', -1)) def test_findbugs_with_exception(self, mock): self.assertRaises(Exception, findbugs, self.filepath, self.firehose_results)
def make_simple_analysis(self): """ Construct a minimal Analysis instance """ a = Analysis( metadata=Metadata(generator=Generator(name='cpychecker'), sut=None, file_=None, stats=None), results=[ Issue(cwe=None, testid=None, location=Location(file=File('foo.c', None), function=None, point=Point(10, 15)), message=Message(text='something bad involving pointers'), notes=None, trace=None) ]) return a, a.results[0]
def parse_file(data_file, sut=None, file_=None, stats=None): """ for each line, the regex for SPARECODE_WARNING is matched :param data_file: file object containing build log :type data_file: file :return: Analysis instance """ generator = Generator(name='frama-c') metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) for line in data_file.readlines(): match_warning = FRAMA_C_SPARECODE_PATTERN.match(line) if match_warning: issue = parse_warning(match_warning) analysis.results.append(issue) return analysis
def parse_file(data_file, gccversion=None, sut=None, file_=None, stats=None): """ looks for groups of lines that start with a line identifying a function name, followed by one or more lines with a warning or note :param data_file: file object containing build log :type data_file: file :param gccversion: version of GCC that generated this report :type gccversion: str :return: Analysis instance """ # has a value only when in a block of lines where the first line identifies # a function and is followed by 0 or more warning lines generator = Generator(name='gcc', version=gccversion) metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) current_func_name = None for line in data_file.readlines(): match_func = FUNCTION_PATTERN.match(line) match_global = GLOBAL_PATTERN.match(line) # if we found a line that describes a function name if match_func: current_func_name = match_func.group('func') elif match_global: current_func_name = GLOBAL_FUNC_NAME # if we think the next line might describe a warning elif current_func_name is not None: issue = parse_warning(line, current_func_name) if issue: analysis.results.append(issue) else: # reset this when we run out of warnings associated with it current_func_name = None return analysis
def test_lintian_common(): firehorse_results = Analysis(metadata=Metadata( generator=Generator(name='lintian'), sut=None, file_=None, stats=None), results=[]) return lintian(filepath, firehorse_results)
def parse_file(infile): """ Parser flawfinder output :infile: file-like object :returns: Firehose Analysis object, representing the final XML. Flawfinder can generate multiple cwes for a single issue. Firehose's models does not supports multiple CWEs. For now, when multiple CWEs ocurrs, we get only the first one. A issue was created to track this bug: https://github.com/fedora-static-analysis/firehose/issues/35 """ line = infile.readline() generator = Generator(name='flawfinder', version=get_flawfinder_version(line)) metadata = Metadata(generator, None, None, None) analysis = Analysis(metadata, []) # A regex for "filename:linenum:" ISSUE_LINE_PATTERN = r"(\S.*)\:([0-9]+)\:" # A regex for the reported severity, e.g. "[2]" ISSUE_SEVERITY_PATTERN = r"\[([0-9]+)\]" # A regex for the reported testid, e.g. "(buffer)" ISSUE_TESTID_PATTERN = r"\(([a-z]+)\)" WHITESPACE = "\s+" FIRST_LINE_PATTERN = (ISSUE_LINE_PATTERN + WHITESPACE + ISSUE_SEVERITY_PATTERN + WHITESPACE + ISSUE_TESTID_PATTERN) prog = re.compile(FIRST_LINE_PATTERN) while line: m = prog.match(line) if m: issue_path = m.group(1) issue_line = m.group(2) issue_severity = m.group(3) testid = m.group(4) location = Location(file=File(issue_path, None), function=None, point=Point(int(issue_line), 0)) message_line = infile.readline() issue_message = "" while not prog.search(message_line) and message_line != "\n": # Build up issue_message as one line, stripping out # extraneous whitespace. if issue_message: issue_message += " " + message_line.strip() else: issue_message = message_line.strip() message_line = infile.readline() line = message_line cwes = [int(cwe) for cwe in re.findall("CWE-([0-9]+)", issue_message)] if cwes: first_cwe = int(cwes[0]) else: first_cwe = None issue = Issue(first_cwe, testid, location, Message(text=issue_message), notes=None, trace=None, severity=issue_severity, customfields=None) analysis.results.append(issue) else: line = infile.readline() return analysis
def test_roodi_common(): firehorse_results = Analysis(metadata=Metadata( generator=Generator(name='roodi'), sut=None, file_=None, stats=None), results=[]) return roodi("tests/resources/bundler_1.7.4-1.dsc", firehorse_results)
class JSHintTestCase(unittest.TestCase): filepath = "tests/resources/libjs-term.js_0.0.4-1.dsc" firehose_results = Analysis( metadata=Metadata( generator=Generator( name='jshint' ), sut=None, file_=None, stats=None), results=[] ) @mock.patch('debile.slave.runners.jshint.run_command', return_value=('output', 'jshint v2.8.0', 0)) def test_jshint_version(self, mock): name, ver = version() self.assertEquals(name, 'jshint') self.assertEquals(ver, 'v2.8.0') @mock.patch('debile.slave.runners.jshint.run_command', return_value=('output', 'jshint v2.8.0', 1)) def test_version_raise_exception(self, mock): self.assertRaises(Exception, version) def test_jshint(self): jshint_analysis = jshint(self.filepath, self.firehose_results) content = jshint_analysis[1] self.assertTrue("Bad line breaking" in content) # It think it is safe to say that the string is not 4 chars long self.assertTrue(len(content) > 4) @mock.patch('debile.slave.runners.jshint.run_command', return_value=(None, 'jshint v2.8.0', 1)) def test_jshint_with_none_output(self, mock): jshint_analysis = jshint(self.filepath, self.firehose_results) self.assertEquals(jshint_analysis[0], self.firehose_results) self.assertIsNone(jshint_analysis[1]) self.assertTrue(jshint_analysis[2]) self.assertIsNone(jshint_analysis[3]) self.assertIsNone(jshint_analysis[4]) def test_jshint_wrappers(self): jshint_analysis = jshint(self.filepath, self.firehose_results) issues = parse_jshint(jshint_analysis[1].splitlines()) i = 0 found = None for issue in issues: if issue.location.file.givenpath == "test/index.js" and \ issue.location.point.line==13 and issue.location.point.column==19: found = issue i += 1 print found self.assertEquals(found.testid, "W014") self.assertEquals(found.location.file.givenpath, "test/index.js") self.assertEquals(found.location.point.line, 13) self.assertEquals(found.location.point.column, 19) self.assertEquals(found.severity, "warning") self.assertIsNone(found.notes) self.assertIsNone(found.customfields) self.assertTrue("Bad line breaking" in found.message.text) self.assertTrue(i > 75)
class CppcheckTestCase(unittest.TestCase): file_path="tests/resources/libjsoncpp_0.6.0~rc2-3.1.dsc" firehose_results = Analysis( metadata=Metadata( generator=Generator( name='cppcheck' ), sut=None, file_=None, stats=None), results=[] ) @mock.patch('debile.slave.runners.cppcheck.run_command', return_value=('Cppcheck 1.69', '', 0)) def test_cppcheck_version(self, mock): name, ver = version() self.assertEquals(name, 'Cppcheck') self.assertEquals(ver, '1.69') @mock.patch('debile.slave.runners.cppcheck.run_command', return_value=('Cppcheck 1.69', '', 1)) def test_cppcheck_version_with_error(self, mock): self.assertRaises(Exception, version) def test_cppcheck(self): cpp_analysis = cppcheck(self.file_path, self.firehose_results) xml_content = cpp_analysis[1] tree = lxml.etree.fromstring(xml_content.encode('utf-16')) i = 0 paths = [] lines = [] severity = [] messages = [] testid = [] for result in tree.xpath("//results/error"): paths.append(result.attrib['file']) lines.append(result.attrib['line']) severity.append(result.attrib['severity']) messages.append(result.attrib['msg']) testid.append(result.attrib['id']) i += 1 # It think it is safe to say that this number won't be less than 4 self.assertTrue(i > 4) # Check that some values exist (the order might change) self.assertTrue("src/lib_json/json_value.cpp" in paths) self.assertTrue("style" in severity) self.assertTrue("704" in lines) self.assertTrue("toomanyconfigs" in testid) @mock.patch('debile.slave.runners.cppcheck.run_command', return_value=('', ' ', 0)) def test_cppcheck_with_withespace_in_stderr(self, mock): cpp_analysis = cppcheck(self.file_path, self.firehose_results) self.assertEquals(cpp_analysis[0], self.firehose_results) self.assertEquals(cpp_analysis[1], ' ') self.assertFalse(cpp_analysis[2]) self.assertIsNone(cpp_analysis[3]) self.assertIsNone(cpp_analysis[4]) def test_cppcheck_wrappers(self): cpp_analysis = cppcheck(self.file_path, self.firehose_results) issues = parse_cppcheck(cpp_analysis[1]) i = 0 for issue in issues: if issue.testid == "toomanyconfigs": found = issue i += 1 self.assertEquals(found.testid, "toomanyconfigs") self.assertEquals(found.location.file.givenpath, "src/lib_json/json_value.cpp") self.assertEquals(found.location.point.line, 0) self.assertEquals(found.location.point.column, 0) self.assertEquals(found.severity, "style") self.assertIsNone(found.notes) self.assertIsNone(found.customfields) self.assertTrue(i > 4)
def test_pep8_common(): firehorse_results = Analysis(metadata=Metadata( generator=Generator(name='pep8'), sut=None, file_=None, stats=None), results=[]) return pep8(filepath, firehorse_results)
def parse_plist(pathOrFile, analyzerversion=None, sut=None, file_=None, stats=None): """ Given a .plist file emitted by clang-static-analyzer (e.g. via scan-build), parse it and return an Analysis instance """ plist = plistlib.readPlist(pathOrFile) # We now have the .plist file as a hierarchy of dicts, lists, etc # Handy debug dump: if 0: pprint(plist) # A list of filenames, apparently referenced by index within # diagnostics: files = plist['files'] generator = Generator(name='clang-analyzer', version=analyzerversion) metadata = Metadata(generator, sut, file_, stats) analysis = Analysis(metadata, []) if 'clang_version' in plist: generator.version = plist['clang_version'] for diagnostic in plist['diagnostics']: if 0: pprint(diagnostic) cwe = None customfields = CustomFields() for key in ['category', 'issue_context', 'issue_context_kind']: if key in diagnostic: customfields[key] = diagnostic[key] message = Message(text=diagnostic['description']) loc = diagnostic['location'] location = Location( file=File(givenpath=files[loc['file']], abspath=None), # FIXME: doesn't tell us function name # TODO: can we patch this upstream? function=None, point=Point(int(loc['line']), int(loc['col']))) notes = None trace = make_trace(files, diagnostic['path']) issue = Issue( cwe, # Use the 'type' field for the testid: diagnostic['type'], location, message, notes, trace, customfields=customfields) analysis.results.append(issue) return analysis