feat: Job template for golang projects (OD-2106)

This commit is contained in:
Robin Shen 2024-09-29 15:33:21 +08:00
parent 5ac68b9a91
commit 4ef6a697d5
24 changed files with 316 additions and 311 deletions

View File

@ -2148,5 +2148,19 @@ public class BuildSpec implements Serializable, Validatable {
}
});
}
private void migrate36(VersionedYamlDoc doc, Stack<Integer> versions) {
migrateSteps(doc, versions, stepsNode -> {
for (var itStepNode = stepsNode.getValue().iterator(); itStepNode.hasNext();) {
MappingNode stepNode = (MappingNode) itStepNode.next();
var stepType = stepNode.getTag().getValue();
if (stepType.equals("!PublishCheckstyleReportStep")) {
stepNode.getValue().add(new NodeTuple(
new ScalarNode(Tag.STR, "tabWidth"),
new ScalarNode(Tag.STR, "8")));
}
}
});
}
}

View File

@ -26,6 +26,7 @@
<module>server-plugin-buildspec-python</module>
<module>server-plugin-buildspec-cmake</module>
<module>server-plugin-buildspec-bazel</module>
<module>server-plugin-buildspec-golang</module>
<module>server-plugin-authenticator-ldap</module>
<module>server-plugin-mailservice-smtpimap</module>
<module>server-plugin-mailservice-office365</module>

View File

@ -57,14 +57,14 @@ public class BazelJobSuggestion implements JobSuggestion {
setupCache.getLoadKeys().add("bazel_cache");
job.getSteps().add(setupCache);
CommandStep testAndCheck = new CommandStep();
testAndCheck.setName("build and test");
testAndCheck.setImage("1dev/bazelisk:1.0.2");
testAndCheck.getInterpreter().setCommands("" +
CommandStep buildAndTest = new CommandStep();
buildAndTest.setName("build and test");
buildAndTest.setImage("1dev/bazelisk:1.0.2");
buildAndTest.getInterpreter().setCommands("" +
"set -e\n" +
"bazelisk build //...\n" +
"bazelisk test //...");
job.getSteps().add(testAndCheck);
job.getSteps().add(buildAndTest);
addCommonJobsAndTriggers(job);
jobs.add(job);
}

View File

@ -67,7 +67,7 @@ public class CmakeJobSuggestion implements JobSuggestion {
var publishCoverageReport = new PublishCoberturaReportStep();
publishCoverageReport.setName("publish coverage report");
publishCoverageReport.setReportName("Coverage");
publishCoverageReport.setFilePatterns("coverage*.xml");
publishCoverageReport.setFilePatterns("coverage.xml");
publishCoverageReport.setCondition(ExecuteCondition.ALWAYS);
return publishCoverageReport;
}

View File

@ -0,0 +1,30 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>server-plugin-buildspec-golang</artifactId>
<parent>
<groupId>io.onedev</groupId>
<artifactId>server-plugin</artifactId>
<version>11.2.1</version>
</parent>
<dependencies>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-report-cobertura</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-report-junit</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-report-checkstyle</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<properties>
<moduleClass>io.onedev.server.plugin.buildspec.golang.GolangModule</moduleClass>
</properties>
</project>

View File

@ -0,0 +1,148 @@
package io.onedev.server.plugin.buildspec.golang;
import com.google.common.collect.Lists;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.k8shelper.ExecuteCondition;
import io.onedev.server.buildspec.job.Job;
import io.onedev.server.buildspec.job.JobSuggestion;
import io.onedev.server.buildspec.job.trigger.BranchUpdateTrigger;
import io.onedev.server.buildspec.job.trigger.PullRequestUpdateTrigger;
import io.onedev.server.buildspec.step.CheckoutStep;
import io.onedev.server.buildspec.step.CommandStep;
import io.onedev.server.buildspec.step.GenerateChecksumStep;
import io.onedev.server.buildspec.step.SetupCacheStep;
import io.onedev.server.git.BlobIdent;
import io.onedev.server.model.Build;
import io.onedev.server.model.Project;
import io.onedev.server.model.support.administration.GroovyScript;
import io.onedev.server.plugin.report.checkstyle.PublishCheckstyleReportStep;
import io.onedev.server.plugin.report.cobertura.PublishCoberturaReportStep;
import io.onedev.server.plugin.report.coverage.PublishCoverageReportStep;
import io.onedev.server.plugin.report.junit.PublishJUnitReportStep;
import io.onedev.server.util.interpolative.VariableInterpolator;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class GolangJobSuggestion implements JobSuggestion {
public static final String DETERMINE_GO_VERSION = "golang:determine-go-version";
private GenerateChecksumStep newChecksumGenerateStep(String name, String files) {
var generateChecksum = new GenerateChecksumStep();
generateChecksum.setName(name);
generateChecksum.setFiles(files);
generateChecksum.setTargetFile("checksum");
return generateChecksum;
}
private Job newJob() {
Job job = new Job();
job.setName("go ci");
return job;
}
private void addCommonJobsAndTriggers(Job job) {
var checkout = new CheckoutStep();
checkout.setName("checkout code");
job.getSteps().add(0, checkout);
job.getSteps().add(newCoverageReportPublishStep());
job.getSteps().add(newUnitTestReportPublishStep());
job.getSteps().add(newLintReportPublishStep());
job.getTriggers().add(new BranchUpdateTrigger());
job.getTriggers().add(new PullRequestUpdateTrigger());
}
private PublishCoverageReportStep newCoverageReportPublishStep() {
var publishCoverageReport = new PublishCoberturaReportStep();
publishCoverageReport.setName("publish coverage report");
publishCoverageReport.setReportName("Coverage");
publishCoverageReport.setFilePatterns("coverage.xml");
publishCoverageReport.setCondition(ExecuteCondition.ALWAYS);
return publishCoverageReport;
}
private PublishJUnitReportStep newUnitTestReportPublishStep() {
var publishUnitTestReport = new PublishJUnitReportStep();
publishUnitTestReport.setName("publish unit test report");
publishUnitTestReport.setReportName("Unit Test");
publishUnitTestReport.setFilePatterns("test-result.xml");
publishUnitTestReport.setCondition(ExecuteCondition.ALWAYS);
return publishUnitTestReport;
}
private PublishCheckstyleReportStep newLintReportPublishStep() {
var publishCheckstyleReportStep = new PublishCheckstyleReportStep();
publishCheckstyleReportStep.setName("publish lint report");
publishCheckstyleReportStep.setReportName("Lint");
publishCheckstyleReportStep.setFilePatterns("lint-result.xml");
publishCheckstyleReportStep.setCondition(ExecuteCondition.ALWAYS);
publishCheckstyleReportStep.setTabWidth(1);
return publishCheckstyleReportStep;
}
@Override
public Collection<Job> suggestJobs(Project project, ObjectId commitId) {
List<Job> jobs = new ArrayList<>();
if (project.getBlob(new BlobIdent(commitId.name(), "go.mod", FileMode.TYPE_FILE), false) != null) {
Job job = newJob();
job.getSteps().add(newChecksumGenerateStep("generate dependency checksum", "**/go.mod"));
var setupCache = new SetupCacheStep();
setupCache.setName("set up dependency cache");
setupCache.setKey("go_cache_@file:checksum@");
setupCache.setPaths(Lists.newArrayList("/root/.cache/go_build", "/root/.cache/golangci-lint", "/go/pkg/mod"));
setupCache.getLoadKeys().add("go_cache");
job.getSteps().add(setupCache);
CommandStep buildAndTest = new CommandStep();
buildAndTest.setName("build and test");
buildAndTest.setImage("golang:@" + VariableInterpolator.PREFIX_SCRIPT + GroovyScript.BUILTIN_PREFIX + DETERMINE_GO_VERSION + "@");
buildAndTest.getInterpreter().setCommands("" +
"set -e\n" +
"# Use double at to avoid being interpreted as OneDev variable substitution\n" +
"go install github.com/axw/gocov/gocov@@latest\n" +
"go install github.com/AlekSi/gocov-xml@@latest\n" +
"go install github.com/jstemmer/go-junit-report/v2@@latest\n" +
"set +e\n" +
"# Turn off vet as the optional golangci-lint can do this\n" +
"go test -vet=off -v -coverprofile=coverage.out ./... > test-result.out\n" +
"TEST_STATUS=$?\n" +
"go-junit-report -in test-result.out -out test-result.xml -set-exit-code\n" +
"if [ $? -ne 0 ]; then echo \"\\033[1;31mThere are test failures. Check test report for details\\033[0m\"; exit 1; fi\n" +
"if [ $TEST_STATUS -ne 0 ]; then exit 1; fi\n" +
"gocov convert coverage.out | gocov-xml > coverage.xml");
job.getSteps().add(buildAndTest);
var checkAndLint = new CommandStep();
checkAndLint.setName("check and lint");
checkAndLint.setImage("golangci/golangci-lint");
checkAndLint.setCondition(ExecuteCondition.NEVER);
checkAndLint.getInterpreter().setCommands("golangci-lint run --timeout=10m --issues-exit-code=0 --out-format=checkstyle > lint-result.xml");
job.getSteps().add(checkAndLint);
addCommonJobsAndTriggers(job);
jobs.add(job);
}
return jobs;
}
public static String determineGoVersion() {
String goVersion = "latest";
Build build = Build.get();
if (build != null) {
var blob = build.getProject().getBlob(new BlobIdent(build.getCommitHash(), "go.mod", FileMode.TYPE_FILE), false);
if (blob != null && blob.getText() != null) {
for (var line : blob.getText().getLines()) {
if (line.startsWith("go "))
goVersion = line.substring("go ".length()).trim();
}
}
}
return goVersion;
}
}

View File

@ -0,0 +1,35 @@
package io.onedev.server.plugin.buildspec.golang;
import com.google.common.collect.Lists;
import io.onedev.commons.loader.AbstractPluginModule;
import io.onedev.server.buildspec.job.JobSuggestion;
import io.onedev.server.model.support.administration.GroovyScript;
import io.onedev.server.util.ScriptContribution;
/**
* NOTE: Do not forget to rename moduleClass property defined in the pom if you've renamed this class.
*
*/
public class GolangModule extends AbstractPluginModule {
@Override
protected void configure() {
super.configure();
// put your guice bindings here
contribute(JobSuggestion.class, GolangJobSuggestion.class);
contribute(ScriptContribution.class, new ScriptContribution() {
@Override
public GroovyScript getScript() {
GroovyScript script = new GroovyScript();
script.setName(GolangJobSuggestion.DETERMINE_GO_VERSION);
script.setContent(Lists.newArrayList("io.onedev.server.plugin.buildspec.golang.GolangJobSuggestion.determineGoVersion()"));
return script;
}
});
}
}

View File

@ -42,7 +42,7 @@ public class PythonJobSuggestion implements JobSuggestion {
var publishUnitTestReport = new PublishJUnitReportStep();
publishUnitTestReport.setName("publish unit test report");
publishUnitTestReport.setReportName("Unit Test");
publishUnitTestReport.setFilePatterns("pytest-result*.xml");
publishUnitTestReport.setFilePatterns("pytest-result.xml");
publishUnitTestReport.setCondition(ExecuteCondition.ALWAYS);
return publishUnitTestReport;
}
@ -51,7 +51,7 @@ public class PythonJobSuggestion implements JobSuggestion {
var publishCoverageReport = new PublishCoberturaReportStep();
publishCoverageReport.setName("publish coverage report");
publishCoverageReport.setReportName("Coverage");
publishCoverageReport.setFilePatterns("coverage*.xml");
publishCoverageReport.setFilePatterns("coverage.xml");
publishCoverageReport.setCondition(ExecuteCondition.ALWAYS);
return publishCoverageReport;
}

View File

@ -34,7 +34,7 @@ public class PublishCheckstyleReportStep extends PublishProblemReportStep {
private static final long serialVersionUID = 1L;
private static final int TAB_WIDTH = 8;
private int tabWidth = 8;
@Editable(order=100, description="Specify checkstyle result xml file relative to <a href='https://docs.onedev.io/concepts#job-workspace'>job workspace</a>, "
+ "for instance, <tt>target/checkstyle-result.xml</tt>. "
@ -79,9 +79,12 @@ public class PublishCheckstyleReportStep extends PublishProblemReportStep {
Severity severity;
String severityStr = violationElement.attributeValue("severity");
if (severityStr.equalsIgnoreCase("error"))
severity = Severity.HIGH;
else if (severityStr.equalsIgnoreCase("warning"))
severity = Severity.MEDIUM;
else
severity = Severity.LOW;
String message = violationElement.attributeValue("source") + ": "
+ HtmlEscape.escapeHtml5(violationElement.attributeValue("message"));
int lineNo = Integer.parseInt(violationElement.attributeValue("line"))-1;
@ -90,9 +93,9 @@ public class PublishCheckstyleReportStep extends PublishProblemReportStep {
PlanarRange location;
if (column != null) {
int columnNo = Integer.parseInt(column)-1;
location = new PlanarRange(lineNo, columnNo, lineNo, -1, TAB_WIDTH);
location = new PlanarRange(lineNo, columnNo, lineNo, -1, tabWidth);
} else {
location = new PlanarRange(lineNo, -1, lineNo, -1, TAB_WIDTH);
location = new PlanarRange(lineNo, -1, lineNo, -1, tabWidth);
}
problems.add(new CodeProblem(severity, new BlobTarget(blobPath, location), message));
@ -110,5 +113,15 @@ public class PublishCheckstyleReportStep extends PublishProblemReportStep {
return problems;
}
@Editable(order=1000, group="More Settings", description="Specify tab width used to calculate " +
"column value of found problems in provided report")
public int getTabWidth() {
return tabWidth;
}
public void setTabWidth(int tabWidth) {
this.tabWidth = tabWidth;
}
}

View File

@ -1,21 +1,12 @@
package io.onedev.server.plugin.report.gtest;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import javax.validation.constraints.NotEmpty;
import com.google.common.collect.Lists;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Patterns;
import io.onedev.server.buildspec.BuildSpec;
import io.onedev.server.buildspec.step.StepGroup;
import io.onedev.server.model.Build;
@ -23,9 +14,16 @@ import io.onedev.server.plugin.report.unittest.PublishUnitTestReportStep;
import io.onedev.server.plugin.report.unittest.UnitTestReport;
import io.onedev.server.plugin.report.unittest.UnitTestReport.TestCase;
import io.onedev.server.util.XmlUtils;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Patterns;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import javax.validation.constraints.NotEmpty;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@Editable(order=10000, group=StepGroup.PUBLISH, name="Google Test Report")
public class PublishGTestReportStep extends PublishUnitTestReportStep {
@ -54,11 +52,6 @@ public class PublishGTestReportStep extends PublishUnitTestReportStep {
return BuildSpec.suggestVariables(matchWith, true, true, false);
}
@Override
public boolean requireCommitIndex() {
return true;
}
@Override
protected UnitTestReport process(Build build, File inputDir, TaskLogger logger) {
SAXReader reader = new SAXReader();

View File

@ -13,12 +13,6 @@
<artifactId>server-plugin-report-unittest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>commons-loader</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<moduleClass>io.onedev.server.plugin.report.junit.JUnitModule</moduleClass>

View File

@ -1,26 +1,23 @@
package io.onedev.server.plugin.report.junit;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.model.Build;
import io.onedev.server.plugin.report.unittest.UnitTestReport.Status;
import io.onedev.server.plugin.report.unittest.UnitTestReport.TestCase;
import io.onedev.server.plugin.report.unittest.UnitTestReport.TestSuite;
import org.apache.wicket.Component;
import org.apache.wicket.markup.html.basic.Label;
import org.dom4j.Document;
import org.dom4j.Element;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneDev;
import io.onedev.server.model.Build;
import io.onedev.server.plugin.report.unittest.UnitTestReport.Status;
import io.onedev.server.plugin.report.unittest.UnitTestReport.TestCase;
import io.onedev.server.plugin.report.unittest.UnitTestReport.TestSuite;
import io.onedev.server.search.code.CodeSearchManager;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class JUnitReportParser {
public static List<TestCase> parse(Build build, Document doc) {
public static List<TestCase> parse(Document doc) {
List<TestCase> testCases = new ArrayList<>();
Element rootElement = doc.getRootElement();
@ -58,12 +55,7 @@ public class JUnitReportParser {
else
status = Status.PASSED;
var symbolHit = OneDev.getInstance(CodeSearchManager.class).findPrimarySymbol(
build.getProject(), build.getCommitId(), name, ".");
var blobPath = symbolHit != null? symbolHit.getBlobPath(): null;
var position = symbolHit != null? symbolHit.getHitPos(): null;
TestSuite testSuite = new TestSuite(name, status, duration, blobPath, position) {
TestSuite testSuite = new TestSuite(name, status, duration, null, null) {
@Nullable
@Override
@ -86,7 +78,7 @@ public class JUnitReportParser {
} else {
duration = getDouble(testCaseElement.attributeValue("time"));
status = Status.PASSED;
String message = null;
String message;
Element failureElement = testCaseElement.element("failure");
Element errorElement = testCaseElement.element("error");
if (failureElement != null) {
@ -95,16 +87,16 @@ public class JUnitReportParser {
} else if (errorElement != null) {
status = Status.NOT_PASSED;
message = errorElement.getText();
} else {
message = null;
}
var finalMessage = message;
testCases.add(new TestCase(testSuite, name, status, null, duration) {
@Nullable
@Override
protected Component renderDetail(String componentId, Build build) {
if (finalMessage != null)
return new Label(componentId, finalMessage);
if (message != null)
return new Label(componentId, message);
else
return null;
}

View File

@ -1,21 +1,12 @@
package io.onedev.server.plugin.report.junit;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import javax.validation.constraints.NotEmpty;
import com.google.common.collect.Lists;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Patterns;
import io.onedev.server.buildspec.BuildSpec;
import io.onedev.server.buildspec.step.StepGroup;
import io.onedev.server.model.Build;
@ -23,9 +14,16 @@ import io.onedev.server.plugin.report.unittest.PublishUnitTestReportStep;
import io.onedev.server.plugin.report.unittest.UnitTestReport;
import io.onedev.server.plugin.report.unittest.UnitTestReport.TestCase;
import io.onedev.server.util.XmlUtils;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Patterns;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import javax.validation.constraints.NotEmpty;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@Editable(order=10000, group=StepGroup.PUBLISH, name="JUnit Report")
public class PublishJUnitReportStep extends PublishUnitTestReportStep {
@ -52,11 +50,6 @@ public class PublishJUnitReportStep extends PublishUnitTestReportStep {
return BuildSpec.suggestVariables(matchWith, true, true, false);
}
@Override
public boolean requireCommitIndex() {
return true;
}
@Override
protected UnitTestReport process(Build build, File inputDir, TaskLogger logger) {
SAXReader reader = new SAXReader();
@ -70,7 +63,7 @@ public class PublishJUnitReportStep extends PublishUnitTestReportStep {
try {
String xml = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
xml = XmlUtils.stripDoctype(xml);
testCases.addAll(JUnitReportParser.parse(build, reader.read(new StringReader(xml))));
testCases.addAll(JUnitReportParser.parse(reader.read(new StringReader(xml))));
} catch (DocumentException e) {
logger.warning("Ignored test report '" + relativePath + "' as it is not a valid XML");
} catch (IOException e) {

View File

@ -1,74 +1,24 @@
package io.onedev.server.plugin.report.junit;
import com.google.common.io.Resources;
import io.onedev.commons.jsymbol.Symbol;
import io.onedev.commons.loader.AppLoader;
import io.onedev.commons.loader.AppLoaderMocker;
import io.onedev.server.model.Build;
import io.onedev.server.model.Project;
import io.onedev.server.plugin.report.unittest.UnitTestReport;
import io.onedev.server.plugin.report.unittest.UnitTestReport.Status;
import io.onedev.server.search.code.CodeSearchManager;
import io.onedev.server.search.code.hit.QueryHit;
import io.onedev.server.search.code.hit.SymbolHit;
import io.onedev.server.search.code.query.BlobQuery;
import io.onedev.server.search.code.query.TooGeneralQueryException;
import org.apache.lucene.search.IndexSearcher;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.eclipse.jgit.lib.ObjectId;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class JUnitReportParserTest extends AppLoaderMocker {
public class JUnitReportParserTest {
@Test
public void testJUnit() {
try (InputStream is = Resources.getResource(JUnitReportParserTest.class, "test-result.xml").openStream()) {
Mockito.when(AppLoader.getInstance(CodeSearchManager.class)).thenReturn(new CodeSearchManager() {
@Override
public List<QueryHit> search(Project project, ObjectId commit, BlobQuery query)
throws TooGeneralQueryException {
return null;
}
@Override
public List<Symbol> getSymbols(Project project, ObjectId blobId, String blobPath) {
return null;
}
@Override
public List<Symbol> getSymbols(IndexSearcher searcher, ObjectId blobId, String blobPath) {
return null;
}
@Override
public String findBlobPathBySuffix(Project project, ObjectId commit, String blobPathSuffix) {
return "Test.java";
}
@Nullable
@Override
public SymbolHit findPrimarySymbol(Project project, ObjectId commitId, String symbolFQN, String fqnSeparator) {
return null;
}
});
Build build = new Build();
build.setCommitHash(ObjectId.zeroId().name());
public void testParse() {
try (var is = Resources.getResource(JUnitReportParserTest.class, "test-result.xml").openStream()) {
SAXReader reader = new SAXReader();
UnitTestReport report = new UnitTestReport(JUnitReportParser.parse(build, reader.read(is)), true);
UnitTestReport report = new UnitTestReport(JUnitReportParser.parse(reader.read(is)), true);
assertEquals(1, report.getTestSuites().size());
assertEquals(1, report.getTestCases(null, null, Sets.newSet(Status.PASSED)).size());
@ -77,48 +27,11 @@ public class JUnitReportParserTest extends AppLoaderMocker {
} catch (IOException|DocumentException e) {
throw new RuntimeException(e);
}
}
@Test
public void testJUnitReport() {
try (InputStream is = Resources.getResource(JUnitReportParserTest.class, "test-results.xml").openStream()) {
Mockito.when(AppLoader.getInstance(CodeSearchManager.class)).thenReturn(new CodeSearchManager() {
@Override
public List<QueryHit> search(Project project, ObjectId commit, BlobQuery query)
throws TooGeneralQueryException {
return null;
}
@Override
public List<Symbol> getSymbols(Project project, ObjectId blobId, String blobPath) {
return null;
}
@Override
public List<Symbol> getSymbols(IndexSearcher searcher, ObjectId blobId, String blobPath) {
return null;
}
@Override
public String findBlobPathBySuffix(Project project, ObjectId commit, String blobPathSuffix) {
return "Test.java";
}
@Nullable
@Override
public SymbolHit findPrimarySymbol(Project project, ObjectId commitId, String symbolFQN, String fqnSeparator) {
return null;
}
});
Build build = new Build();
build.setCommitHash(ObjectId.zeroId().name());
}
try (var is = Resources.getResource(JUnitReportParserTest.class, "test-result2.xml").openStream()) {
SAXReader reader = new SAXReader();
UnitTestReport report = new UnitTestReport(JUnitReportParser.parse(build, reader.read(is)), true);
UnitTestReport report = new UnitTestReport(JUnitReportParser.parse(reader.read(is)), true);
assertEquals(2, report.getTestSuites().size());
assertEquals(2, report.getTestCases(null, null, Sets.newSet(Status.PASSED)).size());
@ -130,12 +43,4 @@ public class JUnitReportParserTest extends AppLoaderMocker {
}
}
@Override
protected void setup() {
}
@Override
protected void teardown() {
}
}

View File

@ -13,12 +13,6 @@
<artifactId>server-plugin-report-unittest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>commons-loader</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<moduleClass>io.onedev.server.plugin.report.trx.TRXModule</moduleClass>

View File

@ -69,7 +69,7 @@ public class PublishTRXReportStep extends PublishUnitTestReportStep {
try {
String xml = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
xml = XmlUtils.stripDoctype(StringUtils.removeBOM(xml));
testCases.addAll(TRXReportParser.parse(build, reader.read(new StringReader(xml))));
testCases.addAll(TRXReportParser.parse(reader.read(new StringReader(xml))));
} catch (DocumentException e) {
logger.warning("Ignored TRX report '" + relativePath + "' as it is not a valid XML");
} catch (IOException e) {

View File

@ -3,13 +3,11 @@ package io.onedev.server.plugin.report.trx;
import com.google.common.base.Splitter;
import io.onedev.commons.utils.PlanarRange;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneDev;
import io.onedev.server.git.BlobIdent;
import io.onedev.server.model.Build;
import io.onedev.server.plugin.report.unittest.UnitTestReport.Status;
import io.onedev.server.plugin.report.unittest.UnitTestReport.TestCase;
import io.onedev.server.plugin.report.unittest.UnitTestReport.TestSuite;
import io.onedev.server.search.code.CodeSearchManager;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.StringTransformer;
import io.onedev.server.web.page.project.blob.ProjectBlobPage;
@ -36,7 +34,7 @@ public class TRXReportParser {
private static final Pattern PATTERN_LOCATION = Pattern.compile("\\sin\\s(.*):line\\s(\\d+)(\\s|$)", Pattern.MULTILINE);
public static List<TestCase> parse(Build build, Document doc) {
public static List<TestCase> parse(Document doc) {
List<TestCase> testCases = new ArrayList<>();
Element testRunElement = doc.getRootElement();
@ -97,14 +95,10 @@ public class TRXReportParser {
testCaseDatum.computeIfAbsent(testClass, it -> new ArrayList<>()).add(testCaseData);
}
var searchManager = OneDev.getInstance(CodeSearchManager.class);
for (var entry: testCaseDatum.entrySet()) {
Status status = getOverallStatus(entry.getValue().stream().map(it->it.status).collect(toSet()));
var duration = entry.getValue().stream().mapToLong(it -> it.duration).sum();
var symbolHit = searchManager.findPrimarySymbol(build.getProject(), build.getCommitId(), entry.getKey(), ".");
var blobPath = symbolHit != null? symbolHit.getBlobPath(): null;
var position = symbolHit != null? symbolHit.getHitPos(): null;
var testSuite = new TestSuite(entry.getKey(), status, duration, blobPath, position) {
var testSuite = new TestSuite(entry.getKey(), status, duration, null, null) {
@Override
protected Component renderDetail(String componentId, Build build) {

View File

@ -1,89 +1,29 @@
package io.onedev.server.plugin.report.trx;
import com.google.common.io.Resources;
import io.onedev.commons.jsymbol.Symbol;
import io.onedev.commons.jsymbol.java.symbols.TypeSymbol;
import io.onedev.commons.loader.AppLoader;
import io.onedev.commons.loader.AppLoaderMocker;
import io.onedev.server.model.Build;
import io.onedev.server.model.Project;
import io.onedev.server.plugin.report.unittest.UnitTestReport;
import io.onedev.server.search.code.CodeSearchManager;
import io.onedev.server.search.code.hit.QueryHit;
import io.onedev.server.search.code.hit.SymbolHit;
import io.onedev.server.search.code.query.BlobQuery;
import io.onedev.server.search.code.query.TooGeneralQueryException;
import org.apache.lucene.search.IndexSearcher;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.eclipse.jgit.lib.ObjectId;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class TRXReportParserTest extends AppLoaderMocker {
public class TRXReportParserTest {
@Test
public void testParse() {
try (InputStream is = Resources.getResource(TRXReportParserTest.class, "test.trx").openStream()) {
Mockito.when(AppLoader.getInstance(CodeSearchManager.class)).thenReturn(new CodeSearchManager() {
@Override
public List<QueryHit> search(Project project, ObjectId commit, BlobQuery query)
throws TooGeneralQueryException {
return null;
}
@Override
public List<Symbol> getSymbols(Project project, ObjectId blobId, String blobPath) {
return null;
}
@Override
public List<Symbol> getSymbols(IndexSearcher searcher, ObjectId blobId, String blobPath) {
return null;
}
@Override
public String findBlobPathBySuffix(Project project, ObjectId commit, String blobPathSuffix) {
return "Test.java";
}
@Nullable
@Override
public SymbolHit findPrimarySymbol(Project project, ObjectId commitId, String symbolFQN, String fqnSeparator) {
return null;
}
});
Build build = new Build();
build.setCommitHash(ObjectId.zeroId().name());
try (var is = Resources.getResource(TRXReportParserTest.class, "test.trx").openStream()) {
SAXReader reader = new SAXReader();
UnitTestReport report = new UnitTestReport(TRXReportParser.parse(build, reader.read(is)), true);
UnitTestReport report = new UnitTestReport(TRXReportParser.parse(reader.read(is)), true);
assertEquals(4, report.getTestSuites().size());
assertEquals(3, report.getTestCases(null, null, Sets.newSet(UnitTestReport.Status.PASSED)).size());
assertEquals(2, report.getTestCases(null, null, Sets.newSet(UnitTestReport.Status.NOT_PASSED)).size());
} catch (IOException | DocumentException e) {
throw new RuntimeException(e);
}
}
@Override
protected void setup() {
}
@Override
protected void teardown() {
}
}

View File

@ -424,21 +424,9 @@ public class UnitTestCasesPage extends UnitTestReportPage {
TestCase testCase = item.getModelObject();
item.add(new TestStatusBadge("status", testCase.getStatus()));
var name = escapeHtml5(testCase.getName());
if (testCase.getTestSuite().getBlobPath() != null && SecurityUtils.canReadCode(getProject())) {
var sourceViewState = new ProjectBlobPage.State();
sourceViewState.blobIdent = new BlobIdent(getBuild().getCommitHash(), testCase.getTestSuite().getBlobPath());
if (testCase.getTestSuite().getPosition() != null)
sourceViewState.position = BlobRenderer.getSourcePosition(testCase.getTestSuite().getPosition());
var blobUrl = urlFor(ProjectBlobPage.class, ProjectBlobPage.paramsOf(getProject(), sourceViewState));
name += " (<a href='" + blobUrl + "' target='_blank'>" + escapeHtml5(testCase.getTestSuite().getName()) + "</a>)";
} else {
name += " (" + escapeHtml5(testCase.getTestSuite().getName()) + ")";
}
if (testCase.getStatusText() != null && !testCase.getStatusText().equalsIgnoreCase(testCase.getStatus().name().replace("_", " "))) {
var name = escapeHtml5(testCase.getName()) + (" (" + escapeHtml5(testCase.getTestSuite().getName()) + ")");
if (testCase.getStatusText() != null && !testCase.getStatusText().equalsIgnoreCase(testCase.getStatus().name().replace("_", " ")))
name = name + escapeHtml5(" [" + testCase.getStatusText() + "]");
}
item.add(new Label("name", name).setEscapeModelStrings(false));
if (getReport().hasTestCaseDuration())
item.add(new Label("duration", DurationFormatUtils.formatDuration(testCase.getDuration(), "s.SSS 's'")));

View File

@ -1,9 +1,9 @@
package io.onedev.server.plugin.report.unittest;
import io.onedev.commons.utils.PlanarRange;
import io.onedev.server.model.Build;
import io.onedev.commons.utils.match.Matcher;
import io.onedev.commons.utils.match.PathMatcher;
import io.onedev.server.model.Build;
import io.onedev.server.util.patternset.PatternSet;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.wicket.Component;
@ -158,17 +158,11 @@ public class UnitTestReport implements Serializable {
private final long duration;
private final String blobPath;
private final PlanarRange position;
public TestSuite(String name, Status status, long duration, @Nullable String blobPath,
@Nullable PlanarRange position) {
this.name = name;
this.status = status;
this.duration = duration;
this.blobPath = blobPath;
this.position = position;
}
public String getName() {
@ -183,16 +177,6 @@ public class UnitTestReport implements Serializable {
return status;
}
@Nullable
public String getBlobPath() {
return blobPath;
}
@Nullable
public PlanarRange getPosition() {
return position;
}
@Nullable
protected abstract Component renderDetail(String componentId, Build build);

View File

@ -29,7 +29,6 @@
<span wicket:id="status" class="mr-3"></span>
<span class="mr-4">
<a wicket:id="testCases" class="text-break" title="Show test cases of this test suite"><span wicket:id="label"></span></a>
<a wicket:id="viewSource" title="View source file" target="_blank"><wicket:svg href="file" class="icon"/></a>
</span>
<span wicket:id="duration" class="ml-auto"></span>
</div>

View File

@ -5,11 +5,9 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.commons.codeassist.parser.TerminalExpect;
import io.onedev.server.git.BlobIdent;
import io.onedev.server.model.Build;
import io.onedev.server.plugin.report.unittest.UnitTestReport.Status;
import io.onedev.server.plugin.report.unittest.UnitTestReport.TestSuite;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.patternset.PatternSet;
import io.onedev.server.web.WebConstants;
import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener;
@ -19,8 +17,6 @@ import io.onedev.server.web.component.chart.pie.PieChartPanel;
import io.onedev.server.web.component.chart.pie.PieSlice;
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
import io.onedev.server.web.component.pagenavigator.OnePagingNavigator;
import io.onedev.server.web.page.project.blob.ProjectBlobPage;
import io.onedev.server.web.page.project.blob.render.BlobRenderer;
import io.onedev.server.web.util.SuggestionUtils;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.apache.wicket.Component;
@ -42,7 +38,6 @@ import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.string.StringValue;
import org.eclipse.jgit.lib.FileMode;
import javax.annotation.Nullable;
import java.io.Serializable;
@ -325,18 +320,6 @@ public class UnitTestSuitesPage extends UnitTestReportPage {
Link<Void> link = new ViewStateAwarePageLink<Void>("testCases",
UnitTestCasesPage.class, params);
link.add(new Label("label", testSuite.getName()));
if (testSuite.getBlobPath() != null && SecurityUtils.canReadCode(getProject())) {
var sourceViewState = new ProjectBlobPage.State();
sourceViewState.blobIdent = new BlobIdent(getBuild().getCommitHash(), testSuite.getBlobPath());
if (testSuite.getPosition() != null)
sourceViewState.position = BlobRenderer.getSourcePosition(testSuite.getPosition());
item.add(new ViewStateAwarePageLink<Void>("viewSource", ProjectBlobPage.class,
ProjectBlobPage.paramsOf(getProject(), sourceViewState)));
} else {
item.add(new WebMarkupContainer("viewSource").setVisible(false));
}
item.add(new Label("duration", DurationFormatUtils.formatDuration(testSuite.getDuration(), "s.SSS 's'")));
item.add(link);

View File

@ -80,6 +80,11 @@
<artifactId>server-plugin-buildspec-bazel</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-buildspec-golang</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-buildspec-node</artifactId>