io.airlift.configuration.testing.ConfigAssertions Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2010 Proofpoint, Inc.
*
* Licensed 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 io.airlift.configuration.testing;
import com.google.common.collect.ImmutableSet;
import io.airlift.configuration.ConfigurationFactory;
import io.airlift.configuration.ConfigurationMetadata;
import io.airlift.configuration.ConfigurationMetadata.AttributeMetadata;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import static com.google.common.collect.Sets.newConcurrentHashSet;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
public final class ConfigAssertions
{
private static final Method GET_RECORDING_CONFIG_METHOD;
static {
try {
GET_RECORDING_CONFIG_METHOD = $$RecordingConfigProxy.class.getMethod("$$getRecordedConfig");
}
catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private ConfigAssertions() {}
public static void assertDefaults(Map expectedAttributeValues, Class configClass)
{
ConfigurationMetadata> metadata = ConfigurationMetadata.getValidConfigurationMetadata(configClass);
// verify all supplied attributes are supported
if (!metadata.getAttributes().keySet().containsAll(expectedAttributeValues.keySet())) {
Set unsupportedAttributes = new TreeSet<>(expectedAttributeValues.keySet());
unsupportedAttributes.removeAll(metadata.getAttributes().keySet());
throw new AssertionError("Unsupported attributes: " + unsupportedAttributes);
}
// verify all supplied attributes are supported not deprecated
Set nonDeprecatedAttributes = new TreeSet<>();
for (AttributeMetadata attribute : metadata.getAttributes().values()) {
if (attribute.getInjectionPoint().getProperty() != null) {
nonDeprecatedAttributes.add(attribute.getName());
}
}
if (!nonDeprecatedAttributes.containsAll(expectedAttributeValues.keySet())) {
Set unsupportedAttributes = new TreeSet<>(expectedAttributeValues.keySet());
unsupportedAttributes.removeAll(nonDeprecatedAttributes);
throw new AssertionError("Deprecated attributes: " + unsupportedAttributes);
}
// verify all attributes are tested
if (!expectedAttributeValues.keySet().containsAll(nonDeprecatedAttributes)) {
Set untestedAttributes = new TreeSet<>(nonDeprecatedAttributes);
untestedAttributes.removeAll(expectedAttributeValues.keySet());
throw new AssertionError("Untested attributes: " + untestedAttributes);
}
// create an uninitialized default instance
T actual = newDefaultInstance(configClass);
// verify each attribute is either the supplied default value
for (AttributeMetadata attribute : metadata.getAttributes().values()) {
Method getter = attribute.getGetter();
if (getter == null) {
continue;
}
Object actualAttributeValue = invoke(actual, getter);
Object expectedAttributeValue = expectedAttributeValues.get(attribute.getName());
if (!Objects.deepEquals(actualAttributeValue, expectedAttributeValue)) {
throw new AssertionError(notEquals(attribute.getName(), actualAttributeValue, expectedAttributeValue));
}
}
}
public static void assertFullMapping(Map properties, T expected)
{
requireNonNull(properties, "properties");
requireNonNull(expected, "expected");
Class configClass = getClass(expected);
ConfigurationMetadata metadata = ConfigurationMetadata.getValidConfigurationMetadata(configClass);
// verify all supplied properties are supported and not deprecated
assertPropertiesSupported(metadata, properties.keySet(), false);
// verify that every (non-deprecated) property is tested
Set nonDeprecatedProperties = new TreeSet<>();
for (AttributeMetadata attribute : metadata.getAttributes().values()) {
if (attribute.getInjectionPoint().getProperty() != null) {
nonDeprecatedProperties.add(attribute.getInjectionPoint().getProperty());
}
}
if (!properties.keySet().equals(nonDeprecatedProperties)) {
Set untestedProperties = new TreeSet<>(nonDeprecatedProperties);
untestedProperties.removeAll(properties.keySet());
throw new AssertionError("Untested properties " + untestedProperties);
}
// verify that none of the values are the same as a default for the configuration
T actual = newInstance(configClass, properties);
T defaultInstance = newDefaultInstance(configClass);
assertAttributesNotEqual(metadata, actual, defaultInstance);
// verify that a configuration object created from the properties is equivalent to the expected object
assertAttributesEqual(metadata, actual, expected);
}
@SafeVarargs
public static void assertDeprecatedEquivalence(Class configClass, Map currentProperties, Map... oldPropertiesList)
{
requireNonNull(configClass, "configClass");
requireNonNull(currentProperties, "currentProperties");
requireNonNull(oldPropertiesList, "oldPropertiesList");
ConfigurationMetadata metadata = ConfigurationMetadata.getValidConfigurationMetadata(configClass);
// verify all current properties are supported and not deprecated
assertPropertiesSupported(metadata, currentProperties.keySet(), false);
// verify all old properties are supported (deprecation allowed)
for (Map evenOlderProperties : oldPropertiesList) {
assertPropertiesSupported(metadata, evenOlderProperties.keySet(), true);
}
// verify that all deprecated properties are tested
Set knownDeprecatedProperties = new TreeSet<>();
for (AttributeMetadata attribute : metadata.getAttributes().values()) {
for (ConfigurationMetadata.InjectionPointMetaData deprecated : attribute.getLegacyInjectionPoints()) {
knownDeprecatedProperties.add(deprecated.getProperty());
}
}
Set suppliedDeprecatedProperties = new TreeSet<>();
for (Map evenOlderProperties : oldPropertiesList) {
suppliedDeprecatedProperties.addAll(evenOlderProperties.keySet());
}
if (!suppliedDeprecatedProperties.containsAll(knownDeprecatedProperties)) {
Set untestedDeprecatedProperties = new TreeSet<>(knownDeprecatedProperties);
untestedDeprecatedProperties.removeAll(suppliedDeprecatedProperties);
throw new AssertionError("Untested deprecated properties: " + untestedDeprecatedProperties);
}
// verify property sets create equivalent configurations
T currentConfiguration = newInstance(configClass, currentProperties);
for (Map evenOlderProperties : oldPropertiesList) {
T evenOlderConfiguration = newInstance(configClass, evenOlderProperties);
assertAttributesEqual(metadata, currentConfiguration, evenOlderConfiguration);
}
}
private static void assertPropertiesSupported(ConfigurationMetadata> metadata, Set propertyNames, boolean allowDeprecatedProperties)
{
Set supportedProperties = new TreeSet<>();
Set nonDeprecatedProperties = new TreeSet<>();
for (AttributeMetadata attribute : metadata.getAttributes().values()) {
if (attribute.getInjectionPoint().getProperty() != null) {
nonDeprecatedProperties.add(attribute.getInjectionPoint().getProperty());
supportedProperties.add(attribute.getInjectionPoint().getProperty());
}
for (ConfigurationMetadata.InjectionPointMetaData deprecated : attribute.getLegacyInjectionPoints()) {
supportedProperties.add(deprecated.getProperty());
}
}
if (!supportedProperties.containsAll(propertyNames)) {
Set unsupportedProperties = new TreeSet<>(propertyNames);
unsupportedProperties.removeAll(supportedProperties);
throw new AssertionError("Unsupported properties: " + unsupportedProperties);
}
// check for usage of deprecated properties
if (!allowDeprecatedProperties && !nonDeprecatedProperties.containsAll(propertyNames)) {
Set deprecatedProperties = new TreeSet<>(propertyNames);
deprecatedProperties.removeAll(nonDeprecatedProperties);
throw new AssertionError("Deprecated properties: " + deprecatedProperties);
}
}
private static void assertAttributesEqual(ConfigurationMetadata metadata, T actual, T expected)
{
for (AttributeMetadata attribute : metadata.getAttributes().values()) {
Method getter = attribute.getGetter();
if (getter == null) {
continue;
}
Object actualAttributeValue = invoke(actual, getter);
Object expectedAttributeValue = invoke(expected, getter);
if (!Objects.deepEquals(actualAttributeValue, expectedAttributeValue)) {
throw new AssertionError(notEquals(attribute.getName(), actualAttributeValue, expectedAttributeValue));
}
}
}
private static void assertAttributesNotEqual(ConfigurationMetadata metadata, T actual, T expected)
{
for (AttributeMetadata attribute : metadata.getAttributes().values()) {
Method getter = attribute.getGetter();
if (getter == null) {
continue;
}
Object actualAttributeValue = invoke(actual, getter);
Object expectedAttributeValue = invoke(expected, getter);
if (Objects.deepEquals(actualAttributeValue, expectedAttributeValue)) {
throw new AssertionError("Attribute value matches the default: " + attribute.getName());
}
}
}
public static void assertRecordedDefaults(T recordedConfig)
{
$$RecordedConfigData recordedConfigData = getRecordedConfig(recordedConfig);
Set invokedMethods = recordedConfigData.getInvokedMethods();
T config = recordedConfigData.getInstance();
Class configClass = getClass(config);
ConfigurationMetadata> metadata = ConfigurationMetadata.getValidConfigurationMetadata(configClass);
// collect information about the attributes that have been set
Map attributeValues = new TreeMap<>();
Set setDeprecatedAttributes = new TreeSet<>();
Set validSetterMethods = new HashSet<>();
for (AttributeMetadata attribute : metadata.getAttributes().values()) {
if (attribute.getInjectionPoint().getProperty() != null) {
validSetterMethods.add(attribute.getInjectionPoint().getSetter());
}
if (invokedMethods.contains(attribute.getInjectionPoint().getSetter())) {
if (attribute.getInjectionPoint().getProperty() != null) {
Object value = invoke(config, attribute.getGetter());
attributeValues.put(attribute.getName(), value);
}
else {
setDeprecatedAttributes.add(attribute.getName());
}
}
}
// verify no deprecated attribute setters have been called
if (!setDeprecatedAttributes.isEmpty()) {
throw new AssertionError("Invoked deprecated attribute setter methods: " + setDeprecatedAttributes);
}
// verify no other methods have been set
if (!validSetterMethods.containsAll(invokedMethods)) {
Set invalidInvocations = new HashSet<>(invokedMethods);
invalidInvocations.removeAll(validSetterMethods);
throw new AssertionError("Invoked non-attribute setter methods: " + invalidInvocations);
}
assertDefaults(attributeValues, configClass);
}
public static T recordDefaults(Class type)
{
Class extends T> loaded = new ByteBuddy()
.subclass(type)
.implement($$RecordingConfigProxy.class)
.method(ElementMatchers.any())
.intercept(createInvocationHandler(type))
.make()
.load(type.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
try {
return loaded.getConstructor().newInstance();
}
catch (ReflectiveOperationException e) {
throw new AssertionError("Failed to instantiate proxy class for " + type.getName(), e);
}
}
@SuppressWarnings("ObjectEquality")
private static InvocationHandlerAdapter createInvocationHandler(Class type)
{
T instance = newDefaultInstance(type);
Set invokedMethods = newConcurrentHashSet();
return InvocationHandlerAdapter.of((proxy, method, args) -> {
if (GET_RECORDING_CONFIG_METHOD.equals(method)) {
return new $$RecordedConfigData<>(instance, ImmutableSet.copyOf(invokedMethods));
}
invokedMethods.add(method);
Object result = method.invoke(instance, args);
if (result == instance) {
return proxy;
}
return result;
});
}
@SuppressWarnings("unchecked")
static $$RecordedConfigData getRecordedConfig(T config)
{
if (!(config instanceof $$RecordingConfigProxy)) {
throw new IllegalArgumentException("Configuration was not created with the recordDefaults method");
}
return (($$RecordingConfigProxy) config).$$getRecordedConfig();
}
@SuppressWarnings("checkstyle:TypeName")
public static class $$RecordedConfigData
{
private final T instance;
private final Set invokedMethods;
public $$RecordedConfigData(T instance, Set invokedMethods)
{
this.instance = instance;
this.invokedMethods = ImmutableSet.copyOf(invokedMethods);
}
public T getInstance()
{
return instance;
}
public Set getInvokedMethods()
{
return invokedMethods;
}
}
@SuppressWarnings({"checkstyle:TypeName", "checkstyle:MethodName"})
public interface $$RecordingConfigProxy
{
$$RecordedConfigData $$getRecordedConfig();
}
@SuppressWarnings("unchecked")
private static Class getClass(T object)
{
return (Class) object.getClass();
}
private static T newInstance(Class configClass, Map properties)
{
ConfigurationFactory configurationFactory = new ConfigurationFactory(properties);
return configurationFactory.build(configClass);
}
private static T newDefaultInstance(Class configClass)
{
try {
return configClass.getConstructor().newInstance();
}
catch (ReflectiveOperationException e) {
throw new AssertionError("Exception creating default instance of " + configClass.getName(), e);
}
}
private static Object invoke(T actual, Method getter)
{
try {
return getter.invoke(actual);
}
catch (ReflectiveOperationException e) {
throw new AssertionError("Exception invoking " + getter.toGenericString(), e);
}
}
private static String notEquals(String message, Object actual, Object expected)
{
return format("%s expected [%s] but found [%s]", message, expected, actual);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy