From f1a7bc3bfd50e9c9ab29bcba76eed5c6bfad4c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Monkiewicz?= Date: Tue, 29 Nov 2022 20:47:15 +0100 Subject: [PATCH] =?UTF-8?q?Adjustment=20of=20behaviour=20of=20hasXAttribut?= =?UTF-8?q?esSatisfying=20and=20hasXAttribute=E2=80=A6=20(#4882)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adjustment of behaviour of hasXAttributesSatisfying and hasXAttributesSatisfyingExactly assertions * Cleanup * Additional test coverage for new assertion method * Added javadoc on assertions Co-authored-by: Trask Stalnaker Co-authored-by: Trask Stalnaker --- .../opentelemetry-sdk-testing.txt | 23 ++++- .../testing/assertj/LogRecordDataAssert.java | 40 ++++++++ .../testing/assertj/LogAssertionsTest.java | 27 ++++++ .../testing/assertj/AbstractPointAssert.java | 28 +++++- .../sdk/testing/assertj/AssertUtil.java | 28 +++++- .../testing/assertj/DoubleExemplarAssert.java | 31 ++++++- .../sdk/testing/assertj/EventDataAssert.java | 40 ++++++++ .../testing/assertj/LongExemplarAssert.java | 31 ++++++- .../sdk/testing/assertj/SpanDataAssert.java | 49 ++++++---- .../sdk/testing/assertj/AssertUtilTest.java | 85 +++++++++++++++++ .../testing/assertj/MetricAssertionsTest.java | 92 +++++++++++++++++-- .../testing/assertj/TraceAssertionsTest.java | 23 +++++ 12 files changed, 460 insertions(+), 37 deletions(-) create mode 100644 sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/AssertUtilTest.java diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt index df26146497b..6fa6fca8c60 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt @@ -1,2 +1,23 @@ Comparing source compatibility of against -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.testing.assertj.AbstractPointAssert (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.testing.assertj.AbstractPointAssert hasAttributesSatisfyingExactly(io.opentelemetry.sdk.testing.assertj.AttributeAssertion[]) + +++ NEW METHOD: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.testing.assertj.AbstractPointAssert hasAttributesSatisfyingExactly(java.lang.Iterable) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.testing.assertj.DoubleExemplarAssert (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.DoubleExemplarAssert hasFilteredAttributesSatisfyingExactly(io.opentelemetry.sdk.testing.assertj.AttributeAssertion[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.DoubleExemplarAssert hasFilteredAttributesSatisfyingExactly(java.lang.Iterable) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.testing.assertj.EventDataAssert (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EventDataAssert hasAttributesSatisfying(io.opentelemetry.sdk.testing.assertj.AttributeAssertion[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EventDataAssert hasAttributesSatisfying(java.lang.Iterable) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EventDataAssert hasAttributesSatisfyingExactly(io.opentelemetry.sdk.testing.assertj.AttributeAssertion[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EventDataAssert hasAttributesSatisfyingExactly(java.lang.Iterable) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.testing.assertj.LongExemplarAssert (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LongExemplarAssert hasFilteredAttributesSatisfyingExactly(io.opentelemetry.sdk.testing.assertj.AttributeAssertion[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LongExemplarAssert hasFilteredAttributesSatisfyingExactly(java.lang.Iterable) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.testing.assertj.SpanDataAssert (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.SpanDataAssert hasAttributesSatisfying(io.opentelemetry.sdk.testing.assertj.AttributeAssertion[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.SpanDataAssert hasAttributesSatisfying(java.lang.Iterable) diff --git a/sdk/logs-testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java b/sdk/logs-testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java index cb0eb83722f..d23b6e97424 100644 --- a/sdk/logs-testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java +++ b/sdk/logs-testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java @@ -15,6 +15,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; +import java.util.Arrays; import java.util.Map; import java.util.function.Consumer; import org.assertj.core.api.AbstractAssert; @@ -160,6 +161,45 @@ public LogRecordDataAssert hasAttributesSatisfying(Consumer attribut return this; } + /** + * Asserts the log has attributes matching all {@code assertions}. Assertions can be created using + * methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public LogRecordDataAssert hasAttributesSatisfying(AttributeAssertion... assertions) { + return hasAttributesSatisfying(Arrays.asList(assertions)); + } + + /** + * Asserts the log has attributes matching all {@code assertions}. Assertions can be created using + * methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public LogRecordDataAssert hasAttributesSatisfying(Iterable assertions) { + AssertUtil.assertAttributes(actual.getAttributes(), assertions); + return myself; + } + + /** + * Asserts the log has attributes matching all {@code assertions} and no more. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public LogRecordDataAssert hasAttributesSatisfyingExactly(AttributeAssertion... assertions) { + return hasAttributesSatisfyingExactly(Arrays.asList(assertions)); + } + + /** + * Asserts the log has attributes matching all {@code assertions} and no more. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public LogRecordDataAssert hasAttributesSatisfyingExactly( + Iterable assertions) { + AssertUtil.assertAttributesExactly(actual.getAttributes(), assertions); + return myself; + } + private boolean attributesAreEqual(Attributes attributes) { // compare as maps, since implementations do not have equals that work correctly across // implementations. diff --git a/sdk/logs-testing/src/test/java/io/opentelemetry/sdk/testing/assertj/LogAssertionsTest.java b/sdk/logs-testing/src/test/java/io/opentelemetry/sdk/testing/assertj/LogAssertionsTest.java index 750f6e59221..54f1afa67a3 100644 --- a/sdk/logs-testing/src/test/java/io/opentelemetry/sdk/testing/assertj/LogAssertionsTest.java +++ b/sdk/logs-testing/src/test/java/io/opentelemetry/sdk/testing/assertj/LogAssertionsTest.java @@ -8,6 +8,7 @@ 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 io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.api.common.AttributeKey; @@ -111,6 +112,18 @@ void passing() { attributeEntry("conditions", false, true), attributeEntry("scores", 0L, 1L), attributeEntry("coins", 0.01, 0.05, 0.1))) + .hasAttributesSatisfying( + equalTo(AttributeKey.stringKey("bear"), "mya"), + equalTo(AttributeKey.booleanArrayKey("conditions"), Arrays.asList(false, true))) + .hasAttributesSatisfyingExactly( + equalTo(AttributeKey.stringKey("bear"), "mya"), + equalTo(AttributeKey.booleanKey("warm"), true), + equalTo(AttributeKey.longKey("temperature"), 30L), + equalTo(AttributeKey.doubleKey("length"), 1.2), + equalTo(AttributeKey.stringArrayKey("colors"), Arrays.asList("red", "blue")), + equalTo(AttributeKey.booleanArrayKey("conditions"), Arrays.asList(false, true)), + equalTo(AttributeKey.longArrayKey("scores"), Arrays.asList(0L, 1L)), + equalTo(AttributeKey.doubleArrayKey("coins"), Arrays.asList(0.01, 0.05, 0.1))) .hasTotalAttributeCount(999); } @@ -181,6 +194,20 @@ void failure() { AttributeKey.stringKey("bear"), value -> assertThat(value).hasSize(2)))) .isInstanceOf(AssertionError.class); + assertThatThrownBy( + () -> + assertThat(LOG_DATA) + .hasAttributesSatisfying(equalTo(AttributeKey.stringKey("bear"), "moo"))) + .isInstanceOf(AssertionError.class); + assertThatThrownBy( + () -> + assertThat(LOG_DATA) + .hasAttributesSatisfyingExactly( + equalTo(AttributeKey.stringKey("bear"), "mya"), + equalTo(AttributeKey.booleanKey("warm"), true), + equalTo(AttributeKey.longKey("temperature"), 30L), + equalTo(AttributeKey.doubleKey("length"), 1.2))) + .isInstanceOf(AssertionError.class); assertThatThrownBy(() -> assertThat(LOG_DATA).hasTotalAttributeCount(11)) .isInstanceOf(AssertionError.class); } diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AbstractPointAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AbstractPointAssert.java index 528f6136ff1..80c70416e61 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AbstractPointAssert.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AbstractPointAssert.java @@ -93,8 +93,8 @@ public final PointAssertT hasAttributes(Map.Entry, ?>. } /** - * Asserts the point has attributes matching all {@code assertions} and no more. Assertions can be - * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * Asserts the point has attributes matching all {@code assertions}. Assertions can be created + * using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, * OpenTelemetryAssertions.LongAssertConsumer)}. */ public final PointAssertT hasAttributesSatisfying(AttributeAssertion... assertions) { @@ -102,12 +102,32 @@ public final PointAssertT hasAttributesSatisfying(AttributeAssertion... assertio } /** - * Asserts the point has attributes matching all {@code assertions} and no more. Assertions can be - * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * Asserts the point has attributes matching all {@code assertions}. Assertions can be created + * using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, * OpenTelemetryAssertions.LongAssertConsumer)}. */ public final PointAssertT hasAttributesSatisfying(Iterable assertions) { AssertUtil.assertAttributes(actual.getAttributes(), assertions); return myself; } + + /** + * Asserts the point has attributes matching all {@code assertions} and no more. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public final PointAssertT hasAttributesSatisfyingExactly(AttributeAssertion... assertions) { + return hasAttributesSatisfyingExactly(Arrays.asList(assertions)); + } + + /** + * Asserts the point has attributes matching all {@code assertions} and no more. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public final PointAssertT hasAttributesSatisfyingExactly( + Iterable assertions) { + AssertUtil.assertAttributesExactly(actual.getAttributes(), assertions); + return myself; + } } diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AssertUtil.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AssertUtil.java index b605cdabcb5..5ff3d40f14b 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AssertUtil.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AssertUtil.java @@ -34,6 +34,32 @@ static > Consumer[] toConsumers( } static void assertAttributes(Attributes actual, Iterable assertions) { + assertAttributes(actual, assertions, "attribute keys"); + } + + static void assertAttributes( + Attributes actual, Iterable assertions, String name) { + Set> actualKeys = actual.asMap().keySet(); + Set> checkedKeys = new HashSet<>(); + for (AttributeAssertion attributeAssertion : assertions) { + AttributeKey key = attributeAssertion.getKey(); + Object value = actual.get(key); + if (value != null) { + checkedKeys.add(key); + } + AbstractAssert assertion = AttributeAssertion.attributeValueAssertion(key, value); + attributeAssertion.getAssertion().accept(assertion); + } + + assertThat(actualKeys).as(name).containsAll(checkedKeys); + } + + static void assertAttributesExactly(Attributes actual, Iterable assertions) { + assertAttributesExactly(actual, assertions, "attribute keys"); + } + + static void assertAttributesExactly( + Attributes actual, Iterable assertions, String name) { Set> actualKeys = actual.asMap().keySet(); Set> checkedKeys = new HashSet<>(); for (AttributeAssertion attributeAssertion : assertions) { @@ -46,7 +72,7 @@ static void assertAttributes(Attributes actual, Iterable ass attributeAssertion.getAssertion().accept(assertion); } - assertThat(actualKeys).as("attribute keys").containsExactlyInAnyOrderElementsOf(checkedKeys); + assertThat(actualKeys).as(name).containsExactlyInAnyOrderElementsOf(checkedKeys); } /** diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/DoubleExemplarAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/DoubleExemplarAssert.java index 31645e2d081..fea4d74eb26 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/DoubleExemplarAssert.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/DoubleExemplarAssert.java @@ -97,19 +97,44 @@ public final DoubleExemplarAssert hasFilteredAttributes( return hasFilteredAttributes(attributes); } - /** Asserts the exemplar has filtered attributes matching all {@code assertions} and no more. */ + /** + * Asserts the exemplar has filtered attributes matching all {@code assertions}. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ public DoubleExemplarAssert hasFilteredAttributesSatisfying(AttributeAssertion... assertions) { return hasFilteredAttributesSatisfying(Arrays.asList(assertions)); } + /** + * Asserts the exemplar has filtered attributes matching all {@code assertions}. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public DoubleExemplarAssert hasFilteredAttributesSatisfying( + Iterable assertions) { + AssertUtil.assertAttributes(actual.getFilteredAttributes(), assertions); + return myself; + } + /** * Asserts the exemplar has filtered attributes matching all {@code assertions} and no more. * Assertions can be created using methods like {@link * OpenTelemetryAssertions#satisfies(AttributeKey, OpenTelemetryAssertions.LongAssertConsumer)}. */ - public DoubleExemplarAssert hasFilteredAttributesSatisfying( + public DoubleExemplarAssert hasFilteredAttributesSatisfyingExactly( + AttributeAssertion... assertions) { + return hasFilteredAttributesSatisfyingExactly(Arrays.asList(assertions)); + } + + /** + * Asserts the exemplar has filtered attributes matching all {@code assertions} and no more. + * Assertions can be created using methods like {@link + * OpenTelemetryAssertions#satisfies(AttributeKey, OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public DoubleExemplarAssert hasFilteredAttributesSatisfyingExactly( Iterable assertions) { - AssertUtil.assertAttributes(actual.getFilteredAttributes(), assertions); + AssertUtil.assertAttributesExactly(actual.getFilteredAttributes(), assertions); return myself; } } diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EventDataAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EventDataAssert.java index 9dd24e8c59a..d232a8360e9 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EventDataAssert.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EventDataAssert.java @@ -7,9 +7,11 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.trace.data.EventData; import java.time.Instant; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import javax.annotation.Nullable; @@ -82,4 +84,42 @@ public EventDataAssert hasAttributesSatisfying(Consumer attributes) assertThat(actual.getAttributes()).as("attributes").satisfies(attributes); return this; } + + /** + * Asserts the event has attributes matching all {@code assertions}. Assertions can be created + * using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public EventDataAssert hasAttributesSatisfying(AttributeAssertion... assertions) { + return hasAttributesSatisfying(Arrays.asList(assertions)); + } + + /** + * Asserts the event has attributes matching all {@code assertions}. Assertions can be created + * using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public EventDataAssert hasAttributesSatisfying(Iterable assertions) { + AssertUtil.assertAttributes(actual.getAttributes(), assertions); + return this; + } + + /** + * Asserts the event has attributes matching all {@code assertions} and no more. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public EventDataAssert hasAttributesSatisfyingExactly(AttributeAssertion... assertions) { + return hasAttributesSatisfyingExactly(Arrays.asList(assertions)); + } + + /** + * Asserts the event has attributes matching all {@code assertions} and no more. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public EventDataAssert hasAttributesSatisfyingExactly(Iterable assertions) { + AssertUtil.assertAttributesExactly(actual.getAttributes(), assertions); + return this; + } } diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LongExemplarAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LongExemplarAssert.java index 88340854ee5..6fa6659c796 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LongExemplarAssert.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LongExemplarAssert.java @@ -96,19 +96,44 @@ public final LongExemplarAssert hasFilteredAttributes( return hasFilteredAttributes(attributes); } - /** Asserts the exemplar has filtered attributes matching all {@code assertions} and no more. */ + /** + * Asserts the exemplar has filtered attributes matching all {@code assertions}. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ public LongExemplarAssert hasFilteredAttributesSatisfying(AttributeAssertion... assertions) { return hasFilteredAttributesSatisfying(Arrays.asList(assertions)); } + /** + * Asserts the exemplar has filtered attributes matching all {@code assertions}. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public LongExemplarAssert hasFilteredAttributesSatisfying( + Iterable assertions) { + AssertUtil.assertAttributes(actual.getFilteredAttributes(), assertions); + return myself; + } + + /** + * Asserts the exemplar has filtered attributes matching all {@code assertions} and no more. + * Assertions can be created using methods like {@link + * OpenTelemetryAssertions#satisfies(AttributeKey, OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public LongExemplarAssert hasFilteredAttributesSatisfyingExactly( + AttributeAssertion... assertions) { + return hasFilteredAttributesSatisfying(Arrays.asList(assertions)); + } + /** * Asserts the exemplar has filtered attributes matching all {@code assertions} and no more. * Assertions can be created using methods like {@link * OpenTelemetryAssertions#satisfies(AttributeKey, OpenTelemetryAssertions.LongAssertConsumer)}. */ - public LongExemplarAssert hasFilteredAttributesSatisfying( + public LongExemplarAssert hasFilteredAttributesSatisfyingExactly( Iterable assertions) { - AssertUtil.assertAttributes(actual.getFilteredAttributes(), assertions); + AssertUtil.assertAttributesExactly(actual.getFilteredAttributes(), assertions); return myself; } } diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/SpanDataAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/SpanDataAssert.java index 3582b0b1129..bde4d9cd27c 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/SpanDataAssert.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/SpanDataAssert.java @@ -22,7 +22,6 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.time.Instant; import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -307,7 +306,33 @@ public SpanDataAssert hasAttributesSatisfying(Consumer attributes) { return this; } - /** Asserts the span has attributes matching all {@code assertions} and no more. */ + /** + * Asserts the event has attributes matching all {@code assertions}. Assertions can be created + * using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public SpanDataAssert hasAttributesSatisfying(AttributeAssertion... assertions) { + return hasAttributesSatisfying(Arrays.asList(assertions)); + } + + /** + * Asserts the event has attributes matching all {@code assertions}. Assertions can be created + * using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ + public SpanDataAssert hasAttributesSatisfying(Iterable assertions) { + AssertUtil.assertAttributes( + actual.getAttributes(), + assertions, + String.format("span [%s] attribute keys", actual.getName())); + return this; + } + + /** + * Asserts the span has attributes matching all {@code assertions} and no more. Assertions can be + * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey, + * OpenTelemetryAssertions.LongAssertConsumer)}. + */ public SpanDataAssert hasAttributesSatisfyingExactly(AttributeAssertion... assertions) { return hasAttributesSatisfyingExactly(Arrays.asList(assertions)); } @@ -318,22 +343,10 @@ public SpanDataAssert hasAttributesSatisfyingExactly(AttributeAssertion... asser * OpenTelemetryAssertions.LongAssertConsumer)}. */ public SpanDataAssert hasAttributesSatisfyingExactly(Iterable assertions) { - Set> actualKeys = actual.getAttributes().asMap().keySet(); - Set> checkedKeys = new HashSet<>(); - for (AttributeAssertion attributeAssertion : assertions) { - AttributeKey key = attributeAssertion.getKey(); - Object value = actual.getAttributes().get(key); - if (value != null) { - checkedKeys.add(key); - } - AbstractAssert assertion = AttributeAssertion.attributeValueAssertion(key, value); - attributeAssertion.getAssertion().accept(assertion); - } - - assertThat(actualKeys) - .as("span [%s] attribute keys", actual.getName()) - .containsExactlyInAnyOrderElementsOf(checkedKeys); - + AssertUtil.assertAttributesExactly( + actual.getAttributes(), + assertions, + String.format("span [%s] attribute keys", actual.getName())); return this; } diff --git a/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/AssertUtilTest.java b/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/AssertUtilTest.java new file mode 100644 index 00000000000..d2f66094057 --- /dev/null +++ b/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/AssertUtilTest.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.testing.assertj; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +class AssertUtilTest { + private static final AttributeKey WARM = AttributeKey.booleanKey("warm"); + private static final AttributeKey TEMPERATURE = AttributeKey.longKey("temperature"); + private static final AttributeKey LENGTH = AttributeKey.doubleKey("length"); + private static final AttributeKey> COLORS = AttributeKey.stringArrayKey("colors"); + + private static final Attributes ATTRIBUTES = + Attributes.builder() + .put(WARM, true) + .put(TEMPERATURE, 30) + .put(LENGTH, 1.2) + .put(COLORS, Arrays.asList("red", "blue")) + .build(); + + @Test + void assertAttributesShouldThrowIfNoAttributeMatch() { + List assertions = Arrays.asList(equalTo(WARM, false)); + + assertThatThrownBy(() -> AssertUtil.assertAttributes(ATTRIBUTES, assertions)) + .isInstanceOf(AssertionError.class); + } + + @Test + void assertAttributesShouldNotThrowIfSomeAttributesMatch() { + List assertions = Arrays.asList(equalTo(WARM, true)); + + AssertUtil.assertAttributes(ATTRIBUTES, assertions); + } + + @Test + void assertAttributesShouldNotThrowIfAllAttributesMatch() { + List assertions = + Arrays.asList( + equalTo(WARM, true), + equalTo(TEMPERATURE, 30L), + equalTo(LENGTH, 1.2), + equalTo(COLORS, Arrays.asList("red", "blue"))); + + AssertUtil.assertAttributes(ATTRIBUTES, assertions); + } + + @Test + void assertAttributesExactlyShouldThrowIfNoAttributeMatch() { + List assertions = Arrays.asList(equalTo(WARM, false)); + + assertThatThrownBy(() -> AssertUtil.assertAttributesExactly(ATTRIBUTES, assertions)) + .isInstanceOf(AssertionError.class); + } + + @Test + void assertAttributesExactlyShouldThrowIfSomeAttributesMatch() { + List assertions = Arrays.asList(equalTo(WARM, true)); + + assertThatThrownBy(() -> AssertUtil.assertAttributesExactly(ATTRIBUTES, assertions)) + .isInstanceOf(AssertionError.class); + } + + @Test + void assertAttributesExactlyShouldNotThrowIfAllAttributesMatch() { + List assertions = + Arrays.asList( + equalTo(WARM, true), + equalTo(TEMPERATURE, 30L), + equalTo(LENGTH, 1.2), + equalTo(COLORS, Arrays.asList("red", "blue"))); + + AssertUtil.assertAttributesExactly(ATTRIBUTES, assertions); + } +} diff --git a/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/MetricAssertionsTest.java b/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/MetricAssertionsTest.java index b2b68f38539..c8a2e5afd3f 100644 --- a/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/MetricAssertionsTest.java +++ b/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/MetricAssertionsTest.java @@ -299,11 +299,7 @@ void doubleGauge() { equalTo(BEAR, "mya"), equalTo(WARM, true), equalTo(TEMPERATURE, 30), - equalTo(LENGTH, 1.2), - equalTo(COLORS, Arrays.asList("red", "blue")), - equalTo(CONDITIONS, Arrays.asList(false, true)), - equalTo(SCORES, Arrays.asList(0L, 1L)), - equalTo(COINS, Arrays.asList(0.01, 0.05, 0.1))) + equalTo(LENGTH, 1.2)) .hasFilteredAttributesSatisfying( satisfies(BEAR, val -> val.startsWith("mya")), satisfies(WARM, val -> val.isTrue()), @@ -321,6 +317,10 @@ void doubleGauge() { // Demonstrates common usage of many exact matches and one // needing a loose one. .hasFilteredAttributesSatisfying( + equalTo(BEAR, "mya"), + equalTo(COLORS, Arrays.asList("red", "blue")), + satisfies(LENGTH, val -> val.isCloseTo(1, offset(0.3)))) + .hasFilteredAttributesSatisfyingExactly( equalTo(BEAR, "mya"), equalTo(WARM, true), equalTo(TEMPERATURE, 30L), @@ -548,6 +548,37 @@ void doubleGaugeFailure() { exemplar -> {}), point -> {}))) .isInstanceOf(AssertionError.class); + assertThatThrownBy( + () -> + assertThat(DOUBLE_GAUGE_METRIC) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point.hasExemplarsSatisfying( + exemplar -> + // Extra CAT + exemplar.hasFilteredAttributesSatisfyingExactly( + satisfies(WARM, val -> val.isTrue()), + satisfies( + TEMPERATURE, + val -> val.isGreaterThanOrEqualTo(30)), + satisfies( + LENGTH, val -> val.isCloseTo(1, offset(0.3))), + satisfies( + COLORS, + val -> val.containsExactly("red", "blue")), + satisfies( + CONDITIONS, + val -> val.containsExactly(false, true)), + satisfies( + SCORES, val -> val.containsExactly(0L, 1L)), + satisfies( + COINS, + val -> val.containsExactly(0.01, 0.05, 0.1))), + exemplar -> {}), + point -> {}))) + .isInstanceOf(AssertionError.class); assertThatThrownBy( () -> assertThat(DOUBLE_GAUGE_METRIC) @@ -606,7 +637,26 @@ void longGauge() { point .hasValue(Long.MAX_VALUE) .hasExemplarsSatisfying( - exemplar -> exemplar.hasValue(2), exemplar -> exemplar.hasValue(1)), + exemplar -> exemplar.hasValue(2), + exemplar -> + exemplar + .hasValue(1) + .hasFilteredAttributesSatisfying( + equalTo(BEAR, "mya"), + equalTo(WARM, true), + equalTo(TEMPERATURE, 30L), + equalTo(COLORS, Arrays.asList("red", "blue")), + satisfies(LENGTH, val -> val.isCloseTo(1, offset(0.3)))) + .hasFilteredAttributesSatisfyingExactly( + equalTo(BEAR, "mya"), + equalTo(WARM, true), + equalTo(TEMPERATURE, 30L), + equalTo(COLORS, Arrays.asList("red", "blue")), + equalTo(CONDITIONS, Arrays.asList(false, true)), + equalTo(SCORES, Arrays.asList(0L, 1L)), + equalTo(COINS, Arrays.asList(0.01, 0.05, 0.1)), + satisfies( + LENGTH, val -> val.isCloseTo(1, offset(0.3))))), point -> point.hasValue(1))); } @@ -651,10 +701,38 @@ void longGaugeFailure() { exemplar -> exemplar.hasValue(100), exemplar -> {}), point -> point.hasValue(1)))) .isInstanceOf(AssertionError.class); + assertThatThrownBy( + () -> + assertThat(LONG_GAUGE_METRIC) + .hasLongGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point.hasExemplarsSatisfying( + exemplar -> + exemplar.hasFilteredAttributesSatisfying( + equalTo(CAT, "mya"), equalTo(WARM, true)))))) + .isInstanceOf(AssertionError.class); + assertThatThrownBy( + () -> + assertThat(LONG_GAUGE_METRIC) + .hasLongGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point.hasExemplarsSatisfying( + exemplar -> + exemplar.hasFilteredAttributesSatisfyingExactly( + equalTo(BEAR, "mya"), + equalTo(WARM, true), + satisfies( + LENGTH, + val -> val.isCloseTo(1, offset(0.3)))))))) + .isInstanceOf(AssertionError.class); } @Test - void duobleSum() { + void doubleSum() { assertThat(DOUBLE_SUM_METRIC) .hasDoubleSumSatisfying( sum -> sum.isMonotonic().isCumulative().hasPointsSatisfying(point -> {}, point -> {})); diff --git a/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/TraceAssertionsTest.java b/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/TraceAssertionsTest.java index 58b26fe65ef..d13689a38b0 100644 --- a/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/TraceAssertionsTest.java +++ b/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/TraceAssertionsTest.java @@ -158,6 +158,8 @@ void passing() { attributeEntry("conditions", false, true), attributeEntry("scores", 0L, 1L), attributeEntry("coins", 0.01, 0.05, 0.1)) + .hasAttributesSatisfying( + equalTo(BEAR, "mya"), equalTo(WARM, true), equalTo(TEMPERATURE, 30)) .hasAttributesSatisfyingExactly( equalTo(BEAR, "mya"), equalTo(WARM, true), @@ -234,6 +236,15 @@ void passing() { .hasAttributesSatisfying( attributes -> assertThat(attributes).isEqualTo(Attributes.empty())) .hasAttributesSatisfying(attributes -> assertThat(attributes).isEmpty()); + assertThat(events.get(2)) + .hasAttributesSatisfying( + equalTo( + SemanticAttributes.EXCEPTION_TYPE, "java.lang.IllegalArgumentException")) + .hasAttributesSatisfyingExactly( + equalTo( + SemanticAttributes.EXCEPTION_TYPE, "java.lang.IllegalArgumentException"), + equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "bad argument"), + equalTo(SemanticAttributes.EXCEPTION_STACKTRACE, "some obfuscated stack")); }) .hasEventsSatisfyingExactly( event -> event.hasName("event"), @@ -435,6 +446,18 @@ void failure() { attributes -> assertThat(attributes).containsEntry("dogs", "meow")))) .isInstanceOf(AssertionError.class); + assertThatThrownBy( + () -> + assertThat(SPAN1) + .hasEventsSatisfying( + events -> + assertThat(events.get(2)) + .hasAttributesSatisfyingExactly( + equalTo( + SemanticAttributes.EXCEPTION_TYPE, + "java.lang.IllegalArgumentException"), + equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "bad argument")))) + .isInstanceOf(AssertionError.class); assertThatThrownBy( () -> assertThat(SPAN1).hasException(new IllegalStateException("bad argument"))) .isInstanceOf(AssertionError.class);