This is an automated email from the ASF dual-hosted git repository.
jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/main by this push:
new c648a7b24ba Add tests for IncrementalBuildMojo and fix JVM-only test
detection gaps
c648a7b24ba is described below
commit c648a7b24bafcfe37f860de0d2570b44cfef5259
Author: James Netherton <[email protected]>
AuthorDate: Thu Nov 12 11:31:53 2026 +0000
Add tests for IncrementalBuildMojo and fix JVM-only test detection gaps
Add unit tests covering full build fallbacks, affected test module mapping,
grouped modules, the native test matrix, functional test scope, JVM-only
tests, examples, generated source consumers and container image detection.
Fix issues the tests uncovered:
* JVM-only modules affected by a container image change were dropped when
the Scalpel report listed no affected modules
* Container image references under integration-test-groups/ always mapped
to a native <group>-grouped module. They are now resolved via the declared
group-tests.source.dir, so JVM groups such as xml/jvm are routed correctly
* UPSTREAM modules under integration-tests-jvm/ were scheduled for testing
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../camel/quarkus/maven/IncrementalBuildMojo.java | 70 ++-
.../quarkus/maven/IncrementalBuildMojoTest.java | 548 +++++++++++++++++++++
2 files changed, 596 insertions(+), 22 deletions(-)
diff --git
a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/IncrementalBuildMojo.java
b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/IncrementalBuildMojo.java
index 4f9ef86ff1e..90720bbe668 100644
---
a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/IncrementalBuildMojo.java
+++
b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/IncrementalBuildMojo.java
@@ -574,7 +574,7 @@ public class IncrementalBuildMojo extends AbstractMojo {
}
boolean isJvm = trimmedPrefix.equals(jvmTestsPrefix.trim());
- scanTestResourceFiles(dir, changedProps, (moduleName) -> {
+ scanTestResourceFiles(dir, changedProps, (moduleName, file) -> {
if (isJvm) {
result.jvmModules.add(moduleName);
} else {
@@ -586,16 +586,15 @@ public class IncrementalBuildMojo extends AbstractMojo {
// Scan integration-test-groups/ for direct references
Path groupsDir =
projectRootDir.resolve(integrationTestGroupsPrefix.trim());
if (Files.isDirectory(groupsDir)) {
- scanTestResourceFiles(groupsDir, changedProps, (moduleName) -> {
- result.nativeModules.add(moduleName + "-grouped");
- });
+ scanTestResourceFiles(groupsDir, changedProps,
+ (groupName, file) -> addGroupingModules(groupName,
file.getParent(), result));
}
// Scan integration-tests-support/ for references in shared modules
Path supportDir =
projectRootDir.resolve(integrationTestSupportPrefix.trim());
if (Files.isDirectory(supportDir)) {
Set<String> affectedSupportModules = new LinkedHashSet<>();
- scanTestResourceFiles(supportDir, changedProps,
affectedSupportModules::add);
+ scanTestResourceFiles(supportDir, changedProps, (moduleName, file)
-> affectedSupportModules.add(moduleName));
if (!affectedSupportModules.isEmpty()) {
getLog().info("Container properties referenced in support
modules: " + affectedSupportModules);
@@ -628,23 +627,52 @@ public class IncrementalBuildMojo extends AbstractMojo {
paths.add(normalizePath(jvmTestsPrefix.trim()) + "/" + moduleName);
}
- for (String consumer : findTransitiveConsumers(paths, null)) {
- if (consumer.startsWith(jvmTestsPrefix)) {
-
result.jvmModules.add(consumer.substring(jvmTestsPrefix.length()));
- } else if (consumer.startsWith(nativeTestsPrefix)) {
-
result.nativeModules.add(consumer.substring(nativeTestsPrefix.length()));
+ addTestModules(findTransitiveConsumers(paths, null), result);
+ }
+
+ /**
+ * Adds the modules grouping the sources of {@code moduleDir}, a directory
under
+ * {@code integration-test-groups/<group>/}. The grouping modules, and
whether they are native or JVM only, are
+ * resolved from their declared {@code group-tests.source.dir}. Without
one, falls back to a native
+ * {@code <group>-grouped} module.
+ */
+ private void addGroupingModules(String groupName, Path moduleDir,
ContainerAffectedModules result)
+ throws IOException {
+ String path =
normalizePath(projectRootDir.relativize(moduleDir).toString());
+ Set<String> consumers = findTransitiveConsumers(Set.of(path), null);
+ if (consumers.isEmpty()) {
+ result.nativeModules.add(groupName + "-grouped");
+ } else {
+ addTestModules(consumers, result);
+ }
+ }
+
+ /**
+ * Adds test module {@code paths}, relative to the project root, to the
native or JVM module list according to
+ * their prefix.
+ */
+ private void addTestModules(Set<String> paths, ContainerAffectedModules
result) {
+ for (String path : paths) {
+ if (path.startsWith(jvmTestsPrefix)) {
+ result.jvmModules.add(path.substring(jvmTestsPrefix.length()));
+ } else if (path.startsWith(nativeTestsPrefix)) {
+
result.nativeModules.add(path.substring(nativeTestsPrefix.length()));
}
}
}
@FunctionalInterface
private interface ModuleConsumer {
- void accept(String moduleName);
+ /**
+ * @param moduleName the top-level module directory name below the
scanned directory
+ * @param file the matched file
+ */
+ void accept(String moduleName, Path file) throws IOException;
}
/**
* Walks a directory for TestResource.java files containing any of the
given property names.
- * For each match, extracts the top-level module name and passes it to the
consumer.
+ * For each match, passes the top-level module name and the matched file
to the consumer.
*/
private void scanTestResourceFiles(Path dir, Set<String> changedProps,
ModuleConsumer consumer) throws IOException {
walkSourceFiles(dir, name -> name.endsWith("TestResource.java"), file
-> {
@@ -652,7 +680,7 @@ public class IncrementalBuildMojo extends AbstractMojo {
for (String prop : changedProps) {
if (content.contains("\"" + prop + "\"")) {
Path relative = dir.relativize(file);
- consumer.accept(relative.getName(0).toString());
+ consumer.accept(relative.getName(0).toString(), file);
break;
}
}
@@ -679,7 +707,7 @@ public class IncrementalBuildMojo extends AbstractMojo {
}
boolean isJvm = trimmedPrefix.equals(jvmTestsPrefix.trim());
- findDependentModules(dir, artifactId, (moduleName) -> {
+ findDependentModules(dir, artifactId, (moduleName, pomFile) ->
{
if (isJvm) {
result.jvmModules.add(moduleName);
} else {
@@ -691,9 +719,8 @@ public class IncrementalBuildMojo extends AbstractMojo {
// Search integration-test-groups/*/*/pom.xml
Path groupsDir =
projectRootDir.resolve(integrationTestGroupsPrefix.trim());
if (Files.isDirectory(groupsDir)) {
- findDependentModules(groupsDir, artifactId, (moduleName) -> {
- result.nativeModules.add(moduleName + "-grouped");
- });
+ findDependentModules(groupsDir, artifactId,
+ (groupName, pomFile) -> addGroupingModules(groupName,
pomFile.getParent(), result));
}
}
}
@@ -707,7 +734,7 @@ public class IncrementalBuildMojo extends AbstractMojo {
if (content.contains(artifactId)) {
Path relative = dir.relativize(pomFile);
String moduleName = relative.getName(0).toString();
- consumer.accept(moduleName);
+ consumer.accept(moduleName, pomFile);
getLog().debug(" " + moduleName + " depends on " +
artifactId);
}
});
@@ -862,13 +889,12 @@ public class IncrementalBuildMojo extends AbstractMojo {
return result;
}
- if (report.affectedModules.isEmpty()) {
- return result;
- }
-
Set<String> jvmModules = new LinkedHashSet<>();
for (Map<String, Object> module : report.affectedModules) {
String path = (String) module.get("path");
+ if ("UPSTREAM".equals(module.get("category"))) {
+ continue;
+ }
if (path != null && path.startsWith(jvmTestsPrefix)) {
String moduleName = path.substring(jvmTestsPrefix.length());
if (moduleName.contains("/")) {
diff --git
a/tooling/maven-plugin/src/test/java/org/apache/camel/quarkus/maven/IncrementalBuildMojoTest.java
b/tooling/maven-plugin/src/test/java/org/apache/camel/quarkus/maven/IncrementalBuildMojoTest.java
new file mode 100644
index 00000000000..f56e1886c6b
--- /dev/null
+++
b/tooling/maven-plugin/src/test/java/org/apache/camel/quarkus/maven/IncrementalBuildMojoTest.java
@@ -0,0 +1,548 @@
+/*
+ * 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.apache.camel.quarkus.maven;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.ObjectMapper;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class IncrementalBuildMojoTest {
+
+ private static final String PLACEHOLDER =
"${maven.multiModuleProjectDirectory}/";
+
+ private final ObjectMapper jsonMapper = new ObjectMapper();
+
+ @TempDir
+ Path projectRoot;
+
+ private IncrementalBuildMojo mojo;
+
+ @BeforeEach
+ void setUp() {
+ mojo = new IncrementalBuildMojo();
+ mojo.scalpelReportJson =
projectRoot.resolve("target/scalpel-report.json");
+ mojo.outputFile = projectRoot.resolve("target/incremental-build.json");
+ mojo.projectRootDir = projectRoot;
+ mojo.useIncrementalBuild = true;
+ mojo.maxGroups = 13;
+ mojo.maxMatrixSize = 20;
+ mojo.outputCompact = true;
+ mojo.extensionDirs = "extensions/,extensions-jvm/,extensions-core/";
+ mojo.integrationTestDirs = "integration-tests/,integration-tests-jvm/";
+ mojo.nativeTestsPrefix = "integration-tests/";
+ mojo.jvmTestsPrefix = "integration-tests-jvm/";
+ mojo.integrationTestGroupsPrefix = "integration-test-groups/";
+ mojo.functionalScopeDirs =
"extensions-core/:runExtensionsCoreTests,extensions/:runExtensionsTests,"
+ +
"extensions-support/:runExtensionsSupportTests,test-framework/:runTestFrameworkTests,"
+ + "tooling/:runToolingTests,catalog/:runCatalogTests";
+ mojo.integrationTestSupportPrefix = "integration-tests-support/";
+ }
+
+ @Test
+ void fullBuildWhenIncrementalBuildDisabled() throws Exception {
+ mojo.useIncrementalBuild = false;
+ writeReport(false, List.of(module("integration-tests/foo", "DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertFullBuild(output);
+ assertEquals(Map.of(
+ "runExtensionsCoreTests", true,
+ "runExtensionsTests", true,
+ "runExtensionsSupportTests", true,
+ "runTestFrameworkTests", true,
+ "runToolingTests", true,
+ "runCatalogTests", true), output.get("functionalTestScope"));
+ assertEquals(Map.of(), output.get("functionalTestModules"));
+ assertEquals(Map.of("runTests", true, "modules", ""),
output.get("integrationTestsJvm"));
+ assertEquals(true, output.get("runExamples"));
+ }
+
+ @Test
+ void fullBuildWhenScalpelReportMissing() throws Exception {
+ Map<String, Object> output = execute();
+
+ assertFullBuild(output);
+ assertEquals(true, output.get("runExamples"));
+ }
+
+ @Test
+ void fullBuildWhenTriggeredByScalpel() throws Exception {
+ writeReport(true, List.of(module("integration-tests/foo", "DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertFullBuild(output);
+ assertEquals(Map.of(), output.get("functionalTestModules"));
+ assertEquals(true, output.get("runExamples"));
+ }
+
+ @Test
+ void fullBuildWhenNoAffectedModules() throws Exception {
+ writeReport(false, List.of());
+
+ Map<String, Object> output = execute();
+
+ assertFullBuild(output);
+ assertEquals(true, output.get("runExamples"));
+ }
+
+ @Test
+ void fullBuildWhenBomDirectlyAffectedWithoutTestModules() throws Exception
{
+ writeReport(false, List.of(module("poms/bom", "DIRECT")));
+
+ assertFullBuild(execute());
+ }
+
+ @Test
+ void incrementalBuildWhenBomDirectlyAffectedWithTestModules() throws
Exception {
+ writeReport(false, List.of(
+ module("poms/bom", "DIRECT"),
+ module("integration-tests/foo", "DOWNSTREAM")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(true, output.get("incrementalBuild"));
+ assertEquals(List.of("foo"), output.get("affectedModules"));
+ }
+
+ @Test
+ void affectedIntegrationTests() throws Exception {
+ writeReport(false, List.of(
+ module("integration-tests/direct", "DIRECT"),
+ module("integration-tests/downstream", "DOWNSTREAM"),
+ module("integration-tests/transitive", "TRANSITIVE"),
+ module("integration-tests/upstream", "UPSTREAM"),
+ module("integration-tests/nested/child", "DIRECT"),
+ module("integration-tests/direct", "DOWNSTREAM"),
+ module("integration-tests-jvm/jvm-only", "DIRECT"),
+ module("integration-tests-support/support", "DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(true, output.get("incrementalBuild"));
+ assertEquals(List.of("direct", "downstream", "transitive", "nested"),
output.get("affectedModules"));
+ assertEquals(4, output.get("affectedModulesCount"));
+ }
+
+ @Test
+ void groupedIntegrationTestsMapToGroupedModule() throws Exception {
+ writeReport(false, List.of(
+ module("integration-test-groups/aws2/s3", "DIRECT"),
+ module("integration-test-groups/aws2/sqs", "DOWNSTREAM"),
+ module("integration-test-groups/azure", "DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(List.of("aws2-grouped", "azure-grouped"),
output.get("affectedModules"));
+ }
+
+ @Test
+ void nativeTestMatrixDistributesModulesAcrossGroups() throws Exception {
+ mojo.maxGroups = 3;
+ writeReport(false, integrationTestModules("a", "b", "c", "d", "e",
"f", "g"));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(Map.of("include", List.of(
+ Map.of("name", "group-01", "modules", "a,b,c"),
+ Map.of("name", "group-02", "modules", "d,e,f"),
+ Map.of("name", "group-03", "modules", "g"))),
output.get("nativeTestMatrix"));
+ }
+
+ @Test
+ void nativeTestMatrixUsesOneGroupPerModuleBelowMaxGroups() throws
Exception {
+ writeReport(false, integrationTestModules("a", "b"));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(Map.of("include", List.of(
+ Map.of("name", "group-01", "modules", "a"),
+ Map.of("name", "group-02", "modules", "b"))),
output.get("nativeTestMatrix"));
+ }
+
+ @Test
+ void nativeTestMatrixExceedingMaxSizeFails() throws Exception {
+ mojo.maxGroups = 5;
+ mojo.maxMatrixSize = 2;
+ writeReport(false, integrationTestModules("a", "b", "c", "d", "e"));
+
+ MojoExecutionException e = assertThrows(MojoExecutionException.class,
mojo::execute);
+ assertInstanceOf(MojoExecutionException.class, e.getCause());
+ assertTrue(e.getCause().getMessage().contains("exceeds maximum (2)"),
e.getCause().getMessage());
+ }
+
+ @Test
+ void functionalTestScopeAndModules() throws Exception {
+ writeReport(false, List.of(
+ module("extensions/kafka/runtime", "DIRECT"),
+ module("extensions/kafka/deployment", "DOWNSTREAM"),
+ module("tooling", "DIRECT"),
+ module("catalog", "UPSTREAM"),
+ module("extensions-core/core/runtime", "UPSTREAM")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(Map.of(
+ "runExtensionsCoreTests", false,
+ "runExtensionsTests", true,
+ "runExtensionsSupportTests", false,
+ "runTestFrameworkTests", false,
+ "runToolingTests", true,
+ "runCatalogTests", false), output.get("functionalTestScope"));
+ assertEquals(Map.of(
+ "extensions", "kafka/runtime,kafka/deployment",
+ "tooling", "."), output.get("functionalTestModules"));
+ }
+
+ @Test
+ void functionalScopeDoesNotMatchSiblingDirectoriesSharingPrefix() throws
Exception {
+ writeReport(false, List.of(module("extensions-support/foo",
"DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(Map.of(
+ "runExtensionsCoreTests", false,
+ "runExtensionsTests", false,
+ "runExtensionsSupportTests", true,
+ "runTestFrameworkTests", false,
+ "runToolingTests", false,
+ "runCatalogTests", false), output.get("functionalTestScope"));
+ assertEquals(Map.of("extensions-support", "foo"),
output.get("functionalTestModules"));
+ }
+
+ @Test
+ void jvmOnlyIntegrationTests() throws Exception {
+ writeReport(false, List.of(
+ module("integration-tests-jvm/foo", "DIRECT"),
+ module("integration-tests-jvm/bar/child", "DOWNSTREAM"),
+ module("integration-tests-jvm/foo", "DOWNSTREAM")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(true, output.get("incrementalBuild"));
+ assertEquals(List.of(), output.get("affectedModules"));
+ assertEquals(Map.of("include", List.of()),
output.get("nativeTestMatrix"));
+ assertEquals(Map.of("runTests", true, "modules", "foo,bar"),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void upstreamJvmOnlyIntegrationTestsNotRun() throws Exception {
+ writeReport(false, List.of(
+ module("integration-tests-jvm/foo", "UPSTREAM"),
+ module("integration-tests/bar", "DIRECT")));
+
+ assertEquals(Map.of("runTests", false, "modules", ""),
execute().get("integrationTestsJvm"));
+ }
+
+ @Test
+ void jvmOnlyIntegrationTestsNotRunWhenUnaffected() throws Exception {
+ writeReport(false, List.of(module("integration-tests/foo", "DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(Map.of("runTests", false, "modules", ""),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void examplesRunWhenExtensionAffected() throws Exception {
+ writeReport(false, List.of(
+ module("extensions-jvm/foo/runtime", "DIRECT"),
+ module("integration-tests-jvm/foo", "DOWNSTREAM")));
+
+ assertEquals(true, execute().get("runExamples"));
+ }
+
+ @Test
+ void examplesSkippedWhenOnlyIntegrationTestsOrUpstreamExtensionsAffected()
throws Exception {
+ writeReport(false, List.of(
+ module("extensions/foo/runtime", "UPSTREAM"),
+ module("integration-tests/foo", "DIRECT")));
+
+ assertEquals(false, execute().get("runExamples"));
+ }
+
+ @Test
+ void copyTestsConsumersIncluded() throws Exception {
+ Files.createDirectories(projectRoot.resolve("integration-tests/foo"));
+ writeConsumerPom("integration-tests/foo-copy", "copy",
"integration-tests/foo");
+ writeConsumerPom("integration-tests/foo-copy-copy", "copy",
"integration-tests/foo-copy");
+ writeReport(false, List.of(module("integration-tests/foo", "DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(List.of("foo", "foo-copy", "foo-copy-copy"),
output.get("affectedModules"));
+ }
+
+ @Test
+ void copyTestsConsumersIncludedForChangesWithinSourceDir() throws
Exception {
+
Files.createDirectories(projectRoot.resolve("integration-tests/foo/sub"));
+ writeConsumerPom("integration-tests/foo-copy", "copy",
"integration-tests/foo/sub");
+ writeReport(false, List.of(module("integration-tests/foo/sub",
"DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(List.of("foo", "foo-copy"),
output.get("affectedModules"));
+ }
+
+ @Test
+ void generatedSourceConsumersIgnoreBuildOutput() throws Exception {
+ Files.createDirectories(projectRoot.resolve("integration-tests/foo"));
+ writeConsumerPom("integration-tests/bar/target/stale", "copy",
"integration-tests/foo");
+ writeConsumerPom(".git/stale", "copy", "integration-tests/foo");
+ writeReport(false, List.of(module("integration-tests/foo", "DIRECT")));
+
+ assertEquals(List.of("foo"), execute().get("affectedModules"));
+ }
+
+ @Test
+ void generatedSourceConsumersWithUnresolvableSourceDirIgnored() throws
Exception {
+ Files.createDirectories(projectRoot.resolve("integration-tests/foo"));
+ writePom("integration-tests/absolute-copy",
+
"<copy-tests.source.dir>/somewhere/integration-tests/foo</copy-tests.source.dir>");
+ writeConsumerPom("integration-tests/missing-copy", "copy",
"integration-tests/does-not-exist");
+ writeReport(false, List.of(
+ module("integration-tests/foo", "DIRECT"),
+ module("integration-tests/does-not-exist", "DIRECT")));
+
+ assertEquals(List.of("foo", "does-not-exist"),
execute().get("affectedModules"));
+ }
+
+ @Test
+ void groupTestsConsumerResolvedToJvmGroupedModule() throws Exception {
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/jvm/xpath"));
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/native/xslt"));
+ writeConsumerPom("integration-tests-jvm/xml-grouped", "group",
"integration-test-groups/xml/jvm");
+ writeConsumerPom("integration-tests/xml-grouped", "group",
"integration-test-groups/xml/native");
+ writeReport(false,
List.of(module("integration-test-groups/xml/jvm/xpath", "DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(true, output.get("incrementalBuild"));
+ assertEquals(List.of(), output.get("affectedModules"));
+ assertEquals(Map.of("runTests", true, "modules", "xml-grouped"),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void groupTestsAggregatorChangeIncludesAllConsumers() throws Exception {
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/jvm"));
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/native"));
+ writeConsumerPom("integration-tests-jvm/xml-grouped", "group",
"integration-test-groups/xml/jvm");
+ writeConsumerPom("integration-tests/xml-grouped", "group",
"integration-test-groups/xml/native");
+ writeReport(false, List.of(module("integration-test-groups/xml",
"DIRECT")));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(List.of("xml-grouped"), output.get("affectedModules"));
+ assertEquals(Map.of("runTests", true, "modules", "xml-grouped"),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void groupedModuleNameHeuristicUsedWithoutDeclaredSourceDir() throws
Exception {
+ writeReport(false, List.of(module("integration-test-groups/foo/bar",
"DIRECT")));
+
+ assertEquals(List.of("foo-grouped"), execute().get("affectedModules"));
+ }
+
+ @Test
+ void containerImageChangeAffectsReferencingTestModules() throws Exception {
+
writeTestResource("integration-tests/foo/src/test/java/FooTestResource.java",
"foo.container.image");
+
writeTestResource("integration-tests-jvm/bar/src/test/java/BarTestResource.java",
"foo.container.image");
+
writeTestResource("integration-test-groups/grp/mod/src/test/java/ModTestResource.java",
"foo.container.image");
+
writeTestResource("integration-tests/other/src/test/java/OtherTestResource.java",
"other.container.image");
+
writeTestResource("integration-tests/notres/src/test/java/NotATestResourceHelper.java",
"foo.container.image");
+
writeTestResource("integration-tests/stale/target/FooTestResource.java",
"foo.container.image");
+ writeReport(false, List.of(), List.of("foo.container.image",
"foo.version"));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(true, output.get("incrementalBuild"));
+ assertEquals(List.of("foo", "grp-grouped"),
output.get("affectedModules"));
+ assertEquals(Map.of("runTests", true, "modules", "bar"),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void containerImageChangeInGroupResolvedFromDeclaredSourceDir() throws
Exception {
+
writeTestResource("integration-test-groups/xml/jvm/xpath/src/test/java/XpathTestResource.java",
+ "foo.container.image");
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/native/xslt"));
+ writeConsumerPom("integration-tests-jvm/xml-grouped", "group",
"integration-test-groups/xml/jvm");
+ writeConsumerPom("integration-tests/xml-grouped", "group",
"integration-test-groups/xml/native");
+ writeReport(false, List.of(), List.of("foo.container.image"));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(true, output.get("incrementalBuild"));
+ assertEquals(List.of(), output.get("affectedModules"));
+ assertEquals(Map.of("runTests", true, "modules", "xml-grouped"),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void
containerImageChangeInSupportModuleResolvesGroupFromDeclaredSourceDir() throws
Exception {
+
writeTestResource("integration-tests-support/baz/src/main/java/BazTestResource.java",
"baz.container.image");
+ writePom("integration-test-groups/xml/jvm/xpath",
dependency("camel-quarkus-integration-tests-support-baz"));
+
Files.createDirectories(projectRoot.resolve("integration-test-groups/xml/native/xslt"));
+ writeConsumerPom("integration-tests-jvm/xml-grouped", "group",
"integration-test-groups/xml/jvm");
+ writeConsumerPom("integration-tests/xml-grouped", "group",
"integration-test-groups/xml/native");
+ writeReport(false, List.of(), List.of("baz.container.image"));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(List.of(), output.get("affectedModules"));
+ assertEquals(Map.of("runTests", true, "modules", "xml-grouped"),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void containerImageChangeMatchesQuotedPropertyNameOnly() throws Exception {
+
writeTestResource("integration-tests/foo/src/test/java/FooTestResource.java",
"prefix.foo.container.image");
+ writeReport(false, List.of(module("integration-tests/bar", "DIRECT")),
List.of("foo.container.image"));
+
+ assertEquals(List.of("bar"), execute().get("affectedModules"));
+ }
+
+ @Test
+ void nonContainerPropertyChangesIgnored() throws Exception {
+
writeTestResource("integration-tests/foo/src/test/java/FooTestResource.java",
"foo.version");
+ writeReport(false, List.of(), List.of("foo.version"));
+
+ assertFullBuild(execute());
+ }
+
+ @Test
+ void containerImageChangeInSupportModuleAffectsDependents() throws
Exception {
+
writeTestResource("integration-tests-support/baz/src/main/java/BazTestResource.java",
"baz.container.image");
+ writePom("integration-tests/foo",
dependency("camel-quarkus-integration-tests-support-baz"));
+ writePom("integration-tests-jvm/bar",
dependency("camel-quarkus-integration-tests-support-baz"));
+ writePom("integration-test-groups/grp/mod",
dependency("camel-quarkus-integration-tests-support-baz"));
+ writePom("integration-tests/unrelated",
dependency("camel-quarkus-integration-tests-support-other"));
+ writeReport(false, List.of(), List.of("baz.container.image"));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(List.of("foo", "grp-grouped"),
output.get("affectedModules"));
+ assertEquals(Map.of("runTests", true, "modules", "bar"),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void containerImageChangeIncludesGeneratedSourceConsumers() throws
Exception {
+
writeTestResource("integration-tests/foo/src/test/java/FooTestResource.java",
"foo.container.image");
+ writeConsumerPom("integration-tests/foo-copy", "copy",
"integration-tests/foo");
+ writeConsumerPom("integration-tests-jvm/foo-jvm-copy", "copy",
"integration-tests/foo");
+ writeReport(false, List.of(), List.of("foo.container.image"));
+
+ Map<String, Object> output = execute();
+
+ assertEquals(List.of("foo", "foo-copy"),
output.get("affectedModules"));
+ assertEquals(Map.of("runTests", true, "modules", "foo-jvm-copy"),
output.get("integrationTestsJvm"));
+ }
+
+ @Test
+ void outputFormatting() throws Exception {
+ writeReport(false, List.of(module("integration-tests/foo", "DIRECT")));
+
+ execute();
+ assertEquals(1, Files.readAllLines(mojo.outputFile).size());
+
+ mojo.outputCompact = false;
+ execute();
+ assertTrue(Files.readAllLines(mojo.outputFile).size() > 1);
+ }
+
+ private Map<String, Object> execute() throws Exception {
+ mojo.execute();
+ return jsonMapper.readValue(mojo.outputFile.toFile(), new
TypeReference<Map<String, Object>>() {
+ });
+ }
+
+ private static void assertFullBuild(Map<String, Object> output) {
+ assertEquals(false, output.get("incrementalBuild"));
+ assertEquals(List.of(), output.get("affectedModules"));
+ assertEquals(0, output.get("affectedModulesCount"));
+ assertEquals(Map.of("include", List.of()),
output.get("nativeTestMatrix"));
+ }
+
+ private static Map<String, Object> module(String path, String category) {
+ Map<String, Object> module = new LinkedHashMap<>();
+ module.put("path", path);
+ module.put("category", category);
+ return module;
+ }
+
+ private static List<Map<String, Object>> integrationTestModules(String...
names) {
+ List<Map<String, Object>> modules = new ArrayList<>();
+ for (String name : names) {
+ modules.add(module("integration-tests/" + name, "DIRECT"));
+ }
+ return modules;
+ }
+
+ private void writeReport(boolean fullBuildTriggered, List<Map<String,
Object>> affectedModules) throws IOException {
+ writeReport(fullBuildTriggered, affectedModules, null);
+ }
+
+ private void writeReport(boolean fullBuildTriggered, List<Map<String,
Object>> affectedModules,
+ List<String> changedProperties) throws IOException {
+ Map<String, Object> report = new LinkedHashMap<>();
+ report.put("fullBuildTriggered", fullBuildTriggered);
+ report.put("affectedModules", affectedModules);
+ if (changedProperties != null) {
+ report.put("changedProperties", changedProperties);
+ }
+ Files.createDirectories(mojo.scalpelReportJson.getParent());
+ jsonMapper.writeValue(mojo.scalpelReportJson.toFile(), report);
+ }
+
+ private void writeConsumerPom(String modulePath, String type, String
sourceDir) throws IOException {
+ writePom(modulePath, "<" + type + "-tests.source.dir>" + PLACEHOLDER +
sourceDir + "</" + type
+ + "-tests.source.dir>");
+ }
+
+ private void writePom(String modulePath, String content) throws
IOException {
+ writeFile(modulePath + "/pom.xml", "<project>" + content +
"</project>");
+ }
+
+ private static String dependency(String artifactId) {
+ return "<dependency><artifactId>" + artifactId +
"</artifactId></dependency>";
+ }
+
+ private void writeTestResource(String path, String property) throws
IOException {
+ writeFile(path, "class TestResource { String image =
config.getValue(\"" + property + "\", String.class); }");
+ }
+
+ private void writeFile(String path, String content) throws IOException {
+ Path file = projectRoot.resolve(path);
+ Files.createDirectories(file.getParent());
+ Files.writeString(file, content, StandardCharsets.UTF_8);
+ }
+}