-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Move JUnit rendering to ruff_db
#19370
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
Conversation
Summary -- This PR moves the JUnit output format to the new rendering infrastructure. As I mention in a TODO in the code, there's some code that will be shared with the `grouped` output format. Hopefully I'll have that PR up too by the time this one is reviewed. Test Plan -- Existing tests moved to `ruff_db`
|
|
|
|
||
| use crate::diagnostic::{Diagnostic, SecondaryCode, render::FileResolver}; | ||
|
|
||
| pub struct JunitRenderer<'a> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a link to the format specification?
| status.set_description(format!( | ||
| "line {row}, col {col}, {body}", | ||
| row = location.line, | ||
| col = location.column, | ||
| body = diagnostic.body() | ||
| )); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this is a free text field, I'd prefer if we omit row and column for diagnostics without a location rather than making up a location. For the future, I could see us use the full diagnostic renderer for the description similar to https://www.ibm.com/docs/en/developer-for-zos/16.0.x?topic=formats-junit-xml-format
| case.extra.insert( | ||
| XmlString::new("line"), | ||
| XmlString::new(location.line.to_string()), | ||
| ); | ||
| case.extra.insert( | ||
| XmlString::new("column"), | ||
| XmlString::new(location.column.to_string()), | ||
| ); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, I think I'd prefer not to insert a made-up line and column information if they are missing
| let code = diagnostic | ||
| .secondary_code() | ||
| .map_or_else(|| diagnostic.name(), SecondaryCode::as_str); | ||
| let mut case = TestCase::new(format!("org.ruff.{code}"), status); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that's pre-existing but I wonder if the test case name is supposed to be unique (but it isn't if a file contains multiple diagnostics with the same code). But this isn't something that we need to solve in this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point, it does seem like it's probably supposed to be unique. This site says it's the test method name in the test's class specified by classname.
| // Safety: this should be infallible as long as the data we put in the report is valid | ||
| // UTF-8. | ||
| // | ||
| // It's a bit of a shame to call `to_string`, but `Report` otherwise only exposes a | ||
| // `serialize` method, which expects an `io::Write`, not a `fmt::Write`. `to_string` | ||
| // currently (2025-07-15) serializes into a `Vec<u8>` and converts to a `String` from there. | ||
| f.write_str( | ||
| &report | ||
| .to_string() | ||
| .expect("Failed to serialize JUnit report"), | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has now come up a couple of times (JSON reports too).
We could write a io::Write to fmt::Write adapter instead. It's slightly less efficient because it needs to validate that the string is valid UTF8
struct FmtAdapter<'a> {
fmt: &'a mut dyn std::fmt::Write,
}
impl std::io::Write for FmtAdapter<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.fmt
.write_str(str::from_utf8(buf).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid UTF-8 in JUnit report",
)
})?)
.map_err(io_error)?;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
fn write_fmt(&mut self, args: Arguments<'_>) -> std::io::Result<()> {
self.fmt.write_fmt(args).map_err(io_error)
}
}
fn io_error(error: std::fmt::Error) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::Other, error)
}The other alternative (but this might be worth a separate PR) is to explore if we can pass an std::io::Write instead of an std::fmt::Write
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I can add this for now. I poked a bit at passing a std::io::Write to all of these formats before, but I think it would justify its own PR like you said.
Co-authored-by: Micha Reiser <micha@reiser.io>
Summary
This PR moves the JUnit output format to the new rendering infrastructure. As I
mention in a TODO in the code, there's some code that will be shared with the
groupedoutput format. Hopefully I'll have that PR up too by the time this oneis reviewed.
Test Plan
Existing tests moved to
ruff_db