Skip to content

Commit 72cf99b

Browse files
mdealertimjaEdgars Batna
authored
Test history refactoring and improvements (#625)
Co-authored-by: Tim Jacomb <[email protected]> Co-authored-by: Edgars Batna <[email protected]>
1 parent 85201a3 commit 72cf99b

31 files changed

+2220
-301
lines changed

pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,27 @@
181181
<groupId>org.jenkins-ci.plugins</groupId>
182182
<artifactId>jackson2-api</artifactId>
183183
</dependency>
184+
<dependency>
185+
<groupId>com.pivovarit</groupId>
186+
<artifactId>parallel-collectors</artifactId>
187+
<version>2.6.1</version>
188+
</dependency>
184189
<dependency>
185190
<groupId>org.jenkins-ci.plugins</groupId>
186191
<artifactId>pipeline-utility-steps</artifactId>
187192
<scope>test</scope>
188193
</dependency>
194+
<dependency>
195+
<groupId>ca.umontreal.iro.simul</groupId>
196+
<artifactId>ssj</artifactId>
197+
<version>3.3.1</version>
198+
<exclusions>
199+
<exclusion>
200+
<groupId>com.google.code.gson</groupId>
201+
<artifactId>gson</artifactId>
202+
</exclusion>
203+
</exclusions>
204+
</dependency>
189205
</dependencies>
190206
<dependencyManagement>
191207
<dependencies>

src/main/java/hudson/tasks/junit/CaseResult.java

Lines changed: 171 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,15 @@
5151
import java.util.Collections;
5252
import java.util.Comparator;
5353
import java.util.List;
54+
import java.util.logging.Level;
5455
import java.util.logging.Logger;
5556

5657
import static java.util.Collections.emptyList;
5758
import static java.util.Collections.singletonList;
5859

60+
import javax.xml.stream.XMLStreamException;
61+
import javax.xml.stream.XMLStreamReader;
62+
5963
/**
6064
* One test result.
6165
*
@@ -65,7 +69,7 @@
6569
*/
6670
public class CaseResult extends TestResult implements Comparable<CaseResult> {
6771
private static final Logger LOGGER = Logger.getLogger(CaseResult.class.getName());
68-
private final float duration;
72+
private float duration;
6973
/**
7074
* Start time in epoch milliseconds - default is -1 for unset
7175
*/
@@ -74,17 +78,18 @@ public class CaseResult extends TestResult implements Comparable<CaseResult> {
7478
* In JUnit, a test is a method of a class. This field holds the fully qualified class name
7579
* that the test was in.
7680
*/
77-
private final String className;
81+
private String className;
7882
/**
7983
* This field retains the method name.
8084
*/
81-
private final String testName;
85+
private String testName;
8286
private transient String safeName;
83-
private final boolean skipped;
84-
private final String skippedMessage;
85-
private final String errorStackTrace;
86-
private final String errorDetails;
87-
private final Map<String, String> properties;
87+
private boolean skipped;
88+
private boolean keepTestNames;
89+
private String skippedMessage;
90+
private String errorStackTrace;
91+
private String errorDetails;
92+
private Map<String, String> properties;
8893
@SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "Specific method to restore it")
8994
private transient SuiteResult parent;
9095

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

103108
/**
104109
* This test has been failing since this build number (not id.)
105110
*
106111
* If {@link #isPassed() passing}, this field is left unused to 0.
107112
*/
108-
private /*final*/ int failedSince;
113+
private int failedSince;
109114

110115
private static float parseTime(Element testCase) {
111116
String time = testCase.attributeValue("time");
@@ -138,6 +143,7 @@ public CaseResult(SuiteResult parent, String testName, String errorStackTrace, S
138143
this.skipped = false;
139144
this.skippedMessage = null;
140145
this.properties = Collections.emptyMap();
146+
this.keepTestNames = false;
141147
}
142148

143149
@Restricted(Beta.class)
@@ -165,14 +171,15 @@ public CaseResult(
165171
this.skipped = skippedMessage != null;
166172
this.skippedMessage = skippedMessage;
167173
this.properties = Collections.emptyMap();
174+
this.keepTestNames = false;
168175
}
169176

170177
@Deprecated
171-
CaseResult(SuiteResult parent, Element testCase, String testClassName, boolean keepLongStdio, boolean keepProperties) {
172-
this(parent, testCase, testClassName, StdioRetention.fromKeepLongStdio(keepLongStdio), keepProperties);
178+
CaseResult(SuiteResult parent, Element testCase, String testClassName, boolean keepLongStdio, boolean keepProperties, boolean keepTestNames) {
179+
this(parent, testCase, testClassName, StdioRetention.fromKeepLongStdio(keepLongStdio), keepProperties, keepTestNames);
173180
}
174181

175-
CaseResult(SuiteResult parent, Element testCase, String testClassName, StdioRetention stdioRetention, boolean keepProperties) {
182+
CaseResult(SuiteResult parent, Element testCase, String testClassName, StdioRetention stdioRetention, boolean keepProperties, boolean keepTestNames) {
176183
// schema for JUnit report XML format is not available in Ant,
177184
// so I don't know for sure what means what.
178185
// reports in http://www.nabble.com/difference-in-junit-publisher-and-ant-junitreport-tf4308604.html#a12265700
@@ -200,7 +207,7 @@ public CaseResult(
200207
errorStackTrace = getError(testCase);
201208
errorDetails = getErrorMessage(testCase);
202209
this.parent = parent;
203-
duration = parseTime(testCase);
210+
duration = clampDuration(parseTime(testCase));
204211
this.startTime = -1;
205212
skipped = isMarkedAsSkipped(testCase);
206213
skippedMessage = getSkippedMessage(testCase);
@@ -226,6 +233,138 @@ public CaseResult(
226233
}
227234
}
228235
this.properties = properties;
236+
this.keepTestNames = keepTestNames;
237+
}
238+
239+
public CaseResult(CaseResult src) {
240+
this.duration = src.duration;
241+
this.className = src.className;
242+
this.testName = src.testName;
243+
this.skippedMessage = src.skippedMessage;
244+
this.skipped = src.skipped;
245+
this.keepTestNames = src.keepTestNames;
246+
this.errorStackTrace = src.errorStackTrace;
247+
this.errorDetails = src.errorDetails;
248+
this.failedSince = src.failedSince;
249+
this.stdout = src.stdout;
250+
this.stderr = src.stderr;
251+
this.properties = new HashMap<>();
252+
this.properties.putAll(src.properties);
253+
}
254+
255+
public static float clampDuration(float d) {
256+
return Math.min(365.0f * 24 * 60 * 60, Math.max(0.0f, d));
257+
}
258+
259+
public static CaseResult parse(SuiteResult parent, final XMLStreamReader reader, String ver) throws XMLStreamException {
260+
CaseResult r = new CaseResult(parent, null, null, null);
261+
while (reader.hasNext()) {
262+
final int event = reader.next();
263+
if (event == XMLStreamReader.END_ELEMENT && reader.getLocalName().equals("case")) {
264+
return r;
265+
}
266+
if (event == XMLStreamReader.START_ELEMENT) {
267+
final String elementName = reader.getLocalName();
268+
switch (elementName) {
269+
case "duration":
270+
r.duration = clampDuration(new TimeToFloat(reader.getElementText()).parse());
271+
break;
272+
case "startTime":
273+
r.startTime = Long.parseLong(reader.getElementText());
274+
break;
275+
case "className":
276+
r.className = reader.getElementText();
277+
break;
278+
case "testName":
279+
r.testName = reader.getElementText();
280+
break;
281+
case "skippedMessage":
282+
r.skippedMessage = reader.getElementText();
283+
break;
284+
case "skipped":
285+
r.skipped = Boolean.parseBoolean(reader.getElementText());
286+
break;
287+
case "keepTestNames":
288+
r.keepTestNames = Boolean.parseBoolean(reader.getElementText());
289+
break;
290+
case "errorStackTrace":
291+
r.errorStackTrace = reader.getElementText();
292+
break;
293+
case "errorDetails":
294+
r.errorDetails = reader.getElementText();
295+
break;
296+
case "failedSince":
297+
r.failedSince = Integer.parseInt(reader.getElementText());
298+
break;
299+
case "stdout":
300+
r.stdout = reader.getElementText();
301+
break;
302+
case "stderr":
303+
r.stderr = reader.getElementText();
304+
break;
305+
case "properties":
306+
r.properties = new HashMap<>();
307+
parseProperties(r.properties, reader, ver);
308+
break;
309+
default:
310+
if (LOGGER.isLoggable(Level.FINEST)) {
311+
LOGGER.finest("CaseResult.parse encountered an unknown field: " + elementName);
312+
}
313+
}
314+
}
315+
}
316+
return r;
317+
}
318+
319+
public static void parseProperties(Map<String, String> r, final XMLStreamReader reader, String ver) throws XMLStreamException {
320+
while (reader.hasNext()) {
321+
final int event = reader.next();
322+
if (event == XMLStreamReader.END_ELEMENT && reader.getLocalName().equals("properties")) {
323+
return;
324+
}
325+
if (event == XMLStreamReader.START_ELEMENT) {
326+
final String elementName = reader.getLocalName();
327+
switch (elementName) {
328+
case "entry":
329+
parseProperty(r, reader, ver);
330+
break;
331+
default:
332+
if (LOGGER.isLoggable(Level.FINEST)) {
333+
LOGGER.finest("CaseResult.parseProperties encountered an unknown field: " + elementName);
334+
}
335+
}
336+
}
337+
}
338+
}
339+
340+
public static void parseProperty(Map<String, String> r, final XMLStreamReader reader, String ver) throws XMLStreamException {
341+
String name = null, value = null;
342+
while (reader.hasNext()) {
343+
final int event = reader.next();
344+
if (event == XMLStreamReader.END_ELEMENT && reader.getLocalName().equals("entry")) {
345+
if (name != null && value != null) {
346+
r.put(name, value);
347+
}
348+
return;
349+
}
350+
if (event == XMLStreamReader.START_ELEMENT) {
351+
final String elementName = reader.getLocalName();
352+
switch (elementName) {
353+
case "string":
354+
if (name == null) {
355+
name = reader.getElementText();
356+
} else {
357+
value = reader.getElementText();
358+
}
359+
break;
360+
default:
361+
if (LOGGER.isLoggable(Level.FINEST)) {
362+
LOGGER.finest("CaseResult.parseProperty encountered an unknown field: " + elementName);
363+
}
364+
}
365+
}
366+
}
367+
229368
}
230369

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

721+
static int PREVIOUS_TEST_RESULT_BACKTRACK_BUILDS_MAX =
722+
Integer.parseInt(System.getProperty(History.HistoryTableResult.class.getName() + ".PREVIOUS_TEST_RESULT_BACKTRACK_BUILDS_MAX","25"));
723+
582724
@Override
583725
public CaseResult getPreviousResult() {
584726
if (parent == null) return null;
585727

586-
TestResult previousResult = parent.getParent().getPreviousResult();
587-
if (previousResult == null) return null;
588-
if (previousResult instanceof hudson.tasks.junit.TestResult) {
589-
hudson.tasks.junit.TestResult pr = (hudson.tasks.junit.TestResult) previousResult;
590-
return pr.getCase(parent.getName(), getTransformedFullDisplayName());
728+
TestResult previousResult = parent.getParent();
729+
int n = 0;
730+
while (previousResult != null && n < PREVIOUS_TEST_RESULT_BACKTRACK_BUILDS_MAX) {
731+
previousResult = previousResult.getPreviousResult();
732+
if (previousResult == null)
733+
return null;
734+
if (previousResult instanceof hudson.tasks.junit.TestResult) {
735+
hudson.tasks.junit.TestResult pr = (hudson.tasks.junit.TestResult) previousResult;
736+
CaseResult cr = pr.getCase(parent.getName(), getTransformedFullDisplayName());
737+
if (cr != null) {
738+
return cr;
739+
}
740+
}
741+
++n;
591742
}
592743
return null;
593744
}

src/main/java/hudson/tasks/junit/ClassResult.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
import org.kohsuke.stapler.StaplerResponse;
3232
import org.kohsuke.stapler.export.Exported;
3333

34-
import java.util.ArrayList;
35-
import java.util.Collections;
36-
import java.util.List;
34+
import java.util.*;
3735

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

47-
private final List<CaseResult> cases = new ArrayList<>();
45+
private final Set<CaseResult> cases = new TreeSet<CaseResult>();
4846

4947
private int passCount,failCount,skipCount;
5048

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

147145
@Exported(name="child")
148146
@Override
149-
public List<CaseResult> getChildren() {
147+
public Collection<CaseResult> getChildren() {
150148
return cases;
151149
}
152150

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

217215
void freeze() {
218216
this.tally();
219-
Collections.sort(cases);
220217
}
221218

222219
public String getClassName() {

0 commit comments

Comments
 (0)