Skip to content

Commit 3502cf0

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 3502cf0

File tree

31 files changed

+1725
-4
lines changed

31 files changed

+1725
-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: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
26+
import static org.junit.jupiter.api.Assertions.assertFalse;
27+
import static org.junit.jupiter.api.Assertions.assertThrows;
28+
import static org.junit.jupiter.api.Assertions.assertTrue;
29+
30+
/**
31+
* Integration tests for Maven 4 new dependency scopes: compile-only, test-only, and test-runtime.
32+
*
33+
* Verifies that:
34+
* - compile-only dependencies appear in compile classpath but not runtime classpath
35+
* - test-only dependencies appear in test compile classpath but not test runtime classpath
36+
* - test-runtime dependencies appear in test runtime classpath but not test compile classpath
37+
*
38+
* Each test method runs a small test project (under src/test/resources/mng-8750-new-scopes)
39+
* that writes out classpath files and asserts expected inclusion/exclusion behavior.
40+
*/
41+
public class MavenITmng8750NewScopesTest extends AbstractMavenIntegrationTestCase {
42+
43+
public MavenITmng8750NewScopesTest() {
44+
super("[4.0.0,)");
45+
}
46+
47+
@BeforeEach
48+
void installDependencies() throws VerificationException, IOException {
49+
File testDir = extractResources("/mng-8750-new-scopes");
50+
51+
File depsDir = new File(testDir, "deps");
52+
Verifier deps = newVerifier(depsDir.getAbsolutePath());
53+
deps.addCliArgument("install");
54+
deps.execute();
55+
deps.verifyErrorFreeLog();
56+
}
57+
58+
/**
59+
* compile-only: available during compilation only.
60+
* Behavior validated by the test project:
61+
* - Present on compile classpath
62+
* - Not present on runtime classpath
63+
* - Not transitive
64+
*
65+
* @throws Exception in case of failure
66+
*/
67+
@Test
68+
public void testCompileOnlyScope() throws Exception {
69+
File testDir = extractResources("/mng-8750-new-scopes");
70+
File projectDir = new File(testDir, "compile-only-test");
71+
72+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
73+
verifier.addCliArgument("clean");
74+
verifier.addCliArgument("test");
75+
verifier.execute();
76+
verifier.verifyErrorFreeLog();
77+
78+
// Verify execution completed; detailed checks are within the test project
79+
verifier.verifyErrorFreeLog();
80+
81+
// Verify classpath files were generated
82+
File compileClasspath = new File(projectDir, "target/compile-classpath.txt");
83+
File runtimeClasspath = new File(projectDir, "target/runtime-classpath.txt");
84+
85+
assertTrue(compileClasspath.exists(), "Compile classpath file should exist");
86+
assertTrue(runtimeClasspath.exists(), "Runtime classpath file should exist");
87+
}
88+
89+
/**
90+
* test-only: available during test compilation only.
91+
* Behavior validated by the test project:
92+
* - Present on test compile classpath
93+
* - Not present on test runtime classpath
94+
* - Not transitive
95+
*
96+
* @throws Exception in case of failure
97+
*/
98+
@Test
99+
public void testTestOnlyScope() throws Exception {
100+
File testDir = extractResources("/mng-8750-new-scopes");
101+
File projectDir = new File(testDir, "test-only-test");
102+
103+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
104+
verifier.addCliArgument("clean");
105+
verifier.addCliArgument("test");
106+
verifier.execute();
107+
verifier.verifyErrorFreeLog();
108+
109+
// Verify execution completed; detailed checks are within the test project
110+
verifier.verifyErrorFreeLog();
111+
112+
// Verify classpath files were generated
113+
File testCompileClasspath = new File(projectDir, "target/test-compile-classpath.txt");
114+
File testRuntimeClasspath = new File(projectDir, "target/test-runtime-classpath.txt");
115+
116+
assertTrue(testCompileClasspath.exists(), "Test compile classpath file should exist");
117+
assertTrue(testRuntimeClasspath.exists(), "Test runtime classpath file should exist");
118+
}
119+
120+
/**
121+
* test-runtime: available during test runtime only.
122+
* Behavior validated by the test project:
123+
* - Not present on test compile classpath
124+
* - Present on test runtime classpath
125+
* - Transitive
126+
*
127+
* @throws Exception in case of failure
128+
*/
129+
@Test
130+
public void testTestRuntimeScope() throws Exception {
131+
File testDir = extractResources("/mng-8750-new-scopes");
132+
File projectDir = new File(testDir, "test-runtime-test");
133+
134+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
135+
verifier.addCliArgument("clean");
136+
verifier.addCliArgument("test");
137+
verifier.execute();
138+
verifier.verifyErrorFreeLog();
139+
140+
// Verify execution completed; detailed checks are within the test project
141+
verifier.verifyErrorFreeLog();
142+
143+
// Verify classpath files were generated
144+
File testCompileClasspath = new File(projectDir, "target/test-compile-classpath.txt");
145+
File testRuntimeClasspath = new File(projectDir, "target/test-runtime-classpath.txt");
146+
147+
assertTrue(testCompileClasspath.exists(), "Test compile classpath file should exist");
148+
assertTrue(testRuntimeClasspath.exists(), "Test runtime classpath file should exist");
149+
}
150+
151+
/**
152+
* Comprehensive scenario exercising all new scopes together.
153+
* The test project asserts the expected inclusion/exclusion on all classpaths.
154+
*
155+
* @throws Exception in case of failure
156+
*/
157+
@Test
158+
public void testAllNewScopesTogether() throws Exception {
159+
File testDir = extractResources("/mng-8750-new-scopes");
160+
File projectDir = new File(testDir, "comprehensive-test");
161+
162+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
163+
verifier.addCliArgument("clean");
164+
verifier.addCliArgument("test");
165+
verifier.execute();
166+
verifier.verifyErrorFreeLog();
167+
168+
// Verify execution completed; detailed checks are within the test project
169+
verifier.verifyErrorFreeLog();
170+
171+
// Verify all classpath files were generated
172+
File compileClasspath = new File(projectDir, "target/compile-classpath.txt");
173+
File runtimeClasspath = new File(projectDir, "target/runtime-classpath.txt");
174+
File testCompileClasspath = new File(projectDir, "target/test-compile-classpath.txt");
175+
File testRuntimeClasspath = new File(projectDir, "target/test-runtime-classpath.txt");
176+
177+
assertTrue(compileClasspath.exists(), "Compile classpath file should exist");
178+
assertTrue(runtimeClasspath.exists(), "Runtime classpath file should exist");
179+
assertTrue(testCompileClasspath.exists(), "Test compile classpath file should exist");
180+
assertTrue(testRuntimeClasspath.exists(), "Test runtime classpath file should exist");
181+
}
182+
183+
/**
184+
* Validation rule: modelVersion 4.0.0 must reject new scopes (compile-only, test-only, test-runtime).
185+
* This test uses a POM with modelVersion 4.0.0 and new scopes and expects validation to fail.
186+
*
187+
* @throws Exception in case of failure
188+
*/
189+
@Test
190+
public void testValidationFailureWithModelVersion40() throws Exception {
191+
File testDir = extractResources("/mng-8750-new-scopes");
192+
File projectDir = new File(testDir, "validation-failure-test");
193+
194+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
195+
verifier.addCliArgument("clean");
196+
verifier.addCliArgument("validate");
197+
198+
assertThrows(
199+
VerificationException.class,
200+
verifier::execute,
201+
"Expected validation to fail when using new scopes with modelVersion 4.0.0");
202+
String log = verifier.loadLogContent();
203+
assertTrue(
204+
log.contains("is not supported") || log.contains("Unknown scope"),
205+
"Error should indicate unsupported/unknown scope");
206+
}
207+
208+
/**
209+
* Validation rule: modelVersion 4.1.0 (and later) accepts new scopes.
210+
* This test uses a POM with modelVersion 4.1.0 and new scopes and expects validation to succeed.
211+
*
212+
* @throws Exception in case of failure
213+
*/
214+
@Test
215+
public void testValidationSuccessWithModelVersion41() throws Exception {
216+
File testDir = extractResources("/mng-8750-new-scopes");
217+
File projectDir = new File(testDir, "validation-success-test");
218+
219+
Verifier verifier = newVerifier(projectDir.getAbsolutePath());
220+
verifier.addCliArgument("clean");
221+
verifier.addCliArgument("validate");
222+
verifier.execute();
223+
verifier.verifyErrorFreeLog();
224+
225+
// Verify that validation succeeded - no errors about unsupported scopes
226+
String log = verifier.loadLogContent();
227+
assertFalse(log.contains("is not supported"), "Validation should succeed with modelVersion 4.1.0");
228+
assertFalse(log.contains("Unknown scope"), "No unknown scope errors should occur with modelVersion 4.1.0");
229+
}
230+
}

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)