From c42a1280e7bba9ecc6f961c4938e3771e944ab0d Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Tue, 31 Mar 2026 15:00:50 +0200 Subject: [PATCH 01/12] Removes Initialization error from junit XML --- .../dd-trace-java.configure-tests.gradle.kts | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts b/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts index a3158c37ca7..aa73a82bace 100644 --- a/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts +++ b/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts @@ -9,6 +9,12 @@ import org.gradle.kotlin.dsl.withType import org.gradle.testing.base.TestingExtension import java.time.Duration import java.time.temporal.ChronoUnit +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult +import org.w3c.dom.Element // Need concrete implementation of BuildService in Kotlin abstract class ForkedTestLimit : BuildService @@ -114,3 +120,54 @@ tasks.withType().configureEach { } } } + +tasks.withType().configureEach { + + // Gradle generates synthetic test cases in JUnit reports for setup methods. When a setup is retried + // and eventually succeeds, multiple test cases are created, with only the last one passing. Since the + // retry succeeds, this does not fail the CI. + // + // However, all intermediate attempts are reported as failures in TestOptimization, which is misleading. + // + // Ideally, we would expose a final_status field: + // - "skip" for intermediate retries + // - "fail"/"pass" for the final attempt + // + // Unfortunately, the test framework provides very limited control over this, and no built-in solution was found. + // + // As a workaround, this post-processor removes those synthetic test cases. Given that these errors are arguably + // not actionable for test owners (TBD), this is considered an acceptable trade-off. + // + // Charles de Beauchesne, March 2025 + + val reportsLocation = reports.junitXml.outputLocation + val removeInitErrors = tasks.register("${name}RemoveInitializationErrors") { + doLast { + val dir = reportsLocation.get().asFile + if (!dir.exists()) return@doLast + dir.walkTopDown() + .filter { it.isFile && it.extension == "xml" } + .forEach { xmlFile -> + try { + removeInitializationErrors(xmlFile) + } catch (e: Exception) { + logger.warn("Failed to remove initializationError testcases from {}: {}", xmlFile.name, e.message) + } + } + } + } + finalizedBy(removeInitErrors) +} + +fun removeInitializationErrors(xmlFile: File) { + val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile) + val testcases = doc.getElementsByTagName("testcase") + val toRemove = (0 until testcases.length) + .map { testcases.item(it) as Element } + .filter { it.getAttribute("name") == "initializationError" } + if (toRemove.isEmpty()) return + toRemove.forEach { it.parentNode.removeChild(it) } + val transformer = TransformerFactory.newInstance().newTransformer() + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8") + transformer.transform(DOMSource(doc), StreamResult(xmlFile)) +} From 1f3708d4413c3486144a649b15c9f733f58bb951 Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Tue, 31 Mar 2026 19:01:34 +0200 Subject: [PATCH 02/12] Address comments --- .../dd-trace-java.configure-tests.gradle.kts | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts b/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts index aa73a82bace..b88dca02928 100644 --- a/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts +++ b/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts @@ -129,44 +129,42 @@ tasks.withType().configureEach { // // However, all intermediate attempts are reported as failures in TestOptimization, which is misleading. // - // Ideally, we would expose a final_status field: + // To tackle this, we'll expose a final_status field: // - "skip" for intermediate retries - // - "fail"/"pass" for the final attempt - // - // Unfortunately, the test framework provides very limited control over this, and no built-in solution was found. - // - // As a workaround, this post-processor removes those synthetic test cases. Given that these errors are arguably - // not actionable for test owners (TBD), this is considered an acceptable trade-off. + // - nothing on last attempt, using the defaulting made by Test Optimization // // Charles de Beauchesne, March 2025 - val reportsLocation = reports.junitXml.outputLocation - val removeInitErrors = tasks.register("${name}RemoveInitializationErrors") { - doLast { - val dir = reportsLocation.get().asFile - if (!dir.exists()) return@doLast - dir.walkTopDown() - .filter { it.isFile && it.extension == "xml" } - .forEach { xmlFile -> - try { - removeInitializationErrors(xmlFile) - } catch (e: Exception) { - logger.warn("Failed to remove initializationError testcases from {}: {}", xmlFile.name, e.message) - } + doLast("post-process-junit-xml-report") { + val dir = reports.junitXml.outputLocation.get().asFile + if (!dir.exists()) return@doLast + dir.walkTopDown() + .filter { it.isFile && it.extension == "xml" } + .forEach { xmlFile -> + try { + tagInitializationErrors(xmlFile) + } catch (e: Exception) { + logger.warn("Failed to remove initializationError testcases from {}: {}", xmlFile.name, e.message) } - } + } } - finalizedBy(removeInitErrors) } -fun removeInitializationErrors(xmlFile: File) { +fun tagInitializationErrors(xmlFile: File) { val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile) val testcases = doc.getElementsByTagName("testcase") - val toRemove = (0 until testcases.length) + val initErrorCases = (0 until testcases.length) .map { testcases.item(it) as Element } .filter { it.getAttribute("name") == "initializationError" } - if (toRemove.isEmpty()) return - toRemove.forEach { it.parentNode.removeChild(it) } + if (initErrorCases.size <= 1) return + initErrorCases.dropLast(1).forEach { testcase -> + val properties = doc.createElement("properties") + val property = doc.createElement("property") + property.setAttribute("name", "dd_tags[test.final_status]") + property.setAttribute("value", "skip") + properties.appendChild(property) + testcase.appendChild(properties) + } val transformer = TransformerFactory.newInstance().newTransformer() transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8") transformer.transform(DOMSource(doc), StreamResult(xmlFile)) From c044bbae6293ff3be1078a4590d3e12910ba5189 Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Wed, 1 Apr 2026 19:18:09 +0200 Subject: [PATCH 03/12] Use java script --- .gitlab/TagInitializationErrors.java | 54 ++++++++++++++++++ .gitlab/collect_results.sh | 3 + .../dd-trace-java.configure-tests.gradle.kts | 55 ------------------- 3 files changed, 57 insertions(+), 55 deletions(-) create mode 100644 .gitlab/TagInitializationErrors.java diff --git a/.gitlab/TagInitializationErrors.java b/.gitlab/TagInitializationErrors.java new file mode 100644 index 00000000000..624c26584b6 --- /dev/null +++ b/.gitlab/TagInitializationErrors.java @@ -0,0 +1,54 @@ +import org.w3c.dom.Element; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Tags intermediate initializationError retries with dd_tags[test.final_status]=skip. + * + *

Gradle generates synthetic "initializationError" testcases in JUnit reports for setup methods. + * When a setup is retried and eventually succeeds, multiple testcases are created, with only the + * last one passing. All intermediate attempts are marked skip so Test Optimization is not misled. + * + *

Usage (JEP 330): java TagInitializationErrors.java + */ +class TagInitializationErrors { + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.err.println("Usage: java TagInitializationErrors.java "); + System.exit(1); + } + var xmlFile = new File(args[0]); + if (!xmlFile.exists()) { + System.err.println("File not found: " + xmlFile); + System.exit(1); + } + var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile); + var testcases = doc.getElementsByTagName("testcase"); + List initErrorCases = new ArrayList<>(); + for (int i = 0; i < testcases.getLength(); i++) { + var e = (Element) testcases.item(i); + if ("initializationError".equals(e.getAttribute("name"))) { + initErrorCases.add(e); + } + } + if (initErrorCases.size() <= 1) return; + for (int i = 0; i < initErrorCases.size() - 1; i++) { + var testcase = initErrorCases.get(i); + var properties = doc.createElement("properties"); + var property = doc.createElement("property"); + property.setAttribute("name", "dd_tags[test.final_status]"); + property.setAttribute("value", "skip"); + properties.appendChild(property); + testcase.appendChild(properties); + } + var transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.transform(new DOMSource(doc), new StreamResult(xmlFile)); + } +} diff --git a/.gitlab/collect_results.sh b/.gitlab/collect_results.sh index 5991a125902..f748b77e13d 100755 --- a/.gitlab/collect_results.sh +++ b/.gitlab/collect_results.sh @@ -90,6 +90,9 @@ do echo " (non-stable test names detected)" fi + echo "Add dd_tags[test.final_status] property on retried synthetics testcase initializationErrors" + java "$(dirname "$0")/TagInitializationErrors.java" "$TARGET_DIR/$AGGREGATED_FILE_NAME" + echo "Add dd_tags[test.final_status] property to each testcase on $TARGET_DIR/$AGGREGATED_FILE_NAME" xsl_file="$(dirname "$0")/add_final_status.xsl" tmp_file="$(mktemp)" diff --git a/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts b/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts index b88dca02928..a3158c37ca7 100644 --- a/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts +++ b/buildSrc/src/main/kotlin/dd-trace-java.configure-tests.gradle.kts @@ -9,12 +9,6 @@ import org.gradle.kotlin.dsl.withType import org.gradle.testing.base.TestingExtension import java.time.Duration import java.time.temporal.ChronoUnit -import javax.xml.parsers.DocumentBuilderFactory -import javax.xml.transform.OutputKeys -import javax.xml.transform.TransformerFactory -import javax.xml.transform.dom.DOMSource -import javax.xml.transform.stream.StreamResult -import org.w3c.dom.Element // Need concrete implementation of BuildService in Kotlin abstract class ForkedTestLimit : BuildService @@ -120,52 +114,3 @@ tasks.withType().configureEach { } } } - -tasks.withType().configureEach { - - // Gradle generates synthetic test cases in JUnit reports for setup methods. When a setup is retried - // and eventually succeeds, multiple test cases are created, with only the last one passing. Since the - // retry succeeds, this does not fail the CI. - // - // However, all intermediate attempts are reported as failures in TestOptimization, which is misleading. - // - // To tackle this, we'll expose a final_status field: - // - "skip" for intermediate retries - // - nothing on last attempt, using the defaulting made by Test Optimization - // - // Charles de Beauchesne, March 2025 - - doLast("post-process-junit-xml-report") { - val dir = reports.junitXml.outputLocation.get().asFile - if (!dir.exists()) return@doLast - dir.walkTopDown() - .filter { it.isFile && it.extension == "xml" } - .forEach { xmlFile -> - try { - tagInitializationErrors(xmlFile) - } catch (e: Exception) { - logger.warn("Failed to remove initializationError testcases from {}: {}", xmlFile.name, e.message) - } - } - } -} - -fun tagInitializationErrors(xmlFile: File) { - val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile) - val testcases = doc.getElementsByTagName("testcase") - val initErrorCases = (0 until testcases.length) - .map { testcases.item(it) as Element } - .filter { it.getAttribute("name") == "initializationError" } - if (initErrorCases.size <= 1) return - initErrorCases.dropLast(1).forEach { testcase -> - val properties = doc.createElement("properties") - val property = doc.createElement("property") - property.setAttribute("name", "dd_tags[test.final_status]") - property.setAttribute("value", "skip") - properties.appendChild(property) - testcase.appendChild(properties) - } - val transformer = TransformerFactory.newInstance().newTransformer() - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8") - transformer.transform(DOMSource(doc), StreamResult(xmlFile)) -} From c1bbc59ddda56e07df229352bbc35f2c266d0857 Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Wed, 1 Apr 2026 19:40:05 +0200 Subject: [PATCH 04/12] apply the logic by classname --- .gitlab/TagInitializationErrors.java | 32 ++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/.gitlab/TagInitializationErrors.java b/.gitlab/TagInitializationErrors.java index 624c26584b6..b6aaae21ca0 100644 --- a/.gitlab/TagInitializationErrors.java +++ b/.gitlab/TagInitializationErrors.java @@ -6,7 +6,9 @@ import javax.xml.transform.stream.StreamResult; import java.io.File; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * Tags intermediate initializationError retries with dd_tags[test.final_status]=skip. @@ -30,23 +32,31 @@ public static void main(String[] args) throws Exception { } var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile); var testcases = doc.getElementsByTagName("testcase"); - List initErrorCases = new ArrayList<>(); + Map> byClassname = new LinkedHashMap<>(); for (int i = 0; i < testcases.getLength(); i++) { var e = (Element) testcases.item(i); if ("initializationError".equals(e.getAttribute("name"))) { - initErrorCases.add(e); + byClassname.computeIfAbsent(e.getAttribute("classname"), k -> new ArrayList<>()).add(e); } } - if (initErrorCases.size() <= 1) return; - for (int i = 0; i < initErrorCases.size() - 1; i++) { - var testcase = initErrorCases.get(i); - var properties = doc.createElement("properties"); - var property = doc.createElement("property"); - property.setAttribute("name", "dd_tags[test.final_status]"); - property.setAttribute("value", "skip"); - properties.appendChild(property); - testcase.appendChild(properties); + boolean modified = false; + for (var group : byClassname.values()) { + if (group.size() <= 1) continue; + for (int i = 0; i < group.size() - 1; i++) { + var testcase = group.get(i); + var existingProperties = testcase.getElementsByTagName("properties"); + var properties = existingProperties.getLength() > 0 + ? (Element) existingProperties.item(0) + : doc.createElement("properties"); + var property = doc.createElement("property"); + property.setAttribute("name", "dd_tags[test.final_status]"); + property.setAttribute("value", "skip"); + properties.appendChild(property); + if (existingProperties.getLength() == 0) testcase.appendChild(properties); + modified = true; + } } + if (!modified) return; var transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.transform(new DOMSource(doc), new StreamResult(xmlFile)); From fc75f5f00892bedc163277cad15b4e698ed3aa65 Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Thu, 2 Apr 2026 11:14:10 +0200 Subject: [PATCH 05/12] Use JAVA_25_HOME --- .gitlab/collect_results.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/collect_results.sh b/.gitlab/collect_results.sh index f748b77e13d..dec34ed5d59 100755 --- a/.gitlab/collect_results.sh +++ b/.gitlab/collect_results.sh @@ -91,7 +91,7 @@ do fi echo "Add dd_tags[test.final_status] property on retried synthetics testcase initializationErrors" - java "$(dirname "$0")/TagInitializationErrors.java" "$TARGET_DIR/$AGGREGATED_FILE_NAME" + $JAVA_25_HOME/bin/java "$(dirname "$0")/TagInitializationErrors.java" "$TARGET_DIR/$AGGREGATED_FILE_NAME" echo "Add dd_tags[test.final_status] property to each testcase on $TARGET_DIR/$AGGREGATED_FILE_NAME" xsl_file="$(dirname "$0")/add_final_status.xsl" From 814275c5b97516204a3679ea506de59625888912 Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Thu, 2 Apr 2026 16:41:41 +0200 Subject: [PATCH 06/12] Do not tag if the property already exists --- .gitlab/TagInitializationErrors.java | 31 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/.gitlab/TagInitializationErrors.java b/.gitlab/TagInitializationErrors.java index b6aaae21ca0..789ae1a0ced 100644 --- a/.gitlab/TagInitializationErrors.java +++ b/.gitlab/TagInitializationErrors.java @@ -45,14 +45,29 @@ public static void main(String[] args) throws Exception { for (int i = 0; i < group.size() - 1; i++) { var testcase = group.get(i); var existingProperties = testcase.getElementsByTagName("properties"); - var properties = existingProperties.getLength() > 0 - ? (Element) existingProperties.item(0) - : doc.createElement("properties"); - var property = doc.createElement("property"); - property.setAttribute("name", "dd_tags[test.final_status]"); - property.setAttribute("value", "skip"); - properties.appendChild(property); - if (existingProperties.getLength() == 0) testcase.appendChild(properties); + if (existingProperties.getLength() > 0) { + var props = (Element) existingProperties.item(0); + var existingProps = props.getElementsByTagName("property"); + boolean alreadyTagged = false; + for (int j = 0; j < existingProps.getLength(); j++) { + if ("dd_tags[test.final_status]".equals(((Element) existingProps.item(j)).getAttribute("name"))) { + alreadyTagged = true; + break; + } + } + if (alreadyTagged) continue; + var property = doc.createElement("property"); + property.setAttribute("name", "dd_tags[test.final_status]"); + property.setAttribute("value", "skip"); + props.appendChild(property); + } else { + var properties = doc.createElement("properties"); + var property = doc.createElement("property"); + property.setAttribute("name", "dd_tags[test.final_status]"); + property.setAttribute("value", "skip"); + properties.appendChild(property); + testcase.appendChild(properties); + } modified = true; } } From 899ee14234c280bc302cf4d75fe9a88ac31f5fed Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Tue, 7 Apr 2026 15:44:22 +0200 Subject: [PATCH 07/12] Better comment --- .gitlab/TagInitializationErrors.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitlab/TagInitializationErrors.java b/.gitlab/TagInitializationErrors.java index 789ae1a0ced..4b477ea1074 100644 --- a/.gitlab/TagInitializationErrors.java +++ b/.gitlab/TagInitializationErrors.java @@ -17,6 +17,11 @@ * When a setup is retried and eventually succeeds, multiple testcases are created, with only the * last one passing. All intermediate attempts are marked skip so Test Optimization is not misled. * + *

For any suite with multiple initializationError testcases (i.e. retries occurred), all entries + * except the last one are already tagged with an existing `dd_tags[test.final_status]`. The last + * entry is left unmodified, allowing Test Optimization to apply its default status inference based + * on the actual outcome. Files with only one (or zero) initializationError testcases are not modified. + * *

Usage (JEP 330): java TagInitializationErrors.java */ class TagInitializationErrors { From 96b50d9b7fc66cf8cf58372020375ebcbc391fc7 Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Thu, 9 Apr 2026 14:23:09 +0200 Subject: [PATCH 08/12] Update .gitlab/TagInitializationErrors.java Co-authored-by: Brice Dutheil --- .gitlab/TagInitializationErrors.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab/TagInitializationErrors.java b/.gitlab/TagInitializationErrors.java index 4b477ea1074..08ec7f85939 100644 --- a/.gitlab/TagInitializationErrors.java +++ b/.gitlab/TagInitializationErrors.java @@ -17,10 +17,10 @@ * When a setup is retried and eventually succeeds, multiple testcases are created, with only the * last one passing. All intermediate attempts are marked skip so Test Optimization is not misled. * - *

For any suite with multiple initializationError testcases (i.e. retries occurred), all entries - * except the last one are already tagged with an existing `dd_tags[test.final_status]`. The last - * entry is left unmodified, allowing Test Optimization to apply its default status inference based - * on the actual outcome. Files with only one (or zero) initializationError testcases are not modified. + *

For any suite with multiple {@code initializationError} test cases (when retries occurred), all entries + * but the last one are tagged by this script with `dd_tags[test.final_status]=skip`. The last + * entry is left unmodified, allowing Test Optimization to apply its default status inference based + * on the actual outcome. Files with only one (or zero) {@code initializationError} test cases are left unmodified. * *

Usage (JEP 330): java TagInitializationErrors.java */ From e98c7003232713442cc85013bb88642bd8d4144d Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Thu, 9 Apr 2026 14:23:20 +0200 Subject: [PATCH 09/12] Update .gitlab/TagInitializationErrors.java Co-authored-by: Brice Dutheil --- .gitlab/TagInitializationErrors.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/TagInitializationErrors.java b/.gitlab/TagInitializationErrors.java index 08ec7f85939..3560518d649 100644 --- a/.gitlab/TagInitializationErrors.java +++ b/.gitlab/TagInitializationErrors.java @@ -11,7 +11,7 @@ import java.util.Map; /** - * Tags intermediate initializationError retries with dd_tags[test.final_status]=skip. + * Tags intermediate {@code initializationError} retries with {@code dd_tags[test.final_status]=skip}. * *

Gradle generates synthetic "initializationError" testcases in JUnit reports for setup methods. * When a setup is retried and eventually succeeds, multiple testcases are created, with only the From d904031e73ab7cb9ecdf61e3f5a0c327863a3cf9 Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Thu, 9 Apr 2026 14:23:50 +0200 Subject: [PATCH 10/12] Update .gitlab/TagInitializationErrors.java Co-authored-by: Brice Dutheil --- .gitlab/TagInitializationErrors.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/TagInitializationErrors.java b/.gitlab/TagInitializationErrors.java index 3560518d649..97e1c8dcc3d 100644 --- a/.gitlab/TagInitializationErrors.java +++ b/.gitlab/TagInitializationErrors.java @@ -22,7 +22,7 @@ * entry is left unmodified, allowing Test Optimization to apply its default status inference based * on the actual outcome. Files with only one (or zero) {@code initializationError} test cases are left unmodified. * - *

Usage (JEP 330): java TagInitializationErrors.java + *

Usage (Java 25): {@code java TagInitializationErrors.java junit-report.xml} */ class TagInitializationErrors { public static void main(String[] args) throws Exception { From 1b5a33e89ed9de17f0c32f5150a556b619c4a58b Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Thu, 9 Apr 2026 15:35:59 +0200 Subject: [PATCH 11/12] Add before/after in comment --- .gitlab/TagInitializationErrors.java | 43 +++++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/.gitlab/TagInitializationErrors.java b/.gitlab/TagInitializationErrors.java index 97e1c8dcc3d..0593b275e96 100644 --- a/.gitlab/TagInitializationErrors.java +++ b/.gitlab/TagInitializationErrors.java @@ -10,20 +10,35 @@ import java.util.List; import java.util.Map; -/** - * Tags intermediate {@code initializationError} retries with {@code dd_tags[test.final_status]=skip}. - * - *

Gradle generates synthetic "initializationError" testcases in JUnit reports for setup methods. - * When a setup is retried and eventually succeeds, multiple testcases are created, with only the - * last one passing. All intermediate attempts are marked skip so Test Optimization is not misled. - * - *

For any suite with multiple {@code initializationError} test cases (when retries occurred), all entries - * but the last one are tagged by this script with `dd_tags[test.final_status]=skip`. The last - * entry is left unmodified, allowing Test Optimization to apply its default status inference based - * on the actual outcome. Files with only one (or zero) {@code initializationError} test cases are left unmodified. - * - *

Usage (Java 25): {@code java TagInitializationErrors.java junit-report.xml} - */ +/// Tags intermediate `initializationError` retries with `dd_tags[test.final_status]=skip`. +/// +/// Gradle generates synthetic "initializationError" testcases in JUnit reports for setup methods. +/// When a setup is retried and eventually succeeds, multiple testcases are created, with only the +/// last one passing. All intermediate attempts are marked skip so Test Optimization is not misled. +/// +/// For any suite with multiple `initializationError` test cases (when retries occurred), all entries +/// but the last one are tagged by this script with `dd_tags[test.final_status]=skip`. The last +/// entry is left unmodified, allowing **Test Optimization** to apply its default status inference based +/// on the actual outcome. Files with only one (or zero) `initializationError` test cases are left unmodified. +/// +/// Before: +/// +/// ``` +/// +/// ``` +/// +/// After: +/// +/// ``` +/// +/// +/// +/// +/// +/// ``` +/// +/// Usage (Java 25): `java TagInitializationErrors.java junit-report.xml` + class TagInitializationErrors { public static void main(String[] args) throws Exception { if (args.length == 0) { From 3494f99b04840ae5e8342b7608d6f670388a527a Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Fri, 10 Apr 2026 09:53:34 +0200 Subject: [PATCH 12/12] Write output to a temp file then atomically rename it --- .gitlab/TagInitializationErrors.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitlab/TagInitializationErrors.java b/.gitlab/TagInitializationErrors.java index 0593b275e96..d2bee684419 100644 --- a/.gitlab/TagInitializationErrors.java +++ b/.gitlab/TagInitializationErrors.java @@ -5,6 +5,7 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.File; +import java.nio.file.Files; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -92,8 +93,15 @@ public static void main(String[] args) throws Exception { } } if (!modified) return; - var transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - transformer.transform(new DOMSource(doc), new StreamResult(xmlFile)); + var tmpFile = File.createTempFile("TagInitializationErrors", ".xml", xmlFile.getParentFile()); + try { + var transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.transform(new DOMSource(doc), new StreamResult(tmpFile)); + Files.move(tmpFile.toPath(), xmlFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + tmpFile.delete(); + throw e; + } } }