All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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