Skip to content

Commit

Permalink
QuarkusComponentTest: initial support for ConfigMapping
Browse files Browse the repository at this point in the history
  • Loading branch information
mkouba committed Oct 10, 2023
1 parent 89a0142 commit 0739dae
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.test.component;

import io.quarkus.arc.BeanCreator;
import io.quarkus.arc.SyntheticCreationalContext;
import io.smallrye.config.SmallRyeConfig;

public class ConfigMappingBeanCreator implements BeanCreator<Object> {

@Override
public Object create(SyntheticCreationalContext<Object> context) {
String prefix = context.getParams().get("prefix").toString();
Class<?> mappingClass = tryLoad(context.getParams().get("mappingClass").toString());
SmallRyeConfig config = ConfigBeanCreator.getConfig().unwrap(SmallRyeConfig.class);
return config.getConfigMapping(mappingClass, prefix);
}

static Class<?> tryLoad(String name) {
try {
return Thread.currentThread().getContextClassLoader().loadClass(name);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Unable to load type: " + name, e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
Expand Down Expand Up @@ -48,6 +49,7 @@
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
Expand Down Expand Up @@ -95,9 +97,12 @@
import io.quarkus.runtime.configuration.ApplicationPropertiesConfigSourceLoader;
import io.quarkus.test.InjectMock;
import io.smallrye.common.annotation.Experimental;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.ConfigMappings;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigBuilder;
import io.smallrye.config.SmallRyeConfigProviderResolver;
import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix;

/**
* Makes it easy to test Quarkus components. This extension can be registered declaratively with {@link QuarkusComponentTest} or
Expand Down Expand Up @@ -150,6 +155,7 @@ public static QuarkusComponentTestExtensionBuilder builder() {
private static final String KEY_TEST_INSTANCE = "testInstance";
private static final String KEY_CONFIG = "config";
private static final String KEY_TEST_CLASS_CONFIG = "testClassConfig";
private static final String KEY_CONFIG_MAPPINGS = "configMappings";

private static final String QUARKUS_TEST_COMPONENT_OUTPUT_DIRECTORY = "quarkus.test.component.output-directory";

Expand Down Expand Up @@ -298,6 +304,12 @@ private void startContainer(ExtensionContext context, Lifecycle testInstanceLife
context.getRoot().getStore(NAMESPACE).put(KEY_CONFIG, config);
ConfigBeanCreator.setClassLoader(tccl);

// Register the mappings found during bean discovery
@SuppressWarnings("unchecked")
Set<ConfigClassWithPrefix> configMappings = context.getRoot().getStore(NAMESPACE).get(KEY_CONFIG_MAPPINGS,
Set.class);
ConfigMappings.registerConfigMappings(config, configMappings);

// Inject fields declated on the test class
Object testInstance = context.getRequiredTestInstance();
context.getRoot().getStore(NAMESPACE).put(KEY_INJECTED_FIELDS,
Expand Down Expand Up @@ -503,17 +515,15 @@ public void register(RegistrationContext registrationContext) {
Set<TypeAndQualifiers> unsatisfiedInjectionPoints = new HashSet<>();
boolean configInjectionPoint = false;
Set<TypeAndQualifiers> configPropertyInjectionPoints = new HashSet<>();
Map<String, String> prefixToConfigMappingClasses = new HashMap<>();
DotName configDotName = DotName.createSimple(Config.class);
DotName configPropertyDotName = DotName.createSimple(ConfigProperty.class);
DotName configMappingDotName = DotName.createSimple(ConfigMapping.class);

// Analyze injection points
// - find Config and @ConfigProperty injection points
// - find unsatisfied injection points
for (InjectionPointInfo injectionPoint : registrationContext.getInjectionPoints()) {
BuiltinBean builtin = BuiltinBean.resolve(injectionPoint);
if (builtin != null && builtin != BuiltinBean.INSTANCE && builtin != BuiltinBean.LIST) {
continue;
}
if (injectionPoint.getRequiredType().name().equals(configDotName)
&& injectionPoint.hasDefaultedQualifier()) {
configInjectionPoint = true;
Expand All @@ -524,6 +534,12 @@ public void register(RegistrationContext registrationContext) {
injectionPoint.getRequiredQualifiers()));
continue;
}
BuiltinBean builtin = BuiltinBean.resolve(injectionPoint);
if (builtin != null && builtin != BuiltinBean.INSTANCE && builtin != BuiltinBean.LIST) {
continue;
}
// TODO injection points with ConfigMapping??

Type requiredType = injectionPoint.getRequiredType();
Set<AnnotationInstance> requiredQualifiers = injectionPoint.getRequiredQualifiers();
if (builtin == BuiltinBean.LIST) {
Expand All @@ -535,6 +551,19 @@ public void register(RegistrationContext registrationContext) {
requiredQualifiers.add(AnnotationInstance.builder(DotNames.DEFAULT).build());
}
}

if (requiredType.kind() == Kind.CLASS) {
ClassInfo clazz = computingIndex.getClassByName(requiredType.name());
if (clazz != null && clazz.isInterface()) {
AnnotationInstance configMapping = clazz.declaredAnnotation(configMappingDotName);
if (configMapping != null) {
AnnotationValue prefixValue = configMapping.value("prefix");
prefixToConfigMappingClasses.put(prefixValue == null ? "" : prefixValue.asString(),
clazz.name().toString());
}
}
}

if (isSatisfied(requiredType, requiredQualifiers, injectionPoint, beans, beanDeployment,
configuration)) {
continue;
Expand Down Expand Up @@ -591,6 +620,22 @@ public void register(RegistrationContext registrationContext) {
configPropertyConfigurator.done();
}

if (!prefixToConfigMappingClasses.isEmpty()) {
Set<ConfigClassWithPrefix> configMappings = new HashSet<>();
for (Entry<String, String> e : prefixToConfigMappingClasses.entrySet()) {
DotName mappingName = DotName.createSimple(e.getValue());
registrationContext.configure(mappingName)
.addType(mappingName)
.creator(ConfigMappingBeanCreator.class)
.param("mappingClass", e.getValue())
.param("prefix", e.getKey())
.done();
configMappings.add(ConfigClassWithPrefix
.configClassWithPrefix(ConfigMappingBeanCreator.tryLoad(e.getValue()), e.getKey()));
}
extensionContext.getRoot().getStore(NAMESPACE).put(KEY_CONFIG_MAPPINGS, configMappings);
}

LOG.debugf("Test injection points analyzed in %s ms [found: %s, mocked: %s]",
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start),
registrationContext.getInjectionPoints().size(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.quarkus.test.component.config;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

import io.quarkus.test.component.QuarkusComponentTest;
import io.quarkus.test.component.TestConfigProperty;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

@QuarkusComponentTest
@TestConfigProperty(key = "foo.bar", value = "true")
public class ConfigMappingTest {

@Inject
Foo foo;

@Test
public void testMapping() {
assertTrue(foo.config.bar());
assertEquals("loom", foo.config.baz());
}

@Singleton
public static class Foo {

@Inject
FooConfig config;
}

@ConfigMapping(prefix = "foo")
interface FooConfig {

boolean bar();

@WithDefault("loom")
String baz();
}
}

0 comments on commit 0739dae

Please sign in to comment.