Skip to content

Commit

Permalink
add <info> element
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmalcolm committed Feb 12, 2013
1 parent 8442610 commit 0fda32b
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 2 deletions.
41 changes: 40 additions & 1 deletion firehose.rng
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<choice>
<ref name="issue-element"/>
<ref name="failure-element"/>
<ref name="info-element"/>
</choice>
</zeroOrMore>
</element>
Expand Down Expand Up @@ -80,6 +81,13 @@
</element>
</define>

<!--
Definitions of the various kinds of result follow:
<issue>
<failure>
<info>
-->

<!-- A report about a possible problem -->
<define name="issue-element">
<element name="issue">
Expand Down Expand Up @@ -217,9 +225,40 @@
</element>
</define>

<!--
Sometimes you may want a tool to report other kinds of information
about the software-under-test that isn't a problem as such, e.g.
code metrics, copyright/license info, cross-referencing information
etc, hence the <info> element:
-->
<define name="info-element">
<element name="info">
<optional>
<!--
An optional free-form indentifying the kind of information
being reported:
-->
<attribute name="info-id"/>
</optional>
<optional>
<ref name="location-element"/>
</optional>
<optional>
<element name="message">
<text/>
</element>
</optional>
<optional>
<ref name="custom-fields-element"/>
</optional>
</element>
</define>

<!-- ...end of result definitions. Various supporting elements follow: -->

<!--
Summary text aimed at a developer. This is required for an <issue>,
but is also can (optionally) be provided by a <failure>.
but is also can (optionally) be provided by a <failure> or <info>
-->
<define name="message-element">
<element name="message"><text/></element>
Expand Down
81 changes: 81 additions & 0 deletions firehose/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ def from_xml(cls, fileobj):
results.append(Issue.from_xml(result_node))
elif result_node.tag == 'failure':
results.append(Failure.from_xml(result_node))
elif result_node.tag == 'info':
results.append(Info.from_xml(result_node))
return Analysis(metadata, results)

def to_xml(self):
Expand Down Expand Up @@ -341,6 +343,82 @@ def accept(self, visitor):
if self.message:
self.message.accept(visitor)

class Info(Result):
__slots__ = ('infoid', 'location', 'message', 'customfields')

def __init__(self, infoid, location, message, customfields):
if infoid is not None:
assert isinstance(infoid, _string_type)
if location is not None:
assert isinstance(location, Location)
if message is not None:
assert isinstance(message, Message)
if customfields is not None:
assert isinstance(customfields, CustomFields)
self.infoid = infoid
self.location = location
self.message = message
self.customfields = customfields

@classmethod
def from_xml(cls, node):
infoid = node.get('info-id')
location_node = node.find('location')
if location_node is not None:
location = Location.from_xml(location_node)
else:
location = None
message_node = node.find('message')
if message_node is not None:
message = Message.from_xml(message_node)
else:
message = None
customfields_node = node.find('custom-fields')
if customfields_node is not None:
customfields = CustomFields.from_xml(customfields_node)
else:
customfields = None
return Info(infoid, location, message, customfields)

def to_xml(self):
node = ET.Element('info')

if self.infoid is not None:
node.set('info-id', self.infoid)

if self.location is not None:
node.append(self.location.to_xml())

if self.message is not None:
node.append(self.message.to_xml())

if self.customfields is not None:
node.append(self.customfields.to_xml())

return node

def __repr__(self):
return ('Info(infoid=%r, location=%r, message=%r, customfields=%r)'
% (self.infoid, self.location, self.message, self.customfields))

def __eq__(self, other):
if self.infoid == other.infoid:
if self.location == other.location:
if self.message == other.message:
if self.customfields == other.customfields:
return True

def __hash__(self):
return (hash(self.infoid) ^ hash(self.location)
^ hash(self.message) ^ hash(self.customfields))

def accept(self, visitor):
visitor.visit_info(self)
if self.location:
self.location.accept(visitor)
if self.message:
self.message.accept(visitor)

class Metadata(object):
__slots__ = ('generator', 'sut', 'file_', 'stats')

Expand Down Expand Up @@ -1185,6 +1263,9 @@ def visit_warning(self, warning):
def visit_failure(self, failure):
pass

def visit_info(self, info):
pass

def visit_metadata(self, metadata):
pass

Expand Down
42 changes: 41 additions & 1 deletion tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from firehose.report import Analysis, Issue, Metadata, Generator, SourceRpm, \
Location, File, Function, Point, Message, Notes, Trace, State, Stats, \
Failure, Range, DebianSource, DebianBinary, CustomFields
Failure, Range, DebianSource, DebianBinary, CustomFields, Info

class AnalysisTests(unittest.TestCase):
def make_simple_analysis(self):
Expand Down Expand Up @@ -101,6 +101,21 @@ def make_failed_analysis(self):
])
return a, a.results[0]

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 test_creating_simple_analysis(self):
a, w = self.make_simple_analysis()
self.assertEqual(a.metadata.generator.name, 'cpychecker')
Expand Down Expand Up @@ -172,6 +187,19 @@ def test_making_failed_analysis(self):
self.assertEqual(f.customfields['stderr'], 'sample stderr')
self.assertEqual(f.customfields['returncode'], -9)

def test_making_info(self):
a, info = self.make_info()

self.assertIsInstance(info, Info)
self.assertEqual(info.infoid, 'gimple-stats')
self.assertEqual(info.location.file.givenpath, 'bar.c')
self.assertEqual(info.location.function.name, 'sample_function')
self.assertEqual(info.location.line, 10)
self.assertEqual(info.location.column, 15)
self.assertEqual(info.message.text, 'sample message')
self.assertEqual(info.customfields['num_stmts'], 57)
self.assertEqual(info.customfields['num_basic_blocks'], 10)

def test_from_xml(self):
num_analyses = 0
for filename in sorted(glob.glob('examples/example-*.xml')):
Expand Down Expand Up @@ -368,6 +396,13 @@ def roundtrip_through_xml(a):
self.assertEqual(a5.results, a6.results)
self.assertEqual(a5, a6)

a7, info = self.make_info()
a8 = roundtrip_through_xml(a7)

self.assertEqual(a7.metadata, a8.metadata)
self.assertEqual(a7.results, a8.results)
self.assertEqual(a7, a8)

def test_repr(self):
# Verify that the various __repr__ methods are sane:
a, w = self.make_simple_analysis()
Expand All @@ -382,6 +417,10 @@ def test_repr(self):
self.assertIn('Analysis(', repr(a))
self.assertIn('Failure(', repr(a))

a, info = self.make_info()
self.assertIn('Analysis(', repr(a))
self.assertIn('Info(', repr(a))

def test_hash(self):
def compare_hashes(creator):
a1, w1 = creator()
Expand All @@ -390,6 +429,7 @@ def compare_hashes(creator):
compare_hashes(self.make_simple_analysis)
compare_hashes(self.make_complex_analysis)
compare_hashes(self.make_failed_analysis)
compare_hashes(self.make_info)

def test_cwe(self):
# Verify that the CWE methods are sane:
Expand Down

0 comments on commit 0fda32b

Please sign in to comment.