Skip to content

Commit 38914f1

Browse files
committed
[TEST] improve ElasticsearchAssertions#assertEquivalent for ToXContent
Rename the method to assertToXContentEquivalent to highlight that it's tailored to ToXContent comparisons. Rather than parsing into a map and replacing byte[] in both those maps, add custom equality assertions that recursively walk maps and lists and call Arrays.equals whenever a byte[] is encountered.
1 parent 04d929f commit 38914f1

File tree

4 files changed

+55
-39
lines changed

4 files changed

+55
-39
lines changed

core/src/test/java/org/elasticsearch/action/get/GetResponseTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
import static org.elasticsearch.index.get.GetResultTests.mutateGetResult;
3838
import static org.elasticsearch.index.get.GetResultTests.randomGetResult;
3939
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
40-
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertEquivalent;
40+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
4141

4242
public class GetResponseTests extends ESTestCase {
4343

@@ -56,7 +56,7 @@ public void testToAndFromXContent() throws Exception {
5656
assertEquals(expectedGetResponse, parsedGetResponse);
5757
//print the parsed object out and test that the output is the same as the original output
5858
BytesReference finalBytes = toXContent(parsedGetResponse, xContentType, false);
59-
assertEquivalent(originalBytes, finalBytes, xContentType);
59+
assertToXContentEquivalent(originalBytes, finalBytes, xContentType);
6060
//check that the source stays unchanged, no shuffling of keys nor anything like that
6161
assertEquals(expectedGetResponse.getSourceAsString(), parsedGetResponse.getSourceAsString());
6262

core/src/test/java/org/elasticsearch/index/get/GetFieldTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
4141
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
42-
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertEquivalent;
42+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
4343

4444
public class GetFieldTests extends ESTestCase {
4545

@@ -72,7 +72,7 @@ public void testToAndFromXContent() throws Exception {
7272
}
7373
assertEquals(expectedGetField, parsedGetField);
7474
BytesReference finalBytes = toXContent(parsedGetField, xContentType, true);
75-
assertEquivalent(originalBytes, finalBytes, xContentType);
75+
assertToXContentEquivalent(originalBytes, finalBytes, xContentType);
7676
}
7777

7878
private static GetField copyGetField(GetField getField) {

core/src/test/java/org/elasticsearch/index/get/GetResultTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
4040
import static org.elasticsearch.index.get.GetFieldTests.randomGetField;
4141
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
42-
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertEquivalent;
42+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
4343

4444
public class GetResultTests extends ESTestCase {
4545

@@ -58,7 +58,7 @@ public void testToAndFromXContent() throws Exception {
5858
assertEquals(expectedGetResult, parsedGetResult);
5959
//print the parsed object out and test that the output is the same as the original output
6060
BytesReference finalBytes = toXContent(parsedGetResult, xContentType, false);
61-
assertEquivalent(originalBytes, finalBytes, xContentType);
61+
assertToXContentEquivalent(originalBytes, finalBytes, xContentType);
6262
//check that the source stays unchanged, no shuffling of keys nor anything like that
6363
assertEquals(expectedGetResult.sourceAsString(), parsedGetResult.sourceAsString());
6464
}

test/framework/src/main/java/org/elasticsearch/test/hamcrest/ElasticsearchAssertions.java

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import org.elasticsearch.cluster.block.ClusterBlockException;
4848
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
4949
import org.elasticsearch.common.Nullable;
50-
import org.elasticsearch.common.bytes.BytesArray;
5150
import org.elasticsearch.common.bytes.BytesReference;
5251
import org.elasticsearch.common.io.stream.BytesStreamOutput;
5352
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
@@ -56,6 +55,8 @@
5655
import org.elasticsearch.common.io.stream.StreamOutput;
5756
import org.elasticsearch.common.io.stream.Streamable;
5857
import org.elasticsearch.common.settings.Settings;
58+
import org.elasticsearch.common.xcontent.ToXContent;
59+
import org.elasticsearch.common.xcontent.XContentBuilder;
5960
import org.elasticsearch.common.xcontent.XContentParser;
6061
import org.elasticsearch.common.xcontent.XContentType;
6162
import org.elasticsearch.rest.RestStatus;
@@ -76,6 +77,7 @@
7677
import java.util.ArrayList;
7778
import java.util.Arrays;
7879
import java.util.HashSet;
80+
import java.util.Iterator;
7981
import java.util.List;
8082
import java.util.Locale;
8183
import java.util.Map;
@@ -772,60 +774,74 @@ public static void assertDirectoryExists(Path dir) {
772774
}
773775

774776
/**
775-
* Asserts that the provided {@link BytesReference}s hold the same content. The comparison is done between the map
776-
* representation of the provided objects.
777+
* Asserts that the provided {@link BytesReference}s created through
778+
* {@link org.elasticsearch.common.xcontent.ToXContent#toXContent(XContentBuilder, ToXContent.Params)} hold the same content.
779+
* The comparison is done by parsing both into a map and comparing those two, so that keys ordering doesn't matter.
780+
* Also binary values (byte[]) are properly compared through arrays comparisons.
777781
*/
778-
public static void assertEquivalent(BytesReference expected, BytesReference actual, XContentType xContentType) throws IOException {
782+
public static void assertToXContentEquivalent(BytesReference expected, BytesReference actual, XContentType xContentType)
783+
throws IOException {
779784
//we tried comparing byte per byte, but that didn't fly for a couple of reasons:
780785
//1) whenever anything goes through a map while parsing, ordering is not preserved, which is perfectly ok
781786
//2) Jackson SMILE parser parses floats as double, which then get printed out as double (with double precision)
787+
//Note that byte[] holding binary values need special treatment as they need to be properly compared item per item.
782788
try (XContentParser actualParser = xContentType.xContent().createParser(actual)) {
783789
Map<String, Object> actualMap = actualParser.map();
784-
replaceBytesArrays(actualMap);
785790
try (XContentParser expectedParser = xContentType.xContent().createParser(expected)) {
786791
Map<String, Object> expectedMap = expectedParser.map();
787-
replaceBytesArrays(expectedMap);
788-
assertEquals(expectedMap, actualMap);
792+
assertMapEquals(expectedMap, actualMap);
789793
}
790794
}
791795
}
792796

793797
/**
794-
* Recursively navigates through the provided map argument and replaces every byte[] with a corresponding BytesArray object holding
795-
* the original byte[]. This helps maps to maps comparisons as arrays need to be compared using Arrays.equals otherwise their
796-
* references are compared, which is what happens in {@link java.util.AbstractMap#equals(Object)}.
798+
* Compares two maps recursively, using arrays comparisons for byte[] through Arrays.equals(byte[], byte[])
797799
*/
798800
@SuppressWarnings("unchecked")
799-
private static void replaceBytesArrays(Map<String, Object> map) {
800-
for (Map.Entry<String, Object> entry : map.entrySet()) {
801-
Object value = entry.getValue();
802-
if (value instanceof byte[]) {
803-
map.put(entry.getKey(), new BytesArray((byte[]) value));
804-
} else if (value instanceof Map) {
805-
replaceBytesArrays((Map<String, Object>) value);
806-
} else if (value instanceof List) {
807-
List<Object> list = (List<Object>) value;
808-
replaceBytesArrays(list);
801+
private static void assertMapEquals(Map<String, Object> expected, Map<String, Object> actual) {
802+
assertEquals(expected.size(), actual.size());
803+
for (Map.Entry<String, Object> expectedEntry : expected.entrySet()) {
804+
String expectedKey = expectedEntry.getKey();
805+
Object expectedValue = expectedEntry.getValue();
806+
if (expectedValue == null) {
807+
assertTrue(actual.get(expectedKey) == null && actual.containsKey(expectedKey));
808+
} else {
809+
Object actualValue = actual.get(expectedKey);
810+
assertObjectEquals(expectedValue, actualValue);
809811
}
810812
}
811813
}
812814

813815
/**
814-
* Recursively navigates through the provided list argument and replaces every byte[] with a corresponding BytesArray object holding
815-
* the original byte[]. This helps maps to maps comparisons as arrays need to be compared using Arrays.equals otherwise their
816-
* references are compared, which is what happens in {@link java.util.AbstractMap#equals(Object)}.
816+
* Compares two lists recursively, but using arrays comparisons for byte[] through Arrays.equals(byte[], byte[])
817817
*/
818818
@SuppressWarnings("unchecked")
819-
private static void replaceBytesArrays(List<Object> list) {
820-
for (int i = 0; i < list.size(); i++) {
821-
Object object = list.get(i);
822-
if (object instanceof byte[]) {
823-
list.set(i, new BytesArray((byte[]) object));
824-
} else if (object instanceof Map) {
825-
replaceBytesArrays((Map<String, Object>) object);
826-
} else if (object instanceof List) {
827-
replaceBytesArrays((List<Object>) object);
828-
}
819+
private static void assertListEquals(List<Object> expected, List<Object> actual) {
820+
assertEquals(expected.size(), actual.size());
821+
Iterator<Object> actualIterator = actual.iterator();
822+
for (Object expectedValue : expected) {
823+
Object actualValue = actualIterator.next();
824+
assertObjectEquals(expectedValue, actualValue);
825+
}
826+
}
827+
828+
/**
829+
* Compares two objects, recursively walking eventual maps and lists encountered, and using arrays comparisons
830+
* for byte[] through Arrays.equals(byte[], byte[])
831+
*/
832+
@SuppressWarnings("unchecked")
833+
private static void assertObjectEquals(Object expected, Object actual) {
834+
if (expected instanceof Map) {
835+
assertThat(actual, instanceOf(Map.class));
836+
assertMapEquals((Map<String, Object>) expected, (Map<String, Object>) actual);
837+
} else if (expected instanceof List) {
838+
assertListEquals((List<Object>) expected, (List<Object>) actual);
839+
} else if (expected instanceof byte[]) {
840+
//byte[] is really a special case for binary values when comparing SMILE and CBOR, arrays of other types
841+
//don't need to be handled. Ordinary arrays get parsed as lists.
842+
assertArrayEquals((byte[]) expected, (byte[]) actual);
843+
} else {
844+
assertEquals(expected, actual);
829845
}
830846
}
831847
}

0 commit comments

Comments
 (0)