diff --git a/independent-projects/bootstrap/bom-test/pom.xml b/independent-projects/bootstrap/bom-test/pom.xml index 2925446821caa7..75ceb03f4c8062 100644 --- a/independent-projects/bootstrap/bom-test/pom.xml +++ b/independent-projects/bootstrap/bom-test/pom.xml @@ -39,6 +39,30 @@ test ${shrinkwrap-depchain.version} + + org.apache.maven.plugin-testing + maven-plugin-testing-harness + 3.3.0 + test + + + + org.codehaus.plexus + plexus-utils + + + commons-io + commons-io + + + + + + org.apache.maven + maven-compat + ${maven.version} + test + diff --git a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java index 23f8a2eaead13d..47f8d83436818d 100644 --- a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java +++ b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java @@ -201,7 +201,7 @@ public static class RemovedResources { @Parameter(required = false, defaultValue = "${skipExtensionValidation}") private boolean skipExtensionValidation; - @Parameter(required = false, defaultValue = "${ignoreNotDetectedQuarkusCoreVersion") + @Parameter(required = false, defaultValue = "${ignoreNotDetectedQuarkusCoreVersion}") boolean ignoreNotDetectedQuarkusCoreVersion; @Parameter diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index 5f4b87169537c0..e1170124f2f524 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -39,6 +39,12 @@ 5.9.0 + + + src/test/resources + true + + @@ -48,6 +54,20 @@ none + + + maven-clean-plugin + + + + src/test/resources + + **/target/** + + + + + org.apache.maven.plugins maven-enforcer-plugin @@ -188,6 +208,13 @@ pom import + + io.quarkus + quarkus-bootstrap-bom-test + ${project.version} + pom + import + com.fasterxml.jackson @@ -196,6 +223,7 @@ import pom + @@ -238,6 +266,29 @@ ${junit.jupiter.version} test + + org.apache.maven.plugin-testing + maven-plugin-testing-harness + test + + + + org.apache.maven + maven-compat + test + + + org.apache.maven.shared + maven-invoker + 3.2.0 + test + + + javax.inject + javax.inject + + + diff --git a/independent-projects/extension-maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java b/independent-projects/extension-maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java index b042d97ee30c65..983f3faab78382 100644 --- a/independent-projects/extension-maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java +++ b/independent-projects/extension-maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java @@ -103,7 +103,7 @@ public static class RemovedResources { * @component */ @Component - private RepositorySystem repoSystem; + RepositorySystem repoSystem; @Component RemoteRepositoryManager remoteRepoManager; @@ -121,7 +121,7 @@ public static class RemovedResources { * @readonly */ @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) - private RepositorySystemSession repoSession; + RepositorySystemSession repoSession; /** * The project's remote repositories to use for the resolution of artifacts and @@ -213,7 +213,7 @@ public static class RemovedResources { * Whether to ignore failure detecting the Quarkus core version used to build the extension, * which would be recorded in the extension's metadata. */ - @Parameter(required = false, defaultValue = "${ignoreNotDetectedQuarkusCoreVersion") + @Parameter(required = false, defaultValue = "${ignoreNotDetectedQuarkusCoreVersion}") boolean ignoreNotDetectedQuarkusCoreVersion; /** diff --git a/independent-projects/extension-maven-plugin/src/test/java/io/quarkus/maven/ExtensionDescriptorMojoTest.java b/independent-projects/extension-maven-plugin/src/test/java/io/quarkus/maven/ExtensionDescriptorMojoTest.java index 53bb40eefdceaa..0c63ac936ed9a7 100644 --- a/independent-projects/extension-maven-plugin/src/test/java/io/quarkus/maven/ExtensionDescriptorMojoTest.java +++ b/independent-projects/extension-maven-plugin/src/test/java/io/quarkus/maven/ExtensionDescriptorMojoTest.java @@ -1,14 +1,181 @@ package io.quarkus.maven; import static io.quarkus.maven.ExtensionDescriptorMojo.getCodestartArtifact; -import static org.junit.jupiter.api.Assertions.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Comparator; + +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; +import org.apache.maven.artifact.repository.MavenArtifactRepository; +import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout; +import org.apache.maven.execution.DefaultMavenExecutionRequest; +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.testing.AbstractMojoTestCase; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuilder; +import org.apache.maven.project.ProjectBuildingException; +import org.apache.maven.project.ProjectBuildingRequest; +import org.apache.maven.shared.invoker.DefaultInvocationRequest; +import org.apache.maven.shared.invoker.DefaultInvoker; +import org.apache.maven.shared.invoker.InvocationRequest; +import org.apache.maven.shared.invoker.Invoker; +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class ExtensionDescriptorMojoTest { +import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; + +class ExtensionDescriptorMojoTest extends AbstractMojoTestCase { + + private static final boolean RESOLVE_OFFLINE = true; + // Test resources end up in target/test-classes after filtering + public static final String TEST_RESOURCES = "target/test-classes/"; + + @BeforeEach + public void setup() throws Exception { + super.setUp(); + } + + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + + // Assume that all our test data has a common org, for ease of cleanup + File repoPath = new File(getLocalRepoPath(), "io/quackiverse"); + deleteDirectory(repoPath); + } @Test - void testGetCodestartArtifact() { + public void shouldExecuteSimplePomCleanly() + throws Exception { + ExtensionDescriptorMojo mojo = makeMojo("simple-pom-with-checks-disabled"); + mojo.execute(); + } + + @Test + public void shouldCreateExtensionProperties() + throws Exception { + ExtensionDescriptorMojo mojo = makeMojo("simple-pom-with-checks-disabled"); + File propertiesFile = getGeneratedExtensionMetadataFile(mojo.project.getBasedir(), + "target/classes/META-INF/quarkus-extension.properties"); + + // Tidy up any artifacts from previous runs + if (propertiesFile.exists()) { + Files.delete(propertiesFile.toPath()); + } + mojo.execute(); + assertTrue(propertiesFile.exists()); + } + + @Test + public void shouldCreateMetadata() + throws Exception { + ExtensionDescriptorMojo mojo = makeMojo("simple-pom-with-checks-disabled"); + File yamlFile = getGeneratedExtensionMetadataFile(mojo.project.getBasedir(), + "target/classes/META-INF/quarkus-extension.yaml"); + + // Tidy up any artifacts from previous runs + if (yamlFile.exists()) { + Files.delete(yamlFile.toPath()); + } + mojo.execute(); + assertTrue(yamlFile.exists()); + + String fileContents = readFileAsString(yamlFile); + assertYamlContains(fileContents, "name", "an arbitrary name"); + assertYamlContains(fileContents, "artifact", "io.quackiverse:test-artifact::jar:1.4.2-SNAPSHOT"); + + } + + @Test + public void shouldProcessRealisticExtensionCleanly() + throws Exception { + + // We need to get these poms into our local repository before executing the plugin + mavenExecPom("fake-extension-runtime"); + mavenExecPom("fake-extension-deployment"); + + ExtensionDescriptorMojo mojo = makeMojo("fake-extension-runtime"); + mojo.execute(); + + } + + @Test + public void shouldFlagMissingDependenciesInARealisticExtension() + throws Exception { + + // Build both halves of the extension first so the install phase is done + mavenExecPom("fake-extension-runtime-with-missing-dependencies"); + mavenExecPom("fake-extension-deployment"); + + Exception thrown = Assertions.assertThrows(MojoExecutionException.class, () -> { + ExtensionDescriptorMojo mojo = makeMojo("fake-extension-runtime-with-missing-dependencies"); + mojo.execute(); + }); + assertTrue("Message format does not match expectations: \n" + thrown.getMessage(), + thrown.getMessage().contains(" corresponding runtime artifacts were not found")); + assertTrue("Missing artifact 'io.quarkus:quarkus-arc-deployment::jar' is not flagged: \n" + thrown.getMessage(), + thrown.getMessage().contains("io.quarkus:quarkus-arc-deployment::jar")); + assertTrue( + "Missing artifact 'io.quarkus:quarkus-smallrye-context-propagation-deployment::jar' is not flagged: \n" + + thrown.getMessage(), + thrown.getMessage().contains("io.quarkus:quarkus-smallrye-context-propagation-deployment::jar")); + } + + @Test + public void shouldFlagIncorrectRuntimeDependencyOnDeployment() + throws Exception { + + // Build both halves of the extension first so the install phase is done + mavenExecPom("fake-extension-runtime-with-deployment-dependency"); + mavenExecPom("fake-extension-deployment"); + + Exception thrown = Assertions.assertThrows(MojoExecutionException.class, () -> { + ExtensionDescriptorMojo mojo = makeMojo("fake-extension-runtime-with-missing-dependencies"); + mojo.execute(); + }); + String message = thrown.getMessage(); + assertTrue("Message format does not match expectations: \n" + message, + message.contains("depends on the following Quarkus extension deployment artifacts") + || message.contains("The following deployment artifact(s) appear on the runtime classpath")); + } + + // TODO we could also add some more tests about the dependency resolution and other extension descriptor functions here; see ExtensionDescriptorTaskTest for some ideas + + private File getGeneratedExtensionMetadataFile(File project, String child) { + return new File(project, child); + } + + private void assertYamlContains(File file, String key, String value) throws IOException { + assertYamlContains(readFileAsString(file), key, value); + } + + // Naive yaml processing; we do it this way for readability, and simplicity, and because it's how a human would check it by eye. + private void assertYamlContains(String fileContents, String key, String value) { + // ... we should probably cache the file read + assertTrue("Missing key '" + key + "' in \n" + fileContents, fileContents.contains(key)); + assertTrue("Missing value '" + value + "' in \n" + fileContents, fileContents.contains(key + ": \"" + value + "\"")); + + } + + private static String readFileAsString(File file) throws IOException { + return new String(Files.readAllBytes(file.toPath())); + } + + @Test + public void testGetCodestartArtifact() { assertEquals("io.quarkus:my-ext:999-SN", getCodestartArtifact("io.quarkus:my-ext", "999-SN")); assertEquals("io.quarkus:my-ext:codestarts:jar:999-SN", @@ -18,4 +185,96 @@ void testGetCodestartArtifact() { assertEquals("io.quarkus:my-ext:999-SN", getCodestartArtifact("io.quarkus:my-ext:${project.version}", "999-SN")); } + + private ExtensionDescriptorMojo makeMojo(String dirName) throws Exception { + File basedir = getTestFile(TEST_RESOURCES + dirName); + File pom = new File(basedir, "pom.xml"); + + MavenProject project = readMavenProject(basedir); + MavenSession session = newMavenSession(project); + + // add localRepo - framework doesn't do this on its own + ArtifactRepository localRepo = createLocalArtifactRepository(); + session.getRequest().setLocalRepository(localRepo); + + final MavenArtifactResolver mvn = new MavenArtifactResolver( + new BootstrapMavenContext(BootstrapMavenContext.config() + .setCurrentProject(pom.getAbsolutePath()) + .setOffline(RESOLVE_OFFLINE))); + + ExtensionDescriptorMojo mojo = (ExtensionDescriptorMojo) lookupConfiguredMojo(session, + newMojoExecution("extension-descriptor")); + mojo.repoSystem = mvn.getSystem(); + mojo.repoSession = mvn.getSession(); + return mojo; + } + + protected MavenProject readMavenProject(File basedir) + throws ProjectBuildingException, ComponentLookupException { + File pom = getGeneratedExtensionMetadataFile(basedir, "pom.xml"); + assertTrue(pom.exists()); + + MavenExecutionRequest request = new DefaultMavenExecutionRequest(); + request.setBaseDirectory(basedir); + ProjectBuildingRequest configuration = request.getProjectBuildingRequest(); + configuration.setRepositorySession(new DefaultRepositorySystemSession()); + configuration.setLocalRepository(createLocalArtifactRepository()); + MavenProject project = lookup(ProjectBuilder.class).build(pom, configuration).getProject(); + assertNotNull(project); + return project; + } + + private ArtifactRepository createLocalArtifactRepository() { + String repo = getLocalRepoPath(); + File localRepoDir = new File(repo); + return new MavenArtifactRepository("local", + localRepoDir.toURI().toString(), + new DefaultRepositoryLayout(), + new ArtifactRepositoryPolicy(true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, + ArtifactRepositoryPolicy.CHECKSUM_POLICY_IGNORE), + new ArtifactRepositoryPolicy(true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, + ArtifactRepositoryPolicy.CHECKSUM_POLICY_IGNORE) + + ); + } + + private String getLocalRepoPath() { + String repo = System.getProperty("maven.repo.local"); + if (repo == null) { + repo = new File(System.getProperty("user.home"), ".m2/repository").getAbsolutePath(); + } + return repo; + } + + private void mavenExecPom(String dir) throws MavenInvocationException { + InvocationRequest request = new DefaultInvocationRequest(); + File projectPath = getTestFile(TEST_RESOURCES, dir + "/pom.xml"); + + request.setPomFile(projectPath); + + request.setGoals(Collections.singletonList("install")); + Invoker invoker = new DefaultInvoker(); + + // This is a bit ugly, but bake in knowledge about where we are in the hierarchy to find maven + invoker.setMavenHome(new File("../../").getAbsoluteFile()); + invoker.setMavenExecutable(new File("../../mvnw").getAbsoluteFile()); + + invoker.execute(request); + } + + private void deleteDirectory(File fileToDelete) throws IOException { + + if (fileToDelete.exists()) { + Path pathToBeDeleted = fileToDelete.toPath(); + + Files.walk(pathToBeDeleted) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + + assertFalse("Directory still exists", + Files.exists(pathToBeDeleted)); + } + } + } diff --git a/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-deployment/pom.xml b/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-deployment/pom.xml new file mode 100644 index 00000000000000..baf1a16add1055 --- /dev/null +++ b/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-deployment/pom.xml @@ -0,0 +1,102 @@ + + + + + 4.0.0 + + io.quackiverse + quarkus-fake-extension-deployment + ${project.version} + Quarkus - Fake extension - Deployment + A support pom to provide test data to use against the runtime pom + + 1.16 + + + + + io.quackiverse + quarkus-fake-extension + ${project.version} + + + io.quarkus + quarkus-core-deployment + 2.12.0.Final + + + io.quarkus + quarkus-arc-deployment + 2.12.0.Final + + + io.quarkus + quarkus-datasource-deployment + 2.12.0.Final + + + io.quarkus + quarkus-narayana-jta-deployment + 2.12.0.Final + + + + + io.quarkus + quarkus-smallrye-health + true + 2.12.0.Final + + + + io.agroal + agroal-api + ${agroal.version} + + + io.agroal + agroal-narayana + ${agroal.version} + + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + + + + + io.agroal + agroal-pool + ${agroal.version} + + + io.quarkus + quarkus-credentials + 2.12.0.Final + + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${project.version} + + + io.quarkus.agroal + + + + + + + + + diff --git a/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-runtime-with-deployment-dependency/pom.xml b/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-runtime-with-deployment-dependency/pom.xml new file mode 100644 index 00000000000000..29b0306e37dc33 --- /dev/null +++ b/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-runtime-with-deployment-dependency/pom.xml @@ -0,0 +1,104 @@ + + + + + 4.0.0 + + io.quackiverse + quarkus-fake-extension + ${project.version} + Quarkus - Fake extension - Runtime + An artifact which depends on the deployment artifact + + 1.16 + + + + + + io.quarkus + quarkus-core-deployment + 2.12.0.Final + + + io.quarkus + quarkus-core + 2.12.0.Final + + + io.quarkus + quarkus-arc + 2.12.0.Final + + + io.quarkus + quarkus-datasource + 2.12.0.Final + + + io.quarkus + quarkus-narayana-jta + 2.12.0.Final + + + + + io.quarkus + quarkus-smallrye-health + true + 2.12.0.Final + + + + io.agroal + agroal-api + ${agroal.version} + + + io.agroal + agroal-narayana + ${agroal.version} + + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + + + + + io.agroal + agroal-pool + ${agroal.version} + + + io.quarkus + quarkus-credentials + 2.12.0.Final + + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${project.version} + + + io.quarkus.agroal + + + + + + + + + diff --git a/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-runtime-with-missing-dependencies/pom.xml b/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-runtime-with-missing-dependencies/pom.xml new file mode 100644 index 00000000000000..ab711fcd9413b3 --- /dev/null +++ b/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-runtime-with-missing-dependencies/pom.xml @@ -0,0 +1,43 @@ + + + + + 4.0.0 + + io.quackiverse + quarkus-fake-extension + ${project.version} + Quarkus - Fake extension - Deployment + A version of the fake extension whose pom is missing dependencies + + + io.quarkus + quarkus-core + ${project.version} + + + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${project.version} + + + io.quarkus.agroal + + + + + + + + + diff --git a/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-runtime/pom.xml b/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-runtime/pom.xml new file mode 100644 index 00000000000000..970d613ddb987f --- /dev/null +++ b/independent-projects/extension-maven-plugin/src/test/resources/fake-extension-runtime/pom.xml @@ -0,0 +1,99 @@ + + + + + 4.0.0 + + io.quackiverse + quarkus-fake-extension + ${project.version} + Quarkus - Fake extension - Runtime + Pool JDBC database connections (included in Hibernate ORM) + + 1.16 + + + + + + io.quarkus + quarkus-core + 2.12.0.Final + + + io.quarkus + quarkus-arc + 2.12.0.Final + + + io.quarkus + quarkus-datasource + 2.12.0.Final + + + io.quarkus + quarkus-narayana-jta + 2.12.0.Final + + + + + io.quarkus + quarkus-smallrye-health + true + 2.12.0.Final + + + + io.agroal + agroal-api + ${agroal.version} + + + io.agroal + agroal-narayana + ${agroal.version} + + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + + + + + io.agroal + agroal-pool + ${agroal.version} + + + io.quarkus + quarkus-credentials + 2.12.0.Final + + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${project.version} + + + io.quarkus.agroal + + + + + + + + + diff --git a/independent-projects/extension-maven-plugin/src/test/resources/simple-pom-with-checks-disabled/pom.xml b/independent-projects/extension-maven-plugin/src/test/resources/simple-pom-with-checks-disabled/pom.xml new file mode 100644 index 00000000000000..387378d514dbd4 --- /dev/null +++ b/independent-projects/extension-maven-plugin/src/test/resources/simple-pom-with-checks-disabled/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + io.quackiverse + test-artifact + 1.4.2-SNAPSHOT + an arbitrary name + + + true + true + + + + + + quarkus.io + extension-maven-plugin + ${project.version} + + + +