diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java index 9c4b9514fe7019..b12e6869861b59 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java @@ -4,22 +4,19 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.StringReader; import java.io.UncheckedIOException; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,14 +33,15 @@ import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.stream.Collectors; -import java.util.stream.Stream; import jakarta.inject.Inject; import org.jboss.logmanager.Logger; +import org.jboss.shrinkwrap.api.Node; import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.Asset; +import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.exporter.ExplodedExporter; -import org.jboss.shrinkwrap.api.exporter.ZipExporter; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -79,6 +77,7 @@ public class QuarkusProdModeTest private static final int DEFAULT_HTTP_PORT_INT = 8081; private static final String DEFAULT_HTTP_PORT = "" + DEFAULT_HTTP_PORT_INT; private static final String QUARKUS_HTTP_PORT_PROPERTY = "quarkus.http.port"; + private static final String APPLICATION_PROPERTIES = "application.properties"; static final String BUILD_CONTEXT_BUILD_STEP_ENTRIES = "buildStepEntries"; static final String BUILD_CONTEXT_BUILD_STEP_ENTRY_CONSUMES = "buildStepEntryConsumes"; @@ -330,28 +329,44 @@ public Integer getExitCode() { private void exportArchive(Path deploymentDir, Class testClass) { try { JavaArchive archive = getArchiveProducerOrDefault(); - if (customApplicationProperties != null) { - archive.add(new PropertiesAsset(customApplicationProperties), "application.properties"); - } - archive.as(ExplodedExporter.class).exportExplodedInto(deploymentDir.toFile()); - String exportPath = System.getProperty("quarkus.deploymentExportPath"); - if (exportPath != null) { - File exportDir = new File(exportPath); - if (exportDir.exists()) { - if (!exportDir.isDirectory()) { - throw new IllegalStateException("Export path is not a directory: " + exportPath); + Node applicationProperties = archive.get(APPLICATION_PROPERTIES); + if (customApplicationProperties != null) { + if (applicationProperties != null) { + // We need to merge the existing "application.properties" asset and the overriden config properties + Properties mergedProperties = new Properties(); + Asset asset = applicationProperties.getAsset(); + if (asset instanceof StringAsset strAsset) { + mergedProperties.load(new StringReader(strAsset.getSource())); + } else { + try (InputStream in = asset.openStream()) { + mergedProperties.load(in); + } } - try (Stream stream = Files.walk(exportDir.toPath())) { - stream.sorted(Comparator.reverseOrder()).map(Path::toFile) - .forEach(File::delete); + // overrideConfigKey() takes precedence + customApplicationProperties.forEach(mergedProperties::put); + + if (Boolean.parseBoolean(System.getProperty("quarkus.test.log-merged-properties"))) { + System.out.println("Merged config properties:\n" + mergedProperties.entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("\n"))); + } else { + System.out.println( + "NOTE: overrideConfigKey() and application.properties were merged; use quarkus.test.log-merged-properties=true to list the specific values"); } - } else if (!exportDir.mkdirs()) { - throw new IllegalStateException("Export path could not be created: " + exportPath); + + // MemoryMapArchiveBase#addAsset(ArchivePath,Asset) does not overwrite the existing node correctly + // https://github.com/shrinkwrap/shrinkwrap/issues/179 + archive.delete(APPLICATION_PROPERTIES); + archive.add(new PropertiesAsset(mergedProperties), APPLICATION_PROPERTIES); + } else { + archive.add(new PropertiesAsset(customApplicationProperties), APPLICATION_PROPERTIES); } - File exportFile = new File(exportDir, archive.getName()); - archive.as(ZipExporter.class).exportTo(exportFile); } + + archive.as(ExplodedExporter.class).exportExplodedInto(deploymentDir.toFile()); + + ExportUtil.exportToQuarkusDeploymentPath(archive); + } catch (Exception e) { throw new RuntimeException("Unable to create the archive", e); } @@ -793,24 +808,37 @@ public void beforeEach(ExtensionContext context) { }); } + /** + * Add an {@code application.properties} asset loaded from the specified resource file in the test {@link JavaArchive}. + *

+ * Configuration properties added with {@link #overrideConfigKey(String, String)} take precedence over the properties from + * the specified resource file. + * + * @param resourceName + * @return the test configuration + */ public QuarkusProdModeTest withConfigurationResource(String resourceName) { - if (customApplicationProperties == null) { - customApplicationProperties = new Properties(); - } - try { - URL systemResource = ClassLoader.getSystemResource(resourceName); - if (systemResource == null) { - throw new FileNotFoundException("Resource '" + resourceName + "' not found"); - } - try (InputStream in = systemResource.openStream()) { - customApplicationProperties.load(in); + final Supplier producer = archiveProducer; + setArchiveProducer(new Supplier() { + + @Override + public JavaArchive get() { + JavaArchive ret = producer == null ? ShrinkWrap.create(JavaArchive.class) : producer.get(); + ret.addAsResource(resourceName, APPLICATION_PROPERTIES); + return ret; } - return this; - } catch (IOException e) { - throw new UncheckedIOException("Could not load resource: '" + resourceName + "'", e); - } + }); + return this; } + /** + * Overriden configuration properties take precedence over an {@code application.properties} asset added in the test + * {@link JavaArchive}. + * + * @param propertyKey + * @param propertyValue + * @return the test configuration + */ public QuarkusProdModeTest overrideConfigKey(final String propertyKey, final String propertyValue) { if (customApplicationProperties == null) { customApplicationProperties = new Properties(); diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java index 3e0b9bb4d57470..a246fead6ade5c 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java @@ -3,16 +3,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import java.io.FileNotFoundException; -import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; +import java.io.StringReader; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -33,9 +30,13 @@ import java.util.logging.Handler; import java.util.logging.LogManager; import java.util.logging.LogRecord; +import java.util.stream.Collectors; import org.jboss.logmanager.Logger; +import org.jboss.shrinkwrap.api.Node; import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.Asset; +import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.exporter.ExplodedExporter; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.extension.AfterAllCallback; @@ -87,6 +88,7 @@ public class QuarkusUnitTest public static final String THE_BUILD_WAS_EXPECTED_TO_FAIL = "The build was expected to fail"; private static final String APP_ROOT = "app-root"; + private static final String APPLICATION_PROPERTIES = "application.properties"; private static final Logger rootLogger; private Handler[] originalHandlers; @@ -348,9 +350,40 @@ private void exportArchives(Path deploymentDir, Class testClass) { archive.addClass(c); c = c.getSuperclass(); } + + Node applicationProperties = archive.get(APPLICATION_PROPERTIES); if (customApplicationProperties != null) { - archive.add(new PropertiesAsset(customApplicationProperties), "application.properties"); + if (applicationProperties != null) { + // We need to merge the existing "application.properties" asset and the overriden config properties + Properties mergedProperties = new Properties(); + Asset asset = applicationProperties.getAsset(); + if (asset instanceof StringAsset strAsset) { + mergedProperties.load(new StringReader(strAsset.getSource())); + } else { + try (InputStream in = asset.openStream()) { + mergedProperties.load(in); + } + } + // overrideConfigKey() takes precedence + customApplicationProperties.forEach(mergedProperties::put); + + if (Boolean.parseBoolean(System.getProperty("quarkus.test.log-merged-properties"))) { + System.out.println("Merged config properties:\n" + mergedProperties.entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("\n"))); + } else { + System.out.println( + "NOTE: overrideConfigKey() and application.properties were merged; use quarkus.test.log-merged-properties=true to list the specific values"); + } + + // MemoryMapArchiveBase#addAsset(ArchivePath,Asset) does not overwrite the existing node correctly + // https://github.com/shrinkwrap/shrinkwrap/issues/179 + archive.delete(APPLICATION_PROPERTIES); + archive.add(new PropertiesAsset(mergedProperties), APPLICATION_PROPERTIES); + } else { + archive.add(new PropertiesAsset(customApplicationProperties), APPLICATION_PROPERTIES); + } } + archive.as(ExplodedExporter.class).exportExplodedInto(deploymentDir.resolve(APP_ROOT).toFile()); for (JavaArchive dependency : additionalDependencies) { @@ -816,24 +849,37 @@ public QuarkusUnitTest setAfterUndeployListener(Runnable afterUndeployListener) return this; } + /** + * Add an {@code application.properties} asset loaded from the specified resource file in the test {@link JavaArchive}. + *

+ * Configuration properties added with {@link #overrideConfigKey(String, String)} take precedence over the properties from + * the specified resource file. + * + * @param resourceName + * @return the test configuration + */ public QuarkusUnitTest withConfigurationResource(String resourceName) { - if (customApplicationProperties == null) { - customApplicationProperties = new Properties(); - } - try { - URL systemResource = ClassLoader.getSystemResource(resourceName); - if (systemResource == null) { - throw new FileNotFoundException("Resource '" + resourceName + "' not found"); - } - try (InputStream in = systemResource.openStream()) { - customApplicationProperties.load(in); + final Supplier producer = archiveProducer; + setArchiveProducer(new Supplier() { + + @Override + public JavaArchive get() { + JavaArchive ret = producer == null ? ShrinkWrap.create(JavaArchive.class) : producer.get(); + ret.addAsResource(resourceName, APPLICATION_PROPERTIES); + return ret; } - return this; - } catch (IOException e) { - throw new UncheckedIOException("Could not load resource: '" + resourceName + "'", e); - } + }); + return this; } + /** + * Overriden configuration properties take precedence over an {@code application.properties} asset added in the test + * {@link JavaArchive}. + * + * @param propertyKey + * @param propertyValue + * @return the test configuration + */ public QuarkusUnitTest overrideConfigKey(final String propertyKey, final String propertyValue) { if (customApplicationProperties == null) { customApplicationProperties = new Properties();