package com.yammer.telemetry.tracing;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* This is used in tests that create spans to ensure we don't leak any spans beyond the test.
* Essentially it captures a before and after state and fails the test if we've added or removed
* spans which existed before or after the test.
*
* Significantly simplifies identifying where leaks come from.
*/
public class SpanContextRule implements TestRule {
@Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
Throwable caught = null;
final ImmutableList<Span> beforeState = SpanHelper.captureSpans();
try {
base.evaluate();
} catch (Throwable t) {
caught = t;
} finally {
ImmutableList<Span> afterState = SpanHelper.captureSpans();
verify(beforeState, afterState);
}
if (caught != null) {
throw caught;
}
}
};
}
private static void verify(final ImmutableList<Span> beforeState, final ImmutableList<Span> afterState) throws Throwable {
if (!beforeState.equals(afterState)) {
ImmutableList<Span> added = ImmutableList.copyOf(Iterables.filter(afterState, new Predicate<Span>() {
@Override
public boolean apply(Span input) {
return !beforeState.contains(input);
}
}));
ImmutableList<Span> removed = ImmutableList.copyOf(Iterables.filter(beforeState, new Predicate<Span>() {
@Override
public boolean apply(Span input) {
return !afterState.contains(input);
}
}));
if (!added.isEmpty() || !removed.isEmpty()) {
throw new Exception("SpanContext was not cleaned up after test. (before = " + beforeState.size() + " after = " + afterState.size() + ")\n" +
"Added: " + added + "\n" +
"Removed: " + removed);
}
}
}
}