Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JUnit-XML format report #1453

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/brakeman.rb
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ def self.get_formats_from_output_format output_format
[:to_text]
when :table, :to_table
[:to_table]
when :junit, :to_junit
[:to_junit]
else
[:to_text]
end
Expand Down Expand Up @@ -258,6 +260,8 @@ def self.get_formats_from_output_files output_files
:to_text
when /\.table$/i
:to_table
when /\.junit$/i
:to_junit
else
:to_text
end
Expand Down
2 changes: 1 addition & 1 deletion lib/brakeman/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def create_option_parser options

opts.on "-f",
"--format TYPE",
[:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table],
[:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit],
"Specify output formats. Default is text" do |type|

type = "s" if type == :text
Expand Down
5 changes: 4 additions & 1 deletion lib/brakeman/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
class Brakeman::Report
attr_reader :tracker

VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text]
VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text, :to_junit]

def initialize tracker
@app_tree = tracker.app_tree
Expand Down Expand Up @@ -40,6 +40,9 @@ def format format
return self.to_table
when :to_pdf
raise "PDF output is not yet supported."
when :to_junit
require_report 'junit'
Brakeman::Report::JUnit
else
raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
end
Expand Down
104 changes: 104 additions & 0 deletions lib/brakeman/report/report_junit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
require 'time'
require "stringio"
require 'rexml/document'
naokikimura marked this conversation as resolved.
Show resolved Hide resolved

class Brakeman::Report::JUnit < Brakeman::Report::Base
def generate_report
io = StringIO.new
doc = REXML::Document.new
doc.add REXML::XMLDecl.new '1.0', 'UTF-8'

test_suites = REXML::Element.new 'testsuites'
test_suites.add_attribute 'xmlns:brakeman', 'https://brakemanscanner.org/'
properties = test_suites.add_element 'brakeman:properties', { 'xml:id' => 'scan_info' }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'app_path', 'brakeman:value' => tracker.app_path }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'rails_version', 'brakeman:value' => rails_version }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'security_warnings', 'brakeman:value' => all_warnings.length }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'start_time', 'brakeman:value' => tracker.start_time.iso8601 }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'end_time', 'brakeman:value' => tracker.end_time.iso8601 }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'duration', 'brakeman:value' => tracker.duration }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'checks_performed', 'brakeman:value' => checks.checks_run.join(',') }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'number_of_controllers', 'brakeman:value' => tracker.controllers.length }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'number_of_models', 'brakeman:value' => tracker.models.length - 1 }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'ruby_version', 'brakeman:value' => number_of_templates(@tracker) }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'number_of_templates', 'brakeman:value' => RUBY_VERSION }
properties.add_element 'brakeman:property', { 'brakeman:name' => 'brakeman_version', 'brakeman:value' => Brakeman::Version }

errors = test_suites.add_element 'brakeman:errors'
tracker.errors.each { |e|
error = errors.add_element 'brakeman:error'
naokikimura marked this conversation as resolved.
Show resolved Hide resolved
error.add_attribute 'brakeman:message', e[:error]
e[:backtrace].each { |b|
backtrace = error.add_element 'brakeman:backtrace'
backtrace.add_text b
}
}

obsolete = test_suites.add_element 'brakeman:obsolete'
tracker.unused_fingerprints.each { |fingerprint|
obsolete.add_element 'brakeman:warning', { 'brakeman:fingerprint' => fingerprint }
}

ignored = test_suites.add_element 'brakeman:ignored'
ignored_warnings.each { |w|
warning = ignored.add_element 'brakeman:warning'
warning.add_attribute 'brakeman:message', w.message
warning.add_attribute 'brakeman:category', w.warning_type
warning.add_attribute 'brakeman:file', warning_file(w)
warning.add_attribute 'brakeman:line', w.line
warning.add_attribute 'brakeman:fingerprint', w.fingerprint
warning.add_attribute 'brakeman:confidence', TEXT_CONFIDENCE[w.confidence]
warning.add_attribute 'brakeman:code', w.format_code
warning.add_text w.to_s
}

hostname = `hostname`.strip
i = 0
all_warnings
.map { |warning| [warning.file, [warning]] }
.reduce({}) { |entries, entry|
key, value = entry
entries[key] = entries[key] ? entries[key].concat(value) : value
entries
}
.each { |file, warnings|
i += 1
test_suite = test_suites.add_element 'testsuite'
test_suite.add_attribute 'id', i
test_suite.add_attribute 'package', 'brakeman'
test_suite.add_attribute 'name', file.relative
test_suite.add_attribute 'timestamp', tracker.start_time.strftime('%FT%T')
test_suite.add_attribute 'hostname', hostname == '' ? 'localhost' : hostname
test_suite.add_attribute 'tests', checks.checks_run.length
test_suite.add_attribute 'failures', warnings.length
test_suite.add_attribute 'errors', '0'
test_suite.add_attribute 'time', '0'

test_suite.add_element 'properties'

warnings.each { |warning|
test_case = test_suite.add_element 'testcase'
test_case.add_attribute 'name', 'run_check'
test_case.add_attribute 'classname', warning.check
test_case.add_attribute 'time', '0'

failure = test_case.add_element 'failure'
failure.add_attribute 'message', warning.message
failure.add_attribute 'type', warning.warning_type
failure.add_attribute 'brakeman:fingerprint', warning.fingerprint
failure.add_attribute 'brakeman:file', warning_file(warning)
failure.add_attribute 'brakeman:line', warning.line
failure.add_attribute 'brakeman:confidence', TEXT_CONFIDENCE[warning.confidence]
failure.add_attribute 'brakeman:code', warning.format_code
failure.add_text warning.to_s
}

test_suite.add_element 'system-out'
test_suite.add_element 'system-err'
}

doc.add test_suites
doc.write io
io.string
end
end
24 changes: 24 additions & 0 deletions test/tests/junit_output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require_relative '../test'
require 'rexml/document'

class JUnitOutputTests < Minitest::Test
def setup
@@document ||= REXML::Document.new(Brakeman.run("#{TEST_PATH}/apps/rails3.2").report.to_junit)
end

def test_for_errors
assert @@document.get_elements('/testsuites/brakeman:errors/brakeman:error').length == 0
end

def test_for_obsolete
document = REXML::Document.new(Brakeman.run("#{TEST_PATH}/apps/rails4").report.to_junit)
obsolete = document.get_elements('/testsuites/brakeman:obsolete/brakeman:warning')
.map { |e| e.attribute('brakeman:fingerprint').to_s }
assert_equal ["abcdef01234567890ba28050e7faf1d54f218dfa9435c3f65f47cb378c18cf98"], obsolete
end

def test_paths
elements = @@document.get_elements('/testsuites/testsuite/testcase/failure')
assert elements.all? { |e| not e.attribute('brakeman:file').to_s.start_with? "/" }
end
end
7 changes: 7 additions & 0 deletions test/tests/report_generation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,11 @@ def test_github_markdown_sanity
ensure
@@tracker.options[:github_url] = nil
end

def test_junit_sanity
report = @@report.to_junit

assert report.is_a? String
assert report.match(/\A.*<\/testsuites>\z/m)
end
end