|
47 | 47 | import org.elasticsearch.cluster.block.ClusterBlockException;
|
48 | 48 | import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
|
49 | 49 | import org.elasticsearch.common.Nullable;
|
50 |
| -import org.elasticsearch.common.bytes.BytesArray; |
51 | 50 | import org.elasticsearch.common.bytes.BytesReference;
|
52 | 51 | import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
53 | 52 | import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
|
|
56 | 55 | import org.elasticsearch.common.io.stream.StreamOutput;
|
57 | 56 | import org.elasticsearch.common.io.stream.Streamable;
|
58 | 57 | import org.elasticsearch.common.settings.Settings;
|
| 58 | +import org.elasticsearch.common.xcontent.ToXContent; |
| 59 | +import org.elasticsearch.common.xcontent.XContentBuilder; |
59 | 60 | import org.elasticsearch.common.xcontent.XContentParser;
|
60 | 61 | import org.elasticsearch.common.xcontent.XContentType;
|
61 | 62 | import org.elasticsearch.rest.RestStatus;
|
|
76 | 77 | import java.util.ArrayList;
|
77 | 78 | import java.util.Arrays;
|
78 | 79 | import java.util.HashSet;
|
| 80 | +import java.util.Iterator; |
79 | 81 | import java.util.List;
|
80 | 82 | import java.util.Locale;
|
81 | 83 | import java.util.Map;
|
@@ -772,60 +774,74 @@ public static void assertDirectoryExists(Path dir) {
|
772 | 774 | }
|
773 | 775 |
|
774 | 776 | /**
|
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. |
777 | 781 | */
|
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 { |
779 | 784 | //we tried comparing byte per byte, but that didn't fly for a couple of reasons:
|
780 | 785 | //1) whenever anything goes through a map while parsing, ordering is not preserved, which is perfectly ok
|
781 | 786 | //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. |
782 | 788 | try (XContentParser actualParser = xContentType.xContent().createParser(actual)) {
|
783 | 789 | Map<String, Object> actualMap = actualParser.map();
|
784 |
| - replaceBytesArrays(actualMap); |
785 | 790 | try (XContentParser expectedParser = xContentType.xContent().createParser(expected)) {
|
786 | 791 | Map<String, Object> expectedMap = expectedParser.map();
|
787 |
| - replaceBytesArrays(expectedMap); |
788 |
| - assertEquals(expectedMap, actualMap); |
| 792 | + assertMapEquals(expectedMap, actualMap); |
789 | 793 | }
|
790 | 794 | }
|
791 | 795 | }
|
792 | 796 |
|
793 | 797 | /**
|
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[]) |
797 | 799 | */
|
798 | 800 | @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); |
809 | 811 | }
|
810 | 812 | }
|
811 | 813 | }
|
812 | 814 |
|
813 | 815 | /**
|
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[]) |
817 | 817 | */
|
818 | 818 | @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); |
829 | 845 | }
|
830 | 846 | }
|
831 | 847 | }
|
0 commit comments