Back to Repositories

Testing Hystrix Plugin System Implementation in Netflix/Hystrix

This test suite validates the Hystrix plugin system functionality, focusing on dynamic properties, service loading, and plugin initialization in Netflix’s Hystrix library. It ensures proper plugin registration, configuration management, and integration with various Hystrix components.

Test Coverage Overview

The test suite provides comprehensive coverage of Hystrix’s plugin architecture, including:
  • Dynamic properties loading and configuration
  • Service loader implementation and error handling
  • Plugin registration and initialization workflows
  • System property integration testing
  • Mock implementation validation

Implementation Analysis

The testing approach utilizes JUnit to verify plugin behavior through mock implementations and service loading. Key patterns include custom ClassLoader implementation for service loading simulation, proxy-based mock logging, and systematic validation of plugin initialization sequences.

The implementation leverages reflection and dynamic class loading to test various plugin configurations and failure scenarios.

Technical Details

Testing tools and configuration:
  • JUnit test framework
  • Mock ClassLoader implementation
  • Dynamic proxy-based logging
  • System property manipulation
  • Custom service loader resources
  • Concurrent operation testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Proper test isolation through @After cleanup
  • Comprehensive error case handling
  • Systematic verification of plugin chains
  • Mock object usage for external dependencies
  • Clear test method organization
  • Thorough assertion coverage

netflix/hystrix

hystrix-core/src/test/java/com/netflix/hystrix/strategy/HystrixPluginsTest.java

            
/**
 * Copyright 2016 Netflix, 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 com.netflix.hystrix.strategy;

import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.Vector;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.junit.After;
import org.junit.Test;
import org.slf4j.Logger;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.strategy.HystrixPlugins.LoggerSupplier;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixDynamicProperties;
import com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesSystemProperties;
import com.netflix.hystrix.strategy.properties.HystrixDynamicProperty;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;

public class HystrixPluginsTest {
    @After
    public void reset() {
        //HystrixPlugins.reset();
        dynamicPropertyEvents.clear();
        System.clearProperty("hystrix.plugin.HystrixDynamicProperties.implementation");
    }
    
    private static ConcurrentLinkedQueue<String> dynamicPropertyEvents = new ConcurrentLinkedQueue<String>();

    
    @Test
    public void testDynamicProperties() throws Exception {
        fakeServiceLoaderResource = 
                "FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties";
        HystrixPlugins plugins = setupMockServiceLoader();
        HystrixDynamicProperties properties = plugins.getDynamicProperties();
        plugins.getCommandExecutionHook();
        plugins.getPropertiesStrategy();
        assertTrue(properties instanceof MockHystrixDynamicPropertiesTest);

        assertEvents(
                "[serviceloader: META-INF/services/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties"
                        + ", debug: [Created HystrixDynamicProperties instance by loading from ServiceLoader. Using class: {}, com.netflix.hystrix.strategy.HystrixPluginsTest.MockHystrixDynamicPropertiesTest]"
                        + ", property: hystrix.plugin.HystrixCommandExecutionHook.implementation"
                        + ", serviceloader: META-INF/services/com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook"
                        + ", property: hystrix.plugin.HystrixPropertiesStrategy.implementation"
                        + ", serviceloader: META-INF/services/com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy]");
    }
    
    void assertEvents(String expect) throws Exception {
        List<String> keys = getEvents();
        String actual = keys.toString();
        if (! actual.equals(expect)) {
            javaPrintList(System.out, keys);
        }
        assertEquals(expect, actual);
    }
    
    static List<String> getEvents() {
        return new ArrayList<String>(dynamicPropertyEvents);
    }
    
    static void javaPrintList(Appendable a, Iterable<String> list) throws IOException {
        boolean first = true;
        
        for (String o : list) {
            if (first) {
                a.append("\"[");
                first = false;
            }
            else {
                a.append("\"");
                a.append("\n+ \", ");
            }
            a.append(o);
        }
        a.append("]\"");
    }
    
    @Test(expected=ServiceConfigurationError.class)
    public void testDynamicPropertiesFailure() throws Exception {
        /*
         * James Bond: Do you expect me to talk?
         * Auric Goldfinger: No, Mr. Bond, I expect you to die!
         */
        fakeServiceLoaderResource = 
                "FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesFail";
        HystrixPlugins plugins = setupMockServiceLoader();
        plugins.getDynamicProperties();

    }
    
    @Test
    public void testDynamicSystemProperties() throws Exception {
        //On the off chance this is the first test lets not screw up all the other tests
        HystrixPlugins.getInstance();
        
        System.setProperty("hystrix.plugin.HystrixDynamicProperties.implementation", 
                "com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesSystemProperties");
        
        HystrixPlugins plugins = setupMockServiceLoader();
        assertTrue(plugins.getDynamicProperties() instanceof HystrixDynamicPropertiesSystemProperties);
        
        HystrixDynamicProperties p = plugins.getDynamicProperties();
        //Some minimum testing of system properties wrapper
        //this probably should be in its own test class.
        assertTrue(p.getBoolean("USE_DEFAULT", true).get());
        assertEquals("string", p.getString("USE_DEFAULT", "string").get());
        assertEquals(1L, p.getLong("USE_DEFAULT", 1L).get().longValue());
        assertEquals(1, p.getInteger("USE_DEFAULT", 1).get().intValue());
        assertNotNull(p.getString("path.separator", null).get());
        
        assertEvents("[debug: [Created HystrixDynamicProperties instance from System property named \"hystrix.plugin.HystrixDynamicProperties.implementation\". Using class: {}, com.netflix.hystrix.strategy.properties.HystrixDynamicPropertiesSystemProperties]]");

        System.clearProperty("hystrix.plugin.HystrixDynamicProperties.implementation");

    }
        
    static String fakeServiceLoaderResource = 
            "FAKE_META_INF_SERVICES/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties";
    
    private HystrixPlugins setupMockServiceLoader() throws Exception {
        final ClassLoader realLoader = HystrixPlugins.class.getClassLoader();
        ClassLoader loader = new WrappedClassLoader(realLoader) {

            @Override
            public Enumeration<URL> getResources(String name) throws IOException {
                dynamicPropertyEvents.add("serviceloader: " + name);
                final Enumeration<URL> r;
                if (name.endsWith("META-INF/services/com.netflix.hystrix.strategy.properties.HystrixDynamicProperties")) {
                    Vector<URL> vs = new Vector<URL>();
                    URL u = super.getResource(fakeServiceLoaderResource);
                    vs.add(u);
                    return vs.elements();
                } else {
                    r = super.getResources(name);
                }
                return r;
            }
        };
        final Logger mockLogger = (Logger) 
                Proxy.newProxyInstance(realLoader, new Class<?>[] {Logger.class}, new MockLoggerInvocationHandler());
        return HystrixPlugins.create(loader, new LoggerSupplier() {
            @Override
            public Logger getLogger() {
                return mockLogger;
            }
        });
    }
    
    static class MockLoggerInvocationHandler implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            dynamicPropertyEvents.offer(method.getName() + ": " + asList(args));
            return null;
        }
        
    }

    static class WrappedClassLoader extends ClassLoader {

        final ClassLoader delegate;

        public WrappedClassLoader(ClassLoader delegate) {
            super();
            this.delegate = delegate;
        }

        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return delegate.loadClass(name);
        }

        public URL getResource(String name) {
            return delegate.getResource(name);
        }

        public Enumeration<URL> getResources(String name) throws IOException {
            return delegate.getResources(name);
        }

        public InputStream getResourceAsStream(String name) {
            return delegate.getResourceAsStream(name);
        }

        public void setDefaultAssertionStatus(boolean enabled) {
            delegate.setDefaultAssertionStatus(enabled);
        }

        public void setPackageAssertionStatus(String packageName, boolean enabled) {
            delegate.setPackageAssertionStatus(packageName, enabled);
        }

        public void setClassAssertionStatus(String className, boolean enabled) {
            delegate.setClassAssertionStatus(className, enabled);
        }

        public void clearAssertionStatus() {
            delegate.clearAssertionStatus();
        }
    }

    private static class NoOpProperty<T> implements HystrixDynamicProperty<T> {

        @Override
        public T get() {
            return null;
        }

        @Override
        public void addCallback(Runnable callback) {
        }

        @Override
        public String getName() {
            return "NOP";
        }
        
    }
    
    public static class MockHystrixDynamicPropertiesTest implements HystrixDynamicProperties {

        @Override
        public HystrixDynamicProperty<String> getString(String name, String fallback) {
            dynamicPropertyEvents.offer("property: " + name);
            return new NoOpProperty<String>();
        }

        @Override
        public HystrixDynamicProperty<Integer> getInteger(String name, Integer fallback) {
            dynamicPropertyEvents.offer("property: " + name);
            return new NoOpProperty<Integer>();
        }

        @Override
        public HystrixDynamicProperty<Long> getLong(String name, Long fallback) {
            dynamicPropertyEvents.offer("property: " + name);
            return new NoOpProperty<Long>();

        }

        @Override
        public HystrixDynamicProperty<Boolean> getBoolean(String name, Boolean fallback) {
            dynamicPropertyEvents.offer("property: " + name);
            return new NoOpProperty<Boolean>();

        }
        
    }

    /*    @Test
    public void testCommandExecutionHookDefaultImpl() {
        HystrixCommandExecutionHook impl = HystrixPlugins.getInstance().getCommandExecutionHook();
        assertTrue(impl instanceof HystrixCommandExecutionHookDefault);
    }

    @Test
    public void testCommandExecutionHookViaRegisterMethod() {
        HystrixPlugins.getInstance().registerCommandExecutionHook(new HystrixCommandExecutionHookTestImpl());
        HystrixCommandExecutionHook impl = HystrixPlugins.getInstance().getCommandExecutionHook();
        assertTrue(impl instanceof HystrixCommandExecutionHookTestImpl);
	}*/

    public static class HystrixCommandExecutionHookTestImpl extends HystrixCommandExecutionHook {
    }

    /*@Test
    public void testEventNotifierDefaultImpl() {
        HystrixEventNotifier impl = HystrixPlugins.getInstance().getEventNotifier();
        assertTrue(impl instanceof HystrixEventNotifierDefault);
    }

    @Test
    public void testEventNotifierViaRegisterMethod() {
        HystrixPlugins.getInstance().registerEventNotifier(new HystrixEventNotifierTestImpl());
        HystrixEventNotifier impl = HystrixPlugins.getInstance().getEventNotifier();
        assertTrue(impl instanceof HystrixEventNotifierTestImpl);
    }

    @Test
    public void testEventNotifierViaProperty() {
        try {
            String fullClass = HystrixEventNotifierTestImpl.class.getName();
            System.setProperty("hystrix.plugin.HystrixEventNotifier.implementation", fullClass);
            HystrixEventNotifier impl = HystrixPlugins.getInstance().getEventNotifier();
            assertTrue(impl instanceof HystrixEventNotifierTestImpl);
        } finally {
            System.clearProperty("hystrix.plugin.HystrixEventNotifier.implementation");
        }
	}*/

    // inside UnitTest so it is stripped from Javadocs
    public static class HystrixEventNotifierTestImpl extends HystrixEventNotifier {
        // just use defaults
    }

    /*@Test
    public void testConcurrencyStrategyDefaultImpl() {
        HystrixConcurrencyStrategy impl = HystrixPlugins.getInstance().getConcurrencyStrategy();
        assertTrue(impl instanceof HystrixConcurrencyStrategyDefault);
    }

    @Test
    public void testConcurrencyStrategyViaRegisterMethod() {
        HystrixPlugins.getInstance().registerConcurrencyStrategy(new HystrixConcurrencyStrategyTestImpl());
        HystrixConcurrencyStrategy impl = HystrixPlugins.getInstance().getConcurrencyStrategy();
        assertTrue(impl instanceof HystrixConcurrencyStrategyTestImpl);
    }

    @Test
    public void testConcurrencyStrategyViaProperty() {
        try {
            String fullClass = HystrixConcurrencyStrategyTestImpl.class.getName();
            System.setProperty("hystrix.plugin.HystrixConcurrencyStrategy.implementation", fullClass);
            HystrixConcurrencyStrategy impl = HystrixPlugins.getInstance().getConcurrencyStrategy();
            assertTrue(impl instanceof HystrixConcurrencyStrategyTestImpl);
        } finally {
            System.clearProperty("hystrix.plugin.HystrixConcurrencyStrategy.implementation");
        }
	}*/

    // inside UnitTest so it is stripped from Javadocs
    public static class HystrixConcurrencyStrategyTestImpl extends HystrixConcurrencyStrategy {
        // just use defaults
    }

    /*@Test
    public void testMetricsPublisherDefaultImpl() {
        HystrixMetricsPublisher impl = HystrixPlugins.getInstance().getMetricsPublisher();
        assertTrue(impl instanceof HystrixMetricsPublisherDefault);
    }

    @Test
    public void testMetricsPublisherViaRegisterMethod() {
        HystrixPlugins.getInstance().registerMetricsPublisher(new HystrixMetricsPublisherTestImpl());
        HystrixMetricsPublisher impl = HystrixPlugins.getInstance().getMetricsPublisher();
        assertTrue(impl instanceof HystrixMetricsPublisherTestImpl);
    }

    @Test
    public void testMetricsPublisherViaProperty() {
        try {
            String fullClass = HystrixMetricsPublisherTestImpl.class.getName();
            System.setProperty("hystrix.plugin.HystrixMetricsPublisher.implementation", fullClass);
            HystrixMetricsPublisher impl = HystrixPlugins.getInstance().getMetricsPublisher();
            assertTrue(impl instanceof HystrixMetricsPublisherTestImpl);
        } finally {
            System.clearProperty("hystrix.plugin.HystrixMetricsPublisher.implementation");
        }
	}*/

    // inside UnitTest so it is stripped from Javadocs
    public static class HystrixMetricsPublisherTestImpl extends HystrixMetricsPublisher {
        // just use defaults
    }

    /*@Test
    public void testPropertiesStrategyDefaultImpl() {
        HystrixPropertiesStrategy impl = HystrixPlugins.getInstance().getPropertiesStrategy();
        assertTrue(impl instanceof HystrixPropertiesStrategyDefault);
    }

    @Test
    public void testPropertiesStrategyViaRegisterMethod() {
        HystrixPlugins.getInstance().registerPropertiesStrategy(new HystrixPropertiesStrategyTestImpl());
        HystrixPropertiesStrategy impl = HystrixPlugins.getInstance().getPropertiesStrategy();
        assertTrue(impl instanceof HystrixPropertiesStrategyTestImpl);
    }

    @Test
    public void testPropertiesStrategyViaProperty() {
        try {
            String fullClass = HystrixPropertiesStrategyTestImpl.class.getName();
            System.setProperty("hystrix.plugin.HystrixPropertiesStrategy.implementation", fullClass);
            HystrixPropertiesStrategy impl = HystrixPlugins.getInstance().getPropertiesStrategy();
            assertTrue(impl instanceof HystrixPropertiesStrategyTestImpl);
        } finally {
            System.clearProperty("hystrix.plugin.HystrixPropertiesStrategy.implementation");
        }
	}*/

    // inside UnitTest so it is stripped from Javadocs
    public static class HystrixPropertiesStrategyTestImpl extends HystrixPropertiesStrategy {
        // just use defaults
    }
    
    /*@Test
    public void testRequestContextViaPluginInTimeout() {
        HystrixPlugins.getInstance().registerConcurrencyStrategy(new HystrixConcurrencyStrategy() {
            @Override
            public <T> Callable<T> wrapCallable(final Callable<T> callable) {
                return new RequestIdCallable<T>(callable);
            }
        });

        HystrixRequestContext context = HystrixRequestContext.initializeContext();

        testRequestIdThreadLocal.set("foobar");
        final AtomicReference<String> valueInTimeout = new AtomicReference<String>();

        new DummyCommand().toObservable()
                .doOnError(new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        System.out.println("initialized = " + HystrixRequestContext.isCurrentThreadInitialized());
                        System.out.println("requestId (timeout) = " + testRequestIdThreadLocal.get());
                        valueInTimeout.set(testRequestIdThreadLocal.get());
                    }
                })
                .materialize()
                .toBlocking().single();

        context.shutdown();
        Hystrix.reset();
        
        assertEquals("foobar", valueInTimeout.get());
	}*/

    private static class RequestIdCallable<T> implements Callable<T> {
        private final Callable<T> callable;
        private final String requestId;

        public RequestIdCallable(Callable<T> callable) {
            this.callable = callable;
            this.requestId = testRequestIdThreadLocal.get();
        }

        @Override
        public T call() throws Exception {
            String original = testRequestIdThreadLocal.get();
            testRequestIdThreadLocal.set(requestId);
            try {
                return callable.call();
            } finally {
                testRequestIdThreadLocal.set(original);
            }
        }
    }
    
    private static final ThreadLocal<String> testRequestIdThreadLocal = new ThreadLocal<String>();

    public static class DummyCommand extends HystrixCommand<Void> {

        public DummyCommand() {
            super(HystrixCommandGroupKey.Factory.asKey("Dummy"));
        }

        @Override
        protected Void run() throws Exception {
            System.out.println("requestId (run) = " + testRequestIdThreadLocal.get());
            Thread.sleep(2000);
            return null;
        }
    }
}