-
Notifications
You must be signed in to change notification settings - Fork 169
Assertions refactoring #1580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Assertions refactoring #1580
Changes from 34 commits
c3deb51
fcf2bf8
d833450
8cb96ad
f3bac7e
c9eb0a7
2c85c38
bd1947f
1e64f80
f7c6373
b10a340
65ddfb3
db54835
1bdf694
9f8a394
6fb7960
a458c1c
b9f054c
cf72d19
eab7c69
9c3390c
9392780
5ae735d
2c65318
a670561
df09034
06a968f
0e5fd2c
8062a96
ba95efa
4bed658
fddc5fc
fd82042
80994f2
b26cf42
0bf10f5
9f47ef6
2971ef7
7d7e504
19f5d4c
6431feb
ba0f900
fdc64e3
7754e3a
7b7f0d0
cdf4c74
b170021
59a7dfc
34b3ab3
45ba126
5e69a82
dfe3af3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.contrib.jmxscraper.assertions; | ||
|
|
||
| import static io.opentelemetry.contrib.jmxscraper.assertions.AttributeValueMatcher.ANY_VALUE_MATCHER; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| /** Implements functionality of matching data point attributes. */ | ||
| public class AttributeMatcher { | ||
| private final String name; | ||
| private final AttributeValueMatcher attributeValueMatcher; | ||
|
|
||
| /** | ||
| * Create instance used to match data point attribute with te same name and with any value. | ||
| * | ||
| * @param name attribute name | ||
| */ | ||
| AttributeMatcher(String name) { | ||
| this.name = name; | ||
| this.attributeValueMatcher = ANY_VALUE_MATCHER; | ||
| } | ||
|
|
||
| /** | ||
| * Create instance used to match data point attribute with te same name and with the same value. | ||
| * | ||
| * @param name attribute name | ||
| * @param value attribute value | ||
| */ | ||
| AttributeMatcher(String name, String value) { | ||
| this.name = name; | ||
| this.attributeValueMatcher = new AttributeValueMatcher(value); | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } | ||
| if (!(o instanceof AttributeMatcher)) { | ||
| return false; | ||
| } | ||
| AttributeMatcher other = (AttributeMatcher) o; | ||
| return Objects.equals(name, other.name) | ||
| && attributeValueMatcher.matchValue(other.attributeValueMatcher); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| // Do not use value matcher here to support value wildcards | ||
| return Objects.hash(name); | ||
| } | ||
|
||
|
|
||
| @Override | ||
| public String toString() { | ||
| return attributeValueMatcher.getValue() == null | ||
| ? '{' + name + '}' | ||
| : '{' + name + '=' + attributeValueMatcher.getValue() + '}'; | ||
| } | ||
| ; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.contrib.jmxscraper.assertions; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| class AttributeValueMatcher { | ||
| static final AttributeValueMatcher ANY_VALUE_MATCHER = new AttributeValueMatcher(); | ||
|
|
||
| private final String value; | ||
|
|
||
| AttributeValueMatcher() { | ||
| this(null); | ||
| } | ||
|
|
||
| AttributeValueMatcher(String value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| String getValue() { | ||
| return value; | ||
| } | ||
|
|
||
| /** | ||
| * Match the value held by this and the other AttributeValueMatcher instances. Null value means | ||
| * "any value", that's why if any of the values is null they are considered as matching. If both | ||
| * values are not null then they must be equal. | ||
| * | ||
| * @param other the other matcher to compare value with | ||
| * @return true if values held by both matchers are matching, false otherwise. | ||
| */ | ||
| boolean matchValue(AttributeValueMatcher other) { | ||
| if ((value == null) || (other.value == null)) { | ||
| return true; | ||
| } | ||
| return Objects.equals(value, other.value); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.contrib.jmxscraper.assertions; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| /** | ||
| * Utility class implementing convenience static methods to construct data point attribute matchers | ||
| * and sets of matchers. | ||
| */ | ||
| public class DataPointAttributes { | ||
| private DataPointAttributes() {} | ||
|
|
||
| public static AttributeMatcher attribute(String name, String value) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] add javadoc to explain the differences between all those methods: exact match, match with any value (even if the method name is quite clear on the latter, but not on the first on the exact match here). |
||
| return new AttributeMatcher(name, value); | ||
| } | ||
|
|
||
| public static AttributeMatcher attributeWithAnyValue(String name) { | ||
| return new AttributeMatcher(name); | ||
| } | ||
|
|
||
| public static Set<AttributeMatcher> attributeSet(AttributeMatcher... attributes) { | ||
| return Arrays.stream(attributes).collect(Collectors.toSet()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,31 +5,30 @@ | |
|
|
||
| package io.opentelemetry.contrib.jmxscraper.assertions; | ||
|
|
||
| import static io.opentelemetry.contrib.jmxscraper.assertions.DataPointAttributes.attribute; | ||
|
|
||
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | ||
| import io.opentelemetry.proto.common.v1.KeyValue; | ||
| import io.opentelemetry.proto.metrics.v1.Metric; | ||
| import io.opentelemetry.proto.metrics.v1.NumberDataPoint; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.HashMap; | ||
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.function.Consumer; | ||
| import java.util.stream.Collectors; | ||
| import org.assertj.core.api.AbstractAssert; | ||
| import org.assertj.core.internal.Integers; | ||
| import org.assertj.core.internal.Iterables; | ||
| import org.assertj.core.internal.Maps; | ||
| import org.assertj.core.internal.Objects; | ||
|
|
||
| public class MetricAssert extends AbstractAssert<MetricAssert, Metric> { | ||
|
|
||
| private static final Objects objects = Objects.instance(); | ||
| private static final Iterables iterables = Iterables.instance(); | ||
| private static final Integers integers = Integers.instance(); | ||
| private static final Maps maps = Maps.instance(); | ||
|
|
||
| private boolean strict; | ||
|
|
||
|
|
@@ -59,7 +58,7 @@ private void strictCheck( | |
| if (!strict) { | ||
| return; | ||
| } | ||
| String failMsgPrefix = expectedCheckStatus ? "duplicate" : "missing"; | ||
| String failMsgPrefix = expectedCheckStatus ? "Missing" : "Duplicate"; | ||
| info.description( | ||
| "%s assertion on %s for metric '%s'", failMsgPrefix, metricProperty, actual.getName()); | ||
| objects.assertEqual(info, actualCheckStatus, expectedCheckStatus); | ||
|
|
@@ -122,8 +121,8 @@ private MetricAssert hasSum(boolean monotonic) { | |
| info.description("sum expected for metric '%s'", actual.getName()); | ||
| objects.assertEqual(info, actual.hasSum(), true); | ||
|
|
||
| String prefix = monotonic ? "monotonic" : "non-monotonic"; | ||
| info.description(prefix + " sum expected for metric '%s'", actual.getName()); | ||
| String sumType = monotonic ? "monotonic" : "non-monotonic"; | ||
| info.description("sum for metric '%s' is expected to be %s", actual.getName(), sumType); | ||
| objects.assertEqual(info, actual.getSum().getIsMonotonic(), monotonic); | ||
| return this; | ||
| } | ||
|
|
@@ -156,6 +155,11 @@ public MetricAssert isUpDownCounter() { | |
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that there is no attribute in any of data points. | ||
| * | ||
| * @return this | ||
| */ | ||
| @CanIgnoreReturnValue | ||
| public MetricAssert hasDataPointsWithoutAttributes() { | ||
| isNotNull(); | ||
|
|
@@ -195,6 +199,7 @@ private MetricAssert checkDataPoints(Consumer<List<NumberDataPoint>> listConsume | |
| return this; | ||
| } | ||
|
|
||
| // TODO: To be removed and calls will be replaced with hasDataPointsWithAttributes() | ||
| @CanIgnoreReturnValue | ||
| public MetricAssert hasTypedDataPoints(Collection<String> types) { | ||
| return checkDataPoints( | ||
|
|
@@ -229,66 +234,48 @@ private void dataPointsCommonCheck(List<NumberDataPoint> dataPoints) { | |
| } | ||
|
|
||
| /** | ||
| * Verifies that all data points have all the expected attributes | ||
| * Verifies that all data points have the same expected one attribute | ||
| * | ||
| * @param attributes expected attributes | ||
| * @param expectedAttribute expected attribute | ||
| * @return this | ||
| */ | ||
| @SafeVarargs | ||
| @CanIgnoreReturnValue | ||
| public final MetricAssert hasDataPointsAttributes(Map.Entry<String, String>... attributes) { | ||
| return checkDataPoints( | ||
| dataPoints -> { | ||
| dataPointsCommonCheck(dataPoints); | ||
|
|
||
| Map<String, String> attributesMap = new HashMap<>(); | ||
| for (Map.Entry<String, String> attributeEntry : attributes) { | ||
| attributesMap.put(attributeEntry.getKey(), attributeEntry.getValue()); | ||
| } | ||
| for (NumberDataPoint dataPoint : dataPoints) { | ||
| Map<String, String> dataPointAttributes = toMap(dataPoint.getAttributesList()); | ||
|
|
||
| // all attributes must match | ||
| info.description( | ||
| "missing/unexpected data points attributes for metric '%s'", actual.getName()); | ||
| containsExactly(dataPointAttributes, attributes); | ||
| maps.assertContainsAllEntriesOf(info, dataPointAttributes, attributesMap); | ||
| } | ||
| }); | ||
| public final MetricAssert hasDataPointsWithOneAttribute(AttributeMatcher expectedAttribute) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [for reviewer] I left this method for convenience, because there are lots of assertions with just one attribute, but I can remove it if it makes matching logic harder to understand. |
||
| return hasDataPointsWithAttributes(Collections.singleton(expectedAttribute)); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that all data points have their attributes match one of the attributes set and that | ||
| * all provided attributes sets matched at least once. | ||
| * Verifies that every provided attribute matcher set matches attributes of at least one metric | ||
| * data point. Attribute matcher set is considered matching data point attributes if each matcher | ||
| * matches exactly one data point attribute | ||
| * | ||
| * @param attributeSets sets of attributes as maps | ||
| * @param attributeSets array of attribute matcher sets | ||
| * @return this | ||
| */ | ||
| @SafeVarargs | ||
| @CanIgnoreReturnValue | ||
| @SuppressWarnings("varargs") // required to avoid warning | ||
| public final MetricAssert hasDataPointsAttributes(Map<String, String>... attributeSets) { | ||
| public final MetricAssert hasDataPointsWithAttributes(Set<AttributeMatcher>... attributeSets) { | ||
| return checkDataPoints( | ||
| dataPoints -> { | ||
| dataPointsCommonCheck(dataPoints); | ||
|
|
||
| boolean[] matchedSets = new boolean[attributeSets.length]; | ||
|
|
||
| // validate each datapoint attributes match exactly one of the provided attributes set | ||
| // validate each datapoint attributes match exactly one of the provided attributes sets | ||
| for (NumberDataPoint dataPoint : dataPoints) { | ||
| Map<String, String> map = toMap(dataPoint.getAttributesList()); | ||
|
|
||
| Set<AttributeMatcher> dataPointAttributes = toSet(dataPoint.getAttributesList()); | ||
|
||
| int matchCount = 0; | ||
| for (int i = 0; i < attributeSets.length; i++) { | ||
| if (mapEquals(map, attributeSets[i])) { | ||
| if (attributeSets[i].equals(dataPointAttributes)) { | ||
|
||
| matchedSets[i] = true; | ||
| matchCount++; | ||
| } | ||
| } | ||
|
|
||
| info.description( | ||
| "data point attributes '%s' for metric '%s' must match exactly one of the attribute sets '%s'", | ||
| map, actual.getName(), Arrays.asList(attributeSets)); | ||
| dataPointAttributes, actual.getName(), Arrays.asList(attributeSets)); | ||
| integers.assertEqual(info, matchCount, 1); | ||
| } | ||
|
|
||
|
|
@@ -302,29 +289,9 @@ public final MetricAssert hasDataPointsAttributes(Map<String, String>... attribu | |
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Map equality utility | ||
| * | ||
| * @param m1 first map | ||
| * @param m2 second map | ||
| * @return true if the maps have exactly the same keys and values | ||
| */ | ||
| private static boolean mapEquals(Map<String, String> m1, Map<String, String> m2) { | ||
| if (m1.size() != m2.size()) { | ||
| return false; | ||
| } | ||
| return m1.entrySet().stream().allMatch(e -> e.getValue().equals(m2.get(e.getKey()))); | ||
| } | ||
|
|
||
| @SafeVarargs | ||
| @SuppressWarnings("varargs") // required to avoid warning | ||
| private final void containsExactly( | ||
| Map<String, String> map, Map.Entry<String, String>... entries) { | ||
| maps.assertContainsExactly(info, map, entries); | ||
| } | ||
|
|
||
| private static Map<String, String> toMap(List<KeyValue> list) { | ||
| private static Set<AttributeMatcher> toSet(List<KeyValue> list) { | ||
| return list.stream() | ||
| .collect(Collectors.toMap(KeyValue::getKey, kv -> kv.getValue().getStringValue())); | ||
| .map(entry -> attribute(entry.getKey(), entry.getValue().getStringValue())) | ||
| .collect(Collectors.toSet()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[minor] add javadoc that it returns the name of the attribute