@Before
  public void doSetup() {
    threadSnapshot = new ThreadSnapshot().take();

    AeronTestUtils.setAeronEnvProps();
  }
  @After
  public void doTeardown() throws InterruptedException {
    AeronTestUtils.awaitMediaDriverIsTerminated(TIMEOUT);

    assertTrue(threadSnapshot.takeAndCompare(new String[] {"hash", "global"}, TIMEOUT.toMillis()));
  }
/** @author Anatoly Kadyshev */
public abstract class CommonAeronProcessorTest {

  protected static final Duration TIMEOUT = Duration.ofSeconds(5);

  private ThreadSnapshot threadSnapshot;

  protected String CHANNEL = AeronTestUtils.availableLocalhostChannel();

  @Before
  public void doSetup() {
    threadSnapshot = new ThreadSnapshot().take();

    AeronTestUtils.setAeronEnvProps();
  }

  @After
  public void doTeardown() throws InterruptedException {
    AeronTestUtils.awaitMediaDriverIsTerminated(TIMEOUT);

    assertTrue(threadSnapshot.takeAndCompare(new String[] {"hash", "global"}, TIMEOUT.toMillis()));
  }

  protected Context createContext() {
    return Context.create().senderChannel(CHANNEL).errorConsumer(Throwable::printStackTrace);
  }

  @Test
  public void testNextSignalIsReceived() throws InterruptedException {
    AeronProcessor processor = AeronProcessor.create(createContext());
    TestSubscriber<String> subscriber = TestSubscriber.create(0);
    Buffer.bufferToString(processor).subscribe(subscriber);
    subscriber.request(4);

    Flux.just(
            Buffer.wrap("Live"),
            Buffer.wrap("Hard"),
            Buffer.wrap("Die"),
            Buffer.wrap("Harder"),
            Buffer.wrap("Extra"))
        .subscribe(processor);

    subscriber.awaitAndAssertNextValues("Live", "Hard", "Die", "Harder");

    subscriber.request(1);

    subscriber.awaitAndAssertNextValues("Extra");
    subscriber.await();
  }

  @Test
  public void testCompleteSignalIsReceived() throws InterruptedException {
    AeronProcessor processor = AeronProcessor.create(createContext());
    Flux.just(Buffer.wrap("One"), Buffer.wrap("Two"), Buffer.wrap("Three")).subscribe(processor);

    TestSubscriber<String> subscriber = TestSubscriber.create(0);
    Buffer.bufferToString(processor).subscribe(subscriber);

    subscriber.request(1);
    subscriber.awaitAndAssertNextValues("One");

    subscriber.request(1);
    subscriber.awaitAndAssertNextValues("Two");

    subscriber.request(1);
    subscriber.awaitAndAssertNextValues("Three").assertComplete();
  }

  @Test
  @Ignore
  public void testCompleteShutdownsProcessorWithNoSubscribers() {
    AeronProcessor processor = AeronProcessor.create(createContext());

    Publisher<Buffer> publisher = Subscriber::onComplete;

    publisher.subscribe(processor);
  }

  @Test
  public void testWorksWithTwoSubscribersViaEmitter() throws InterruptedException {
    AeronProcessor processor = AeronProcessor.create(createContext());
    Flux.just(Buffer.wrap("Live"), Buffer.wrap("Hard"), Buffer.wrap("Die"), Buffer.wrap("Harder"))
        .subscribe(processor);

    FluxProcessor<Buffer, Buffer> emitter = EmitterProcessor.create();
    processor.subscribe(emitter);

    TestSubscriber<String> subscriber1 = TestSubscriber.create();
    Buffer.bufferToString(emitter).subscribe(subscriber1);

    TestSubscriber<String> subscriber2 = TestSubscriber.create();
    Buffer.bufferToString(emitter).subscribe(subscriber2);

    subscriber1.awaitAndAssertNextValues("Live", "Hard", "Die", "Harder").assertComplete();
    subscriber2.awaitAndAssertNextValues("Live", "Hard", "Die", "Harder").assertComplete();
  }

  @Test
  public void testClientReceivesException() throws InterruptedException {
    AeronProcessor processor = AeronProcessor.create(createContext());

    // as error is delivered on a different channelId compared to signal
    // its delivery could shutdown the processor before the processor subscriber
    // receives signal
    Flux.concat(
            Flux.just(Buffer.wrap("Item")),
            Flux.error(new RuntimeException("Something went wrong")))
        .subscribe(processor);

    TestSubscriber<String> subscriber = TestSubscriber.create();
    Buffer.bufferToString(processor).subscribe(subscriber);

    subscriber
        .await(TIMEOUT)
        .assertErrorWith(t -> assertThat(t.getMessage(), is("Something went wrong")));
  }

  @Test
  public void testExceptionWithNullMessageIsHandled() throws InterruptedException {
    AeronProcessor processor = AeronProcessor.create(createContext());

    TestSubscriber<String> subscriber = TestSubscriber.create();
    Buffer.bufferToString(processor).subscribe(subscriber);

    Flux<Buffer> sourceStream = Flux.error(new RuntimeException());
    sourceStream.subscribe(processor);

    subscriber.await(TIMEOUT).assertErrorWith(t -> assertThat(t.getMessage(), is("")));
  }

  @Test
  public void testCancelsUpstreamSubscriptionWhenLastSubscriptionIsCancelledAndAutoCancel()
      throws InterruptedException {
    AeronProcessor processor = AeronProcessor.create(createContext().autoCancel(true));

    final CountDownLatch subscriptionCancelledLatch = new CountDownLatch(1);
    Publisher<Buffer> dataPublisher =
        new Publisher<Buffer>() {
          @Override
          public void subscribe(Subscriber<? super Buffer> subscriber) {
            subscriber.onSubscribe(
                new Subscription() {
                  @Override
                  public void request(long n) {
                    System.out.println("Requested: " + n);
                  }

                  @Override
                  public void cancel() {
                    System.out.println("Upstream subscription cancelled");
                    subscriptionCancelledLatch.countDown();
                  }
                });
          }
        };
    dataPublisher.subscribe(processor);

    TestSubscriber<String> client = TestSubscriber.create();

    Buffer.bufferToString(processor).subscribe(client);

    processor.onNext(Buffer.wrap("Hello"));

    client.awaitAndAssertNextValues("Hello").cancel();

    assertTrue(
        "Subscription wasn't cancelled",
        subscriptionCancelledLatch.await(TIMEOUT.getSeconds(), TimeUnit.SECONDS));
  }

  @Test
  public void testRemotePublisherReceivesCompleteBeforeProcessorIsShutdown()
      throws InterruptedException {
    AeronProcessor processor = AeronProcessor.create(createContext());

    Flux.just(Buffer.wrap("Live")).subscribe(processor);

    TestSubscriber<String> subscriber = TestSubscriber.create(0);
    Buffer.bufferToString(processor).subscribe(subscriber);

    AeronFlux remotePublisher = new AeronFlux(createContext());
    TestSubscriber<String> remoteSubscriber = TestSubscriber.create(0);
    Buffer.bufferToString(remotePublisher).subscribe(remoteSubscriber);

    subscriber.request(1);
    remoteSubscriber.request(1);

    subscriber.awaitAndAssertNextValues("Live").assertComplete();
    remoteSubscriber.awaitAndAssertNextValues("Live").assertComplete();
  }

  @Test
  public void testRemotePublisherReceivesErrorBeforeProcessorIsShutdown()
      throws InterruptedException {
    AeronProcessor processor = AeronProcessor.create(createContext());

    Flux.<Buffer>error(new Exception("Oops!")).subscribe(processor);

    TestSubscriber<String> subscriber = TestSubscriber.create(0);
    Buffer.bufferToString(processor).subscribe(subscriber);

    AeronFlux remotePublisher = new AeronFlux(createContext());
    TestSubscriber<String> remoteSubscriber = TestSubscriber.create(0);
    Buffer.bufferToString(remotePublisher).subscribe(remoteSubscriber);

    subscriber.request(1);
    remoteSubscriber.request(1);

    subscriber.await(TIMEOUT).assertError();
    remoteSubscriber.await(TIMEOUT).assertError();
  }
}