Skip to content

Commit

Permalink
Test history refactoring and improvements (#625)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim Jacomb <[email protected]>
Co-authored-by: Edgars Batna <[email protected]>
  • Loading branch information
3 people authored Jul 24, 2024
1 parent 85201a3 commit 72cf99b
Show file tree
Hide file tree
Showing 31 changed files with 2,220 additions and 301 deletions.
16 changes: 16 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,27 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>jackson2-api</artifactId>
</dependency>
<dependency>
<groupId>com.pivovarit</groupId>
<artifactId>parallel-collectors</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>pipeline-utility-steps</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.umontreal.iro.simul</groupId>
<artifactId>ssj</artifactId>
<version>3.3.1</version>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
Expand Down
191 changes: 171 additions & 20 deletions src/main/java/hudson/tasks/junit/CaseResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

/**
* One test result.
*
Expand All @@ -65,7 +69,7 @@
*/
public class CaseResult extends TestResult implements Comparable<CaseResult> {
private static final Logger LOGGER = Logger.getLogger(CaseResult.class.getName());
private final float duration;
private float duration;
/**
* Start time in epoch milliseconds - default is -1 for unset
*/
Expand All @@ -74,17 +78,18 @@ public class CaseResult extends TestResult implements Comparable<CaseResult> {
* In JUnit, a test is a method of a class. This field holds the fully qualified class name
* that the test was in.
*/
private final String className;
private String className;
/**
* This field retains the method name.
*/
private final String testName;
private String testName;
private transient String safeName;
private final boolean skipped;
private final String skippedMessage;
private final String errorStackTrace;
private final String errorDetails;
private final Map<String, String> properties;
private boolean skipped;
private boolean keepTestNames;
private String skippedMessage;
private String errorStackTrace;
private String errorDetails;
private Map<String, String> properties;
@SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "Specific method to restore it")
private transient SuiteResult parent;

Expand All @@ -98,14 +103,14 @@ public class CaseResult extends TestResult implements Comparable<CaseResult> {
* If these information are reported at the test case level, these fields are set,
* otherwise null, in which case {@link SuiteResult#stdout}.
*/
private final String stdout,stderr;
private String stdout,stderr;

/**
* This test has been failing since this build number (not id.)
*
* If {@link #isPassed() passing}, this field is left unused to 0.
*/
private /*final*/ int failedSince;
private int failedSince;

private static float parseTime(Element testCase) {
String time = testCase.attributeValue("time");
Expand Down Expand Up @@ -138,6 +143,7 @@ public CaseResult(SuiteResult parent, String testName, String errorStackTrace, S
this.skipped = false;
this.skippedMessage = null;
this.properties = Collections.emptyMap();
this.keepTestNames = false;
}

@Restricted(Beta.class)
Expand Down Expand Up @@ -165,14 +171,15 @@ public CaseResult(
this.skipped = skippedMessage != null;
this.skippedMessage = skippedMessage;
this.properties = Collections.emptyMap();
this.keepTestNames = false;
}

@Deprecated
CaseResult(SuiteResult parent, Element testCase, String testClassName, boolean keepLongStdio, boolean keepProperties) {
this(parent, testCase, testClassName, StdioRetention.fromKeepLongStdio(keepLongStdio), keepProperties);
CaseResult(SuiteResult parent, Element testCase, String testClassName, boolean keepLongStdio, boolean keepProperties, boolean keepTestNames) {
this(parent, testCase, testClassName, StdioRetention.fromKeepLongStdio(keepLongStdio), keepProperties, keepTestNames);

Check warning on line 179 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 179 is not covered by tests
}

CaseResult(SuiteResult parent, Element testCase, String testClassName, StdioRetention stdioRetention, boolean keepProperties) {
CaseResult(SuiteResult parent, Element testCase, String testClassName, StdioRetention stdioRetention, boolean keepProperties, boolean keepTestNames) {
// schema for JUnit report XML format is not available in Ant,
// so I don't know for sure what means what.
// reports in http://www.nabble.com/difference-in-junit-publisher-and-ant-junitreport-tf4308604.html#a12265700
Expand Down Expand Up @@ -200,7 +207,7 @@ public CaseResult(
errorStackTrace = getError(testCase);
errorDetails = getErrorMessage(testCase);
this.parent = parent;
duration = parseTime(testCase);
duration = clampDuration(parseTime(testCase));
this.startTime = -1;
skipped = isMarkedAsSkipped(testCase);
skippedMessage = getSkippedMessage(testCase);
Expand All @@ -226,6 +233,138 @@ public CaseResult(
}
}
this.properties = properties;
this.keepTestNames = keepTestNames;
}

public CaseResult(CaseResult src) {
this.duration = src.duration;
this.className = src.className;
this.testName = src.testName;
this.skippedMessage = src.skippedMessage;
this.skipped = src.skipped;
this.keepTestNames = src.keepTestNames;
this.errorStackTrace = src.errorStackTrace;
this.errorDetails = src.errorDetails;
this.failedSince = src.failedSince;
this.stdout = src.stdout;
this.stderr = src.stderr;
this.properties = new HashMap<>();
this.properties.putAll(src.properties);
}

public static float clampDuration(float d) {
return Math.min(365.0f * 24 * 60 * 60, Math.max(0.0f, d));
}

public static CaseResult parse(SuiteResult parent, final XMLStreamReader reader, String ver) throws XMLStreamException {
CaseResult r = new CaseResult(parent, null, null, null);
while (reader.hasNext()) {

Check warning on line 261 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 261 is only partially covered, one branch is missing
final int event = reader.next();
if (event == XMLStreamReader.END_ELEMENT && reader.getLocalName().equals("case")) {

Check warning on line 263 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 263 is only partially covered, one branch is missing
return r;
}
if (event == XMLStreamReader.START_ELEMENT) {
final String elementName = reader.getLocalName();
switch (elementName) {

Check warning on line 268 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 268 is only partially covered, one branch is missing
case "duration":
r.duration = clampDuration(new TimeToFloat(reader.getElementText()).parse());
break;
case "startTime":
r.startTime = Long.parseLong(reader.getElementText());
break;
case "className":
r.className = reader.getElementText();
break;
case "testName":
r.testName = reader.getElementText();
break;
case "skippedMessage":
r.skippedMessage = reader.getElementText();
break;
case "skipped":
r.skipped = Boolean.parseBoolean(reader.getElementText());
break;
case "keepTestNames":
r.keepTestNames = Boolean.parseBoolean(reader.getElementText());
break;
case "errorStackTrace":
r.errorStackTrace = reader.getElementText();
break;
case "errorDetails":
r.errorDetails = reader.getElementText();
break;
case "failedSince":
r.failedSince = Integer.parseInt(reader.getElementText());
break;
case "stdout":
r.stdout = reader.getElementText();
break;
case "stderr":
r.stderr = reader.getElementText();
break;
case "properties":
r.properties = new HashMap<>();
parseProperties(r.properties, reader, ver);
break;
default:
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("CaseResult.parse encountered an unknown field: " + elementName);

Check warning on line 311 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 310-311 are not covered by tests
}
}
}
}
return r;

Check warning on line 316 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 316 is not covered by tests
}

public static void parseProperties(Map<String, String> r, final XMLStreamReader reader, String ver) throws XMLStreamException {
while (reader.hasNext()) {

Check warning on line 320 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 320 is only partially covered, one branch is missing
final int event = reader.next();
if (event == XMLStreamReader.END_ELEMENT && reader.getLocalName().equals("properties")) {

Check warning on line 322 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 322 is only partially covered, one branch is missing
return;
}
if (event == XMLStreamReader.START_ELEMENT) {
final String elementName = reader.getLocalName();
switch (elementName) {

Check warning on line 327 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 327 is only partially covered, one branch is missing
case "entry":
parseProperty(r, reader, ver);
break;
default:
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("CaseResult.parseProperties encountered an unknown field: " + elementName);

Check warning on line 333 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 332-333 are not covered by tests
}
}
}
}
}

Check warning on line 338 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 338 is not covered by tests

public static void parseProperty(Map<String, String> r, final XMLStreamReader reader, String ver) throws XMLStreamException {
String name = null, value = null;
while (reader.hasNext()) {

Check warning on line 342 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 342 is only partially covered, one branch is missing
final int event = reader.next();
if (event == XMLStreamReader.END_ELEMENT && reader.getLocalName().equals("entry")) {

Check warning on line 344 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 344 is only partially covered, one branch is missing
if (name != null && value != null) {

Check warning on line 345 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 345 is only partially covered, 2 branches are missing
r.put(name, value);
}
return;
}
if (event == XMLStreamReader.START_ELEMENT) {
final String elementName = reader.getLocalName();
switch (elementName) {

Check warning on line 352 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 352 is only partially covered, one branch is missing
case "string":
if (name == null) {
name = reader.getElementText();
} else {
value = reader.getElementText();
}
break;
default:
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("CaseResult.parseProperty encountered an unknown field: " + elementName);

Check warning on line 362 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 361-362 are not covered by tests
}
}
}
}

}

static String possiblyTrimStdio(Collection<CaseResult> results, StdioRetention stdioRetention, String stdio) { // HUDSON-6516
Expand Down Expand Up @@ -354,7 +493,7 @@ public String getDisplayName() {
private String getNameWithEnclosingBlocks(String rawName) {
// Only prepend the enclosing flow node names if there are any and the run this is in has multiple blocks directly containing
// test results.
if (!getEnclosingFlowNodeNames().isEmpty()) {
if (!keepTestNames && !getEnclosingFlowNodeNames().isEmpty()) {
Run<?, ?> r = getRun();
if (r != null) {
TestResultAction action = r.getAction(TestResultAction.class);
Expand Down Expand Up @@ -579,15 +718,27 @@ public String getStderr() {
return getSuiteResult().getStderr();
}

static int PREVIOUS_TEST_RESULT_BACKTRACK_BUILDS_MAX =
Integer.parseInt(System.getProperty(History.HistoryTableResult.class.getName() + ".PREVIOUS_TEST_RESULT_BACKTRACK_BUILDS_MAX","25"));

@Override
public CaseResult getPreviousResult() {
if (parent == null) return null;

TestResult previousResult = parent.getParent().getPreviousResult();
if (previousResult == null) return null;
if (previousResult instanceof hudson.tasks.junit.TestResult) {
hudson.tasks.junit.TestResult pr = (hudson.tasks.junit.TestResult) previousResult;
return pr.getCase(parent.getName(), getTransformedFullDisplayName());
TestResult previousResult = parent.getParent();
int n = 0;
while (previousResult != null && n < PREVIOUS_TEST_RESULT_BACKTRACK_BUILDS_MAX) {

Check warning on line 730 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 730 is only partially covered, 2 branches are missing
previousResult = previousResult.getPreviousResult();
if (previousResult == null)
return null;
if (previousResult instanceof hudson.tasks.junit.TestResult) {
hudson.tasks.junit.TestResult pr = (hudson.tasks.junit.TestResult) previousResult;
CaseResult cr = pr.getCase(parent.getName(), getTransformedFullDisplayName());
if (cr != null) {

Check warning on line 737 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 737 is only partially covered, one branch is missing
return cr;
}
}
++n;

Check warning on line 741 in src/main/java/hudson/tasks/junit/CaseResult.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 741 is not covered by tests
}
return null;
}
Expand Down
9 changes: 3 additions & 6 deletions src/main/java/hudson/tasks/junit/ClassResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.*;

/**
* Cumulative test result of a test class.
Expand All @@ -44,7 +42,7 @@ public final class ClassResult extends TabulatedResult implements Comparable<Cla
private final String className; // simple name
private transient String safeName;

private final List<CaseResult> cases = new ArrayList<>();
private final Set<CaseResult> cases = new TreeSet<CaseResult>();

private int passCount,failCount,skipCount;

Expand Down Expand Up @@ -146,7 +144,7 @@ public Object getDynamic(String name, StaplerRequest req, StaplerResponse rsp) {

@Exported(name="child")
@Override
public List<CaseResult> getChildren() {

This comment has been minimized.

Copy link
@Bananeweizen

Bananeweizen Jul 26, 2024

The refactoring broke https://plugins.jenkins.io/test-stability/

java.lang.NoSuchMethodError: 'java.util.List hudson.tasks.junit.ClassResult.getChildren()'
at PluginClassLoader for test-stability//de.esailors.jenkins.teststability.StabilityTestDataPublisher.getClassAndCaseResults(StabilityTestDataPublisher.java:274)
at PluginClassLoader for test-stability//de.esailors.jenkins.teststability.StabilityTestDataPublisher.contributeTestData(StabilityTestDataPublisher.java:76)
at PluginClassLoader for junit//hudson.tasks.junit.JUnitResultArchiver.parseAndSummarize(JUnitResultArchiver.java:297)
at PluginClassLoader for junit//hudson.tasks.junit.pipeline.JUnitResultsStepExecution.run(JUnitResultsStepExecution.java:63)
at PluginClassLoader for junit//hudson.tasks.junit.pipeline.JUnitResultsStepExecution.run(JUnitResultsStepExecution.java:29)
at PluginClassLoader for workflow-step-api//org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:47)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:840)

This comment has been minimized.

Copy link
@jtnord

jtnord Sep 6, 2024

Member

and it looks like it also broke https://plugins.jenkins.io/parallel-test-executor/ for the same reason

splitTests – java.lang.NoSuchMethodError: 'java.util.List hudson.tasks.junit.ClassResult.getChildren()'

This comment has been minimized.

Copy link
@Bananeweizen

Bananeweizen Sep 7, 2024

@jtnord for test-stability is was mainly just recompiling (except for the plugin needing adaptation to not being released for a long time): jenkinsci/test-stability-plugin#6. maybe you can take that as blueprint.

This comment has been minimized.

Copy link
@imonteroperez

imonteroperez Sep 9, 2024

JiraTestResultReporter broken too jenkinsci/JiraTestResultReporter-plugin#147

This comment has been minimized.

Copy link
@jtnord

This comment has been minimized.

Copy link
@timja

timja Sep 10, 2024

Author Member

can you just post this on the pull request please instead of the commit.

GitHub notifications don't jump to comment on commits, I have to type jtnord knowing that James has commented on the thread to find it as its a big commit

public Collection<CaseResult> getChildren() {
return cases;
}

Expand Down Expand Up @@ -216,7 +214,6 @@ else if(r.isPassed()) {

void freeze() {
this.tally();
Collections.sort(cases);
}

public String getClassName() {
Expand Down
Loading

0 comments on commit 72cf99b

Please sign in to comment.