Back to Repositories

Testing Hystrix Metrics Publisher Factory Implementation in Netflix/Hystrix

This test suite validates the functionality of the HystrixMetricsPublisherFactory, which manages metrics publishers for Hystrix commands and thread pools. The tests ensure proper initialization and singleton behavior of metrics publishers across multiple threads and reset scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of the HystrixMetricsPublisherFactory functionality.

  • Tests single initialization of publishers per command/thread pool key
  • Verifies concurrent access handling with multiple threads
  • Validates publisher reset behavior and proper instance management
  • Covers edge cases for publisher initialization and replacement

Implementation Analysis

The testing approach uses JUnit framework with a combination of synchronization and concurrency testing patterns.

Key implementation aspects include:
  • Thread-safe testing with multiple concurrent initializations
  • Custom test implementations of HystrixMetricsPublisher
  • Atomic counters for tracking publisher creation
  • Reset functionality testing using HystrixPlugins

Technical Details

Testing infrastructure utilizes:

  • JUnit testing framework
  • Custom TestHystrixMetricsPublisher implementation
  • AtomicInteger for thread-safe counting
  • HystrixPlugins for metrics publisher management
  • Mock implementations of HystrixCommandKey and HystrixThreadPoolKey

Best Practices Demonstrated

The test suite exemplifies several testing best practices.

  • Proper test isolation using @Before cleanup
  • Comprehensive concurrent access testing
  • Clear test method naming and organization
  • Effective use of custom test implementations
  • Thorough validation of singleton behavior

netflix/hystrix

hystrix-core/src/test/java/com/netflix/hystrix/strategy/metrics/HystrixMetricsPublisherFactoryTest.java

            
/**
 * Copyright 2015 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.metrics;

import static junit.framework.Assert.assertNotSame;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import com.netflix.hystrix.strategy.HystrixPlugins;
import org.junit.Before;
import org.junit.Test;

import com.netflix.hystrix.HystrixCircuitBreaker;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandMetrics;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolMetrics;
import com.netflix.hystrix.HystrixThreadPoolProperties;

public class HystrixMetricsPublisherFactoryTest {
    @Before
    public void reset() {
        HystrixPlugins.reset();
    }

    /**
     * Assert that we only call a publisher once for a given Command or ThreadPool key.
     */
    @Test
    public void testSingleInitializePerKey() {
        final TestHystrixMetricsPublisher publisher = new TestHystrixMetricsPublisher();
        HystrixPlugins.getInstance().registerMetricsPublisher(publisher);
        final HystrixMetricsPublisherFactory factory = new HystrixMetricsPublisherFactory();
        ArrayList<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < 20; i++) {
            threads.add(new Thread(new Runnable() {

                @Override
                public void run() {
                    factory.getPublisherForCommand(TestCommandKey.TEST_A, null, null, null, null);
                    factory.getPublisherForCommand(TestCommandKey.TEST_B, null, null, null, null);
                    factory.getPublisherForThreadPool(TestThreadPoolKey.TEST_A, null, null);
                }

            }));
        }

        // start them
        for (Thread t : threads) {
            t.start();
        }

        // wait for them to finish
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // we should see 2 commands and 1 threadPool publisher created
        assertEquals(2, publisher.commandCounter.get());
        assertEquals(1, publisher.threadCounter.get());
    }

    @Test
    public void testMetricsPublisherReset() {
        // precondition: HystrixMetricsPublisherFactory class is not loaded. Calling HystrixPlugins.reset() here should be good enough to run this with other tests.

        // set first custom publisher
        HystrixCommandKey key = HystrixCommandKey.Factory.asKey("key");
        HystrixMetricsPublisherCommand firstCommand = new HystrixMetricsPublisherCommandDefault(key, null, null, null, null);
        HystrixMetricsPublisher firstPublisher = new CustomPublisher(firstCommand);
        HystrixPlugins.getInstance().registerMetricsPublisher(firstPublisher);

        // ensure that first custom publisher is used
        HystrixMetricsPublisherCommand cmd = HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(key, null, null, null, null);
        assertSame(firstCommand, cmd);

        // reset, then change to second custom publisher
        HystrixPlugins.reset();
        HystrixMetricsPublisherCommand secondCommand = new HystrixMetricsPublisherCommandDefault(key, null, null, null, null);
        HystrixMetricsPublisher secondPublisher = new CustomPublisher(secondCommand);
        HystrixPlugins.getInstance().registerMetricsPublisher(secondPublisher);

        // ensure that second custom publisher is used
        cmd = HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(key, null, null, null, null);
        assertNotSame(firstCommand, cmd);
        assertSame(secondCommand, cmd);
    }

    private static class TestHystrixMetricsPublisher extends HystrixMetricsPublisher {

        AtomicInteger commandCounter = new AtomicInteger();
        AtomicInteger threadCounter = new AtomicInteger();

        @Override
        public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandOwner, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) {
            return new HystrixMetricsPublisherCommand() {
                @Override
                public void initialize() {
                    commandCounter.incrementAndGet();
                }
            };
        }

        @Override
        public HystrixMetricsPublisherThreadPool getMetricsPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) {
            return new HystrixMetricsPublisherThreadPool() {
                @Override
                public void initialize() {
                    threadCounter.incrementAndGet();
                }
            };
        }

    }

    private static enum TestCommandKey implements HystrixCommandKey {
        TEST_A, TEST_B
    }

    private static enum TestThreadPoolKey implements HystrixThreadPoolKey {
        TEST_A, TEST_B
    }

    static class CustomPublisher extends HystrixMetricsPublisher{
        private HystrixMetricsPublisherCommand commandToReturn;
        public CustomPublisher(HystrixMetricsPublisherCommand commandToReturn){
            this.commandToReturn = commandToReturn;
        }

        @Override
        public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) {
            return commandToReturn;
        }
    }
}