|
17 | 17 | import java.util.List;
|
18 | 18 | import java.util.Objects;
|
19 | 19 | import java.util.Optional;
|
20 |
| -import java.util.Set; |
21 | 20 | import java.util.function.Function;
|
22 |
| -import java.util.stream.Collectors; |
| 21 | +import java.util.function.Supplier; |
23 | 22 |
|
24 | 23 | import static org.elasticsearch.datageneration.matchers.Messages.formatErrorMessage;
|
25 | 24 | import static org.elasticsearch.datageneration.matchers.Messages.prettyPrintCollections;
|
26 | 25 |
|
27 | 26 | class DynamicFieldMatcher {
|
| 27 | + private static final double FLOAT_ERROR_MARGIN = 1e-8; |
28 | 28 | private final XContentBuilder actualMappings;
|
29 | 29 | private final Settings.Builder actualSettings;
|
30 | 30 | private final XContentBuilder expectedMappings;
|
@@ -62,31 +62,51 @@ public MatchResult match(List<Object> actual, List<Object> expected) {
|
62 | 62 |
|
63 | 63 | var normalizedActual = normalizeDoubles(actual);
|
64 | 64 | var normalizedExpected = normalizeDoubles(expected);
|
| 65 | + Supplier<MatchResult> noMatchSupplier = () -> MatchResult.noMatch( |
| 66 | + formatErrorMessage( |
| 67 | + actualMappings, |
| 68 | + actualSettings, |
| 69 | + expectedMappings, |
| 70 | + expectedSettings, |
| 71 | + "Values of dynamically mapped field containing double values don't match after normalization, normalized " |
| 72 | + + prettyPrintCollections(normalizedActual, normalizedExpected) |
| 73 | + ) |
| 74 | + ); |
65 | 75 |
|
66 |
| - return normalizedActual.equals(normalizedExpected) |
67 |
| - ? MatchResult.match() |
68 |
| - : MatchResult.noMatch( |
69 |
| - formatErrorMessage( |
70 |
| - actualMappings, |
71 |
| - actualSettings, |
72 |
| - expectedMappings, |
73 |
| - expectedSettings, |
74 |
| - "Values of dynamically mapped field containing double values don't match after normalization, normalized " |
75 |
| - + prettyPrintCollections(normalizedActual, normalizedExpected) |
76 |
| - ) |
77 |
| - ); |
| 76 | + if (normalizedActual.size() != normalizedExpected.size()) { |
| 77 | + return noMatchSupplier.get(); |
| 78 | + } |
| 79 | + |
| 80 | + for (int i = 0; i < normalizedActual.size(); i++) { |
| 81 | + if (floatEquals(normalizedActual.get(i), normalizedExpected.get(i)) == false) { |
| 82 | + return noMatchSupplier.get(); |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + return MatchResult.match(); |
78 | 87 | }
|
79 | 88 |
|
80 | 89 | return matchWithGenericMatcher(actual, expected);
|
81 | 90 | }
|
82 | 91 |
|
83 |
| - private static Set<Float> normalizeDoubles(List<Object> values) { |
| 92 | + /** |
| 93 | + * We make the normalisation of double values stricter than {@link SourceTransforms#normalizeValues} to facilitate the equality of the |
| 94 | + * values within a margin of error. Synthetic source does support duplicate values and preserves the order, but it loses some accuracy, |
| 95 | + * this is why the margin of error is very important. In the future, we can make {@link SourceTransforms#normalizeValues} also stricter. |
| 96 | + */ |
| 97 | + private static List<Float> normalizeDoubles(List<Object> values) { |
84 | 98 | if (values == null) {
|
85 |
| - return Set.of(); |
| 99 | + return List.of(); |
86 | 100 | }
|
87 | 101 |
|
88 | 102 | Function<Object, Float> toFloat = (o) -> o instanceof Number n ? n.floatValue() : Float.parseFloat((String) o);
|
89 |
| - return values.stream().filter(Objects::nonNull).map(toFloat).collect(Collectors.toSet()); |
| 103 | + |
| 104 | + // We skip nulls because they trip the pretty print collections. |
| 105 | + return values.stream().filter(Objects::nonNull).map(toFloat).toList(); |
| 106 | + } |
| 107 | + |
| 108 | + private static boolean floatEquals(Float actual, Float expected) { |
| 109 | + return Math.abs(actual - expected) < FLOAT_ERROR_MARGIN; |
90 | 110 | }
|
91 | 111 |
|
92 | 112 | private MatchResult matchWithGenericMatcher(List<Object> actualValues, List<Object> expectedValues) {
|
|
0 commit comments