Skip to content

Commit 71851dd

Browse files
committed
Add integration test for Maven 4 new dependency scopes (MNG-8750)
This integration test verifies the correct behavior of the new dependency scopes introduced in Maven 4: - compile-only: Available during compilation, not at runtime - test-only: Available during test compilation, not at test runtime - test-runtime: Available during test runtime, not at test compilation The test also verifies that consumer POMs exclude these new scopes for Maven 3 compatibility.
1 parent 65d213b commit 71851dd

File tree

31 files changed

+1726
-4
lines changed

31 files changed

+1726
-4
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
.java-version
1515
.checkstyle
1616
.factorypath
17-
repo/
1817

1918
# VSCode
2019
.vscode/

impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.stream.Collectors;
4040
import java.util.stream.StreamSupport;
4141

42+
import org.apache.maven.api.DependencyScope;
4243
import org.apache.maven.api.Session;
4344
import org.apache.maven.api.annotations.Nullable;
4445
import org.apache.maven.api.di.Inject;
@@ -77,7 +78,6 @@
7778
import org.apache.maven.impl.InternalSession;
7879
import org.apache.maven.model.v4.MavenModelVersion;
7980
import org.apache.maven.model.v4.MavenTransformer;
80-
import org.eclipse.aether.scope.DependencyScope;
8181
import org.eclipse.aether.scope.ScopeManager;
8282

8383
/**
@@ -1157,6 +1157,25 @@ private void validate20RawDependencies(
11571157
}
11581158
}
11591159

1160+
// MNG-8750: New dependency scopes are only supported starting with modelVersion 4.1.0
1161+
// When using modelVersion 4.0.0, fail validation if one of the new scopes is present
1162+
if (!is41OrBeyond) {
1163+
String scope = dependency.getScope();
1164+
if (DependencyScope.COMPILE_ONLY.id().equals(scope)
1165+
|| DependencyScope.TEST_ONLY.id().equals(scope)
1166+
|| DependencyScope.TEST_RUNTIME.id().equals(scope)) {
1167+
addViolation(
1168+
problems,
1169+
Severity.ERROR,
1170+
Version.V20,
1171+
prefix + prefix2 + "scope",
1172+
SourceHint.dependencyManagementKey(dependency),
1173+
"scope '" + scope + "' is not supported with modelVersion 4.0.0; "
1174+
+ "use modelVersion 4.1.0 or remove this scope.",
1175+
dependency);
1176+
}
1177+
}
1178+
11601179
if (equals("LATEST", dependency.getVersion()) || equals("RELEASE", dependency.getVersion())) {
11611180
addViolation(
11621181
problems,
@@ -1272,7 +1291,7 @@ private void validateEffectiveDependencies(
12721291
SourceHint.dependencyManagementKey(dependency),
12731292
dependency,
12741293
scopeManager.getDependencyScopeUniverse().stream()
1275-
.map(DependencyScope::getId)
1294+
.map(org.eclipse.aether.scope.DependencyScope::getId)
12761295
.distinct()
12771296
.toArray(String[]::new),
12781297
false);
@@ -1282,7 +1301,7 @@ private void validateEffectiveDependencies(
12821301
ScopeManager scopeManager =
12831302
InternalSession.from(session).getSession().getScopeManager();
12841303
Set<String> scopes = scopeManager.getDependencyScopeUniverse().stream()
1285-
.map(DependencyScope::getId)
1304+
.map(org.eclipse.aether.scope.DependencyScope::getId)
12861305
.collect(Collectors.toCollection(HashSet::new));
12871306
scopes.add("import");
12881307
validateDependencyScope(
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.it;
20+
21+
import java.io.File;
22+
import java.io.IOException;
23+
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.Test;
26+
27+
import static org.junit.jupiter.api.Assertions.assertFalse;
28+
import static org.junit.jupiter.api.Assertions.assertThrows;
29+
import static org.junit.jupiter.api.Assertions.assertTrue;
30+
31+
/**
32+
* Integration tests for Maven 4 new dependency scopes: compile-only, test-only, and test-runtime.
33+
*
34+
* Verifies that:
35+
* - compile-only dependencies appear in compile classpath but not runtime classpath
36+
* - test-only dependencies appear in test compile classpath but not test runtime classpath
37+
* - test-runtime dependencies appear in test runtime classpath but not test compile classpath
38+
*
39+
* Each test method runs a small test project (under src/test/resources/mng-8750-new-scopes)
40+
* that writes out classpath files and asserts expected inclusion/exclusion behavior.
41+
*/
42+
public class MavenITmng8750NewScopesTest extends AbstractMavenIntegrationTestCase {
43+
44+
public MavenITmng8750NewScopesTest() {
45+
super("[4.0.0,)");
46+
}
47+
48+
@BeforeEach
49+
void installDependencies() throws VerificationException, IOException {
50+
File testDir = extractResources("/mng-8750-new-scopes");
51+
52+
File depsDir = new File(testDir, "deps");
53+
Verifier deps = newVerifier(depsDir.getAbsolutePath());
54+
deps.addCliArgument("install");
55+
deps.execute();
56+
deps.verifyErrorFreeLog();
57+
}
58+
59+
/**
60+
* compile-only: available during compilation only.
61+
* Behavior validated by the test project:
62+
* - Present on compile classpath
63+
* - Not present on runtime classpath
64+
* - Not transitive
65+
*
66+
* @throws Exception in case of failure
67+
*/
68+
@Test
69+
public void testCompileOnlyScope() throws Exception {
70+
File testDir = extractResources("/mng-8750-new-scopes");
71+
File projectDir = new File(testDir, "compile-only-test");
72+
73+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
74+
verifier.addCliArgument("clean");
75+
verifier.addCliArgument("test");
76+
verifier.execute();
77+
verifier.verifyErrorFreeLog();
78+
79+
// Verify execution completed; detailed checks are within the test project
80+
verifier.verifyErrorFreeLog();
81+
82+
// Verify classpath files were generated
83+
File compileClasspath = new File(projectDir, "target/compile-classpath.txt");
84+
File runtimeClasspath = new File(projectDir, "target/runtime-classpath.txt");
85+
86+
assertTrue(compileClasspath.exists(), "Compile classpath file should exist");
87+
assertTrue(runtimeClasspath.exists(), "Runtime classpath file should exist");
88+
}
89+
90+
/**
91+
* test-only: available during test compilation only.
92+
* Behavior validated by the test project:
93+
* - Present on test compile classpath
94+
* - Not present on test runtime classpath
95+
* - Not transitive
96+
*
97+
* @throws Exception in case of failure
98+
*/
99+
@Test
100+
public void testTestOnlyScope() throws Exception {
101+
File testDir = extractResources("/mng-8750-new-scopes");
102+
File projectDir = new File(testDir, "test-only-test");
103+
104+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
105+
verifier.addCliArgument("clean");
106+
verifier.addCliArgument("test");
107+
verifier.execute();
108+
verifier.verifyErrorFreeLog();
109+
110+
// Verify execution completed; detailed checks are within the test project
111+
verifier.verifyErrorFreeLog();
112+
113+
// Verify classpath files were generated
114+
File testCompileClasspath = new File(projectDir, "target/test-compile-classpath.txt");
115+
File testRuntimeClasspath = new File(projectDir, "target/test-runtime-classpath.txt");
116+
117+
assertTrue(testCompileClasspath.exists(), "Test compile classpath file should exist");
118+
assertTrue(testRuntimeClasspath.exists(), "Test runtime classpath file should exist");
119+
}
120+
121+
/**
122+
* test-runtime: available during test runtime only.
123+
* Behavior validated by the test project:
124+
* - Not present on test compile classpath
125+
* - Present on test runtime classpath
126+
* - Transitive
127+
*
128+
* @throws Exception in case of failure
129+
*/
130+
@Test
131+
public void testTestRuntimeScope() throws Exception {
132+
File testDir = extractResources("/mng-8750-new-scopes");
133+
File projectDir = new File(testDir, "test-runtime-test");
134+
135+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
136+
verifier.addCliArgument("clean");
137+
verifier.addCliArgument("test");
138+
verifier.execute();
139+
verifier.verifyErrorFreeLog();
140+
141+
// Verify execution completed; detailed checks are within the test project
142+
verifier.verifyErrorFreeLog();
143+
144+
// Verify classpath files were generated
145+
File testCompileClasspath = new File(projectDir, "target/test-compile-classpath.txt");
146+
File testRuntimeClasspath = new File(projectDir, "target/test-runtime-classpath.txt");
147+
148+
assertTrue(testCompileClasspath.exists(), "Test compile classpath file should exist");
149+
assertTrue(testRuntimeClasspath.exists(), "Test runtime classpath file should exist");
150+
}
151+
152+
/**
153+
* Comprehensive scenario exercising all new scopes together.
154+
* The test project asserts the expected inclusion/exclusion on all classpaths.
155+
*
156+
* @throws Exception in case of failure
157+
*/
158+
@Test
159+
public void testAllNewScopesTogether() throws Exception {
160+
File testDir = extractResources("/mng-8750-new-scopes");
161+
File projectDir = new File(testDir, "comprehensive-test");
162+
163+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
164+
verifier.addCliArgument("clean");
165+
verifier.addCliArgument("test");
166+
verifier.execute();
167+
verifier.verifyErrorFreeLog();
168+
169+
// Verify execution completed; detailed checks are within the test project
170+
verifier.verifyErrorFreeLog();
171+
172+
// Verify all classpath files were generated
173+
File compileClasspath = new File(projectDir, "target/compile-classpath.txt");
174+
File runtimeClasspath = new File(projectDir, "target/runtime-classpath.txt");
175+
File testCompileClasspath = new File(projectDir, "target/test-compile-classpath.txt");
176+
File testRuntimeClasspath = new File(projectDir, "target/test-runtime-classpath.txt");
177+
178+
assertTrue(compileClasspath.exists(), "Compile classpath file should exist");
179+
assertTrue(runtimeClasspath.exists(), "Runtime classpath file should exist");
180+
assertTrue(testCompileClasspath.exists(), "Test compile classpath file should exist");
181+
assertTrue(testRuntimeClasspath.exists(), "Test runtime classpath file should exist");
182+
}
183+
184+
/**
185+
* Validation rule: modelVersion 4.0.0 must reject new scopes (compile-only, test-only, test-runtime).
186+
* This test uses a POM with modelVersion 4.0.0 and new scopes and expects validation to fail.
187+
*
188+
* @throws Exception in case of failure
189+
*/
190+
@Test
191+
public void testValidationFailureWithModelVersion40() throws Exception {
192+
File testDir = extractResources("/mng-8750-new-scopes");
193+
File projectDir = new File(testDir, "validation-failure-test");
194+
195+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
196+
verifier.addCliArgument("clean");
197+
verifier.addCliArgument("validate");
198+
199+
assertThrows(
200+
VerificationException.class,
201+
verifier::execute,
202+
"Expected validation to fail when using new scopes with modelVersion 4.0.0");
203+
String log = verifier.loadLogContent();
204+
assertTrue(
205+
log.contains("is not supported") || log.contains("Unknown scope"),
206+
"Error should indicate unsupported/unknown scope");
207+
}
208+
209+
/**
210+
* Validation rule: modelVersion 4.1.0 (and later) accepts new scopes.
211+
* This test uses a POM with modelVersion 4.1.0 and new scopes and expects validation to succeed.
212+
*
213+
* @throws Exception in case of failure
214+
*/
215+
@Test
216+
public void testValidationSuccessWithModelVersion41() throws Exception {
217+
File testDir = extractResources("/mng-8750-new-scopes");
218+
File projectDir = new File(testDir, "validation-success-test");
219+
220+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
221+
verifier.addCliArgument("clean");
222+
verifier.addCliArgument("validate");
223+
verifier.execute();
224+
verifier.verifyErrorFreeLog();
225+
226+
// Verify that validation succeeded - no errors about unsupported scopes
227+
String log = verifier.loadLogContent();
228+
assertFalse(log.contains("is not supported"), "Validation should succeed with modelVersion 4.1.0");
229+
assertFalse(log.contains("Unknown scope"), "No unknown scope errors should occur with modelVersion 4.1.0");
230+
}
231+
}

its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public TestSuiteOrdering() {
103103
* the tests are to finishing. Newer tests are also more likely to fail, so this is
104104
* a fail fast technique as well.
105105
*/
106+
suite.addTestSuite(MavenITmng8750NewScopesTest.class);
106107
suite.addTestSuite(MavenITgh11055DIServiceInjectionTest.class);
107108
suite.addTestSuite(MavenITgh11084ReactorReaderPreferConsumerPomTest.class);
108109
suite.addTestSuite(MavenITgh10210SettingsXmlDecryptTest.class);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Re-include the pre-populated test repo for this IT, even though root .gitignore ignores 'repo/'
2+
# The IT preference is to keep a pre-populated test repository in resources.
3+
!.gitignore
4+
!repo/
5+
# But still ignore any transient build outputs within the repo if any appear
6+
repo/**/target/
7+

0 commit comments

Comments
 (0)