diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculator.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculator.java index bfc6c52ff5..205c4c646e 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculator.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculator.java @@ -532,6 +532,17 @@ private Truthness computeHeuristic(NotInOperation operation, Object document) } } + /** + * Computes the heuristic score for a {"f",{"$all": [v1, ..., vn] }} query. + * The condition holds when the array held by "f" contains every one of the expected values, + * so the score is an AND aggregation over the expected values, each of which is scored by an + * OR aggregation over the elements of the array. Note the direction: extra elements in the + * document are irrelevant, whereas a missing expected value makes the condition false. + * + * @param operation the {"f",{"$all": [...]}} query encapsulated as an AllOperation + * @param document the BSON document to evaluate the heuristic score against + * @return a Truthness object representing the distance of the document from meeting the condition + */ private Truthness computeHeuristic(AllOperation operation, Object document) { requireNonNullQueryAndDocument(operation, document); @@ -550,10 +561,10 @@ private Truthness computeHeuristic(AllOperation operation, Object document) { if (actualValuesList.isEmpty()) { return C_FALSE; } else { - Truthness res = buildAndAggregationTruthness(actualValuesList + Truthness res = buildAndAggregationTruthness(expectedValues .stream() - .map(actualValuesListElement -> - computeHeuristic(actualValuesListElement, expectedValues)) + .map(expectedValue -> + computeHeuristicForContainedValue(expectedValue, actualValuesList)) .toArray(Truthness[]::new)); return buildSafeScaledTruthness(res); } @@ -561,6 +572,23 @@ private Truthness computeHeuristic(AllOperation operation, Object document) { } } + /** + * Computes the heuristic score for the presence of a single expected value inside the array + * held by the queried field, ie an OR aggregation over the elements of that array. + * + * @param expectedValue a value that a {"f",{"$all": [...]}} query requires to be present + * @param actualValues the non-empty array held by the field "f" in the document + * @return a Truthness object representing how close the array is to containing the expected value + */ + private Truthness computeHeuristicForContainedValue(Object expectedValue, List actualValues) { + Objects.requireNonNull(actualValues); + + return buildOrAggregationTruthness(actualValues.stream() + .map(actualValue -> computeHeuristicComparisonNullableValues(expectedValue, actualValue, + SqlExpressionEvaluator.ComparisonOperatorType.EQUALS_TO)) + .toArray(Truthness[]::new)); + } + private static Truthness buildSafeScaledTruthness(Truthness truthness) { return buildSafeScaledTruthness(truthness.getOfTrue()); } diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculatorTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculatorTest.java index 50675c235e..b330d2c219 100644 --- a/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculatorTest.java +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculatorTest.java @@ -273,6 +273,40 @@ public void testAllBothActualAndExpectedListsAreEmpty() { assertTrue(calculator.computeHeuristicDocument(convertToDocument(allQuery), document).isFalse()); } + @Test + public void testAllMatchesWhenDocumentArrayHasExtraElements() { + // $all only requires the expected values to be present; extra elements are irrelevant + Document doc = new Document().append("employees", new ArrayList<>(Arrays.asList(1, 5, 6))); + Bson allQuery = Filters.all("employees", new ArrayList<>(Arrays.asList(1, 5))); + MongoHeuristicsCalculator calculator = new MongoHeuristicsCalculator(); + assertTrue(calculator.computeHeuristicDocument(convertToDocument(allQuery), doc).isTrue()); + } + + @Test + public void testAllDoesNotMatchWhenAnExpectedValueIsMissing() { + // the array contains 1, but not 2 nor 3, so the condition does not hold + Document doc = new Document().append("employees", new ArrayList<>(Arrays.asList(1))); + Bson allQuery = Filters.all("employees", new ArrayList<>(Arrays.asList(1, 2, 3))); + MongoHeuristicsCalculator calculator = new MongoHeuristicsCalculator(); + assertTrue(calculator.computeHeuristicDocument(convertToDocument(allQuery), doc).isFalse()); + } + + @Test + public void testAllGivesBetterScoreWhenFewerExpectedValuesAreMissing() { + Bson allQuery = Filters.all("employees", new ArrayList<>(Arrays.asList(1, 2, 3))); + Document closer = new Document().append("employees", new ArrayList<>(Arrays.asList(1, 2))); + Document farther = new Document().append("employees", new ArrayList<>(Arrays.asList(1))); + + MongoHeuristicsCalculator calculator = new MongoHeuristicsCalculator(); + Truthness closerTruthness = calculator.computeHeuristicDocument(convertToDocument(allQuery), closer); + Truthness fartherTruthness = calculator.computeHeuristicDocument(convertToDocument(allQuery), farther); + + assertTrue(closerTruthness.isFalse()); + assertTrue(fartherTruthness.isFalse()); + assertTrue(closerTruthness.getOfTrue() > fartherTruthness.getOfTrue(), + "a document missing fewer expected values must be scored closer to true"); + } + @Test public void testSize() {