Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

External storage - Part 1 #141

Merged
merged 39 commits into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
28c001a
Preparing for external storage.
jglick Aug 20, 2018
dc87e71
Merge branch 'TestResultParser' into external-storage
jglick Aug 20, 2018
5e62358
Merge branch 'TestResultParser' into external-storage
jglick Aug 21, 2018
ea8e7f5
Thinking.
jglick Aug 24, 2018
d39d5c3
Skeleton of smoke test.
jglick Aug 27, 2018
67bf585
Now successfully writing basic test results to a database from the ag…
jglick Aug 27, 2018
c500c1e
Initial work on master-side queries.
jglick Aug 28, 2018
b52397b
More test coverage.
jglick Aug 28, 2018
96c738e
Oops, was building against older LTS lines.
jglick Aug 28, 2018
7541ad4
FindBugs
jglick Aug 28, 2018
3b1bbfe
If you are going to patch a third-party library without repackaging/s…
jglick Aug 29, 2018
8e4aca0
Threading the TestResultSummary back from the agent so that the junit…
jglick Aug 29, 2018
2d10039
We do not want to attach a TestResultAction when there is an empty re…
jglick Aug 29, 2018
049a9fe
[JENKINS-48250] Removing bogus test from #93. 8e4aca0638f8483b05ee02d…
jglick Aug 30, 2018
6e704e8
The TestResultSummary constructor silently fails on an untallied/unfr…
jglick Aug 30, 2018
5665a2c
Making TestResultSummary more robust by detecting an untallied/unfroz…
jglick Aug 30, 2018
c6a4223
Simplified distinction between methods returning TestResult vs. TestR…
jglick Aug 30, 2018
ad66963
How was this plugin getting a test dep on an older version of itself?!
jglick Aug 30, 2018
79b1937
Supporting repeated archiving, and reporting on total, pass, and skip…
jglick Aug 30, 2018
2f0e93f
Javadoc
jglick Aug 30, 2018
d014339
Preparing more tests.
jglick Sep 25, 2018
e097352
Merge branch 'master' into external-storage
jglick Sep 25, 2018
cff4c0a
Merge branch 'master' into external-storage
jglick Sep 25, 2018
79a7297
Merge branch 'master' into external-storage
timja Aug 10, 2020
c3af197
Merge branch 'master' into external-storage-v2
timja Aug 10, 2020
656e083
Finished implementing existing test
timja Aug 11, 2020
805709e
Skipped and passed
timja Aug 11, 2020
a87088d
Add package result methods
timja Aug 11, 2020
0e8a000
getFailedSince run
timja Aug 12, 2020
f9e629c
Add getByPackage
timja Aug 12, 2020
2fd51f8
Add a fallback
timja Aug 12, 2020
23af1da
More hardening
timja Aug 14, 2020
019ae4d
Test results now work in report
timja Aug 15, 2020
3645b64
Update src/test/java/hudson/tasks/junit/pipeline/JUnitResultsStepTest…
timja Aug 15, 2020
d53b8dc
Update src/test/java/hudson/tasks/junit/storage/TestResultStorageTest…
timja Aug 15, 2020
3f3eacb
Remove debug line
timja Aug 15, 2020
970ca51
Apparently toString on a stapler dispatched object is a bad idea
timja Aug 15, 2020
385d2be
Fix results not getting counted if no failures
timja Aug 15, 2020
5189e6b
Fix spotbugs
timja Aug 15, 2020
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
26 changes: 26 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,32 @@
<artifactId>workflow-durable-task-step</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>database</artifactId>
<version>1.5</version>
<scope>test</scope>
<exclusions> <!-- TODO pending fixes upstream -->
<exclusion>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
</exclusion>
<exclusion>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</exclusion>
<exclusion> <!-- clashes with org.jenkins-ci.dom4j:dom4j -->
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>database-h2</artifactId>
<version>1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
Expand Down
105 changes: 78 additions & 27 deletions src/main/java/hudson/tasks/junit/CaseResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
package hudson.tasks.junit;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.tasks.junit.storage.TestResultImpl;
import hudson.tasks.junit.storage.TestResultStorage;
import hudson.util.TextFile;
import org.apache.commons.collections.iterators.ReverseListIterator;
import org.apache.commons.io.FileUtils;
Expand All @@ -34,6 +36,7 @@
import hudson.tasks.test.TestResult;

import org.dom4j.Element;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.export.Exported;

import javax.annotation.CheckForNull;
Expand Down Expand Up @@ -101,6 +104,46 @@ private static float parseTime(Element testCase) {
return new TimeToFloat(time).parse();
}


/**
* Used to create a fake failure, when Hudson fails to load data from XML files.
* Public since 1.526.
*
* @param parent Parent result.
* @param testName Test name.
* @param errorStackTrace Error stack trace.
*/
public CaseResult(SuiteResult parent, String testName, String errorStackTrace) {
Copy link
Member Author

Choose a reason for hiding this comment

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

these are just moved up so that all the constructors are together

this(parent, testName, errorStackTrace, "");
}

public CaseResult(SuiteResult parent, String testName, String errorStackTrace, String errorDetails) {
this.className = parent == null ? "unnamed" : parent.getName();
this.testName = testName;
this.errorStackTrace = errorStackTrace;
this.errorDetails = errorDetails;
this.parent = parent;
this.stdout = null;
this.stderr = null;
this.duration = 0.0f;
this.skipped = false;
this.skippedMessage = null;
}

public CaseResult(SuiteResult parent, String className, String testName, String errorDetails, String skippedMessage) {
this.className = className;
this.testName = testName;
this.errorStackTrace = null;
this.errorDetails = errorDetails;
this.parent = parent;
this.stdout = null;
this.stderr = null;
this.duration = 0.0f;

this.skipped = skippedMessage != null;
this.skippedMessage = skippedMessage;
}

CaseResult(SuiteResult parent, Element testCase, String testClassName, boolean keepLongStdio) {
// schema for JUnit report XML format is not available in Ant,
// so I don't know for sure what means what.
Expand Down Expand Up @@ -197,32 +240,6 @@ private static int halfMaxSize(Collection<CaseResult> results) {
return HALF_MAX_SIZE;
}


/**
* Used to create a fake failure, when Hudson fails to load data from XML files.
* Public since 1.526.
*
* @param parent Parent result.
* @param testName Test name.
* @param errorStackTrace Error stack trace.
*/
public CaseResult(SuiteResult parent, String testName, String errorStackTrace) {
this(parent, testName, errorStackTrace, "");
}

public CaseResult(SuiteResult parent, String testName, String errorStackTrace, String errorDetails) {
this.className = parent == null ? "unnamed" : parent.getName();
this.testName = testName;
this.errorStackTrace = errorStackTrace;
this.errorDetails = errorDetails;
this.parent = parent;
this.stdout = null;
this.stderr = null;
this.duration = 0.0f;
this.skipped = false;
this.skippedMessage = null;
}

public ClassResult getParent() {
return classResult;
}
Expand Down Expand Up @@ -427,6 +444,13 @@ else if (getRun() != null) {
}

public Run<?,?> getFailedSinceRun() {
TestResultStorage storage = TestResultStorage.find();
if (storage != null) {
Run<?, ?> run = Stapler.getCurrentRequest().findAncestorObject(Run.class);
TestResultImpl pluggableStorage = storage.load(run.getParent().getFullName(), run.getNumber());
return pluggableStorage.getFailedSinceRun(this);
}

return getRun().getParent().getBuildByNumber(getFailedSince());
}

Expand Down Expand Up @@ -650,6 +674,33 @@ public void freeze(SuiteResult parent) {
recomputeFailedSinceIfNeeded();
}

@Override
public String toString() {
StringBuilder b = new StringBuilder("CaseResult{className=").append(className).append(", testName=").append(testName);
if (errorDetails != null) {
b.append(", errorDetails=").append(errorDetails);
}
if (errorStackTrace != null) {
b.append(", errorStackTrace=").append(errorStackTrace);
}
if (skipped) {
b.append(", skipped=true");
}
if (skippedMessage != null) {
b.append(", skippedMessage=").append(skippedMessage);
}
if (duration != 0.0f) {
b.append(", duration=").append(duration);
}
if (stdout != null) {
b.append(", stdout=").append(stdout);
}
if (stderr != null) {
b.append(", stderr=").append(stderr);
}
return b.append('}').toString();
}

public int compareTo(CaseResult that) {
if (this == that) {
return 0;
Expand Down Expand Up @@ -693,7 +744,7 @@ public Status getStatus() {
}
}

/*package*/ void setClass(ClassResult classResult) {
public void setClass(ClassResult classResult) {
this.classResult = classResult;
}

Expand Down
15 changes: 13 additions & 2 deletions src/main/java/hudson/tasks/junit/ClassResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@
package hudson.tasks.junit;

import hudson.model.Run;
import hudson.tasks.junit.storage.TestResultImpl;
import hudson.tasks.junit.storage.TestResultStorage;
import hudson.tasks.test.TabulatedResult;
import hudson.tasks.test.TestResult;
import hudson.tasks.test.TestObject;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
Expand All @@ -52,7 +55,7 @@ public final class ClassResult extends TabulatedResult implements Comparable<Cla

private final PackageResult parent;

ClassResult(PackageResult parent, String className) {
public ClassResult(PackageResult parent, String className) {
this.parent = parent;
this.className = className;
}
Expand Down Expand Up @@ -128,7 +131,15 @@ public CaseResult getCaseResult(String name) {

@Override
public Object getDynamic(String name, StaplerRequest req, StaplerResponse rsp) {
CaseResult c = getCaseResult(name);
TestResultStorage storage = TestResultStorage.find();
CaseResult c;
if (storage != null) {
Run<?, ?> run = Stapler.getCurrentRequest().findAncestorObject(Run.class);
TestResultImpl pluggableStorage = storage.load(run.getParent().getFullName(), run.getNumber());
c = pluggableStorage.getCaseResult(name);
} else {
c = getCaseResult(name);
}
if (c != null) {
return c;
} else {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/hudson/tasks/junit/History.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public boolean historyAvailable() {

public List<TestResult> getList(int start, int end) {
List<TestResult> list = new ArrayList<>();
// TODO this must not stand
end = Math.min(end, testObject.getRun().getParent().getBuilds().size());
for (Run<?,?> b: testObject.getRun().getParent().getBuilds().subList(start, end)) {
if (b.isBuilding()) continue;
Expand Down Expand Up @@ -245,6 +246,7 @@ public String generateToolTip(CategoryDataset dataset, int row,
}

class ChartLabel implements Comparable<ChartLabel> {
// TODO allow use of a simplified label that does not force Run loading
TestResult o;
String url;
public ChartLabel(TestResult o) {
Expand Down
59 changes: 47 additions & 12 deletions src/main/java/hudson/tasks/junit/JUnitParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import hudson.model.AbstractBuild;
import hudson.model.Run;
import hudson.remoting.VirtualChannel;
import hudson.tasks.junit.storage.TestResultStorage;

import java.io.IOException;
import java.io.File;
Expand Down Expand Up @@ -102,36 +103,35 @@ public TestResult parseResult(String testResultLocations, Run<?,?> build, FilePa
public TestResult parseResult(String testResultLocations, Run<?,?> build, PipelineTestDetails pipelineTestDetails,
FilePath workspace, Launcher launcher, TaskListener listener)
throws InterruptedException, IOException {
final long buildTime = build.getTimestamp().getTimeInMillis();
final long timeOnMaster = System.currentTimeMillis();

// [BUG 3123310] TODO - Test Result Refactor: review and fix TestDataPublisher/TestAction subsystem]
// also get code that deals with testDataPublishers from JUnitResultArchiver.perform
return workspace.act(new DirectParseResultCallable(testResultLocations, build, keepLongStdio, allowEmptyResults, pipelineTestDetails));
}

return workspace.act(new ParseResultCallable(testResultLocations, buildTime, timeOnMaster, keepLongStdio,
allowEmptyResults, pipelineTestDetails));
public TestResultSummary summarizeResult(String testResultLocations, Run<?,?> build, PipelineTestDetails pipelineTestDetails,
FilePath workspace, Launcher launcher, TaskListener listener, TestResultStorage storage)
throws InterruptedException, IOException {
return workspace.act(new StorageParseResultCallable(testResultLocations, build, keepLongStdio, allowEmptyResults, pipelineTestDetails, storage.createRemotePublisher(build), listener));
}

private static final class ParseResultCallable extends MasterToSlaveFileCallable<TestResult> {
private static abstract class ParseResultCallable<T> extends MasterToSlaveFileCallable<T> {
private final long buildTime;
private final String testResults;
private final long nowMaster;
private final boolean keepLongStdio;
private final boolean allowEmptyResults;
private final PipelineTestDetails pipelineTestDetails;

private ParseResultCallable(String testResults, long buildTime, long nowMaster,
private ParseResultCallable(String testResults, Run<?,?> build,
boolean keepLongStdio, boolean allowEmptyResults,
PipelineTestDetails pipelineTestDetails) {
this.buildTime = buildTime;
this.buildTime = build.getTimestamp().getTimeInMillis();
this.testResults = testResults;
this.nowMaster = nowMaster;
this.nowMaster = System.currentTimeMillis();
this.keepLongStdio = keepLongStdio;
this.allowEmptyResults = allowEmptyResults;
this.pipelineTestDetails = pipelineTestDetails;
}

public TestResult invoke(File ws, VirtualChannel channel) throws IOException {
public T invoke(File ws, VirtualChannel channel) throws IOException {
final long nowSlave = System.currentTimeMillis();

FileSet fs = Util.createFileSet(ws, testResults);
Expand All @@ -151,8 +151,43 @@ public TestResult invoke(File ws, VirtualChannel channel) throws IOException {
throw new AbortException(Messages.JUnitResultArchiver_NoTestReportFound());
}
}
return handle(result);
}

protected abstract T handle(TestResult result) throws IOException;

}

private static final class DirectParseResultCallable extends ParseResultCallable<TestResult> {

DirectParseResultCallable(String testResults, Run<?,?> build, boolean keepLongStdio, boolean allowEmptyResults, PipelineTestDetails pipelineTestDetails) {
super(testResults, build, keepLongStdio, allowEmptyResults, pipelineTestDetails);
}

@Override
protected TestResult handle(TestResult result) throws IOException {
return result;
}

}

private static final class StorageParseResultCallable extends ParseResultCallable<TestResultSummary> {

private final TestResultStorage.RemotePublisher publisher;
private final TaskListener listener;

StorageParseResultCallable(String testResults, Run<?,?> build, boolean keepLongStdio, boolean allowEmptyResults, PipelineTestDetails pipelineTestDetails, TestResultStorage.RemotePublisher publisher, TaskListener listener) {
super(testResults, build, keepLongStdio, allowEmptyResults, pipelineTestDetails);
this.publisher = publisher;
this.listener = listener;
}

@Override
protected TestResultSummary handle(TestResult result) throws IOException {
publisher.publish(result, listener);
return new TestResultSummary(result);
}

}

}
Loading