Skip to content

Commit 2fdf1d9

Browse files
authored
(Fix): Parses correct timestamp from test suites (#215)
Fixes issues in #209, and verified on the _actual_ bundle junits we were seeing in https://github.com/trunk-io/trunk/actions/runs/12267338838/job/34227320055?pr=19962 ![Screenshot 2024-12-11 at 1 27 13 AM](https://github.com/user-attachments/assets/000f6bc1-070d-42a7-9be9-c62a027fca9e) - `timestamp` _is not_ an actual part of the well-accepted JUnit spec for [**test cases**](https://github.com/nextest-rs/quick-junit/blob/main/src/report.rs#L309-L312)--only some tools support it, including our C++ test reporters, which I had been using for testing before - `timestamp` _is_ a more common part of the JUnit spec for [**test suites**](https://github.com/nextest-rs/quick-junit/blob/main/src/report.rs#L167-L168), including [example](https://github.com/testmoapp/junitxml?tab=readme-ov-file#complete-junit-xml-example) and in the [Jest reporter](https://github.com/jest-community/jest-junit/blob/master/README.md) we use - Using the suite as a fallback should cover our bases and allow us to properly fallback for the sake of quarantining and dogfooding this - It's worth noting that everywhere I could find, `timestamp` refers to the start time. In theory if you intentionally ran 3 test runs in parallel (rather than jest retries' serial) you might get different results depending on finishing order. In theory, this is acceptable behavior since such a test would be considered flaky (eventually) anyway, but that leaves potential for follow-up work if/when we have a clear use case.
1 parent d90f1ab commit 2fdf1d9

File tree

3 files changed

+45
-2
lines changed

3 files changed

+45
-2
lines changed

cli/src/runner.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,17 @@ fn convert_case_to_test(
136136
org_slug: &str,
137137
parent_name: &String,
138138
case: &quick_junit::TestCase,
139+
suite: &quick_junit::TestSuite,
139140
) -> Test {
140141
let name = String::from(case.name.as_str());
141142
let xml_string_to_string = |s: &quick_junit::XmlString| String::from(s.as_str());
142143
let class_name = case.classname.as_ref().map(xml_string_to_string);
143144
let file = case.extra.get("file").map(xml_string_to_string);
144145
let id: Option<String> = case.extra.get("id").map(xml_string_to_string);
146+
let timestamp = case
147+
.timestamp
148+
.or(suite.timestamp)
149+
.map(|t| t.timestamp_millis());
145150
Test::new(
146151
name,
147152
parent_name.clone(),
@@ -150,7 +155,7 @@ fn convert_case_to_test(
150155
id,
151156
org_slug,
152157
repo,
153-
case.timestamp.map(|t| t.timestamp_millis()),
158+
timestamp,
154159
)
155160
}
156161

@@ -184,7 +189,7 @@ pub async fn extract_failed_tests(
184189
for suite in &report.test_suites {
185190
let parent_name = String::from(suite.name.as_str());
186191
for case in &suite.test_cases {
187-
let test = convert_case_to_test(repo, org_slug, &parent_name, case);
192+
let test = convert_case_to_test(repo, org_slug, &parent_name, case, suite);
188193
match &case.status {
189194
TestCaseStatus::Skipped { .. } => {
190195
continue;
@@ -315,8 +320,10 @@ mod tests {
315320

316321
/// Contains 1 failure at 1:00
317322
const JUNIT0_FAIL: &str = "test_fixtures/junit0_fail.xml";
323+
const JUNIT0_FAIL_SUITE: &str = "test_fixtures/junit0_fail_suite_timestamp.xml";
318324
// Contains 1 pass at 2:00
319325
const JUNIT0_PASS: &str = "test_fixtures/junit0_pass.xml";
326+
const JUNIT0_PASS_SUITE: &str = "test_fixtures/junit0_pass_suite_timestamp.xml";
320327
// Contains 1 failure at 3:00 and 1 failure at 5:00
321328
const JUNIT1_FAIL: &str = "test_fixtures/junit1_fail.xml";
322329
// Contains 2 passes at 4:00
@@ -346,6 +353,28 @@ mod tests {
346353
assert!(retried_failures.is_empty());
347354
}
348355

356+
#[tokio::test(start_paused = true)]
357+
async fn test_extract_retry_suite_failed_tests() {
358+
let file_sets = vec![FileSet {
359+
file_set_type: FileSetType::Junit,
360+
files: vec![
361+
BundledFile {
362+
original_path: get_test_file_path(JUNIT0_FAIL_SUITE),
363+
..BundledFile::default()
364+
},
365+
BundledFile {
366+
original_path: get_test_file_path(JUNIT0_PASS_SUITE),
367+
..BundledFile::default()
368+
},
369+
],
370+
glob: String::from("**/*.xml"),
371+
}];
372+
373+
let retried_failures =
374+
extract_failed_tests(&BundleRepo::default(), ORG_SLUG, &file_sets).await;
375+
assert!(retried_failures.is_empty());
376+
}
377+
349378
#[tokio::test(start_paused = true)]
350379
async fn test_extract_multi_failed_tests() {
351380
let file_sets = vec![FileSet {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<testsuites tests="1" failures="1" disabled="0" errors="0" time="0.001" name="AllTests">
3+
<testsuite name="HelloTest" tests="1" failures="1" disabled="0" skipped="0" errors="0" time="0.001" timestamp="2024-12-10T01:00:00.000">
4+
<testcase name="Hello" file="trunk/hello_world/cc/hello_test.cc" line="9" status="run" result="completed" time="0.001" classname="HelloTest">
5+
<failure message="Failure at 1:00"/>
6+
</testcase>
7+
</testsuite>
8+
</testsuites>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<testsuites tests="1" failures="0" disabled="0" errors="0" time="0." name="AllTests">
3+
<testsuite name="HelloTest" tests="1" failures="0" disabled="0" skipped="0" errors="0" time="0." timestamp="2024-12-10T02:00:00.000">
4+
<testcase name="Hello" file="trunk/hello_world/cc/hello_test.cc" line="9" status="run" result="completed" time="0." classname="HelloTest" />
5+
</testsuite>
6+
</testsuites>

0 commit comments

Comments
 (0)