Add logs testing module (#4016)

This commit is contained in:
jack-berg 2021-12-20 20:48:09 -06:00 committed by GitHub
parent 2b4fdc2e73
commit 4fbcc5c860
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 453 additions and 31 deletions

View File

@ -0,0 +1,6 @@
# OpenTelemetry Logs SDK Testing
[![Javadocs][javadoc-image]][javadoc-url]
[javadoc-image]: https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-sdk-logs-testing.svg
[javadoc-url]: https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-logs-testing

View File

@ -0,0 +1,22 @@
plugins {
id("otel.java-conventions")
id("otel.publish-conventions")
}
description = "OpenTelemetry Logs SDK Testing utilities"
otelJava.moduleName.set("io.opentelemetry.sdk.logs.testing")
dependencies {
api(project(":api:all"))
api(project(":sdk:all"))
api(project(":sdk:logs"))
api(project(":sdk:testing"))
compileOnly("org.assertj:assertj-core")
compileOnly("junit:junit")
compileOnly("org.junit.jupiter:junit-jupiter-api")
annotationProcessor("com.google.auto.value:auto-value")
testImplementation("junit:junit")
}

View File

@ -0,0 +1 @@
otel.release=alpha

View File

@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.testing.assertj;
import io.opentelemetry.sdk.logs.data.LogData;
import org.assertj.core.api.Assertions;
/** Test assertions for data heading to exporters within the Metrics SDK. */
public final class LogAssertions extends Assertions {
/** Returns an assertion for {@link io.opentelemetry.sdk.logs.data.LogData}. */
public static LogDataAssert assertThat(LogData log) {
return new LogDataAssert(log);
}
private LogAssertions() {}
}

View File

@ -0,0 +1,183 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.testing.assertj;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
import io.opentelemetry.sdk.logs.data.LogData;
import io.opentelemetry.sdk.logs.data.Severity;
import io.opentelemetry.sdk.resources.Resource;
import java.util.Map;
import java.util.function.Consumer;
import org.assertj.core.api.AbstractAssert;
/** Test assertions for {@link LogData}. */
public class LogDataAssert extends AbstractAssert<LogDataAssert, LogData> {
protected LogDataAssert(LogData actual) {
super(actual, LogDataAssert.class);
}
/** Asserts the {@link Resource} associated with a log matches the expected value. */
public LogDataAssert hasResource(Resource resource) {
isNotNull();
if (!actual.getResource().equals(resource)) {
failWithActualExpectedAndMessage(
actual,
"resource: " + resource,
"Expected log to have resource <%s> but found <%s>",
resource,
actual.getResource());
}
return this;
}
/**
* Asserts the {@link InstrumentationLibraryInfo} associated with a log matches the expected
* value.
*/
public LogDataAssert hasInstrumentationLibrary(
InstrumentationLibraryInfo instrumentationLibrary) {
isNotNull();
if (!actual.getInstrumentationLibraryInfo().equals(instrumentationLibrary)) {
failWithActualExpectedAndMessage(
actual,
"instrumentation library: " + instrumentationLibrary,
"Expected log to have resource <%s> but found <%s>",
instrumentationLibrary,
actual.getInstrumentationLibraryInfo());
}
return this;
}
/** Asserts the log has the given epoch timestamp. */
public LogDataAssert hasEpochNanos(long epochNanos) {
isNotNull();
if (actual.getEpochNanos() != epochNanos) {
failWithActualExpectedAndMessage(
actual.getEpochNanos(),
epochNanos,
"Expected log to have epoch <%s> nanos but was <%s>",
epochNanos,
actual.getEpochNanos());
}
return this;
}
/** Asserts the log has the given span context. */
public LogDataAssert hasSpanContext(SpanContext spanContext) {
isNotNull();
if (!actual.getSpanContext().equals(spanContext)) {
failWithActualExpectedAndMessage(
actual.getSpanContext(),
spanContext,
"Expected log to have span context <%s> nanos but was <%s>",
spanContext,
actual.getSpanContext());
}
return this;
}
/** Asserts the log has the given severity. */
public LogDataAssert hasSeverity(Severity severity) {
isNotNull();
if (actual.getSeverity() != severity) {
failWithActualExpectedAndMessage(
actual.getSeverity(),
severity,
"Expected log to have severity <%s> but was <%s>",
severity,
actual.getSeverity());
}
return this;
}
/** Asserts the log has the given severity text. */
public LogDataAssert hasSeverityText(String severityText) {
isNotNull();
if (!severityText.equals(actual.getSeverityText())) {
failWithActualExpectedAndMessage(
actual.getSeverityText(),
severityText,
"Expected log to have severity text <%s> but was <%s>",
severityText,
actual.getSeverityText());
}
return this;
}
/** Asserts the log has the given name. */
public LogDataAssert hasName(String name) {
isNotNull();
if (!name.equals(actual.getName())) {
failWithActualExpectedAndMessage(
actual.getName(),
name,
"Expected log to have name <%s> but was <%s>",
name,
actual.getName());
}
return this;
}
/** Asserts the log has the given body. */
public LogDataAssert hasBody(String body) {
isNotNull();
if (!actual.getBody().asString().equals(body)) {
failWithActualExpectedAndMessage(
actual.getBody(),
body,
"Expected log to have body <%s> but was <%s>",
body,
actual.getBody().asString());
}
return this;
}
/** Asserts the log has the given attributes. */
public LogDataAssert hasAttributes(Attributes attributes) {
isNotNull();
if (!attributesAreEqual(attributes)) {
failWithActualExpectedAndMessage(
actual.getAttributes(),
attributes,
"Expected log to have attributes <%s> but was <%s>",
actual.getName(),
attributes,
actual.getAttributes());
}
return this;
}
/** Asserts the log has the given attributes. */
@SuppressWarnings({"rawtypes", "unchecked"})
@SafeVarargs
public final LogDataAssert hasAttributes(Map.Entry<? extends AttributeKey<?>, ?>... entries) {
AttributesBuilder attributesBuilder = Attributes.builder();
for (Map.Entry<? extends AttributeKey<?>, ?> attr : entries) {
attributesBuilder.put((AttributeKey) attr.getKey(), attr.getValue());
}
Attributes attributes = attributesBuilder.build();
return hasAttributes(attributes);
}
/** Asserts the log has attributes satisfying the given condition. */
public LogDataAssert hasAttributesSatisfying(Consumer<Attributes> attributes) {
isNotNull();
assertThat(actual.getAttributes()).as("attributes").satisfies(attributes);
return this;
}
private boolean attributesAreEqual(Attributes attributes) {
// compare as maps, since implementations do not have equals that work correctly across
// implementations.
return actual.getAttributes().asMap().equals(attributes.asMap());
}
}

View File

@ -0,0 +1,9 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
@ParametersAreNonnullByDefault
package io.opentelemetry.sdk.testing.assertj;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -0,0 +1,184 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.testing.assertj;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
import io.opentelemetry.sdk.logs.data.LogData;
import io.opentelemetry.sdk.logs.data.LogDataBuilder;
import io.opentelemetry.sdk.logs.data.Severity;
import io.opentelemetry.sdk.resources.Resource;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
public class LogAssertionsTest {
private static final Resource RESOURCE =
Resource.create(Attributes.of(stringKey("resource_key"), "resource_value"));
private static final InstrumentationLibraryInfo INSTRUMENTATION_LIBRARY_INFO =
InstrumentationLibraryInfo.create("instrumentation_library", null);
private static final String TRACE_ID = "00000000000000010000000000000002";
private static final String SPAN_ID = "0000000000000003";
private static final Attributes ATTRIBUTES =
Attributes.builder()
.put("bear", "mya")
.put("warm", true)
.put("temperature", 30)
.put("length", 1.2)
.put("colors", "red", "blue")
.put("conditions", false, true)
.put("scores", 0L, 1L)
.put("coins", 0.01, 0.05, 0.1)
.build();
private static final LogData LOG_DATA =
LogDataBuilder.create(RESOURCE, INSTRUMENTATION_LIBRARY_INFO)
.setEpoch(100, TimeUnit.NANOSECONDS)
.setSpanContext(
SpanContext.create(
TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault()))
.setSeverity(Severity.INFO)
.setSeverityText("info")
.setName("name")
.setBody("message")
.setAttributes(ATTRIBUTES)
.build();
@Test
void passing() {
assertThat(LOG_DATA)
.hasResource(RESOURCE)
.hasInstrumentationLibrary(INSTRUMENTATION_LIBRARY_INFO)
.hasEpochNanos(100)
.hasSpanContext(
SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault()))
.hasSeverity(Severity.INFO)
.hasSeverityText("info")
.hasName("name")
.hasBody("message")
.hasAttributes(ATTRIBUTES)
.hasAttributes(
attributeEntry("bear", "mya"),
attributeEntry("warm", true),
attributeEntry("temperature", 30),
attributeEntry("length", 1.2),
attributeEntry("colors", "red", "blue"),
attributeEntry("conditions", false, true),
attributeEntry("scores", 0L, 1L),
attributeEntry("coins", 0.01, 0.05, 0.1))
.hasAttributesSatisfying(
attributes ->
OpenTelemetryAssertions.assertThat(attributes)
.hasSize(8)
.containsEntry(AttributeKey.stringKey("bear"), "mya")
.hasEntrySatisfying(
AttributeKey.stringKey("bear"), value -> assertThat(value).hasSize(3))
.containsEntry("bear", "mya")
.containsEntry("warm", true)
.containsEntry("temperature", 30)
.containsEntry(AttributeKey.longKey("temperature"), 30L)
.containsEntry(AttributeKey.longKey("temperature"), 30)
.containsEntry("length", 1.2)
.containsEntry("colors", "red", "blue")
.containsEntryWithStringValuesOf("colors", Arrays.asList("red", "blue"))
.containsEntry("conditions", false, true)
.containsEntryWithBooleanValuesOf("conditions", Arrays.asList(false, true))
.containsEntry("scores", 0L, 1L)
.containsEntryWithLongValuesOf("scores", Arrays.asList(0L, 1L))
.containsEntry("coins", 0.01, 0.05, 0.1)
.containsEntryWithDoubleValuesOf("coins", Arrays.asList(0.01, 0.05, 0.1))
.containsKey(AttributeKey.stringKey("bear"))
.containsKey("bear")
.containsOnly(
attributeEntry("bear", "mya"),
attributeEntry("warm", true),
attributeEntry("temperature", 30),
attributeEntry("length", 1.2),
attributeEntry("colors", "red", "blue"),
attributeEntry("conditions", false, true),
attributeEntry("scores", 0L, 1L),
attributeEntry("coins", 0.01, 0.05, 0.1)));
}
@Test
void failure() {
assertThatThrownBy(() -> assertThat(LOG_DATA).hasResource(Resource.empty()));
assertThatThrownBy(
() -> assertThat(LOG_DATA).hasInstrumentationLibrary(InstrumentationLibraryInfo.empty()));
assertThatThrownBy(() -> assertThat(LOG_DATA).hasEpochNanos(200));
assertThatThrownBy(
() ->
assertThat(LOG_DATA)
.hasSpanContext(
SpanContext.create(
TRACE_ID,
"0000000000000004",
TraceFlags.getDefault(),
TraceState.getDefault())));
assertThatThrownBy(() -> assertThat(LOG_DATA).hasSeverity(Severity.DEBUG));
assertThatThrownBy(() -> assertThat(LOG_DATA).hasSeverityText("warning"));
assertThatThrownBy(() -> assertThat(LOG_DATA).hasName("foo"));
assertThatThrownBy(() -> assertThat(LOG_DATA).hasBody("bar"));
assertThatThrownBy(() -> assertThat(LOG_DATA).hasAttributes(Attributes.empty()))
.isInstanceOf(AssertionError.class);
assertThatThrownBy(() -> assertThat(LOG_DATA).hasAttributes(attributeEntry("food", "burger")))
.isInstanceOf(AssertionError.class);
assertThatThrownBy(
() ->
assertThat(LOG_DATA)
.hasAttributesSatisfying(
attributes ->
OpenTelemetryAssertions.assertThat(attributes)
.containsEntry("cat", "bark")))
.isInstanceOf(AssertionError.class);
assertThatThrownBy(
() ->
assertThat(LOG_DATA)
.hasAttributesSatisfying(
attributes ->
OpenTelemetryAssertions.assertThat(attributes)
.containsKey(AttributeKey.stringKey("cat"))))
.isInstanceOf(AssertionError.class);
assertThatThrownBy(
() ->
assertThat(LOG_DATA)
.hasAttributesSatisfying(
attributes ->
OpenTelemetryAssertions.assertThat(attributes).containsKey("cat")))
.isInstanceOf(AssertionError.class);
assertThatThrownBy(
() ->
assertThat(LOG_DATA)
.hasAttributesSatisfying(
attributes -> OpenTelemetryAssertions.assertThat(attributes).isEmpty()))
.isInstanceOf(AssertionError.class);
assertThatThrownBy(
() ->
assertThat(LOG_DATA)
.hasAttributesSatisfying(
attributes -> OpenTelemetryAssertions.assertThat(attributes).hasSize(33)))
.isInstanceOf(AssertionError.class);
assertThatThrownBy(
() ->
assertThat(LOG_DATA)
.hasAttributesSatisfying(
attributes ->
OpenTelemetryAssertions.assertThat(attributes)
.hasEntrySatisfying(
AttributeKey.stringKey("bear"),
value -> assertThat(value).hasSize(2))))
.isInstanceOf(AssertionError.class);
}
}

View File

@ -11,6 +11,8 @@ otelJava.moduleName.set("io.opentelemetry.sdk.extension.logging")
dependencies {
api(project(":sdk:common"))
testImplementation(project(":sdk:logs-testing"))
testImplementation("org.awaitility:awaitility")
annotationProcessor("com.google.auto.value:auto-value")

View File

@ -5,7 +5,7 @@
package io.opentelemetry.sdk.logs;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -14,6 +14,7 @@ import io.opentelemetry.sdk.logs.data.LogData;
import io.opentelemetry.sdk.resources.Resource;
import java.util.LinkedList;
import java.util.List;
import org.assertj.core.api.AssertionsForClassTypes;
import org.junit.jupiter.api.Test;
class SdkLogEmitterProviderBuilderTest {
@ -35,7 +36,7 @@ class SdkLogEmitterProviderBuilderTest {
SdkLogEmitterProvider provider = builder.build();
provider.logEmitterBuilder("inst").build().logBuilder().emit();
assertThat(seenLogs.size()).isEqualTo(1);
assertThat(seenLogs.get(0).getEpochNanos()).isEqualTo(13L);
AssertionsForClassTypes.assertThat(seenLogs.size()).isEqualTo(1);
assertThat(seenLogs.get(0)).hasEpochNanos(13L);
}
}

View File

@ -5,7 +5,7 @@
package io.opentelemetry.sdk.logs;
import static org.assertj.core.api.Assertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -36,6 +36,6 @@ class SdkLogEmitterTest {
// Have to test through the builder
logBuilder.emit();
assertThat(seenLog.get().getBody().asString()).isEqualTo("foo");
assertThat(seenLog.get()).hasBody("foo");
}
}

View File

@ -5,7 +5,7 @@
package io.opentelemetry.sdk.logs.export;
import static org.assertj.core.api.Assertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
import static org.awaitility.Awaitility.await;
@ -19,6 +19,7 @@ import io.opentelemetry.api.internal.GuardedBy;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
import io.opentelemetry.sdk.logs.data.LogData;
import io.opentelemetry.sdk.testing.assertj.LogAssertions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -121,8 +122,8 @@ class BatchLogProcessorTest {
List<LogData> exported = waitingLogExporter.waitForExport();
assertThat(exported)
.satisfiesExactly(
logData -> assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_1),
logData -> assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_2));
logData -> assertThat(logData).hasBody(LOG_MESSAGE_1),
logData -> assertThat(logData).hasBody(LOG_MESSAGE_2));
}
@Test
@ -153,9 +154,7 @@ class BatchLogProcessorTest {
() ->
assertThat(logExporter.getExported())
.hasSize(6)
.allSatisfy(
logData ->
assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_1)));
.allSatisfy(logData -> assertThat(logData).hasBody(LOG_MESSAGE_1)));
}
@Test
@ -216,13 +215,13 @@ class BatchLogProcessorTest {
assertThat(exported1)
.hasSize(2)
.satisfiesExactly(
logData -> assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_1),
logData -> assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_2));
logData -> assertThat(logData).hasBody(LOG_MESSAGE_1),
logData -> assertThat(logData).hasBody(LOG_MESSAGE_2));
assertThat(exported2)
.hasSize(2)
.satisfiesExactly(
logData -> assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_1),
logData -> assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_2));
logData -> assertThat(logData).hasBody(LOG_MESSAGE_1),
logData -> assertThat(logData).hasBody(LOG_MESSAGE_2));
}
@Test
@ -311,16 +310,12 @@ class BatchLogProcessorTest {
emitLog(sdkLogEmitterProvider, LOG_MESSAGE_1);
List<LogData> exported = waitingLogExporter.waitForExport();
assertThat(exported)
.satisfiesExactly(
logData -> assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_1));
assertThat(exported).satisfiesExactly(logData -> assertThat(logData).hasBody(LOG_MESSAGE_1));
waitingLogExporter.reset();
// Continue to export after the exception was received.
emitLog(sdkLogEmitterProvider, LOG_MESSAGE_2);
exported = waitingLogExporter.waitForExport();
assertThat(exported)
.satisfiesExactly(
logData -> assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_2));
assertThat(exported).satisfiesExactly(logData -> assertThat(logData).hasBody(LOG_MESSAGE_2));
}
@Test
@ -342,8 +337,7 @@ class BatchLogProcessorTest {
argThat(
logs -> {
assertThat(logs)
.anySatisfy(
log -> assertThat(log.getBody().asString()).isEqualTo(LOG_MESSAGE_1));
.anySatisfy(log -> LogAssertions.assertThat(log).hasBody(LOG_MESSAGE_1));
exported.countDown();
return true;
})))
@ -361,8 +355,7 @@ class BatchLogProcessorTest {
argThat(
logs -> {
assertThat(logs)
.anySatisfy(
log -> assertThat(log.getBody().asString()).isEqualTo(LOG_MESSAGE_2));
.anySatisfy(log -> LogAssertions.assertThat(log).hasBody(LOG_MESSAGE_2));
exportedAgain.countDown();
return true;
})))
@ -393,9 +386,8 @@ class BatchLogProcessorTest {
sdkLogEmitterProvider.shutdown().join(10, TimeUnit.SECONDS);
List<LogData> exported = waitingLogExporter.getExported();
assertThat(exported)
.satisfiesExactly(
logData -> assertThat(logData.getBody().asString()).isEqualTo(LOG_MESSAGE_2));
assertThat(exported).satisfiesExactly(logData -> assertThat(logData).hasBody(LOG_MESSAGE_2));
;
assertThat(waitingLogExporter.shutDownCalled.get()).isTrue();
}

View File

@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.sdk.logs.LogEmitter;
import io.opentelemetry.sdk.logs.SdkLogEmitterProvider;
import io.opentelemetry.sdk.logs.data.LogData;
import io.opentelemetry.sdk.testing.assertj.LogAssertions;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
@ -48,9 +49,9 @@ class InMemoryLogExporterTest {
List<LogData> logItems = exporter.getFinishedLogItems();
assertThat(logItems).isNotNull();
assertThat(logItems.size()).isEqualTo(3);
assertThat(logItems.get(0).getBody().asString()).isEqualTo("message 1");
assertThat(logItems.get(1).getBody().asString()).isEqualTo("message 2");
assertThat(logItems.get(2).getBody().asString()).isEqualTo("message 3");
LogAssertions.assertThat(logItems.get(0)).hasBody("message 1");
LogAssertions.assertThat(logItems.get(1)).hasBody("message 2");
LogAssertions.assertThat(logItems.get(2)).hasBody("message 3");
}
@Test

View File

@ -60,6 +60,7 @@ include(":perf-harness")
include(":sdk:all")
include(":sdk:common")
include(":sdk:logs")
include(":sdk:logs-testing")
include(":sdk:metrics")
include(":sdk:metrics-testing")
include(":sdk:testing")