From 7f3817c990732e8686a352a432f7fa198c159488 Mon Sep 17 00:00:00 2001 From: Juergen Fickel Date: Wed, 2 Nov 2022 15:21:16 +0100 Subject: [PATCH] Added test resource for Kamon test span reporters. Reporters should become a unique registration name and the registration should be canceled after the test. A test resource helps with the latter and to reduce redundancy for TestSpanReporter creation and registration. Signed-off-by: Juergen Fickel --- .../utils/tracing/DittoTracingTest.java | 14 +-- .../span/KamonHttpContextPropagationTest.java | 4 +- .../span/KamonTestSpanReporterResource.java | 98 +++++++++++++++++++ .../tracing/span/PreparedKamonSpanTest.java | 23 +++-- .../tracing/span/StartedKamonSpanTest.java | 41 +++++--- 5 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/KamonTestSpanReporterResource.java diff --git a/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/DittoTracingTest.java b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/DittoTracingTest.java index 0b4ff658d7..67db84d260 100644 --- a/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/DittoTracingTest.java +++ b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/DittoTracingTest.java @@ -33,10 +33,10 @@ import org.eclipse.ditto.internal.utils.tracing.config.DefaultTracingConfig; import org.eclipse.ditto.internal.utils.tracing.config.TracingConfig; import org.eclipse.ditto.internal.utils.tracing.filter.AcceptAllTracingFilter; +import org.eclipse.ditto.internal.utils.tracing.span.KamonTestSpanReporterResource; import org.eclipse.ditto.internal.utils.tracing.span.KamonTracingInitResource; import org.eclipse.ditto.internal.utils.tracing.span.SpanOperationName; import org.eclipse.ditto.internal.utils.tracing.span.SpanTagKey; -import org.eclipse.ditto.internal.utils.tracing.span.TestSpanReporter; import org.eclipse.ditto.internal.utils.tracing.span.TracingSpans; import org.junit.After; import org.junit.Before; @@ -48,8 +48,6 @@ import com.typesafe.config.ConfigFactory; -import kamon.Kamon; - /** * Unit test for {@link DittoTracing}. */ @@ -58,7 +56,6 @@ public final class DittoTracingTest { @ClassRule public static final KamonTracingInitResource KAMON_TRACING_INIT_RESOURCE = KamonTracingInitResource.newInstance( KamonTracingInitResource.KamonTracingConfig.defaultValues() - .withIdentifierSchemeDouble() .withSamplerAlways() .withTickInterval(Duration.ofMillis(100L)) ); @@ -66,6 +63,9 @@ public final class DittoTracingTest { @Rule public final TestName testName = new TestName(); + @Rule + public final KamonTestSpanReporterResource testSpanReporterResource = KamonTestSpanReporterResource.newInstance(); + private TracingConfig tracingConfigMock; @Before @@ -246,12 +246,12 @@ public void newPreparedSpanWhenTracingIsEnabledReturnsExpectedPreparedSpan() { @Test public void newStartedSpanByTimerWhenTracingIsEnabledReturnsExpectedStartedSpan() { DittoTracing.init(tracingConfigMock); - final var operationName = SpanOperationName.of(testName.getMethodName()); + final var testMethodName = testName.getMethodName(); + final var operationName = SpanOperationName.of(testMethodName); final var timerTags = TagSet.ofTagCollection(List.of(Tag.of("foo", "bar"), Tag.of("marco", "polo"))); final var startedTimer = Timers.newTimer(operationName.toString()).tags(timerTags).start(); final var dittoHeaders = DittoHeaders.newBuilder().correlationId(operationName).build(); - final var testSpanReporter = TestSpanReporter.newInstance(); - Kamon.addReporter("mySpanReporter", testSpanReporter); + final var testSpanReporter = testSpanReporterResource.registerTestSpanReporter(testMethodName); final var startedSpan = DittoTracing.newStartedSpanByTimer(dittoHeaders, startedTimer); diff --git a/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/KamonHttpContextPropagationTest.java b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/KamonHttpContextPropagationTest.java index 1fd72d87a5..15e11fe994 100644 --- a/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/KamonHttpContextPropagationTest.java +++ b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/KamonHttpContextPropagationTest.java @@ -42,8 +42,8 @@ public final class KamonHttpContextPropagationTest { @ClassRule - public static final KamonTracingInitResource KAMON_INIT_RESOURCE = KamonTracingInitResource.newInstance( - KamonTracingInitResource.KamonTracingConfig.defaultValues().withIdentifierSchemeDouble() + public static final KamonTracingInitResource KAMON_TRACING_INIT_RESOURCE = KamonTracingInitResource.newInstance( + KamonTracingInitResource.KamonTracingConfig.defaultValues() ); private static final String DEFAULT_CHANNEL_NAME = "default"; diff --git a/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/KamonTestSpanReporterResource.java b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/KamonTestSpanReporterResource.java new file mode 100644 index 0000000000..fd539c3ffe --- /dev/null +++ b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/KamonTestSpanReporterResource.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.internal.utils.tracing.span; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.eclipse.ditto.base.model.common.ConditionChecker; +import org.junit.rules.ExternalResource; + +import kamon.Kamon; +import kamon.module.Module; + +/** + * This ExternalResource helps to create instances of {@link TestSpanReporter} and to add them Kamon. + * After the test the registrations are canceled. + * Each {@code TestSpanReporter} must be registered with a unique name. + * Both, cancelling registrations after test and enforcing a unique name stabilises tests which rely on span reporting. + */ +@NotThreadSafe +public final class KamonTestSpanReporterResource extends ExternalResource { + + private final Map reporterRegistrations; + + private KamonTestSpanReporterResource() { + reporterRegistrations = new HashMap<>(3); + } + + /** + * Returns a new instance of {@code KamonTestSpanReporterResource}. + * + * @return the new instance. + */ + public static KamonTestSpanReporterResource newInstance() { + return new KamonTestSpanReporterResource(); + } + + /** + * Creates a {@code TestSpanReporter} and registers it under the specified name argument at Kamon. + * + * @param reporterName the registration name of the returned reporter. + * @return the new {@code TestSpanReporter} instance. + * @throws NullPointerException if {@code reporterName} is {@code null}. + * @throws IllegalArgumentException if a reporter with name {@code reporterName} was already registered. + */ + public TestSpanReporter registerTestSpanReporter(final CharSequence reporterName) { + final var reporterNameAsString = validateReporterName(reporterName); + final var result = TestSpanReporter.newInstance(); + reporterRegistrations.put(reporterNameAsString, Kamon.addReporter(reporterNameAsString, result)); + return result; + } + + private String validateReporterName(final CharSequence reporterName) { + ConditionChecker.checkNotNull(reporterName, "reporterName"); + final var result = reporterName.toString(); + if (reporterRegistrations.containsKey(result)) { + throw new IllegalArgumentException( + MessageFormat.format("A reporter with name <{0}> was already registered.", result) + ); + } + return result; + } + + @Override + protected void after() { + final var registrationEntryIterator = getRegistrationEntryIterator(); + while (registrationEntryIterator.hasNext()) { + cancelRegistration(registrationEntryIterator.next()); + registrationEntryIterator.remove(); + } + super.after(); + } + + private Iterator> getRegistrationEntryIterator() { + final var reporterRegistrationEntries = reporterRegistrations.entrySet(); + return reporterRegistrationEntries.iterator(); + } + + private static void cancelRegistration(final Map.Entry registrationEntry) { + final var registration = registrationEntry.getValue(); + registration.cancel(); + } + +} diff --git a/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/PreparedKamonSpanTest.java b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/PreparedKamonSpanTest.java index ad3b274991..8afd3eb724 100644 --- a/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/PreparedKamonSpanTest.java +++ b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/PreparedKamonSpanTest.java @@ -25,21 +25,19 @@ import org.eclipse.ditto.internal.utils.metrics.instruments.timer.StartInstant; import org.eclipse.ditto.internal.utils.metrics.instruments.timer.Timers; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; -import kamon.Kamon; - /** * Unit test for {@link PreparedKamonSpan}. */ public final class PreparedKamonSpanTest { - @Rule - public final KamonTracingInitResource kamonTracingInitResource = KamonTracingInitResource.newInstance( + @ClassRule + public static final KamonTracingInitResource KAMON_TRACING_INIT_RESOURCE = KamonTracingInitResource.newInstance( KamonTracingInitResource.KamonTracingConfig.defaultValues() - .withIdentifierSchemeDouble() .withSamplerAlways() .withTickInterval(Duration.ofMillis(100L)) ); @@ -47,6 +45,9 @@ public final class PreparedKamonSpanTest { @Rule public final TestName testName = new TestName(); + @Rule + public final KamonTestSpanReporterResource testSpanReporterResource = KamonTestSpanReporterResource.newInstance(); + private PreparedKamonSpan underTest; @Before @@ -135,8 +136,7 @@ public void startReturnsStartedSpanWithExpectedTags() { underTest.tags(TagSet.ofTagCollection( List.of(Tag.of("foo", "bar"), Tag.of("ping", "pong"), Tag.of("marco", "polo")) )); - final var testSpanReporter = TestSpanReporter.newInstance(); - Kamon.addReporter("mySpanReporter", testSpanReporter); + final var testSpanReporter = testSpanReporterResource.registerTestSpanReporter(testName.getMethodName()); final var startedSpan = underTest.start(); @@ -156,8 +156,7 @@ public void startAtReturnsStartedSpanWithExpectedTagsAndStartInstant() throws In underTest.tags(TagSet.ofTagCollection( List.of(Tag.of("foo", "bar"), Tag.of("ping", "pong"), Tag.of("marco", "polo")) )); - final var testSpanReporter = TestSpanReporter.newInstance(); - Kamon.addReporter("mySpanReporter", testSpanReporter); + final var testSpanReporter = testSpanReporterResource.registerTestSpanReporter(testName.getMethodName()); Thread.sleep(150L); final var startedSpan = underTest.startAt(startInstant); @@ -177,9 +176,9 @@ public void startAtReturnsStartedSpanWithExpectedTagsAndStartInstant() throws In @Test public void startByStartedTimerReturnsStartedSpanWithTagsOfTimer() { - final var testSpanReporter = TestSpanReporter.newInstance(); - Kamon.addReporter("mySpanReporter", testSpanReporter); - final var startedTimer = Timers.newTimer(testName.getMethodName()) + final var testMethodName = testName.getMethodName(); + final var testSpanReporter = testSpanReporterResource.registerTestSpanReporter(testMethodName); + final var startedTimer = Timers.newTimer(testMethodName) .tags(TagSet.ofTagCollection( List.of(Tag.of("foo", "bar"), Tag.of("ping", "pong"), Tag.of("marco", "polo")) )) diff --git a/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/StartedKamonSpanTest.java b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/StartedKamonSpanTest.java index 656c3d9387..83945561ea 100644 --- a/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/StartedKamonSpanTest.java +++ b/internal/utils/tracing/src/test/java/org/eclipse/ditto/internal/utils/tracing/span/StartedKamonSpanTest.java @@ -30,6 +30,8 @@ import org.eclipse.ditto.internal.utils.metrics.instruments.tag.KamonTagSetConverter; import org.eclipse.ditto.internal.utils.metrics.instruments.tag.Tag; import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; @@ -44,25 +46,33 @@ */ public final class StartedKamonSpanTest { - private static final Identifier TRACE_ID = Kamon.identifierScheme().traceIdFactory().generate(); - - @Rule - public final KamonTracingInitResource kamonTracingInitResource = KamonTracingInitResource.newInstance( + @ClassRule + public static final KamonTracingInitResource KAMON_TRACING_INIT_RESOURCE = KamonTracingInitResource.newInstance( KamonTracingInitResource.KamonTracingConfig.defaultValues() .withSamplerAlways() .withTickInterval(Duration.ofMillis(100L)) ); + private static Identifier traceId; + @Rule public final TestName testName = new TestName(); + @Rule + public final KamonTestSpanReporterResource testSpanReporterResource = KamonTestSpanReporterResource.newInstance(); + private StartedSpan underTest; + @BeforeClass + public static void beforeClass() { + traceId = Kamon.identifierScheme().traceIdFactory().generate(); + } + @Before public void setup() { underTest = StartedKamonSpan.newInstance( Kamon.spanBuilder(testName.getMethodName()) - .asChildOf(Kamon.spanBuilder("/").traceId(TRACE_ID).start()) + .asChildOf(Kamon.spanBuilder("/").traceId(traceId).start()) .start(), KamonHttpContextPropagation.newInstanceForChannelName("default") ); @@ -110,9 +120,8 @@ public void markWithNullKeyThrowsNullPointerException() { @Test public void markWithKeyAsOnlyArgumentCreatesMarkWithSpecifiedKeyCloseToCurrentInstant() { final var key = "successful"; - final var testSpanReporter = TestSpanReporter.newInstance(); + final var testSpanReporter = registerKamonTestSpanReporter(); final var finishedSpanFuture = testSpanReporter.getFinishedSpanForSpanWithId(underTest.getSpanId()); - Kamon.addReporter("mySpanReporter", testSpanReporter); final var nowInstant = Instant.now(); underTest.mark(key); @@ -130,6 +139,10 @@ public void markWithKeyAsOnlyArgumentCreatesMarkWithSpecifiedKeyCloseToCurrentIn })); } + private TestSpanReporter registerKamonTestSpanReporter() { + return testSpanReporterResource.registerTestSpanReporter(testName.getMethodName()); + } + @Test public void markWithNullKeyButWithInstantThrowsNullPointerException() { assertThatNullPointerException() @@ -149,9 +162,8 @@ public void markWithNullInstantThrowsNullPointerException() { @Test public void markWithInstantProducesExpectedMark() { final var key = "successful"; - final var testSpanReporter = TestSpanReporter.newInstance(); + final var testSpanReporter = registerKamonTestSpanReporter(); final var finishedSpanFuture = testSpanReporter.getFinishedSpanForSpanWithId(underTest.getSpanId()); - Kamon.addReporter("mySpanReporter", testSpanReporter); final var mark = new Span.Mark(Instant.now(), key); underTest.mark(mark.key(), mark.instant()); @@ -177,9 +189,8 @@ public void tagAsFailedWithNullErrorMessageThrowsNullPointerException() { @Test public void tagAsFailedCreatesTagWithSpecifiedErrorMessage() { final var errorMessage = "A foo is not allowed to bar."; - final var testSpanReporter = TestSpanReporter.newInstance(); + final var testSpanReporter = registerKamonTestSpanReporter(); final var finishedSpanFuture = testSpanReporter.getFinishedSpanForSpanWithId(underTest.getSpanId()); - Kamon.addReporter("mySpanReporter", testSpanReporter); underTest.tagAsFailed(errorMessage); underTest.finish(); @@ -205,9 +216,8 @@ public void tagAsFailedWithNullThrowableThrowsNullPointerException() { @Test public void tagAsFailedWithThrowableCreatesExpectedTags() { final var throwable = new NoSuchElementException("Hypermatter"); - final var testSpanReporter = TestSpanReporter.newInstance(); + final var testSpanReporter = registerKamonTestSpanReporter(); final var finishedSpanFuture = testSpanReporter.getFinishedSpanForSpanWithId(underTest.getSpanId()); - Kamon.addReporter("mySpanReporter", testSpanReporter); underTest.tagAsFailed(throwable); underTest.finish(); @@ -231,9 +241,8 @@ public void tagAsFailedWithThrowableCreatesExpectedTags() { public void tagAsFailedWithErrorMessageAndThrowableCreatesExpectedTags() { final var errorMessage = "Failed to fire tachyon pulses."; final var throwable = new NoSuchElementException("Tachyons"); - final var testSpanReporter = TestSpanReporter.newInstance(); + final var testSpanReporter = registerKamonTestSpanReporter(); final var finishedSpanFuture = testSpanReporter.getFinishedSpanForSpanWithId(underTest.getSpanId()); - Kamon.addReporter("mySpanReporter", testSpanReporter); underTest.tagAsFailed(errorMessage, throwable); underTest.finish(); @@ -276,7 +285,7 @@ public void propagateContextToDittoHeadersPutsW3cTraceparent() { } private String getExpectedTraceparentValue() { - return MessageFormat.format("00-0000000000000000{0}-{1}-01", TRACE_ID.string(), underTest.getSpanId()); + return MessageFormat.format("00-{0}-{1}-01", traceId.string(), underTest.getSpanId()); } @Test