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"