diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/RunIntegrationTests.java b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/RunIntegrationTests.java
index bf9e6a214e37..3bfe730d8ed1 100644
--- a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/RunIntegrationTests.java
+++ b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/RunIntegrationTests.java
@@ -17,6 +17,7 @@
package org.springframework.boot.maven;
import java.io.File;
+import java.io.IOException;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.condition.DisabledOnOs;
@@ -101,6 +102,19 @@ void whenUseTestClasspathIsEnabledTheApplicationHasTestDependenciesOnItsClasspat
.execute((project) -> assertThat(buildLog(project)).contains("I haz been run"));
}
+ @TestTemplate
+ void whenUseTestClasspathIsAllTheApplicationRunsWithTestClassesAndDependencies(MavenBuild mavenBuild) {
+ mavenBuild.project("test-run")
+ .goals("spring-boot:run", "-X")
+ .systemProperty("spring-boot.run.useTestClasspath", "ALL")
+ .execute((project) -> assertThat(buildLog(project))
+ .contains("Main class name = org.test.TestSampleApplication")
+ .contains("1. " + canonicalPathOf(project, "target/test-classes"))
+ .contains("2. " + canonicalPathOf(project, "target/classes"))
+ .containsPattern("3\\. .*spring-core")
+ .containsPattern("4\\. .*commons-logging"));
+ }
+
@TestTemplate
void whenAWorkingDirectoryIsConfiguredTheApplicationIsRunFromThatDirectory(MavenBuild mavenBuild) {
mavenBuild.project("run-working-directory")
@@ -163,4 +177,8 @@ private String buildLog(File project) {
return contentOf(new File(project, "target/build.log"));
}
+ private String canonicalPathOf(File project, String path) throws IOException {
+ return new File(project, path).getCanonicalPath();
+ }
+
}
diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/StartStopIntegrationTests.java b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/StartStopIntegrationTests.java
index 5e6b8efe1b04..c93c8fc44002 100644
--- a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/StartStopIntegrationTests.java
+++ b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/StartStopIntegrationTests.java
@@ -36,7 +36,18 @@ class StartStopIntegrationTests {
void startStopWaitsForApplicationToBeReadyAndThenRequestsShutdown(MavenBuild mavenBuild) {
mavenBuild.project("start-stop")
.goals("verify")
- .execute((project) -> assertThat(buildLog(project)).contains("isReady: true")
+ .execute((project) -> assertThat(buildLog(project)).doesNotContain("Running application from test classes")
+ .contains("isReady: true")
+ .contains("Shutdown requested"));
+ }
+
+ @TestTemplate
+ void startStopWithAllTestClasspathRunsApplicationFromTestClasses(MavenBuild mavenBuild) {
+ mavenBuild.project("start-stop")
+ .goals("verify")
+ .systemProperty("use-test-classpath", "ALL")
+ .execute((project) -> assertThat(buildLog(project)).contains("Running application from test classes")
+ .contains("isReady: true")
.contains("Shutdown requested"));
}
diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop/pom.xml b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop/pom.xml
index 3c533ce7669c..9795e613e950 100644
--- a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop/pom.xml
+++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop/pom.xml
@@ -9,6 +9,7 @@
UTF-8
@java.version@
@java.version@
+ false
@@ -51,6 +52,7 @@
${jmx.port}
+ ${use-test-classpath}
diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop/src/test/java/org/test/TestSampleApplication.java b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop/src/test/java/org/test/TestSampleApplication.java
new file mode 100644
index 000000000000..73996bd5d774
--- /dev/null
+++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/start-stop/src/test/java/org/test/TestSampleApplication.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012-present the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.test;
+
+/**
+ * Test application used to verify that the start goal can use test classes.
+ *
+ * @author Henrique (henriquejsza)
+ */
+public class TestSampleApplication {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Running application from test classes");
+ SampleApplication.main(args);
+ }
+
+}
diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java
index 08feb075567f..56212adafce5 100644
--- a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java
+++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java
@@ -51,6 +51,7 @@
* @author Daniel Young
* @author Dmytro Nosan
* @author Moritz Halbritter
+ * @author Henrique (henriquejsza)
* @since 1.3.0
* @see RunMojo
* @see StartMojo
@@ -205,6 +206,14 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
@SuppressWarnings("NullAway.Init")
private File classesDirectory;
+ /**
+ * Directory containing the test classes and resource files that should be used to run
+ * the application.
+ */
+ @Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true)
+ @SuppressWarnings("NullAway.Init")
+ private File testClassesDirectory;
+
/**
* Skip the execution.
*
@@ -241,11 +250,20 @@ private String determineMainClass() throws MojoExecutionException {
* @since 3.1.0
*/
protected List getClassesDirectories() {
- return List.of(this.classesDirectory);
+ List classesDirectories = new ArrayList<>();
+ if (isUseTestClasses()) {
+ classesDirectories.add(this.testClassesDirectory);
+ }
+ classesDirectories.add(this.classesDirectory);
+ return classesDirectories;
}
protected abstract boolean isUseTestClasspath();
+ protected boolean isUseTestClasses() {
+ return false;
+ }
+
private void run(String startClassName) throws MojoExecutionException, MojoFailureException {
List args = new ArrayList<>();
addAgents(args);
diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java
index 8992ed3d55f6..1cbb08840fc4 100644
--- a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java
+++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java
@@ -55,11 +55,15 @@ public class RunMojo extends AbstractRunMojo {
private boolean optimizedLaunch;
/**
- * Flag to include the test classpath when running.
+ * Test classpath to use when running. Valid values are {@code OFF} (no test
+ * classpath), {@code DEPENDENCIES} (test dependencies only), and {@code ALL} (test
+ * dependencies, classes, and resources). For backwards compatibility, {@code false}
+ * is equivalent to {@code OFF} and {@code true} is equivalent to
+ * {@code DEPENDENCIES}.
* @since 1.3.0
*/
@Parameter(property = "spring-boot.run.useTestClasspath", defaultValue = "false")
- private boolean useTestClasspath;
+ private String useTestClasspath = "false";
@Inject
public RunMojo(ToolchainManager toolchainManager) {
@@ -86,7 +90,12 @@ protected void run(JavaProcessExecutor processExecutor, File workingDirectory, L
@Override
protected boolean isUseTestClasspath() {
- return this.useTestClasspath;
+ return TestClasspath.of(this.useTestClasspath).isUseTestDependencies();
+ }
+
+ @Override
+ protected boolean isUseTestClasses() {
+ return TestClasspath.of(this.useTestClasspath).isUseTestClasses();
}
private static final class RunProcessKiller implements Runnable {
diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java
index 728e663546c0..d64475d3350e 100644
--- a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java
+++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java
@@ -90,10 +90,14 @@ public class StartMojo extends AbstractRunMojo {
private final Object lock = new Object();
/**
- * Flag to include the test classpath when running.
+ * Test classpath to use when running. Valid values are {@code OFF} (no test
+ * classpath), {@code DEPENDENCIES} (test dependencies only), and {@code ALL} (test
+ * dependencies, classes, and resources). For backwards compatibility, {@code false}
+ * is equivalent to {@code OFF} and {@code true} is equivalent to
+ * {@code DEPENDENCIES}.
*/
@Parameter(property = "spring-boot.run.useTestClasspath", defaultValue = "false")
- private boolean useTestClasspath;
+ private String useTestClasspath = "false";
@Inject
public StartMojo(ToolchainManager toolchainManager) {
@@ -206,7 +210,12 @@ public T execute(long wait, int maxAttempts, Callable<@Nullable T> callback)
@Override
protected boolean isUseTestClasspath() {
- return this.useTestClasspath;
+ return TestClasspath.of(this.useTestClasspath).isUseTestDependencies();
+ }
+
+ @Override
+ protected boolean isUseTestClasses() {
+ return TestClasspath.of(this.useTestClasspath).isUseTestClasses();
}
private class CreateJmxConnector implements Callable<@Nullable JMXConnector> {
diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/TestClasspath.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/TestClasspath.java
new file mode 100644
index 000000000000..a17f0c3b9d53
--- /dev/null
+++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/TestClasspath.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012-present the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.boot.maven;
+
+import java.util.Locale;
+
+/**
+ * Use of the test classpath when running an application.
+ *
+ * @author Henrique (henriquejsza)
+ */
+enum TestClasspath {
+
+ OFF(false, false),
+
+ DEPENDENCIES(true, false),
+
+ ALL(true, true);
+
+ private final boolean useTestDependencies;
+
+ private final boolean useTestClasses;
+
+ TestClasspath(boolean useTestDependencies, boolean useTestClasses) {
+ this.useTestDependencies = useTestDependencies;
+ this.useTestClasses = useTestClasses;
+ }
+
+ boolean isUseTestDependencies() {
+ return this.useTestDependencies;
+ }
+
+ boolean isUseTestClasses() {
+ return this.useTestClasses;
+ }
+
+ static TestClasspath of(String value) {
+ if ("true".equalsIgnoreCase(value)) {
+ return DEPENDENCIES;
+ }
+ if ("false".equalsIgnoreCase(value)) {
+ return OFF;
+ }
+ try {
+ return valueOf(value.toUpperCase(Locale.ROOT));
+ }
+ catch (IllegalArgumentException ex) {
+ String message = "Unsupported test classpath '%s'. Valid values are OFF, DEPENDENCIES, ALL, true, and false"
+ .formatted(value);
+ throw new IllegalArgumentException(message, ex);
+ }
+ }
+
+}
diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/TestRunMojo.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/TestRunMojo.java
index 4be0c6185e6a..0a3aa69564ea 100644
--- a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/TestRunMojo.java
+++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/TestRunMojo.java
@@ -17,7 +17,6 @@
package org.springframework.boot.maven;
import java.io.File;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -57,24 +56,14 @@ public class TestRunMojo extends AbstractRunMojo {
@Parameter(property = "spring-boot.test-run.optimizedLaunch", defaultValue = "true")
private boolean optimizedLaunch;
- /**
- * Directory containing the test classes and resource files that should be used to run
- * the application.
- */
- @Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true)
- @SuppressWarnings("NullAway.Init")
- private File testClassesDirectory;
-
@Inject
public TestRunMojo(ToolchainManager toolchainManager) {
super(toolchainManager);
}
@Override
- protected List getClassesDirectories() {
- ArrayList classesDirectories = new ArrayList<>(super.getClassesDirectories());
- classesDirectories.add(0, this.testClassesDirectory);
- return classesDirectories;
+ protected boolean isUseTestClasses() {
+ return true;
}
@Override
diff --git a/build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/TestClasspathTests.java b/build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/TestClasspathTests.java
new file mode 100644
index 000000000000..be29fae1c9fe
--- /dev/null
+++ b/build-plugin/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/TestClasspathTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012-present the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.boot.maven;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Tests for {@link TestClasspath}.
+ *
+ * @author Henrique (henriquejsza)
+ */
+class TestClasspathTests {
+
+ @ParameterizedTest
+ @CsvSource({ "false, OFF", "off, OFF", "true, DEPENDENCIES", "dependencies, DEPENDENCIES", "all, ALL" })
+ void mapsValueToTestClasspath(String value, TestClasspath expected) {
+ assertThat(TestClasspath.of(value)).isEqualTo(expected);
+ }
+
+ @ParameterizedTest
+ @CsvSource({ "OFF, false, false", "DEPENDENCIES, true, false", "ALL, true, true" })
+ void exposesIncludedParts(TestClasspath testClasspath, boolean useTestDependencies, boolean useTestClasses) {
+ assertThat(testClasspath.isUseTestDependencies()).isEqualTo(useTestDependencies);
+ assertThat(testClasspath.isUseTestClasses()).isEqualTo(useTestClasses);
+ }
+
+ @ParameterizedTest
+ @CsvSource({ "unknown", "yes" })
+ void rejectsUnsupportedValue(String value) {
+ assertThatIllegalArgumentException().isThrownBy(() -> TestClasspath.of(value))
+ .withMessage("Unsupported test classpath '%s'. Valid values are OFF, DEPENDENCIES, ALL, true, and false",
+ value);
+ }
+
+}