Skip to content

Commit cd9c7d2

Browse files
robsundaySylvainJugebreedx-splk
authored
Assertions refactoring (#1580)
Co-authored-by: Sylvain Juge <[email protected]> Co-authored-by: jason plumb <[email protected]>
1 parent c5948f6 commit cd9c7d2

File tree

8 files changed

+801
-568
lines changed

8 files changed

+801
-568
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.jmxscraper.assertions;
7+
8+
import javax.annotation.Nullable;
9+
10+
/** Implements functionality of matching data point attributes. */
11+
public class AttributeMatcher {
12+
private final String attributeName;
13+
@Nullable private final String attributeValue;
14+
15+
/**
16+
* Create instance used to match data point attribute with any value.
17+
*
18+
* @param attributeName matched attribute name
19+
*/
20+
AttributeMatcher(String attributeName) {
21+
this(attributeName, null);
22+
}
23+
24+
/**
25+
* Create instance used to match data point attribute with te same name and with the same value.
26+
*
27+
* @param attributeName attribute name
28+
* @param attributeValue attribute value
29+
*/
30+
AttributeMatcher(String attributeName, @Nullable String attributeValue) {
31+
this.attributeName = attributeName;
32+
this.attributeValue = attributeValue;
33+
}
34+
35+
/**
36+
* Return name of data point attribute that this AttributeMatcher is supposed to match value with.
37+
*
38+
* @return name of validated attribute
39+
*/
40+
public String getAttributeName() {
41+
return attributeName;
42+
}
43+
44+
@Override
45+
public String toString() {
46+
return attributeValue == null
47+
? '{' + attributeName + '}'
48+
: '{' + attributeName + '=' + attributeValue + '}';
49+
}
50+
51+
/**
52+
* Verify if this matcher is matching provided attribute value. If this matcher holds null value
53+
* then it is matching any attribute value.
54+
*
55+
* @param value a value to be matched
56+
* @return true if this matcher is matching provided value, false otherwise.
57+
*/
58+
boolean matchesValue(String value) {
59+
return attributeValue == null || attributeValue.equals(value);
60+
}
61+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.jmxscraper.assertions;
7+
8+
import java.util.Collection;
9+
import java.util.Map;
10+
import java.util.stream.Collectors;
11+
12+
/** Group of attribute matchers */
13+
public class AttributeMatcherGroup {
14+
15+
// stored as a Map for easy lookup by name
16+
private final Map<String, AttributeMatcher> matchers;
17+
18+
/**
19+
* Constructor for a set of attribute matchers
20+
*
21+
* @param matchers collection of matchers to build a group from
22+
* @throws IllegalStateException if there is any duplicate key
23+
*/
24+
AttributeMatcherGroup(Collection<AttributeMatcher> matchers) {
25+
this.matchers =
26+
matchers.stream().collect(Collectors.toMap(AttributeMatcher::getAttributeName, m -> m));
27+
}
28+
29+
/**
30+
* Checks if attributes match this attribute matcher group
31+
*
32+
* @param attributes attributes to check as map
33+
* @return {@literal true} when the attributes match all attributes from this group
34+
*/
35+
public boolean matches(Map<String, String> attributes) {
36+
if (attributes.size() != matchers.size()) {
37+
return false;
38+
}
39+
40+
for (Map.Entry<String, String> entry : attributes.entrySet()) {
41+
AttributeMatcher matcher = matchers.get(entry.getKey());
42+
if (matcher == null) {
43+
// no matcher for this key: unexpected key
44+
return false;
45+
}
46+
47+
if (!matcher.matchesValue(entry.getValue())) {
48+
// value does not match: unexpected value
49+
return false;
50+
}
51+
}
52+
53+
return true;
54+
}
55+
56+
@Override
57+
public String toString() {
58+
return matchers.values().toString();
59+
}
60+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.jmxscraper.assertions;
7+
8+
import java.util.Arrays;
9+
10+
/**
11+
* Utility class implementing convenience static methods to construct data point attribute matchers
12+
* and sets of matchers.
13+
*/
14+
public class DataPointAttributes {
15+
private DataPointAttributes() {}
16+
17+
/**
18+
* Create instance of matcher that should be used to check if data point attribute with given name
19+
* has value identical to the one provided as a parameter (exact match).
20+
*
21+
* @param name name of the data point attribute to check
22+
* @param value expected value of checked data point attribute
23+
* @return instance of matcher
24+
*/
25+
public static AttributeMatcher attribute(String name, String value) {
26+
return new AttributeMatcher(name, value);
27+
}
28+
29+
/**
30+
* Create instance of matcher that should be used to check if data point attribute with given name
31+
* exists. Any value of the attribute is considered as matching (any value match).
32+
*
33+
* @param name name of the data point attribute to check
34+
* @return instance of matcher
35+
*/
36+
public static AttributeMatcher attributeWithAnyValue(String name) {
37+
return new AttributeMatcher(name);
38+
}
39+
40+
/**
41+
* Creates a group of attribute matchers that should be used to verify data point attributes.
42+
*
43+
* @param attributes list of matchers to create group. It must contain matchers with unique names.
44+
* @return group of attribute matchers
45+
* @throws IllegalArgumentException if provided list contains two or more matchers with the same
46+
* attribute name
47+
* @see MetricAssert#hasDataPointsWithAttributes(AttributeMatcherGroup...) for detailed
48+
* description off the algorithm used for matching
49+
*/
50+
public static AttributeMatcherGroup attributeGroup(AttributeMatcher... attributes) {
51+
return new AttributeMatcherGroup(Arrays.asList(attributes));
52+
}
53+
}

jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/assertions/MetricAssert.java

Lines changed: 32 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55

66
package io.opentelemetry.contrib.jmxscraper.assertions;
77

8+
import static io.opentelemetry.contrib.jmxscraper.assertions.DataPointAttributes.attributeGroup;
9+
810
import com.google.errorprone.annotations.CanIgnoreReturnValue;
911
import io.opentelemetry.proto.common.v1.KeyValue;
1012
import io.opentelemetry.proto.metrics.v1.Metric;
1113
import io.opentelemetry.proto.metrics.v1.NumberDataPoint;
1214
import java.util.Arrays;
1315
import java.util.Collection;
14-
import java.util.HashMap;
1516
import java.util.HashSet;
1617
import java.util.List;
1718
import java.util.Map;
@@ -21,15 +22,13 @@
2122
import org.assertj.core.api.AbstractAssert;
2223
import org.assertj.core.internal.Integers;
2324
import org.assertj.core.internal.Iterables;
24-
import org.assertj.core.internal.Maps;
2525
import org.assertj.core.internal.Objects;
2626

2727
public class MetricAssert extends AbstractAssert<MetricAssert, Metric> {
2828

2929
private static final Objects objects = Objects.instance();
3030
private static final Iterables iterables = Iterables.instance();
3131
private static final Integers integers = Integers.instance();
32-
private static final Maps maps = Maps.instance();
3332

3433
private boolean strict;
3534

@@ -59,7 +58,7 @@ private void strictCheck(
5958
if (!strict) {
6059
return;
6160
}
62-
String failMsgPrefix = expectedCheckStatus ? "duplicate" : "missing";
61+
String failMsgPrefix = expectedCheckStatus ? "Missing" : "Duplicate";
6362
info.description(
6463
"%s assertion on %s for metric '%s'", failMsgPrefix, metricProperty, actual.getName());
6564
objects.assertEqual(info, actualCheckStatus, expectedCheckStatus);
@@ -122,8 +121,8 @@ private MetricAssert hasSum(boolean monotonic) {
122121
info.description("sum expected for metric '%s'", actual.getName());
123122
objects.assertEqual(info, actual.hasSum(), true);
124123

125-
String prefix = monotonic ? "monotonic" : "non-monotonic";
126-
info.description(prefix + " sum expected for metric '%s'", actual.getName());
124+
String sumType = monotonic ? "monotonic" : "non-monotonic";
125+
info.description("sum for metric '%s' is expected to be %s", actual.getName(), sumType);
127126
objects.assertEqual(info, actual.getSum().getIsMonotonic(), monotonic);
128127
return this;
129128
}
@@ -156,6 +155,11 @@ public MetricAssert isUpDownCounter() {
156155
return this;
157156
}
158157

158+
/**
159+
* Verifies that there is no attribute in any of data points.
160+
*
161+
* @return this
162+
*/
159163
@CanIgnoreReturnValue
160164
public MetricAssert hasDataPointsWithoutAttributes() {
161165
isNotNull();
@@ -195,6 +199,7 @@ private MetricAssert checkDataPoints(Consumer<List<NumberDataPoint>> listConsume
195199
return this;
196200
}
197201

202+
// TODO: To be removed and calls will be replaced with hasDataPointsWithAttributes()
198203
@CanIgnoreReturnValue
199204
public MetricAssert hasTypedDataPoints(Collection<String> types) {
200205
return checkDataPoints(
@@ -229,102 +234,61 @@ private void dataPointsCommonCheck(List<NumberDataPoint> dataPoints) {
229234
}
230235

231236
/**
232-
* Verifies that all data points have all the expected attributes
237+
* Verifies that all metric data points have the same expected one attribute
233238
*
234-
* @param attributes expected attributes
239+
* @param expectedAttribute attribute matcher to validate data points attributes
235240
* @return this
236241
*/
237-
@SafeVarargs
238242
@CanIgnoreReturnValue
239-
public final MetricAssert hasDataPointsAttributes(Map.Entry<String, String>... attributes) {
240-
return checkDataPoints(
241-
dataPoints -> {
242-
dataPointsCommonCheck(dataPoints);
243-
244-
Map<String, String> attributesMap = new HashMap<>();
245-
for (Map.Entry<String, String> attributeEntry : attributes) {
246-
attributesMap.put(attributeEntry.getKey(), attributeEntry.getValue());
247-
}
248-
for (NumberDataPoint dataPoint : dataPoints) {
249-
Map<String, String> dataPointAttributes = toMap(dataPoint.getAttributesList());
250-
251-
// all attributes must match
252-
info.description(
253-
"missing/unexpected data points attributes for metric '%s'", actual.getName());
254-
containsExactly(dataPointAttributes, attributes);
255-
maps.assertContainsAllEntriesOf(info, dataPointAttributes, attributesMap);
256-
}
257-
});
243+
public final MetricAssert hasDataPointsWithOneAttribute(AttributeMatcher expectedAttribute) {
244+
return hasDataPointsWithAttributes(attributeGroup(expectedAttribute));
258245
}
259246

260247
/**
261-
* Verifies that all data points have their attributes match one of the attributes set and that
262-
* all provided attributes sets matched at least once.
248+
* Verifies that every data point attributes is matched exactly by one of the matcher groups
249+
* provided. Also, each matcher group must match at least one data point attributes set. Data
250+
* point attributes are matched by matcher group if each attribute is matched by one matcher and
251+
* each matcher matches one attribute. In other words: number of attributes is the same as number
252+
* of matchers and there is 1:1 matching between them.
263253
*
264-
* @param attributeSets sets of attributes as maps
254+
* @param matcherGroups array of attribute matcher groups
265255
* @return this
266256
*/
267-
@SafeVarargs
268257
@CanIgnoreReturnValue
269-
@SuppressWarnings("varargs") // required to avoid warning
270-
public final MetricAssert hasDataPointsAttributes(Map<String, String>... attributeSets) {
258+
public final MetricAssert hasDataPointsWithAttributes(AttributeMatcherGroup... matcherGroups) {
271259
return checkDataPoints(
272260
dataPoints -> {
273261
dataPointsCommonCheck(dataPoints);
274262

275-
boolean[] matchedSets = new boolean[attributeSets.length];
263+
boolean[] matchedSets = new boolean[matcherGroups.length];
276264

277-
// validate each datapoint attributes match exactly one of the provided attributes set
265+
// validate each datapoint attributes match exactly one of the provided attributes sets
278266
for (NumberDataPoint dataPoint : dataPoints) {
279-
Map<String, String> map = toMap(dataPoint.getAttributesList());
280-
267+
Map<String, String> dataPointAttributes =
268+
dataPoint.getAttributesList().stream()
269+
.collect(
270+
Collectors.toMap(KeyValue::getKey, kv -> kv.getValue().getStringValue()));
281271
int matchCount = 0;
282-
for (int i = 0; i < attributeSets.length; i++) {
283-
if (mapEquals(map, attributeSets[i])) {
272+
for (int i = 0; i < matcherGroups.length; i++) {
273+
if (matcherGroups[i].matches(dataPointAttributes)) {
284274
matchedSets[i] = true;
285275
matchCount++;
286276
}
287277
}
288278

289279
info.description(
290280
"data point attributes '%s' for metric '%s' must match exactly one of the attribute sets '%s'",
291-
map, actual.getName(), Arrays.asList(attributeSets));
281+
dataPointAttributes, actual.getName(), Arrays.asList(matcherGroups));
292282
integers.assertEqual(info, matchCount, 1);
293283
}
294284

295285
// check that all attribute sets matched at least once
296286
for (int i = 0; i < matchedSets.length; i++) {
297287
info.description(
298288
"no data point matched attribute set '%s' for metric '%s'",
299-
attributeSets[i], actual.getName());
289+
matcherGroups[i], actual.getName());
300290
objects.assertEqual(info, matchedSets[i], true);
301291
}
302292
});
303293
}
304-
305-
/**
306-
* Map equality utility
307-
*
308-
* @param m1 first map
309-
* @param m2 second map
310-
* @return true if the maps have exactly the same keys and values
311-
*/
312-
private static boolean mapEquals(Map<String, String> m1, Map<String, String> m2) {
313-
if (m1.size() != m2.size()) {
314-
return false;
315-
}
316-
return m1.entrySet().stream().allMatch(e -> e.getValue().equals(m2.get(e.getKey())));
317-
}
318-
319-
@SafeVarargs
320-
@SuppressWarnings("varargs") // required to avoid warning
321-
private final void containsExactly(
322-
Map<String, String> map, Map.Entry<String, String>... entries) {
323-
maps.assertContainsExactly(info, map, entries);
324-
}
325-
326-
private static Map<String, String> toMap(List<KeyValue> list) {
327-
return list.stream()
328-
.collect(Collectors.toMap(KeyValue::getKey, kv -> kv.getValue().getStringValue()));
329-
}
330294
}

0 commit comments

Comments
 (0)