Sfoglia il codice sorgente

Coerce numeric strings in data generation matchers (#133264)

Number mapping types can accept strings which are coerced into the correct number types. To correctly check for equality between numeric types, the NumberMatcher in the data generation tests needs to parse numeric strings.
Parker Timmins 2 mesi fa
parent
commit
a8d1f081f9

+ 0 - 9
muted-tests.yml

@@ -441,15 +441,6 @@ tests:
 - class: org.elasticsearch.xpack.esql.qa.single_node.EsqlSpecIT
   method: test {csv-spec:spatial.ConvertFromStringParseError}
   issue: https://github.com/elastic/elasticsearch/issues/132558
-- class: org.elasticsearch.xpack.logsdb.qa.BulkChallengeRestIT
-  method: testEsqlSource
-  issue: https://github.com/elastic/elasticsearch/issues/132600
-- class: org.elasticsearch.xpack.logsdb.qa.StandardVersusStandardReindexedIntoLogsDbChallengeRestIT
-  method: testEsqlSource
-  issue: https://github.com/elastic/elasticsearch/issues/132601
-- class: org.elasticsearch.xpack.logsdb.qa.StoredSourceLogsDbVersusReindexedLogsDbChallengeRestIT
-  method: testEsqlSource
-  issue: https://github.com/elastic/elasticsearch/issues/132602
 - class: org.elasticsearch.xpack.ml.integration.RevertModelSnapshotIT
   method: testRevertModelSnapshot_DeleteInterveningResults
   issue: https://github.com/elastic/elasticsearch/issues/132349

+ 36 - 0
test/framework/src/main/java/org/elasticsearch/datageneration/matchers/source/FieldSpecificMatcher.java

@@ -13,9 +13,13 @@ import org.apache.lucene.sandbox.document.HalfFloatPoint;
 import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.core.Booleans;
+import org.elasticsearch.datageneration.FieldType;
 import org.elasticsearch.datageneration.matchers.MatchResult;
 import org.elasticsearch.index.mapper.DateFieldMapper;
+import org.elasticsearch.index.mapper.NumberFieldMapper;
 import org.elasticsearch.xcontent.XContentBuilder;
+import org.elasticsearch.xcontent.XContentParserConfiguration;
+import org.elasticsearch.xcontent.XContentType;
 
 import java.math.BigInteger;
 import java.time.Instant;
@@ -353,6 +357,10 @@ interface FieldSpecificMatcher {
     }
 
     class NumberMatcher extends GenericMappingAwareMatcher {
+
+        private final FieldType fieldType;
+        private final NumberFieldMapper.NumberType numberType;
+
         NumberMatcher(
             String fieldType,
             XContentBuilder actualMappings,
@@ -361,6 +369,8 @@ interface FieldSpecificMatcher {
             Settings.Builder expectedSettings
         ) {
             super(fieldType, actualMappings, actualSettings, expectedMappings, expectedSettings);
+            this.fieldType = FieldType.tryParse(fieldType);
+            this.numberType = NumberFieldMapper.NumberType.valueOf(this.fieldType.name());
         }
 
         @Override
@@ -373,6 +383,32 @@ interface FieldSpecificMatcher {
                 return nullValue;
             }
 
+            // Attempt to coerce string values into numbers
+            if (value instanceof String s) {
+                try (var parser = XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, "\"" + s + "\"")) {
+                    parser.nextToken();
+                    return numberType.parse(parser, true);
+                } catch (Exception e) {
+                    // malformed string
+                    return value;
+                }
+            }
+
+            // When a number mapping is coerced, the expected value will come from the above parser and will have the correct java type.
+            // Whereas, if it fits, the actual value will be in an Integer or a Double. To correctly treat expected and actual values as
+            // equal the actual value must be cast to the appropriate type.
+            if (value instanceof Integer v) {
+                return switch (fieldType) {
+                    case LONG -> v.longValue();
+                    case SHORT -> v.shortValue();
+                    case BYTE -> v.byteValue();
+                    default -> value;
+                };
+            }
+            if (value instanceof Double v) {
+                return fieldType == FieldType.FLOAT ? v.floatValue() : value;
+            }
+
             return value;
         }
     }

+ 19 - 0
test/framework/src/test/java/org/elasticsearch/logsdb/datageneration/SourceMatcherTests.java

@@ -141,4 +141,23 @@ public class SourceMatcherTests extends ESTestCase {
         var sut = new SourceMatcher(Map.of(), mapping, Settings.builder(), mapping, Settings.builder(), actual, expected, false);
         assertFalse(sut.match().isMatch());
     }
+
+    public void testCoercedNumberField() throws IOException {
+
+        // Parsing non-ascii digit strings only works for `long` mappings.
+        List<Map<String, Object>> expected = List.of(Map.of("field", List.of("꧕", "123")));
+        List<Map<String, Object>> actual = List.of(Map.of("field", List.of(5, 123)));
+
+        var mapping = XContentBuilder.builder(XContentType.JSON.xContent());
+        mapping.startObject();
+        mapping.startObject("_doc");
+        {
+            mapping.startObject("field").field("type", "long").endObject();
+        }
+        mapping.endObject();
+        mapping.endObject();
+
+        var sut = new SourceMatcher(Map.of(), mapping, Settings.builder(), mapping, Settings.builder(), actual, expected, false);
+        assertTrue(sut.match().isMatch());
+    }
 }