All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.opentelemetry.instrumentation.testing.InstrumentationTestRunner Maven / Gradle / Ivy

There is a newer version: 2.10.0-alpha
Show newest version
/*
 * 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> 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);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy