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

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

/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.instrumentation.testing;

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.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.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import javax.annotation.Nullable;
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));
  }

  @SafeVarargs
  @SuppressWarnings("varargs")
  public final void waitAndAssertTraces(Consumer... assertions) {
    waitAndAssertTraces(Arrays.asList(assertions));
  }

  public final > void waitAndAssertTraces(Iterable assertions) {
    waitAndAssertTraces(null, assertions);
  }

  private > void waitAndAssertTraces(
      @Nullable Comparator> traceComparator, Iterable assertions) {
    List assertionsList = new ArrayList<>();
    assertions.forEach(assertionsList::add);

    try {
      await().untilAsserted(() -> doAssertTraces(traceComparator, assertionsList));
    } catch (ConditionTimeoutException e) {
      // 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);
    }
  }

  private > void doAssertTraces(
      @Nullable Comparator> traceComparator, List assertionsList) {
    List> traces = waitForTraces(assertionsList.size());
    if (traceComparator != null) {
      traces.sort(traceComparator);
    }
    TracesAssert.assertThat(traces).hasTracesSatisfyingExactly(assertionsList);
  }

  /**
   * 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(
      String spanName, ThrowingRunnable callback) throws E {
    runWithHttpServerSpan(
        spanName,
        () -> {
          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(
      String spanName, ThrowingSupplier callback) throws E {
    return testInstrumenters.runWithHttpServerSpan(spanName, 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 - 2025 Weber Informatics LLC | Privacy Policy