io.opentelemetry.instrumentation.testing.InstrumentationTestRunner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of opentelemetry-testing-common Show documentation
Show all versions of opentelemetry-testing-common Show documentation
OpenTelemetry Javaagent testing commons
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.testing;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.awaitility.Awaitility.await;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil;
import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable;
import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.testing.assertj.MetricAssert;
import io.opentelemetry.sdk.testing.assertj.TraceAssert;
import io.opentelemetry.sdk.testing.assertj.TracesAssert;
import io.opentelemetry.sdk.trace.data.SpanData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.assertj.core.api.ListAssert;
import org.awaitility.core.ConditionTimeoutException;
/**
* This interface defines a common set of operations for interaction with OpenTelemetry SDK and
* traces & metrics exporters.
*
* @see LibraryTestRunner
* @see AgentTestRunner
*/
public abstract class InstrumentationTestRunner {
private final TestInstrumenters testInstrumenters;
protected InstrumentationTestRunner(OpenTelemetry openTelemetry) {
testInstrumenters = new TestInstrumenters(openTelemetry);
}
public abstract void beforeTestClass();
public abstract void afterTestClass();
public abstract void clearAllExportedData();
public abstract OpenTelemetry getOpenTelemetry();
public abstract List getExportedSpans();
public abstract List getExportedMetrics();
public abstract List getExportedLogRecords();
public abstract boolean forceFlushCalled();
/** Return a list of all captured traces, where each trace is a sorted list of spans. */
public final List> traces() {
return TelemetryDataUtil.groupTraces(getExportedSpans());
}
public final List> waitForTraces(int numberOfTraces) {
try {
return TelemetryDataUtil.waitForTraces(
this::getExportedSpans, numberOfTraces, 20, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException e) {
throw new AssertionError("Error waiting for " + numberOfTraces + " traces", e);
}
}
@SafeVarargs
@SuppressWarnings("varargs")
public final void waitAndAssertSortedTraces(
Comparator> traceComparator, Consumer... assertions) {
waitAndAssertTraces(traceComparator, Arrays.asList(assertions), true);
}
public final void waitAndAssertSortedTraces(
Comparator> traceComparator,
Iterable extends Consumer> assertions) {
waitAndAssertTraces(traceComparator, assertions, true);
}
@SafeVarargs
@SuppressWarnings("varargs")
public final void waitAndAssertTracesWithoutScopeVersionVerification(
Consumer... assertions) {
waitAndAssertTracesWithoutScopeVersionVerification(Arrays.asList(assertions));
}
public final >
void waitAndAssertTracesWithoutScopeVersionVerification(Iterable assertions) {
waitAndAssertTraces(null, assertions, false);
}
@SafeVarargs
@SuppressWarnings("varargs")
public final void waitAndAssertTraces(Consumer... assertions) {
waitAndAssertTraces(Arrays.asList(assertions));
}
public final > void waitAndAssertTraces(Iterable assertions) {
waitAndAssertTraces(null, assertions, true);
}
private > void waitAndAssertTraces(
@Nullable Comparator> traceComparator,
Iterable assertions,
boolean verifyScopeVersion) {
List assertionsList = new ArrayList<>();
assertions.forEach(assertionsList::add);
try {
await()
.untilAsserted(() -> doAssertTraces(traceComparator, assertionsList, verifyScopeVersion));
} catch (Throwable t) {
// awaitility is doing a jmx call that is not implemented in GraalVM:
// call:
// https://github.com/awaitility/awaitility/blob/fbe16add874b4260dd240108304d5c0be84eabc8/awaitility/src/main/java/org/awaitility/core/ConditionAwaiter.java#L157
// see https://github.com/oracle/graal/issues/6101 (spring boot graal native image)
if (t.getClass().getName().equals("com.oracle.svm.core.jdk.UnsupportedFeatureError")
|| t instanceof ConditionTimeoutException) {
// Don't throw this failure since the stack is the awaitility thread, causing confusion.
// Instead, just assert one more time on the test thread, which will fail with a better
// stack trace.
// TODO(anuraaga): There is probably a better way to do this.
doAssertTraces(traceComparator, assertionsList, verifyScopeVersion);
} else {
throw t;
}
}
}
private > void doAssertTraces(
@Nullable Comparator> traceComparator,
List assertionsList,
boolean verifyScopeVersion) {
List> traces = waitForTraces(assertionsList.size());
if (verifyScopeVersion) {
TelemetryDataUtil.assertScopeVersion(traces);
}
if (traceComparator != null) {
traces.sort(traceComparator);
}
TracesAssert.assertThat(traces).hasTracesSatisfyingExactly(assertionsList);
}
/**
* Waits for the assertion applied to all metrics of the given instrumentation and metric name to
* pass.
*/
public final void waitAndAssertMetrics(
String instrumentationName, String metricName, Consumer> assertion) {
await()
.untilAsserted(
() ->
assertion.accept(
assertThat(getExportedMetrics())
.filteredOn(
data ->
data.getInstrumentationScopeInfo()
.getName()
.equals(instrumentationName)
&& data.getName().equals(metricName))));
}
@SafeVarargs
public final void waitAndAssertMetrics(
String instrumentationName, Consumer... assertions) {
await()
.untilAsserted(
() -> {
Collection metrics = instrumentationMetrics(instrumentationName);
assertThat(metrics).isNotEmpty();
for (Consumer assertion : assertions) {
assertThat(metrics).anySatisfy(metric -> assertion.accept(assertThat(metric)));
}
});
}
private List instrumentationMetrics(String instrumentationName) {
return getExportedMetrics().stream()
.filter(m -> m.getInstrumentationScopeInfo().getName().equals(instrumentationName))
.collect(Collectors.toList());
}
/**
* Runs the provided {@code callback} inside the scope of an INTERNAL span with name {@code
* spanName}.
*/
public final void runWithSpan(String spanName, ThrowingRunnable callback)
throws E {
runWithSpan(
spanName,
() -> {
callback.run();
return null;
});
}
/**
* Runs the provided {@code callback} inside the scope of an INTERNAL span with name {@code
* spanName}.
*/
public final T runWithSpan(
String spanName, ThrowingSupplier callback) throws E {
return testInstrumenters.runWithSpan(spanName, callback);
}
/**
* Runs the provided {@code callback} inside the scope of an HTTP CLIENT span with name {@code
* spanName}.
*/
public final void runWithHttpClientSpan(
String spanName, ThrowingRunnable callback) throws E {
runWithHttpClientSpan(
spanName,
() -> {
callback.run();
return null;
});
}
/**
* Runs the provided {@code callback} inside the scope of an HTTP CLIENT span with name {@code
* spanName}.
*/
public final T runWithHttpClientSpan(
String spanName, ThrowingSupplier callback) throws E {
return testInstrumenters.runWithHttpClientSpan(spanName, callback);
}
/**
* Runs the provided {@code callback} inside the scope of an HTTP SERVER span with name {@code
* spanName}.
*/
public final void runWithHttpServerSpan(ThrowingRunnable callback)
throws E {
runWithHttpServerSpan(
() -> {
callback.run();
return null;
});
}
/**
* Runs the provided {@code callback} inside the scope of an HTTP SERVER span with name {@code
* spanName}.
*/
public final T runWithHttpServerSpan(ThrowingSupplier callback)
throws E {
return testInstrumenters.runWithHttpServerSpan(callback);
}
/** Runs the provided {@code callback} inside the scope of a non-recording span. */
public final T runWithNonRecordingSpan(ThrowingSupplier callback)
throws E {
return testInstrumenters.runWithNonRecordingSpan(callback);
}
}