Skip to content
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

Merged
merged 52 commits into from
Dec 6, 2024
Merged

Conversation

robsunday
Copy link
Contributor

@robsunday robsunday commented Dec 4, 2024

New way of performing data point attributes assertions.
AttributeMatcher and AttributeValueMatcher classes provides possibility to verify attributes basing only on attribute name (if we do not know what attribute value should be, like hostname) and basing on name and value.
Few tests are updated. More will come in subsequent PRs.

Part of #1573

@github-actions github-actions bot requested a review from SylvainJuge December 4, 2024 09:55
maps.assertContainsAllEntriesOf(info, dataPointAttributes, attributesMap);
}
});
public final MetricAssert hasDataPointsWithOneAttribute(AttributeMatcher expectedAttribute) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

.hasDataPointsWithAttributes(
attributeSet(
attribute("state", "replication"),
attributeWithAnyValue("region_server")),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] Thanks to new attribute assertion logic I discovered that in original implementation region_server was not checked!

Comment on lines 38 to 40
public String getName() {
return name;
}
Copy link
Contributor

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

Comment on lines 42 to 59
@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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this breaks the usual equals/hashcode contract, which is generally not a good idea as it does not preserve the "logical equality". If I understand we have two use-cases:

  • store it in a map for lookup by attribute name.
  • check if attribute is matching or not.

For the lookup, we should store it in a map with a call to getName as key, and for the attribute matching it would be simpler to have a public boolean matches(String name, String value) and inline the AttributeValueMatcher which is almost only doing a String.equals(...).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe equals/hashcode contract is not broken here sice two equal objects will always have the same hashcode. However I agree it's a bit tricky use of equals/hashcode, mostly to get Set<> equality working out of the box to support verification if specific set of attributes is matched by set of matchers. I'll think about cleaner solution to avoid confusion.

Copy link
Contributor Author

@robsunday robsunday Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactoring is done. So now code is explicitly calling matchesValue method instead of using equals/hashcode tandem behind the scene

public class DataPointAttributes {
private DataPointAttributes() {}

public static AttributeMatcher attribute(String name, String value) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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).

for (NumberDataPoint dataPoint : dataPoints) {
Map<String, String> map = toMap(dataPoint.getAttributesList());

Set<AttributeMatcher> dataPointAttributes = toSet(dataPoint.getAttributesList());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as suggested in the comment in AttributeMatcher this relies on the equals/hashcode implementation here, keeping a map would allow to avoid relying on this. We could probably add an extra check in the toMap implementation to ensure that there aren't any duplicate entries if there is an error in the test code.

Also, here we are using the AttributeMatcher class to carry the attributes key/values which is definitely not a "matcher".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improved

int matchCount = 0;
for (int i = 0; i < attributeSets.length; i++) {
if (mapEquals(map, attributeSets[i])) {
if (attributeSets[i].equals(dataPointAttributes)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here the call to equals should be replaced by calling the AttributeMatcher.matches(String name, String value) method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not so simple, it is a comparison of collections.

Comment on lines 91 to 92
attributeSet(
attributeWithAnyValue("context"), attributeWithAnyValue("id"))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] in this case the values are random so we could not assert them with exact values.

@github-actions github-actions bot requested a review from SylvainJuge December 4, 2024 15:15
Copy link
Contributor

@breedx-splk breedx-splk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems good to me. It might be nice to try and keep the terminology a bit closer to what exists in the core sdk-testing repo -- like hasAttributesSatisfyingExactly() and hasAttributesSatisfying() etc. In fact, I also wonder if it doesn't make sense to contribute some of this up there and then leverage it here?

Comment on lines 21 to 22
this.attributeName = attributeName;
this.attributeValue = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constructors should call each other:

Suggested change
this.attributeName = attributeName;
this.attributeValue = null;
this(attributeName, null);

Comment on lines 77 to 82
boolean matchesValue(String value) {
if ((attributeValue == null) || (value == null)) {
return true;
}
return Objects.equals(attributeValue, value);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmm, this looks like this would be true:

new AttributeMatcher('foo', 'bar').matchesValue(null)

Is that right? Intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! This is leftover from the previous approach that I missed to update.

@SylvainJuge
Copy link
Contributor

I also wonder if it doesn't make sense to contribute some of this up there and then leverage it here?

Definitely yes. I think we can do that when we have completed the "groovy to yaml" migration in this repository and we start to work on (maybe reuse them as-is) the metrics definitions from instrumentation. The nice thing by doing this here first is that it's easier for us to experiment on what is the right approach that fits the existing definitions.

@robsunday robsunday requested a review from breedx-splk December 6, 2024 15:20
@trask trask merged commit cd9c7d2 into open-telemetry:main Dec 6, 2024
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants