Skip to content

Commit

Permalink
QuarkusUnitTest: fix ignored overrideConfigKey()
Browse files Browse the repository at this point in the history
- if the test archive already contains an "application.properties" asset
- also clarify the javadoc: overriden config properties take precedence
- reimplement withConfigurationResource() to use JavaArchive#addAsResource(resourceName, "application.properties")
and  avoid the ordering inconsistency if both withConfigurationResource() and overrideConfigKey() are used
- fixes quarkusio#43129
  • Loading branch information
mkouba committed Sep 11, 2024
1 parent 26e5053 commit 8bc53b2
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,18 @@
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.UncheckedIOException;
import java.io.StringReader;
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;
Expand All @@ -36,14 +32,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;
Expand Down Expand Up @@ -79,6 +76,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";
Expand All @@ -104,6 +102,7 @@ public class QuarkusProdModeTest
private static final Timer timeoutTimer = new Timer("Test thread dump timer");
private volatile TimerTask timeoutTask;
private Properties customApplicationProperties;
private String configResourceName;
private CuratedApplication curatedApplication;

private boolean run;
Expand Down Expand Up @@ -138,16 +137,8 @@ public class QuarkusProdModeTest
private boolean clearRestAssuredURL;

public QuarkusProdModeTest() {
InputStream appPropsIs = Thread.currentThread().getContextClassLoader().getResourceAsStream("application.properties");
if (appPropsIs != null) {
customApplicationProperties = new Properties();
try (InputStream is = appPropsIs) {
customApplicationProperties.load(is);
} catch (IOException e) {
throw new UncheckedIOException("Failed to load application configuration from "
+ Thread.currentThread().getContextClassLoader().getResource("application.properties"), e);
}
}
this.configResourceName = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(APPLICATION_PROPERTIES) != null ? APPLICATION_PROPERTIES : null;
}

public Supplier<JavaArchive> getArchiveProducer() {
Expand Down Expand Up @@ -330,28 +321,48 @@ public Integer getExitCode() {
private void exportArchive(Path deploymentDir, Class<?> testClass) {
try {
JavaArchive archive = getArchiveProducerOrDefault();
if (customApplicationProperties != null) {
archive.add(new PropertiesAsset(customApplicationProperties), "application.properties");

if (configResourceName != null) {
archive.addAsResource(configResourceName, 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);
if (customApplicationProperties != null) {
Node applicationProperties = archive.get(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);
}
}
try (Stream<Path> 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);
}
Expand Down Expand Up @@ -793,24 +804,28 @@ public void beforeEach(ExtensionContext context) {
});
}

/**
* Add an {@code application.properties} asset loaded from the specified resource file in the test {@link JavaArchive}.
* <p>
* 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);
}
return this;
} catch (IOException e) {
throw new UncheckedIOException("Could not load resource: '" + resourceName + "'", e);
}
this.configResourceName = Objects.requireNonNull(resourceName);
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -114,6 +116,7 @@ public class QuarkusUnitTest
private Timer timeoutTimer;
private volatile TimerTask timeoutTask;
private Properties customApplicationProperties;
private String configResourceName;
private Map<String, String> customRuntimeApplicationProperties;
private Runnable beforeAllCustomizer;
private Runnable afterAllCustomizer;
Expand Down Expand Up @@ -348,9 +351,44 @@ private void exportArchives(Path deploymentDir, Class<?> testClass) {
archive.addClass(c);
c = c.getSuperclass();
}

if (configResourceName != null) {
archive.addAsResource(configResourceName, APPLICATION_PROPERTIES);
}

if (customApplicationProperties != null) {
archive.add(new PropertiesAsset(customApplicationProperties), "application.properties");
Node applicationProperties = archive.get(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) {
Expand Down Expand Up @@ -816,24 +854,28 @@ public QuarkusUnitTest setAfterUndeployListener(Runnable afterUndeployListener)
return this;
}

/**
* Add an {@code application.properties} asset loaded from the specified resource file in the test {@link JavaArchive}.
* <p>
* 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);
}
return this;
} catch (IOException e) {
throw new UncheckedIOException("Could not load resource: '" + resourceName + "'", e);
}
this.configResourceName = Objects.requireNonNull(resourceName);
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();
Expand Down

0 comments on commit 8bc53b2

Please sign in to comment.