Skip to content

Add feature to generate JUnit XML output #720

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

jcoleman
Copy link

@jcoleman jcoleman commented Apr 4, 2025

Background

It's common to use the JUnit plugin on Jenkins to consume machine-readable output about the tests in your CI builds. For example, on our Ruby app tests we use the rspec_junit_formatter to output results from RSpec in a format that Jenkins can consume.

What

This PR adds JUnit style XML output as an option to Spoom so that running Sorbet in CI on Jenkins can output error information that can be displayed natively by Jenkins (and then, in turn, passed along to e.g. Github's interface).

With these changes Sorbet failures on a build look like this:

Screenshot 2025-04-03 at 3 04 34 PM

and a clean Sorbet build looks like this:

Screenshot 2025-04-03 at 2 53 32 PM

TODO

I'm happy to add docs for this feature as well, but I wanted to get initial review of the feature going sooner rather than later.

@jcoleman jcoleman requested a review from a team as a code owner April 4, 2025 14:31
Copy link
Collaborator

@Morriar Morriar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jcoleman,

I think the feature is okay but the code will need to be adjusted.

Thanks!

if errors.empty?
# Avoid creating an empty report when there are no errors so that
# reporting tools know that the type checking ran successfully.
testcase_element = testsuite_element.add_element("testcase")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is happening in both branches, we can extract it before

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other's in a loop, but this one isn't, so I don't think that will work?

)
else
errors.each do |error|
testcase_element = testsuite_element.add_element("testcase")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think moving this to Error there: https://github.com/Shopify/spoom/blob/main/lib/spoom/sorbet/errors.rb#L118 to error.to_junit_xml_element returns a REXML::Element?

You can also move the write_errors_to_xml to the Errors module: https://github.com/Shopify/spoom/blob/main/lib/spoom/sorbet/errors.rb#L6 maybe renamed as to_junit_xml

def write_errors_to_xml(errors, path:)
doc = REXML::Document.new
doc << REXML::XMLDecl.new
testsuite_element = doc.add_element("testsuite")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can just make it a REXML::Element.new("testsuite") so you don't have to create the doc for all the method and only create it at the end:

testsuite = REXML::Element.new("testsuite")
if errors.empty?
  #...
else
  errors.each { |error| testsuite << error.to_junit_xml_element }
end

doc = REXML::Document.new
doc << REXML::XMLDecl.new
doc << testsuite

@@ -2,6 +2,7 @@
# frozen_string_literal: true

require "test_with_project"
require "rexml/document"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REXML isn't included by default, AFAICT, and if we remove this line we get errors running the tests.

Comment on lines 276 to 288
Tempfile.create("sorbet_junit") do |tempfile|
result = @project.spoom("srb tc --junit_output_path=#{tempfile.path}")
expected_doc = <<~XML.chomp
<?xml version='1.0'?>
<testsuite name='Sorbet' failures='0'>
<testcase name='Typecheck' tests='1'/>
</testsuite>
XML
tempfile.rewind
actual_doc = tempfile.read
assert_equal(expected_doc, actual_doc)
assert(result.status)
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@project is already a temporary context, you can do something like this:

Suggested change
Tempfile.create("sorbet_junit") do |tempfile|
result = @project.spoom("srb tc --junit_output_path=#{tempfile.path}")
expected_doc = <<~XML.chomp
<?xml version='1.0'?>
<testsuite name='Sorbet' failures='0'>
<testcase name='Typecheck' tests='1'/>
</testsuite>
XML
tempfile.rewind
actual_doc = tempfile.read
assert_equal(expected_doc, actual_doc)
assert(result.status)
end
result = @project.spoom("srb tc --junit_output_path=junit.xml")
assert_equal(<<~XML.chomp, @project.read("junit.xml"))
<?xml version='1.0'?>
<testsuite name='Sorbet' failures='0'>
<testcase name='Typecheck' tests='1'/>
</testsuite>
XML
assert(result.status)

end

def test_output_errors_to_junit_xml
Tempfile.create("sorbet_junit") do |tempfile|
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, we don't need the Tempfile

@jcoleman
Copy link
Author

@Morriar I think I've responded to all of your feedback; I usually leave clicking "Resolve" for the reviewer so they can confirm the change first; let me know if you have a different preference.

jcoleman and others added 2 commits April 11, 2025 18:25
This is particularly useful when integrating Sorbet into CI builds
running on Jenkins so that Jenkins can display the output as specific
failures.
Co-authored-by: Alexandre Terrasa <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants