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 cc5c57b8d1..069ca69d1b 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt @@ -163,7 +163,14 @@ class FitnessValue( aggregatedFailedWhereQueries.clear() aggregatedFailedWhereQueries.addAll( - databaseExecutions.values.flatMap { a -> a.executionInfo }.map{ b -> b.sqlCommand } + databaseExecutions.values + // Only the commands whose WHERE clause actually failed are relevant. + .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() } ) } 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()) + } +} 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 f8e634b5c8..ba1527e634 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,9 @@ 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.evomaster.core.sql.schema.TableId import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -123,4 +126,78 @@ class FitnessValueTest { assertEquals(3, linesInfo.searchTime) } + @Test + fun testAggregatedFailedWhereQueriesOnlyFromExecutionsWithFailedWhere() { + val fv = FitnessValue(1.0) + + 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) + ) + ) + fv.setDatabaseExecution(0, execution) + fv.aggregateDatabaseData() + + val queries = fv.getViewOfAggregatedFailedWhereQueries() + assertEquals(1, queries.size) + assertTrue(queries.contains("SELECT * FROM foo WHERE id = 1")) + } + }