/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.common.unit; import org.opensearch.OpenSearchParseException; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.test.OpenSearchTestCase; import java.io.IOException; import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.sameInstance; public class FuzzinessTests extends OpenSearchTestCase { public void testNumerics() { String[] options = new String[] { "1.0", "1", "1.000000" }; assertThat(Fuzziness.build(randomFrom(options)).asFloat(), equalTo(1f)); } public void testParseFromXContent() throws IOException { final int iters = randomIntBetween(10, 50); for (int i = 0; i < iters; i++) { { float floatValue = randomFloat(); XContentBuilder json = jsonBuilder().startObject().field(Fuzziness.X_FIELD_NAME, floatValue).endObject(); try (XContentParser parser = createParser(json)) { assertThat(parser.nextToken(), equalTo(XContentParser.Token.START_OBJECT)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.FIELD_NAME)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.VALUE_NUMBER)); Fuzziness fuzziness = Fuzziness.parse(parser); assertThat(fuzziness.asFloat(), equalTo(floatValue)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.END_OBJECT)); } } { Integer intValue = frequently() ? randomIntBetween(0, 2) : randomIntBetween(0, 100); Float floatRep = randomFloat(); Number value = intValue; if (randomBoolean()) { value = Float.valueOf(floatRep += intValue); } XContentBuilder json = jsonBuilder().startObject() .field(Fuzziness.X_FIELD_NAME, randomBoolean() ? value.toString() : value) .endObject(); try (XContentParser parser = createParser(json)) { assertThat(parser.nextToken(), equalTo(XContentParser.Token.START_OBJECT)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.FIELD_NAME)); assertThat( parser.nextToken(), anyOf(equalTo(XContentParser.Token.VALUE_NUMBER), equalTo(XContentParser.Token.VALUE_STRING)) ); Fuzziness fuzziness = Fuzziness.parse(parser); if (value.intValue() >= 1) { assertThat(fuzziness.asDistance(), equalTo(Math.min(2, value.intValue()))); } assertThat(parser.nextToken(), equalTo(XContentParser.Token.END_OBJECT)); if (intValue.equals(value)) { switch (intValue) { case 1: assertThat(fuzziness, sameInstance(Fuzziness.ONE)); break; case 2: assertThat(fuzziness, sameInstance(Fuzziness.TWO)); break; case 0: assertThat(fuzziness, sameInstance(Fuzziness.ZERO)); break; default: break; } } } } { XContentBuilder json; boolean isDefaultAutoFuzzinessTested = randomBoolean(); Fuzziness expectedFuzziness = Fuzziness.AUTO; if (isDefaultAutoFuzzinessTested) { json = Fuzziness.AUTO.toXContent(jsonBuilder().startObject(), null).endObject(); } else { StringBuilder auto = new StringBuilder(); auto = randomBoolean() ? auto.append("AUTO") : auto.append("auto"); if (randomBoolean()) { int lowDistance = randomIntBetween(1, 3); int highDistance = randomIntBetween(4, 10); auto.append(":").append(lowDistance).append(",").append(highDistance); expectedFuzziness = Fuzziness.build(auto.toString()); } json = expectedFuzziness.toXContent(jsonBuilder().startObject(), null).endObject(); } try (XContentParser parser = createParser(json)) { assertThat(parser.nextToken(), equalTo(XContentParser.Token.START_OBJECT)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.FIELD_NAME)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.VALUE_STRING)); Fuzziness fuzziness = Fuzziness.parse(parser); if (isDefaultAutoFuzzinessTested) { assertThat(fuzziness, sameInstance(expectedFuzziness)); } else { assertEquals(expectedFuzziness, fuzziness); } assertThat(parser.nextToken(), equalTo(XContentParser.Token.END_OBJECT)); } } } } public void testFuzzinessValidationWithStrings() throws IOException { String[] invalidStrings = new String[] { "+++", "asdfghjkl", "2k23", "-1.0", "-100" }; XContentBuilder json = jsonBuilder().startObject().field(Fuzziness.X_FIELD_NAME, randomFrom(invalidStrings)).endObject(); try (XContentParser parser = createParser(json)) { assertThat(parser.nextToken(), equalTo(XContentParser.Token.START_OBJECT)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.FIELD_NAME)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.VALUE_STRING)); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> Fuzziness.parse(parser)); assertTrue(e.getMessage().startsWith("Invalid fuzziness value:")); } json = jsonBuilder().startObject().field(Fuzziness.X_FIELD_NAME, "AUTO:").endObject(); try (XContentParser parser = createParser(json)) { assertThat(parser.nextToken(), equalTo(XContentParser.Token.START_OBJECT)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.FIELD_NAME)); assertThat(parser.nextToken(), equalTo(XContentParser.Token.VALUE_STRING)); OpenSearchParseException e = expectThrows(OpenSearchParseException.class, () -> Fuzziness.parse(parser)); assertTrue(e.getMessage().startsWith("failed to find low and high distance values")); } } public void testAuto() { assertThat(Fuzziness.AUTO.asFloat(), equalTo(1f)); } public void testAsDistance() { final int iters = randomIntBetween(10, 50); for (int i = 0; i < iters; i++) { Integer integer = Integer.valueOf(randomIntBetween(0, 10)); String value = "" + (randomBoolean() ? integer.intValue() : integer.floatValue()); assertThat(Fuzziness.build(value).asDistance(), equalTo(Math.min(2, integer.intValue()))); } } public void testSerialization() throws IOException { Fuzziness fuzziness = Fuzziness.AUTO; Fuzziness deserializedFuzziness = doSerializeRoundtrip(fuzziness); assertEquals(fuzziness, deserializedFuzziness); fuzziness = Fuzziness.fromEdits(randomIntBetween(0, 2)); deserializedFuzziness = doSerializeRoundtrip(fuzziness); assertEquals(fuzziness, deserializedFuzziness); } public void testSerializationDefaultAuto() throws IOException { Fuzziness fuzziness = Fuzziness.AUTO; Fuzziness deserializedFuzziness = doSerializeRoundtrip(fuzziness); assertEquals(fuzziness, deserializedFuzziness); assertEquals(fuzziness.asFloat(), deserializedFuzziness.asFloat(), 0f); } public void testSerializationCustomAuto() throws IOException { Fuzziness original = Fuzziness.build("AUTO:4,7"); Fuzziness deserializedFuzziness = doSerializeRoundtrip(original); assertNotSame(original, deserializedFuzziness); assertEquals(original, deserializedFuzziness); assertEquals(original.asString(), deserializedFuzziness.asString()); original = Fuzziness.customAuto(4, 7); deserializedFuzziness = doSerializeRoundtrip(original); assertNotSame(original, deserializedFuzziness); assertEquals(original, deserializedFuzziness); assertEquals(original.asString(), deserializedFuzziness.asString()); } private static Fuzziness doSerializeRoundtrip(Fuzziness in) throws IOException { BytesStreamOutput output = new BytesStreamOutput(); in.writeTo(output); StreamInput streamInput = output.bytes().streamInput(); return new Fuzziness(streamInput); } public void testAsDistanceString() { Fuzziness fuzziness = Fuzziness.build("0"); assertEquals(0, fuzziness.asDistance(randomAlphaOfLengthBetween(0, 10))); fuzziness = Fuzziness.build("1"); assertEquals(1, fuzziness.asDistance(randomAlphaOfLengthBetween(0, 10))); fuzziness = Fuzziness.build("2"); assertEquals(2, fuzziness.asDistance(randomAlphaOfLengthBetween(0, 10))); fuzziness = Fuzziness.build("AUTO"); assertEquals(0, fuzziness.asDistance("")); assertEquals(0, fuzziness.asDistance("ab")); assertEquals(1, fuzziness.asDistance("abc")); assertEquals(1, fuzziness.asDistance("abcde")); assertEquals(2, fuzziness.asDistance("abcdef")); fuzziness = Fuzziness.build("AUTO:5,7"); assertEquals(0, fuzziness.asDistance("")); assertEquals(0, fuzziness.asDistance("abcd")); assertEquals(1, fuzziness.asDistance("abcde")); assertEquals(1, fuzziness.asDistance("abcdef")); assertEquals(2, fuzziness.asDistance("abcdefg")); fuzziness = Fuzziness.customAuto(5, 7); assertEquals(0, fuzziness.asDistance("")); assertEquals(0, fuzziness.asDistance("abcd")); assertEquals(1, fuzziness.asDistance("abcde")); assertEquals(1, fuzziness.asDistance("abcdef")); assertEquals(2, fuzziness.asDistance("abcdefg")); } }