From 7e195b087ad3de525b2e4df3bb09d82b01b0c0ca Mon Sep 17 00:00:00 2001 From: Agustina Aldasoro Date: Fri, 20 Mar 2026 13:48:35 -0300 Subject: [PATCH 1/4] Filter blank failedWhere Queries from mutator We were sending empty strings as a valid query --- .../evomaster/core/problem/api/service/ApiWsStructureMutator.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt index e2085301bf..fefc12a420 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt @@ -277,6 +277,7 @@ abstract class ApiWsStructureMutator : StructureMutator() { val oldSqlActions = mutableListOf().plus(ind.seeInitializingActions()) val failedWhereQueries = evaluatedIndividual.fitness.getViewOfAggregatedFailedWhereQueries() + .filter { it.isNotBlank() } val addedSqlInsertions = handleFailedWhereSQL(ind, fw, failedWhereQueries, mutatedGenes, sampler) ind.repairInitializationActions(randomness) From 063753703d60469da93149e448f8918aab8aa59e Mon Sep 17 00:00:00 2001 From: Agustina Aldasoro Date: Thu, 9 Apr 2026 09:03:59 -0300 Subject: [PATCH 2/4] Add tests --- .../evomaster/core/search/FitnessValueTest.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/core/src/test/kotlin/org/evomaster/core/search/FitnessValueTest.kt b/core/src/test/kotlin/org/evomaster/core/search/FitnessValueTest.kt index b7ce647f0f..d518524071 100644 --- a/core/src/test/kotlin/org/evomaster/core/search/FitnessValueTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/search/FitnessValueTest.kt @@ -5,6 +5,8 @@ import org.evomaster.client.java.controller.api.dto.BootTimeInfoDto import org.evomaster.client.java.controller.api.dto.TargetInfoDto import org.evomaster.client.java.instrumentation.shared.ObjectiveNaming import org.evomaster.core.search.service.IdMapper +import org.evomaster.core.sql.DatabaseExecution +import org.evomaster.core.sql.SqlExecutionInfo import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -123,4 +125,37 @@ class FitnessValueTest { assertEquals(3, linesInfo.searchTime) } + @Test + fun testAggregatedFailedWhereQueriesExcludesBlankEntries() { + val fv = FitnessValue(1.0) + + val execution = DatabaseExecution( + queriedData = emptyMap(), + updatedData = emptyMap(), + insertedData = emptyMap(), + failedWhere = emptyMap(), + deletedData = emptyList(), + numberOfSqlCommands = 3, + sqlParseFailureCount = 0, + executionInfo = listOf( + SqlExecutionInfo("SELECT * FROM foo WHERE id = 1", false, 10L), + SqlExecutionInfo("", false, 5L), + SqlExecutionInfo(" ", false, 5L), + SqlExecutionInfo("SELECT * FROM bar WHERE name = 'x'", false, 8L) + ) + ) + fv.setDatabaseExecution(0, execution) + fv.aggregateDatabaseData() + + val allQueries = fv.getViewOfAggregatedFailedWhereQueries() + assertEquals(4, allQueries.size) + + // Simulate the filter applied in ApiWsStructureMutator before sending queries to the solver + val nonBlankQueries = allQueries.filter { it.isNotBlank() } + assertEquals(2, nonBlankQueries.size) + assertTrue(nonBlankQueries.none { it.isBlank() }) + assertTrue(nonBlankQueries.contains("SELECT * FROM foo WHERE id = 1")) + assertTrue(nonBlankQueries.contains("SELECT * FROM bar WHERE name = 'x'")) + } + } \ No newline at end of file From b5f0ffa355f495c653e5264d624cc7a12b888681 Mon Sep 17 00:00:00 2001 From: Agustina Aldasoro Date: Wed, 15 Apr 2026 20:45:00 -0300 Subject: [PATCH 3/4] Move the fix to the correct function --- .../api/service/ApiWsStructureMutator.kt | 1 - .../org/evomaster/core/search/FitnessValue.kt | 10 ++- .../evomaster/core/search/FitnessValueTest.kt | 68 +++++++++++++++---- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt index fefc12a420..e2085301bf 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt @@ -277,7 +277,6 @@ abstract class ApiWsStructureMutator : StructureMutator() { val oldSqlActions = mutableListOf().plus(ind.seeInitializingActions()) val failedWhereQueries = evaluatedIndividual.fitness.getViewOfAggregatedFailedWhereQueries() - .filter { it.isNotBlank() } val addedSqlInsertions = handleFailedWhereSQL(ind, fw, failedWhereQueries, mutatedGenes, sampler) ind.repairInitializationActions(randomness) diff --git a/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt b/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt index b3c25961f9..42f773ccf5 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt @@ -152,7 +152,15 @@ class FitnessValue( aggregatedFailedWhereQueries.clear() aggregatedFailedWhereQueries.addAll( - databaseExecutions.values.flatMap { a -> a.executionInfo }.map{ b -> b.sqlCommand } + databaseExecutions.values + // Only executions that actually had failing WHERE clauses are relevant; + // collecting from all executions would include unrelated INSERT/UPDATE/DELETE/etc. + .filter { it.failedWhere.isNotEmpty() } + .flatMap { it.executionInfo } + .map { it.sqlCommand } + // JSQLParser >= 4.9 may produce empty-string SQL commands (it used to throw, now + // parses "" to null). Guard here at the source rather than at every call site. + .filter { it.isNotBlank() } ) } fun aggregateMongoDatabaseData(){ diff --git a/core/src/test/kotlin/org/evomaster/core/search/FitnessValueTest.kt b/core/src/test/kotlin/org/evomaster/core/search/FitnessValueTest.kt index d518524071..483974dbb1 100644 --- a/core/src/test/kotlin/org/evomaster/core/search/FitnessValueTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/search/FitnessValueTest.kt @@ -7,6 +7,7 @@ import org.evomaster.client.java.instrumentation.shared.ObjectiveNaming import org.evomaster.core.search.service.IdMapper import org.evomaster.core.sql.DatabaseExecution import org.evomaster.core.sql.SqlExecutionInfo +import org.evomaster.core.sql.schema.TableId import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -126,36 +127,77 @@ class FitnessValueTest { } @Test - fun testAggregatedFailedWhereQueriesExcludesBlankEntries() { + fun testAggregatedFailedWhereQueriesOnlyFromExecutionsWithFailedWhere() { val fv = FitnessValue(1.0) - val execution = DatabaseExecution( + val tableId = TableId("foo") + + // Execution that has a failing WHERE clause — its queries should be collected + val executionWithFailedWhere = DatabaseExecution( + queriedData = emptyMap(), + updatedData = emptyMap(), + insertedData = emptyMap(), + failedWhere = mapOf(tableId to setOf("id")), + deletedData = emptyList(), + numberOfSqlCommands = 1, + sqlParseFailureCount = 0, + executionInfo = listOf( + SqlExecutionInfo("SELECT * FROM foo WHERE id = 1", false, 10L) + ) + ) + + // Execution with no failing WHERE clause — its queries must NOT be collected, + // even though it has executionInfo entries + val executionWithoutFailedWhere = DatabaseExecution( queriedData = emptyMap(), updatedData = emptyMap(), insertedData = emptyMap(), failedWhere = emptyMap(), deletedData = emptyList(), + numberOfSqlCommands = 1, + sqlParseFailureCount = 0, + executionInfo = listOf( + SqlExecutionInfo("INSERT INTO bar VALUES (1)", false, 5L) + ) + ) + + fv.setDatabaseExecution(0, executionWithFailedWhere) + fv.setDatabaseExecution(1, executionWithoutFailedWhere) + fv.aggregateDatabaseData() + + val queries = fv.getViewOfAggregatedFailedWhereQueries() + assertEquals(1, queries.size) + assertTrue(queries.contains("SELECT * FROM foo WHERE id = 1")) + } + + @Test + fun testAggregatedFailedWhereQueriesExcludesBlankSqlCommands() { + val fv = FitnessValue(1.0) + + val tableId = TableId("foo") + + // JSQLParser >= 4.9 can produce empty-string SQL commands; they must be filtered out + // at the source in aggregateDatabaseData() rather than at every call site + val execution = DatabaseExecution( + queriedData = emptyMap(), + updatedData = emptyMap(), + insertedData = emptyMap(), + failedWhere = mapOf(tableId to setOf("id")), + deletedData = emptyList(), numberOfSqlCommands = 3, sqlParseFailureCount = 0, executionInfo = listOf( SqlExecutionInfo("SELECT * FROM foo WHERE id = 1", false, 10L), SqlExecutionInfo("", false, 5L), - SqlExecutionInfo(" ", false, 5L), - SqlExecutionInfo("SELECT * FROM bar WHERE name = 'x'", false, 8L) + SqlExecutionInfo(" ", false, 5L) ) ) fv.setDatabaseExecution(0, execution) fv.aggregateDatabaseData() - val allQueries = fv.getViewOfAggregatedFailedWhereQueries() - assertEquals(4, allQueries.size) - - // Simulate the filter applied in ApiWsStructureMutator before sending queries to the solver - val nonBlankQueries = allQueries.filter { it.isNotBlank() } - assertEquals(2, nonBlankQueries.size) - assertTrue(nonBlankQueries.none { it.isBlank() }) - assertTrue(nonBlankQueries.contains("SELECT * FROM foo WHERE id = 1")) - assertTrue(nonBlankQueries.contains("SELECT * FROM bar WHERE name = 'x'")) + val queries = fv.getViewOfAggregatedFailedWhereQueries() + assertEquals(1, queries.size) + assertTrue(queries.contains("SELECT * FROM foo WHERE id = 1")) } } \ No newline at end of file From 29d7fe7dbadb3db4f4a158aff6f96eb0b9373fcf Mon Sep 17 00:00:00 2001 From: Agustina Aldasoro Date: Thu, 16 Apr 2026 16:20:02 -0300 Subject: [PATCH 4/4] Add integration test --- .../org/evomaster/core/search/FitnessValue.kt | 3 +- .../search/FitnessValueSqlIntegrationTest.kt | 88 +++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 core/src/test/kotlin/org/evomaster/core/search/FitnessValueSqlIntegrationTest.kt diff --git a/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt b/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt index 91a310ed83..069ca69d1b 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt @@ -164,8 +164,7 @@ class FitnessValue( aggregatedFailedWhereQueries.clear() aggregatedFailedWhereQueries.addAll( databaseExecutions.values - // Only executions that actually had failing WHERE clauses are relevant; - // collecting from all executions would include unrelated INSERT/UPDATE/DELETE/etc. + // Only the commands whose WHERE clause actually failed are relevant. .filter { it.failedWhere.isNotEmpty() } .flatMap { it.executionInfo } .map { it.sqlCommand } diff --git a/core/src/test/kotlin/org/evomaster/core/search/FitnessValueSqlIntegrationTest.kt b/core/src/test/kotlin/org/evomaster/core/search/FitnessValueSqlIntegrationTest.kt new file mode 100644 index 0000000000..1dcd9a1307 --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/search/FitnessValueSqlIntegrationTest.kt @@ -0,0 +1,88 @@ +package org.evomaster.core.search + +import org.evomaster.client.java.sql.DbInfoExtractor +import org.evomaster.client.java.sql.SqlScriptRunner +import org.evomaster.client.java.sql.internal.SqlHandler +import org.evomaster.client.java.controller.api.dto.database.execution.SqlExecutionLogDto +import org.evomaster.core.sql.DatabaseExecution +import org.evomaster.core.sql.schema.TableId +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.* +import java.sql.Connection +import java.sql.DriverManager + +class FitnessValueSqlIntegrationTest { + + companion object { + + private lateinit var connection: Connection + + @BeforeAll + @JvmStatic + fun initClass() { + connection = DriverManager.getConnection("jdbc:h2:mem:db_test", "sa", "") + } + + @AfterAll + @JvmStatic + fun clean() { + connection.close() + } + } + + @BeforeEach + fun initTest() { + SqlScriptRunner.execCommand(connection, "DROP ALL OBJECTS;") + SqlScriptRunner.execCommand(connection, """ + CREATE TABLE Person ( + person_id INT PRIMARY KEY, + age INT + ); + """) + } + + private fun runSelect(sqlCommand: String): DatabaseExecution { + val schema = DbInfoExtractor.extract(connection) + val tableIds = schema.tables.map { t -> TableId(t.id.name) }.toSet() + + val handler = SqlHandler(null) + handler.setConnection(connection) + handler.setSchema(schema) + + handler.handle(SqlExecutionLogDto(sqlCommand, false, 0L)) + handler.getSqlDistances(null, true) + + return DatabaseExecution.fromDto(handler.getExecutionDto(), tableIds) + } + + @Test + fun testSelectWithFailedWhereIsCollected() { + // Empty table: WHERE clause will fail (no rows match) + val sqlCommand = "SELECT * FROM Person WHERE age = 30" + + val fv = FitnessValue(1.0) + fv.setDatabaseExecution(0, runSelect(sqlCommand)) + fv.aggregateDatabaseData() + + val queries = fv.getViewOfAggregatedFailedWhereQueries() + assertEquals(1, queries.size) + assertTrue(queries.contains(sqlCommand)) + } + + @Test + fun testSelectWithSuccessfulWhereIsNotCollected() { + // Insert a row so the WHERE clause succeeds + SqlScriptRunner.execCommand(connection, "INSERT INTO Person VALUES (1, 30)") + val sqlCommand = "SELECT * FROM Person WHERE age = 30" + + val fv = FitnessValue(1.0) + fv.setDatabaseExecution(0, runSelect(sqlCommand)) + fv.aggregateDatabaseData() + + val queries = fv.getViewOfAggregatedFailedWhereQueries() + assertTrue(queries.isEmpty()) + } +}