Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .gitlab/TagInitializationErrors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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.LinkedHashMap;
import java.util.List;
import java.util.Map;

/// 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:
///
/// ```
/// <testcase name="initializationError" />
/// ```
///
/// After:
///
/// ```
/// <testcase name="initializationError">
/// <properties>
/// <property name="dd_tags[test.final_status]" value="skip" />
/// </properties>
/// </testcase>
/// ```
///
/// Usage (Java 25): `java TagInitializationErrors.java junit-report.xml`

class TagInitializationErrors {
public static void main(String[] args) throws Exception {
if (args.length == 0) {
System.err.println("Usage: java TagInitializationErrors.java <xml-file>");
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ question: ‏Should we add some flags about entity resolution (for example) here to prevent security issue?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which security issue do you have in mind ? The entire workflow and data are derivated from the public content of this repo, and the script itself can be modified during a PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's those, everything that runs here is produced by the PR content, including the script that execute the command. So i'm don't think that there is any increase in the surface attack.

var testcases = doc.getElementsByTagName("testcase");
Map<String, List<Element>> byClassname = new LinkedHashMap<>();
for (int i = 0; i < testcases.getLength(); i++) {
var e = (Element) testcases.item(i);
if ("initializationError".equals(e.getAttribute("name"))) {
byClassname.computeIfAbsent(e.getAttribute("classname"), k -> new ArrayList<>()).add(e);
}
}
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");
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;
}
}
if (!modified) return;
var transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.transform(new DOMSource(doc), new StreamResult(xmlFile));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: This modifies the file in-place. What happens if the app fails? Does it leaves invalid documents?

}
}
3 changes: 3 additions & 0 deletions .gitlab/collect_results.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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_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"
tmp_file="$(mktemp)"
Expand Down
Loading