Skip to content

Commit

Permalink
Support Java parameterized test cases
Browse files Browse the repository at this point in the history
From maven-surefire-plugin:3.0.0-M6 onwards method filtering (thus parameters) are supported in includesFile and excludesFile.

Add new testmode that allows to split and run in parallel Java parameterized tests. The JavaTestCaseName mode is retained for backwards compatibility reasons.
  • Loading branch information
zabetak committed Sep 10, 2024
1 parent fe24090 commit 4cf0586
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ public boolean isSplitByCase() {
return false;
}

public boolean useParameters() {
return false;

Check warning on line 52 in src/main/java/org/jenkinsci/plugins/parallel_test_executor/testmode/JavaClassName.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 52 is not covered by tests
}

@Override
@NonNull
public Map<String, TestEntity> getTestEntitiesMap(@NonNull ClassResult classResult) {
if (isSplitByCase()) {
return classResult.getChildren().stream().map(JavaTestCase::new).collect(Collectors.toMap(JavaTestCase::getKey, identity(), JavaTestCase::new));
return classResult.getChildren().stream().map(cr -> new JavaTestCase(cr, useParameters())).collect(Collectors.toMap(JavaTestCase::getKey, identity(), JavaTestCase::new));
} else {
TestClass testClass = new TestClass(classResult);
return Map.of(testClass.getKey(), testClass);
Expand Down Expand Up @@ -90,10 +94,14 @@ public String getWord() {

private static class JavaTestCase extends TestEntity {
private final String output;
private JavaTestCase(CaseResult cr) {
private JavaTestCase(CaseResult cr, boolean useParams) {
// Parameterized tests use ${fqdnClassName}#${methodName}[{parametersDescription}] format
// passing parameters to surefire is not supported, so just drop them and will sum durations
this.output = cr.getClassName() + "#" + cr.getName().split("\\[")[0];
if (useParams) {
this.output = cr.getClassName() + "#" + cr.getName();
} else {
// Some surefire versions don't support parameters, so just drop them and will sum durations
this.output = cr.getClassName() + "#" + cr.getName().split("\\[")[0];
}
this.duration = (long)(cr.getDuration()*1000); // milliseconds is a good enough precision for us
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jenkinsci.plugins.parallel_test_executor.testmode;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Descriptor;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;

/**
* This mode works best with java projects.
* <p>
* Parallelize per java test case including parameters if present.
* </p>
* <p>
* It is also able to estimate tests to run from the workspace content if no historical context could be found.
* </p>
*/
public class JavaParameterizedTestCaseName extends JavaClassName {
@DataBoundConstructor
public JavaParameterizedTestCaseName() {
}

@Override
public boolean isSplitByCase() {
return true;
}

@Override
public boolean useParameters() {
return true;
}

@Extension
@Symbol("javaParamTestCase")
public static class DescriptorImpl extends Descriptor<TestMode> {
@Override
@NonNull
public String getDisplayName() {
return "By Java test cases with parameters";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* This mode works best with java projects.
* <p>
* Parallelize per java test case.
* Parallelize per java test case ingoring parameters if present.
* </p>
* <p>
* It is also able to estimate tests to run from the workspace content if no historical context could be found.
Expand All @@ -24,13 +24,17 @@ public boolean isSplitByCase() {
return true;
}

@Override public boolean useParameters() {
return false;
}

@Extension
@Symbol("javaTestCase")
public static class DescriptorImpl extends Descriptor<TestMode> {
@Override
@NonNull
public String getDisplayName() {
return "By Java test cases";
return "By Java test cases without parameters";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div>
This mode works best with Java projects.
<p>
Parallelize per Java test case including parameters.
<p>
It is also able to estimate tests (per class) to run from the workspace content if no historical context could be found.
</div>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div>
This mode works best with Java projects.
<p>
Parallelize per Java test case.
Parallelize per Java test case ignoring parameters. If parameters are present they are considered the same test.
<p>
It is also able to estimate tests (per class) to run from the workspace content if no historical context could be found.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.stream.Collectors;
import org.apache.tools.ant.DirectoryScanner;
import org.hamcrest.Matchers;
import org.jenkinsci.plugins.parallel_test_executor.testmode.JavaParameterizedTestCaseName;
import org.jenkinsci.plugins.parallel_test_executor.testmode.JavaTestCaseName;
import org.jenkinsci.plugins.parallel_test_executor.testmode.JavaClassName;
import org.jenkinsci.plugins.parallel_test_executor.testmode.TestClassAndCaseName;
Expand Down Expand Up @@ -152,6 +153,19 @@ public void findTestCasesWithParameters() throws Exception {
assertThat(allSplits, hasItem("org.jenkinsci.plugins.parallel_test_executor.Test1#testCase"));
}

@Test
public void findTestCasesWithParametersIncluded() throws Exception {
TestResult testResult = new TestResult(0L, scanner, false);
testResult.tally();
when(action.getResult()).thenReturn(testResult);
CountDrivenParallelism parallelism = new CountDrivenParallelism(3);
List<InclusionExclusionPattern> splits = Splitter.findTestSplits(parallelism, new JavaParameterizedTestCaseName(), build, listener, false, null, null);
assertEquals(3, splits.size());
var allSplits = splits.stream().flatMap(s -> s.getList().stream()).collect(Collectors.toSet());
assertThat(allSplits, hasSize(22));
assertThat(allSplits, hasItem("org.jenkinsci.plugins.parallel_test_executor.Test1#testCase[param1]"));
}

@Test
public void findTestSplitsInclusions() throws Exception {
CountDrivenParallelism parallelism = new CountDrivenParallelism(5);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="org.jenkinsci.plugins.parallel_test_executor.Test1" time="112.22" tests="21" errors="0" skipped="0" failures="0">
<testcase name="testCase[param1]" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="1.00"/>
<testcase name="testCase[param2]" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="1.00"/>
<testcase name="testCase[param3]" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="1.00"/>
<testcase name="test1Case2" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="2.00"/>
<testcase name="test1Case3" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="3.00"/>
<testcase name="test1Case4" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="4.00"/>
<testcase name="test1Case5" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="5.00"/>
<testcase name="test1Case6" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="6.00"/>
<testcase name="test1Case7" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="7.00"/>
<testcase name="test1Case8" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="8.00"/>
<testcase name="test1Case9" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="9.00"/>
<testcase name="test1Case10" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="10.00"/>
<testcase name="test1Case11" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="1.00"/>
<testcase name="test1Case12" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="2.00"/>
<testcase name="test1Case13" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="3.00"/>
<testcase name="test1Case14" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="4.00"/>
<testcase name="test1Case15" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="5.00"/>
<testcase name="test1Case16" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="6.00"/>
<testcase name="test1Case17" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="7.00"/>
<testcase name="test1Case18" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="8.00"/>
<testcase name="test1Case19" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="9.00"/>
<testcase name="test1Case20" classname="org.jenkinsci.plugins.parallel_test_executor.Test1" time="10.22"/>
</testsuite>

0 comments on commit 4cf0586

Please sign in to comment.