diff --git a/instrumentation/guava-10.0/javaagent/build.gradle.kts b/instrumentation/guava-10.0/javaagent/build.gradle.kts index 5d4425fc37..befa9a0c24 100644 --- a/instrumentation/guava-10.0/javaagent/build.gradle.kts +++ b/instrumentation/guava-10.0/javaagent/build.gradle.kts @@ -22,5 +22,6 @@ dependencies { implementation(project(":instrumentation:guava-10.0:library")) + testImplementation(project(":instrumentation:opentelemetry-annotations-1.0:testing")) testImplementation("io.opentelemetry:opentelemetry-extension-annotations") } diff --git a/instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/GuavaWithSpanTest.java b/instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/GuavaWithSpanTest.java new file mode 100644 index 0000000000..95fde75277 --- /dev/null +++ b/instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/GuavaWithSpanTest.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.guava; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import io.opentelemetry.extension.annotations.WithSpan; +import io.opentelemetry.javaagent.instrumentation.otelannotations.AbstractTraced; +import io.opentelemetry.javaagent.instrumentation.otelannotations.AbstractWithSpanTest; +import org.testcontainers.shaded.com.google.common.base.Throwables; + +class GuavaWithSpanTest + extends AbstractWithSpanTest, ListenableFuture> { + + @Override + protected AbstractTraced, ListenableFuture> newTraced() { + return new Traced(); + } + + @Override + protected void complete(SettableFuture future, String value) { + future.set(value); + } + + @Override + protected void fail(SettableFuture future, Throwable error) { + future.setException(error); + } + + @Override + protected void cancel(SettableFuture future) { + future.cancel(true); + } + + @Override + protected String getCompleted(ListenableFuture future) { + return Futures.getUnchecked(future); + } + + @Override + protected Throwable unwrapError(Throwable t) { + return Throwables.getRootCause(t); + } + + @Override + protected String canceledKey() { + return "guava.canceled"; + } + + static final class Traced + extends AbstractTraced, ListenableFuture> { + + @Override + @WithSpan + protected SettableFuture completable() { + return SettableFuture.create(); + } + + @Override + @WithSpan + protected ListenableFuture alreadySucceeded() { + return Futures.immediateFuture("Value"); + } + + @Override + @WithSpan + protected ListenableFuture alreadyFailed() { + return Futures.immediateFailedFuture(FAILURE); + } + } +} diff --git a/instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/TracedWithSpan.java b/instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/TracedWithSpan.java deleted file mode 100644 index 5e2c13c26e..0000000000 --- a/instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/TracedWithSpan.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.guava; - -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; -import io.opentelemetry.extension.annotations.WithSpan; - -final class TracedWithSpan { - static final IllegalArgumentException FAILURE = new IllegalArgumentException("Boom"); - - @WithSpan - SettableFuture completable() { - return SettableFuture.create(); - } - - @WithSpan - ListenableFuture alreadySucceeded() { - return Futures.immediateFuture("Value"); - } - - @WithSpan - ListenableFuture alreadyFailed() { - return Futures.immediateFailedFuture(FAILURE); - } -} diff --git a/instrumentation/opentelemetry-annotations-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/WithSpanInstrumentation.java b/instrumentation/opentelemetry-annotations-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/WithSpanInstrumentation.java index 7334865711..0722629711 100644 --- a/instrumentation/opentelemetry-annotations-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/WithSpanInstrumentation.java +++ b/instrumentation/opentelemetry-annotations-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/WithSpanInstrumentation.java @@ -119,7 +119,6 @@ public class WithSpanInstrumentation implements TypeInstrumentation { @Advice.Local("otelMethod") Method method, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - // Every usage of @Advice.Origin Method is replaced with a call to Class.getMethod, copy it // to local variable so that there would be only one call to Class.getMethod. method = originMethod; @@ -159,8 +158,6 @@ public class WithSpanInstrumentation implements TypeInstrumentation { @Advice.Origin Method originMethod, @Advice.Local("otelMethod") Method method, @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args, - @Advice.Local("otelOperationEndSupport") - AsyncOperationEndSupport operationEndSupport, @Advice.Local("otelRequest") MethodRequest request, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { @@ -176,16 +173,12 @@ public class WithSpanInstrumentation implements TypeInstrumentation { if (instrumenter.shouldStart(current, request)) { context = instrumenter.start(current, request); scope = context.makeCurrent(); - operationEndSupport = - AsyncOperationEndSupport.create(instrumenter, Object.class, method.getReturnType()); } } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void stopSpan( @Advice.Local("otelMethod") Method method, - @Advice.Local("otelOperationEndSupport") - AsyncOperationEndSupport operationEndSupport, @Advice.Local("otelRequest") MethodRequest request, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope, @@ -195,6 +188,9 @@ public class WithSpanInstrumentation implements TypeInstrumentation { return; } scope.close(); + AsyncOperationEndSupport operationEndSupport = + AsyncOperationEndSupport.create( + instrumenterWithAttributes(), Object.class, method.getReturnType()); returnValue = operationEndSupport.asyncEnd(context, request, returnValue, throwable); } } diff --git a/instrumentation/opentelemetry-annotations-1.0/testing/build.gradle.kts b/instrumentation/opentelemetry-annotations-1.0/testing/build.gradle.kts new file mode 100644 index 0000000000..7a990e83ed --- /dev/null +++ b/instrumentation/opentelemetry-annotations-1.0/testing/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + api(project(":testing-common")) + + implementation("io.opentelemetry:opentelemetry-extension-annotations") +} diff --git a/instrumentation/opentelemetry-annotations-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/AbstractTraced.java b/instrumentation/opentelemetry-annotations-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/AbstractTraced.java new file mode 100644 index 0000000000..8764da04e8 --- /dev/null +++ b/instrumentation/opentelemetry-annotations-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/AbstractTraced.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.otelannotations; + +public abstract class AbstractTraced { + + protected static final String SUCCESS_VALUE = "Value"; + + protected static final IllegalArgumentException FAILURE = new IllegalArgumentException("Boom"); + + protected AbstractTraced() { + if (!getClass().getSimpleName().equals("Traced")) { + throw new IllegalStateException("Subclasses of AbstractTraced must be named Traced"); + } + } + + protected abstract T completable(); + + protected abstract U alreadySucceeded(); + + protected abstract U alreadyFailed(); +} diff --git a/instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/GuavaWithSpanInstrumentationTest.java b/instrumentation/opentelemetry-annotations-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/AbstractWithSpanTest.java similarity index 55% rename from instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/GuavaWithSpanInstrumentationTest.java rename to instrumentation/opentelemetry-annotations-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/AbstractWithSpanTest.java index 419fbee471..5f10e0d889 100644 --- a/instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/GuavaWithSpanInstrumentationTest.java +++ b/instrumentation/opentelemetry-annotations-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/otelannotations/AbstractWithSpanTest.java @@ -3,15 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.guava; +package io.opentelemetry.javaagent.instrumentation.otelannotations; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowable; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.SettableFuture; -import com.google.common.util.concurrent.UncheckedExecutionException; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; @@ -20,21 +17,41 @@ import io.opentelemetry.sdk.trace.data.StatusData; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -class GuavaWithSpanInstrumentationTest { +public abstract class AbstractWithSpanTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + protected abstract AbstractTraced newTraced(); + + protected abstract void complete(T future, String value); + + protected abstract void fail(T future, Throwable error); + + protected abstract void cancel(T future); + + protected abstract String getCompleted(U future); + + protected abstract Throwable unwrapError(Throwable t); + + protected abstract String canceledKey(); + + protected final InstrumentationExtension testing() { + return testing; + } + @Test void success() { - SettableFuture future = new TracedWithSpan().completable(); - future.set("Value"); + T future = newTraced().completable(); + complete(future, AbstractTraced.SUCCESS_VALUE); + + assertThat(getCompleted(future)).isEqualTo(AbstractTraced.SUCCESS_VALUE); testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("TracedWithSpan.completable") + span.hasName("Traced.completable") .hasKind(SpanKind.INTERNAL) .hasNoParent() .hasAttributes(Attributes.empty()))); @@ -42,46 +59,49 @@ class GuavaWithSpanInstrumentationTest { @Test void failure() { - IllegalArgumentException error = new IllegalArgumentException("Boom"); - SettableFuture future = new TracedWithSpan().completable(); - future.setException(error); + T future = newTraced().completable(); + fail(future, AbstractTraced.FAILURE); + + Throwable thrown = catchThrowable(() -> getCompleted(future)); + assertThat(unwrapError(thrown)).isEqualTo(AbstractTraced.FAILURE); testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("TracedWithSpan.completable") + span.hasName("Traced.completable") .hasKind(SpanKind.INTERNAL) .hasNoParent() .hasStatus(StatusData.error()) - .hasException(error) + .hasException(AbstractTraced.FAILURE) .hasAttributes(Attributes.empty()))); } @Test void canceled() { - SettableFuture future = new TracedWithSpan().completable(); - future.cancel(true); + T future = newTraced().completable(); + cancel(future); testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("TracedWithSpan.completable") + span.hasName("Traced.completable") .hasKind(SpanKind.INTERNAL) .hasNoParent() - .hasAttributes(attributeEntry("guava.canceled", true)))); + .hasAttributes(attributeEntry(canceledKey(), true)))); } @Test void immediateSuccess() { - assertThat(Futures.getUnchecked(new TracedWithSpan().alreadySucceeded())).isEqualTo("Value"); + assertThat(getCompleted(newTraced().alreadySucceeded())) + .isEqualTo(AbstractTraced.SUCCESS_VALUE); testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("TracedWithSpan.alreadySucceeded") + span.hasName("Traced.alreadySucceeded") .hasKind(SpanKind.INTERNAL) .hasNoParent() .hasAttributes(Attributes.empty()))); @@ -89,19 +109,18 @@ class GuavaWithSpanInstrumentationTest { @Test void immediateFailure() { - assertThatThrownBy(() -> Futures.getUnchecked(new TracedWithSpan().alreadyFailed())) - .isInstanceOf(UncheckedExecutionException.class) - .hasCause(TracedWithSpan.FAILURE); + Throwable error = catchThrowable(() -> getCompleted(newTraced().alreadyFailed())); + assertThat(unwrapError(error)).isEqualTo(AbstractTraced.FAILURE); testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("TracedWithSpan.alreadyFailed") + span.hasName("Traced.alreadyFailed") .hasKind(SpanKind.INTERNAL) .hasNoParent() .hasStatus(StatusData.error()) - .hasException(TracedWithSpan.FAILURE) + .hasException(AbstractTraced.FAILURE) .hasAttributes(Attributes.empty()))); } } diff --git a/instrumentation/reactor/reactor-3.1/javaagent/build.gradle.kts b/instrumentation/reactor/reactor-3.1/javaagent/build.gradle.kts index 5525a07439..32e32e315c 100644 --- a/instrumentation/reactor/reactor-3.1/javaagent/build.gradle.kts +++ b/instrumentation/reactor/reactor-3.1/javaagent/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { compileOnly(project(path = ":opentelemetry-api-shaded-for-instrumenting", configuration = "shadow")) testLibrary("io.projectreactor:reactor-test:3.1.0.RELEASE") + testImplementation(project(":instrumentation:opentelemetry-annotations-1.0:testing")) testImplementation(project(":instrumentation:reactor:reactor-3.1:testing")) testImplementation("io.opentelemetry:opentelemetry-extension-annotations") diff --git a/instrumentation/reactor/reactor-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/HooksInstrumentation.java b/instrumentation/reactor/reactor-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/HooksInstrumentation.java index 4a4892e036..534ef1c2f1 100644 --- a/instrumentation/reactor/reactor-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/HooksInstrumentation.java +++ b/instrumentation/reactor/reactor-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/HooksInstrumentation.java @@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.reactor; import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import io.opentelemetry.instrumentation.api.config.Config; import io.opentelemetry.instrumentation.reactor.ContextPropagationOperator; @@ -19,7 +20,11 @@ import net.bytebuddy.matcher.ElementMatcher; public class HooksInstrumentation implements TypeInstrumentation { @Override public ElementMatcher typeMatcher() { - return named("reactor.core.publisher.Hooks"); + return namedOneOf( + "reactor.core.publisher.Hooks", + // Hooks may not be loaded early enough so also match our main targets + "reactor.core.publisher.Flux", + "reactor.core.publisher.Mono"); } @Override diff --git a/instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/FluxWithSpanTest.java b/instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/FluxWithSpanTest.java new file mode 100644 index 0000000000..95dbd0e72d --- /dev/null +++ b/instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/FluxWithSpanTest.java @@ -0,0 +1,162 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.reactor; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.extension.annotations.WithSpan; +import io.opentelemetry.javaagent.instrumentation.otelannotations.AbstractTraced; +import io.opentelemetry.javaagent.instrumentation.otelannotations.AbstractWithSpanTest; +import org.junit.jupiter.api.Test; +import reactor.core.Scannable; +import reactor.core.publisher.Flux; +import reactor.core.publisher.UnicastProcessor; +import reactor.test.StepVerifier; + +class FluxWithSpanTest extends AbstractWithSpanTest, Flux> { + + @Override + protected AbstractTraced, Flux> newTraced() { + return new Traced(); + } + + @Override + protected void complete(Flux future, String value) { + UnicastProcessor source = processor(future); + source.onNext(value); + source.onComplete(); + } + + @Override + protected void fail(Flux future, Throwable error) { + UnicastProcessor source = processor(future); + source.onError(error); + } + + @Override + protected void cancel(Flux future) { + StepVerifier.create(future).expectSubscription().thenCancel().verify(); + } + + @Override + protected String getCompleted(Flux future) { + return future.blockLast(); + } + + @Override + protected Throwable unwrapError(Throwable t) { + return t; + } + + @Override + protected String canceledKey() { + return "reactor.canceled"; + } + + @Test + void nested() { + Flux flux = + Flux.defer( + () -> { + testing().runWithSpan("inner-manual", () -> {}); + return Flux.just("Value"); + }); + + Flux result = new TracedWithSpan().flux(flux); + + StepVerifier.create(result).expectNext("Value").verifyComplete(); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("TracedWithSpan.flux") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributes(Attributes.empty()), + span -> + span.hasName("inner-manual") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributes(Attributes.empty()))); + } + + @Test + void nestedFromCurrent() { + testing() + .runWithSpan( + "parent", + () -> { + Flux result = + new TracedWithSpan() + .flux( + Flux.defer( + () -> { + testing().runWithSpan("inner-manual", () -> {}); + return Flux.just("Value"); + })); + + StepVerifier.create(result).expectNext("Value").verifyComplete(); + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("parent") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributes(Attributes.empty()), + span -> + span.hasName("TracedWithSpan.flux") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributes(Attributes.empty()), + span -> + span.hasName("inner-manual") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(1)) + .hasAttributes(Attributes.empty()))); + } + + // While a UnicastProcessor is a Flux and we'd expect a simpler way to access it to provide + // values, + // our instrumentation adds operations and causes the return type to always be just a Flux. We + // need + // to go through the parents to get back to the processor. + @SuppressWarnings("unchecked") + private static UnicastProcessor processor(Flux flux) { + return ((Scannable) flux) + .parents() + .filter(UnicastProcessor.class::isInstance) + .map(UnicastProcessor.class::cast) + .findFirst() + .get(); + } + + static class Traced extends AbstractTraced, Flux> { + + @Override + @WithSpan + protected Flux completable() { + return UnicastProcessor.create(); + } + + @Override + @WithSpan + protected Flux alreadySucceeded() { + return Flux.just(SUCCESS_VALUE); + } + + @Override + @WithSpan + protected Flux alreadyFailed() { + return Flux.error(FAILURE); + } + } +} diff --git a/instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/MonoWithSpanTest.java b/instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/MonoWithSpanTest.java new file mode 100644 index 0000000000..f53bf92514 --- /dev/null +++ b/instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/MonoWithSpanTest.java @@ -0,0 +1,165 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.reactor; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.extension.annotations.WithSpan; +import io.opentelemetry.javaagent.instrumentation.otelannotations.AbstractTraced; +import io.opentelemetry.javaagent.instrumentation.otelannotations.AbstractWithSpanTest; +import org.junit.jupiter.api.Test; +import reactor.core.Scannable; +import reactor.core.publisher.Mono; +import reactor.core.publisher.UnicastProcessor; +import reactor.test.StepVerifier; + +class MonoWithSpanTest extends AbstractWithSpanTest, Mono> { + + @Override + protected AbstractTraced, Mono> newTraced() { + return new Traced(); + } + + @Override + protected void complete(Mono future, String value) { + UnicastProcessor source = processor(future); + source.onNext(value); + source.onComplete(); + } + + @Override + protected void fail(Mono future, Throwable error) { + UnicastProcessor source = processor(future); + source.onError(error); + } + + @Override + protected void cancel(Mono future) { + StepVerifier.create(future).expectSubscription().thenCancel().verify(); + } + + @Override + protected String getCompleted(Mono future) { + return future.block(); + } + + @Override + protected Throwable unwrapError(Throwable t) { + return t; + } + + @Override + protected String canceledKey() { + return "reactor.canceled"; + } + + @Test + void nested() { + Mono mono = + Mono.defer( + () -> { + testing().runWithSpan("inner-manual", () -> {}); + return Mono.just("Value"); + }); + + Mono result = new TracedWithSpan().outer(mono); + + StepVerifier.create(result).expectNext("Value").verifyComplete(); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("TracedWithSpan.outer") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributes(Attributes.empty()), + span -> + span.hasName("TracedWithSpan.mono") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributes(Attributes.empty()), + span -> + span.hasName("inner-manual") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(1)) + .hasAttributes(Attributes.empty()))); + } + + @Test + void nestedFromCurrent() { + testing() + .runWithSpan( + "parent", + () -> { + Mono result = + new TracedWithSpan() + .mono( + Mono.defer( + () -> { + testing().runWithSpan("inner-manual", () -> {}); + return Mono.just("Value"); + })); + + StepVerifier.create(result).expectNext("Value").verifyComplete(); + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("parent") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributes(Attributes.empty()), + span -> + span.hasName("TracedWithSpan.mono") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributes(Attributes.empty()), + span -> + span.hasName("inner-manual") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(1)) + .hasAttributes(Attributes.empty()))); + } + + // Because we test on the Mono API but need to be able to complete the processor, we + // use this hacky approach to access the processor from the mono ancestor. + @SuppressWarnings("unchecked") + private static UnicastProcessor processor(Mono mono) { + return ((Scannable) mono) + .parents() + .filter(UnicastProcessor.class::isInstance) + .map(UnicastProcessor.class::cast) + .findFirst() + .get(); + } + + static class Traced extends AbstractTraced, Mono> { + + @Override + @WithSpan + protected Mono completable() { + UnicastProcessor source = UnicastProcessor.create(); + return source.singleOrEmpty(); + } + + @Override + @WithSpan + protected Mono alreadySucceeded() { + return Mono.just(SUCCESS_VALUE); + } + + @Override + @WithSpan + protected Mono alreadyFailed() { + return Mono.error(FAILURE); + } + } +} diff --git a/instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/ReactorWithSpanInstrumentationTest.java b/instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/ReactorWithSpanInstrumentationTest.java deleted file mode 100644 index fda0a5111f..0000000000 --- a/instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/ReactorWithSpanInstrumentationTest.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.reactor; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.sdk.trace.data.StatusData; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.UnicastProcessor; -import reactor.test.StepVerifier; - -@SuppressWarnings("ClassCanBeStatic") -class ReactorWithSpanInstrumentationTest { - - @RegisterExtension - static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - - @Nested - class MonoTest { - @Test - void success() { - UnicastProcessor source = UnicastProcessor.create(); - Mono mono = source.singleOrEmpty(); - Mono result = new TracedWithSpan().mono(mono); - StepVerifier.Step verifier = StepVerifier.create(result).expectSubscription(); - - source.onNext("Value"); - source.onComplete(); - verifier.expectNext("Value").verifyComplete(); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.mono") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(Attributes.empty()))); - } - - @Test - void failure() { - IllegalArgumentException error = new IllegalArgumentException("Boom"); - - UnicastProcessor source = UnicastProcessor.create(); - Mono mono = source.singleOrEmpty(); - Mono result = new TracedWithSpan().mono(mono); - StepVerifier.Step verifier = StepVerifier.create(result).expectSubscription(); - - source.onError(error); - verifier.verifyErrorMatches(t -> t.equals(error)); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.mono") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasStatus(StatusData.error()) - .hasException(error) - .hasAttributes(Attributes.empty()))); - } - - @Test - void canceled() { - UnicastProcessor source = UnicastProcessor.create(); - Mono mono = source.singleOrEmpty(); - Mono result = new TracedWithSpan().mono(mono); - StepVerifier.Step verifier = StepVerifier.create(result).expectSubscription(); - - verifier.thenCancel().verify(); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.mono") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(attributeEntry("reactor.canceled", true)))); - } - - @Test - void immediateSuccess() { - Mono result = new TracedWithSpan().mono(Mono.just("Value")); - StepVerifier.create(result).expectNext("Value").verifyComplete(); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.mono") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(Attributes.empty()))); - } - - @Test - void immediateFailure() { - IllegalArgumentException error = new IllegalArgumentException("Boom"); - Mono result = new TracedWithSpan().mono(Mono.error(error)); - StepVerifier.create(result).verifyErrorMatches(t -> t.equals(error)); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.mono") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasStatus(StatusData.error()) - .hasException(error) - .hasAttributes(Attributes.empty()))); - } - - @Test - void nested() { - Mono mono = - Mono.defer( - () -> { - testing.runWithSpan("inner-manual", () -> {}); - return Mono.just("Value"); - }); - - Mono result = new TracedWithSpan().outer(mono); - - StepVerifier.create(result).expectNext("Value").verifyComplete(); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.outer") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(Attributes.empty()), - span -> - span.hasName("TracedWithSpan.mono") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributes(Attributes.empty()), - span -> - span.hasName("inner-manual") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(1)) - .hasAttributes(Attributes.empty()))); - } - - @Test - void nestedFromCurrent() { - testing.runWithSpan( - "parent", - () -> { - Mono result = - new TracedWithSpan() - .mono( - Mono.defer( - () -> { - testing.runWithSpan("inner-manual", () -> {}); - return Mono.just("Value"); - })); - - StepVerifier.create(result).expectNext("Value").verifyComplete(); - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("parent") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(Attributes.empty()), - span -> - span.hasName("TracedWithSpan.mono") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributes(Attributes.empty()), - span -> - span.hasName("inner-manual") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(1)) - .hasAttributes(Attributes.empty()))); - } - } - - @Nested - class FluxTest { - @Test - void success() { - UnicastProcessor source = UnicastProcessor.create(); - Flux result = new TracedWithSpan().flux(source); - StepVerifier.Step verifier = StepVerifier.create(result).expectSubscription(); - - source.onNext("Value"); - source.onComplete(); - verifier.expectNext("Value").verifyComplete(); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.flux") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(Attributes.empty()))); - } - - @Test - void failure() { - IllegalArgumentException error = new IllegalArgumentException("Boom"); - - UnicastProcessor source = UnicastProcessor.create(); - Flux result = new TracedWithSpan().flux(source); - StepVerifier.Step verifier = StepVerifier.create(result).expectSubscription(); - - source.onError(error); - verifier.verifyErrorMatches(t -> t.equals(error)); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.flux") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasStatus(StatusData.error()) - .hasException(error) - .hasAttributes(Attributes.empty()))); - } - - @Test - void canceled() { - UnicastProcessor source = UnicastProcessor.create(); - Flux result = new TracedWithSpan().flux(source); - StepVerifier.Step verifier = StepVerifier.create(result).expectSubscription(); - - verifier.thenCancel().verify(); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.flux") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(attributeEntry("reactor.canceled", true)))); - } - - @Test - void immediateSuccess() { - Flux result = new TracedWithSpan().flux(Flux.just("Value")); - StepVerifier.create(result).expectNext("Value").verifyComplete(); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.flux") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(Attributes.empty()))); - } - - @Test - void immediateFailure() { - IllegalArgumentException error = new IllegalArgumentException("Boom"); - Flux result = new TracedWithSpan().flux(Flux.error(error)); - StepVerifier.create(result).verifyErrorMatches(t -> t.equals(error)); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.flux") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasStatus(StatusData.error()) - .hasException(error) - .hasAttributes(Attributes.empty()))); - } - - @Test - void nested() { - Flux flux = - Flux.defer( - () -> { - testing.runWithSpan("inner-manual", () -> {}); - return Flux.just("Value"); - }); - - Flux result = new TracedWithSpan().flux(flux); - - StepVerifier.create(result).expectNext("Value").verifyComplete(); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("TracedWithSpan.flux") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(Attributes.empty()), - span -> - span.hasName("inner-manual") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributes(Attributes.empty()))); - } - - @Test - void nestedFromCurrent() { - testing.runWithSpan( - "parent", - () -> { - Flux result = - new TracedWithSpan() - .flux( - Flux.defer( - () -> { - testing.runWithSpan("inner-manual", () -> {}); - return Flux.just("Value"); - })); - - StepVerifier.create(result).expectNext("Value").verifyComplete(); - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("parent") - .hasKind(SpanKind.INTERNAL) - .hasNoParent() - .hasAttributes(Attributes.empty()), - span -> - span.hasName("TracedWithSpan.flux") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributes(Attributes.empty()), - span -> - span.hasName("inner-manual") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(1)) - .hasAttributes(Attributes.empty()))); - } - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 6aa489ccf6..8af69b44da 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -316,6 +316,7 @@ include(":instrumentation:okhttp:okhttp-3.0:javaagent") include(":instrumentation:okhttp:okhttp-3.0:library") include(":instrumentation:okhttp:okhttp-3.0:testing") include(":instrumentation:opentelemetry-annotations-1.0:javaagent") +include(":instrumentation:opentelemetry-annotations-1.0:testing") include(":instrumentation:opentelemetry-api:opentelemetry-api-1.0:javaagent") include(":instrumentation:opentelemetry-api:opentelemetry-api-1.0:testing") include(":instrumentation:opentelemetry-api:opentelemetry-api-1.4:javaagent")