ManagedTimeoutSourceTest.java

package redis.clients.jedis;

import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;

import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import org.junit.jupiter.api.Test;

import redis.clients.jedis.TimeoutSource.TimeoutInfo;

/**
 * Unit tests for {@link ManagedTimeoutSource}: a chain link whose opinion is whatever its supplier
 * says right now, and whose identity for {@link ChainedTimeoutSource#seekBy} is the owner that
 * installed it.
 */
public class ManagedTimeoutSourceTest {

  private static final TimeoutInfo BASE = new TimeoutInfo(2000, 5000);
  private static final TimeoutInfo RELAXED = new TimeoutInfo(10_000, 15_000);
  private static final TimeoutInfo OTHER = new TimeoutInfo(20_000, 25_000);

  private final Object owner = new Object();

  @Test
  public void supplierOpinionOverridesTheBase() {
    ChainedTimeoutSource base = new ChainedTimeoutSource(BASE);
    base.addOverride(new ManagedTimeoutSource(() -> RELAXED, owner));

    assertSame(RELAXED, base.get());
  }

  @Test
  public void silentSupplierFallsThroughToTheBase() {
    ChainedTimeoutSource base = new ChainedTimeoutSource(BASE);
    base.addOverride(new ManagedTimeoutSource(() -> null, owner));

    assertSame(BASE, base.get());
  }

  @Test
  public void supplierIsConsultedOnEveryRead() {
    AtomicReference<TimeoutInfo> gate = new AtomicReference<>();
    ChainedTimeoutSource base = new ChainedTimeoutSource(BASE);
    base.addOverride(new ManagedTimeoutSource(gate::get, owner));

    assertSame(BASE, base.get());
    gate.set(RELAXED);
    assertSame(RELAXED, base.get());
    gate.set(null);
    assertSame(BASE, base.get());
  }

  @Test
  public void identityIsTheOwnerNotTheSupplierOrTheClass() {
    Supplier<TimeoutInfo> supplier = () -> RELAXED;
    ManagedTimeoutSource source = new ManagedTimeoutSource(supplier, owner);
    ChainedTimeoutSource base = new ChainedTimeoutSource(BASE);
    base.addOverride(source);

    assertSame(source, base.seekBy(owner));
    assertNull(base.seekBy(supplier), "the supplier is the opinion, not the handle");
    assertNull(base.seekBy(ManagedTimeoutSource.class),
      "several managed sources may share a chain, so the class is not a usable identity");
    assertNull(base.seekBy(new Object()), "a different owner is a different source");
  }

  @Test
  public void sourcesOfDistinctOwnersAreTornDownIndependently() {
    Object first = new Object();
    Object second = new Object();
    ChainedTimeoutSource base = new ChainedTimeoutSource(BASE);
    base.addOverride(new ManagedTimeoutSource(() -> RELAXED, first));
    base.addOverride(new ManagedTimeoutSource(() -> OTHER, second));
    assertSame(OTHER, base.get(), "the last added override takes precedence");

    base.removeOverride(base.seekBy(second));
    assertSame(RELAXED, base.get());
    assertNull(base.seekBy(second));

    base.removeOverride(base.seekBy(first));
    assertSame(BASE, base.get());
    assertNull(base.seekBy(first));
  }

  @Test
  public void oneOwnerMaySupplyTheSameOpinionToSeveralChains() {
    Supplier<TimeoutInfo> shared = () -> RELAXED;
    ChainedTimeoutSource one = new ChainedTimeoutSource(BASE);
    ChainedTimeoutSource two = new ChainedTimeoutSource(BASE);
    one.addOverride(new ManagedTimeoutSource(shared, owner));
    two.addOverride(new ManagedTimeoutSource(shared, owner));

    one.removeOverride(one.seekBy(owner));
    assertSame(BASE, one.get());
    assertSame(RELAXED, two.get(), "each chain holds its own link for the owner");
  }

  @Test
  public void aSilentLaterOverrideYieldsToAnEarlierOpinion() {
    AtomicReference<TimeoutInfo> later = new AtomicReference<>();
    ChainedTimeoutSource base = new ChainedTimeoutSource(BASE);
    base.addOverride(new ManagedTimeoutSource(() -> RELAXED, new Object()));
    base.addOverride(new ManagedTimeoutSource(later::get, new Object()));

    assertSame(RELAXED, base.get());
    later.set(OTHER);
    assertSame(OTHER, base.get());
  }
}