diff --git a/.gitlab/TagSyntheticFailures.java b/.gitlab/TagSyntheticFailures.java
index a52b8d1e5a9..27ef53c44f7 100644
--- a/.gitlab/TagSyntheticFailures.java
+++ b/.gitlab/TagSyntheticFailures.java
@@ -5,6 +5,7 @@
import java.util.List;
import java.util.Map;
import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerFactory;
@@ -27,6 +28,7 @@
/// **`executionError`** and **`test exception`** — Framework-level synthetic failures that do not
/// represent real test results. Tagged skip unconditionally so Test Optimization treats them as
// non-failures.
+
///
/// Before (two retries of the same class — first is intermediate, second is the final outcome):
///
@@ -46,59 +48,70 @@
///
/// ```
///
-/// Usage (Java 25): `java TagSyntheticFailures.java junit-report.xml`
+/// Usage (Java 25): `java TagSyntheticFailures.java batch.list`
+///
+/// The argument is a list file with one XML path per line.
+/// Each referenced XML is processed in turn.
class TagSyntheticFailures {
public static void main(String[] args) throws Exception {
- if (args.length == 0) {
- System.err.println("Usage: java TagSyntheticFailures.java ");
- System.exit(1);
- }
- var xmlFile = new File(args[0]);
- if (!xmlFile.exists()) {
- System.err.println("File not found: " + xmlFile);
- System.exit(1);
- }
var dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setExpandEntityReferences(false);
- var doc = dbf.newDocumentBuilder().parse(xmlFile);
- var testcases = doc.getElementsByTagName("testcase");
+ var docBuilder = dbf.newDocumentBuilder();
+ var transformerFactory = TransformerFactory.newInstance();
+
+ var listFile = new File(args[0]);
+ for (String line : Files.readAllLines(listFile.toPath())) {
+ processFile(new File(line), docBuilder, transformerFactory);
+ }
+ }
+
+ static void processFile(File xmlFile, DocumentBuilder docBuilder, TransformerFactory transformerFactory) throws Exception {
+ if (!xmlFile.exists()) {
+ System.err.println("xml file not found, skipping: " + xmlFile);
+ return;
+ }
+
+ var doc = docBuilder.parse(xmlFile);
+ var testCases = doc.getElementsByTagName("testcase");
Map> byClassname = new LinkedHashMap<>();
boolean modified = false;
- for (int i = 0; i < testcases.getLength(); i++) {
- var e = (Element) testcases.item(i);
+ for (int i = 0; i < testCases.getLength(); i++) {
+ var e = (Element) testCases.item(i);
var name = e.getAttribute("name");
if ("initializationError".equals(name)) {
byClassname.computeIfAbsent(e.getAttribute("classname"), k -> new ArrayList<>()).add(e);
} else if ("executionError".equals(name) || "test exception".equals(name)) {
- if (tagSkip(doc, e)) modified = true;
+ if (tagFinalStatus(doc, e, "skip")) modified = true;
}
}
+
for (var group : byClassname.values()) {
for (int i = 0; i < group.size() - 1; i++) {
- if (tagSkip(doc, group.get(i))) {
+ if (tagFinalStatus(doc, group.get(i), "skip")) {
modified = true;
}
}
}
+
+ // Tag remaining testcases with their pass/skip/fail status.
+ // tagFinalStatus is idempotent — already-tagged synthetics from the passes above are skipped.
+ for (int i = 0; i < testCases.getLength(); i++) {
+ var e = (Element) testCases.item(i);
+ if (tagFinalStatus(doc, e, computeStatus(e))) modified = true;
+ }
+
if (!modified) {
return;
}
- var tmpFile = File.createTempFile("TagSyntheticFailures", ".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;
- }
+
+ var transformer = transformerFactory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.transform(new DOMSource(doc), new StreamResult(xmlFile));
}
static Element firstChildElement(Element parent, String tagName) {
@@ -112,7 +125,7 @@ static Element firstChildElement(Element parent, String tagName) {
return null;
}
- static boolean tagSkip(Document doc, Element testcase) {
+ static boolean tagFinalStatus(Document doc, Element testcase, String status) {
var props = firstChildElement(testcase, "properties");
if (props != null) {
var children = props.getChildNodes();
@@ -123,18 +136,33 @@ static boolean tagSkip(Document doc, Element testcase) {
return false;
}
}
- var property = doc.createElement("property");
- property.setAttribute("name", "dd_tags[test.final_status]");
- property.setAttribute("value", "skip");
- props.appendChild(property);
+ props.appendChild(newStatusProperty(doc, status));
} 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);
+ properties.appendChild(newStatusProperty(doc, status));
testcase.appendChild(properties);
}
return true;
}
+
+ static Element newStatusProperty(Document doc, String status) {
+ var p = doc.createElement("property");
+ p.setAttribute("name", "dd_tags[test.final_status]");
+ p.setAttribute("value", status);
+ return p;
+ }
+
+ /// Derives a testcase status: failure/error -> fail, skipped -> skip, else pass.
+ static String computeStatus(Element testcase) {
+ var children = testcase.getChildNodes();
+ boolean hasSkipped = false;
+ for (int i = 0; i < children.getLength(); i++) {
+ if (children.item(i) instanceof Element e) {
+ var tag = e.getTagName();
+ if ("failure".equals(tag) || "error".equals(tag)) return "fail";
+ if ("skipped".equals(tag)) hasSkipped = true;
+ }
+ }
+ return hasSkipped ? "skip" : "pass";
+ }
}
diff --git a/.gitlab/add_final_status.xsl b/.gitlab/add_final_status.xsl
deleted file mode 100644
index 4b4c0da17fd..00000000000
--- a/.gitlab/add_final_status.xsl
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- fail
- skip
- pass
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.gitlab/collect_results.sh b/.gitlab/collect_results.sh
index 36614632481..710ee5344f3 100755
--- a/.gitlab/collect_results.sh
+++ b/.gitlab/collect_results.sh
@@ -60,6 +60,12 @@ function get_source_file () {
}
echo "Saving test results:"
+
+# Collect normalized XML paths so the Java tagger can run once for the whole batch
+# instead of paying JVM startup per file.
+BATCH_FILE="./synthetic-tag-batch.list"
+: > "$BATCH_FILE"
+
while IFS= read -r -d '' RESULT_XML_FILE
do
echo -n "- $RESULT_XML_FILE"
@@ -90,13 +96,14 @@ do
echo " (non-stable test names detected)"
fi
- echo "Add dd_tags[test.final_status] property on retried synthetics testcase initializationErrors, and all executionError and test exception synthetic testcases"
- $JAVA_25_HOME/bin/java "$(dirname "$0")/TagSyntheticFailures.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)"
- xsltproc --huge --output "$tmp_file" "$xsl_file" "$TARGET_DIR/$AGGREGATED_FILE_NAME"
- mv "$tmp_file" "$TARGET_DIR/$AGGREGATED_FILE_NAME"
-
+ echo "$TARGET_DIR/$AGGREGATED_FILE_NAME" >> "$BATCH_FILE"
done < <(find "${TEST_RESULT_DIRS[@]}" -name \*.xml -print0)
+
+# Tag every testcase with dd_tags[test.final_status]:
+# - synthetic testcases (intermediate initializationError, executionError, test exception) -> skip
+# - everything else -> pass/skip/fail derived from // children
+if [ -s "$BATCH_FILE" ]; then
+ echo "Add dd_tags[test.final_status] property to every testcase (batched, $(wc -l < "$BATCH_FILE") files)"
+ $JAVA_25_HOME/bin/java "$(dirname "$0")/TagSyntheticFailures.java" "$BATCH_FILE"
+fi
+rm -f "$BATCH_FILE"