Skip to content

Commit c943596

Browse files
committed
PR feedback
1 parent 3ddb68c commit c943596

8 files changed

Lines changed: 231 additions & 353 deletions

File tree

core/pom.xml

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@
1414

1515
<properties>
1616
<skipTests>${skipCore}</skipTests>
17+
<!--
18+
core has the heaviest gene test suite (GeneRandomizedTest: 1001 seeds × ~100 gene types).
19+
On CI runners where algorithm tests complete faster than usual, less GC activity
20+
accumulates before gene tests run, leaving the heap more fragmented and full.
21+
5120m (vs the global 4096m) provides the headroom to survive runner variability.
22+
GitHub Actions ubuntu-latest has 7 GB RAM: 5 GB heap + 1 GB Maven + 1 GB OS = 7 GB.
23+
Overrides the root-pom testHeapXmx to avoid duplicating the surefire/failsafe argLine.
24+
-->
25+
<testHeapXmx>5120m</testHeapXmx>
1726
</properties>
1827

1928
<dependencies>
@@ -33,10 +42,6 @@
3342
<groupId>org.evomaster</groupId>
3443
<artifactId>evomaster-client-java-controller-api</artifactId>
3544
</dependency>
36-
<dependency>
37-
<groupId>org.evomaster</groupId>
38-
<artifactId>evomaster-client-java-sql</artifactId>
39-
</dependency>
4045
<dependency>
4146
<groupId>org.evomaster</groupId>
4247
<artifactId>evomaster-client-java-instrumentation-shared</artifactId>
@@ -477,21 +482,6 @@
477482
<groupId>org.antlr</groupId>
478483
<artifactId>antlr4-maven-plugin</artifactId>
479484
</plugin>
480-
481-
<!--
482-
core has the heaviest gene test suite (GeneRandomizedTest: 1001 seeds × ~100 gene types).
483-
On CI runners where algorithm tests complete faster than usual, less GC activity
484-
accumulates before gene tests run, leaving the heap more fragmented and full.
485-
5120m (vs the global 4096m) provides the headroom to survive runner variability.
486-
GitHub Actions ubuntu-latest has 7 GB RAM: 5 GB heap + 1 GB Maven + 1 GB OS = 7 GB.
487-
-->
488-
<plugin>
489-
<groupId>org.apache.maven.plugins</groupId>
490-
<artifactId>maven-surefire-plugin</artifactId>
491-
<configuration>
492-
<argLine>@{argLine} -ea -Xms1024m -Xmx5120m -Xss4m -Dfile.encoding=UTF-8 -Djdk.attach.allowAttachSelf=true -Duser.language=en -Duser.country=GB ${addOpens}</argLine>
493-
</configuration>
494-
</plugin>
495485
</plugins>
496486
</build>
497487

core/src/main/kotlin/org/evomaster/core/EMConfig.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,14 +1972,6 @@ class EMConfig {
19721972
@DependsOnTrueFor("generateSqlDataWithZ3")
19731973
var collectSqlZ3Stats = false
19741974

1975-
@Experimental
1976-
@Cfg("Measure the correctness of Z3-generated SQL inserts by computing the heuristic " +
1977-
"distance between the original failing WHERE query and the generated INSERT data. " +
1978-
"Distance=0 means the insert satisfies the WHERE; distance>0 means it does not. " +
1979-
"Only meaningful when generateSqlDataWithZ3=true.")
1980-
@DependsOnTrueFor("generateSqlDataWithZ3")
1981-
var measureSqlZ3Correctness = false
1982-
19831975
@Experimental
19841976
@Cfg("Soft timeout, in milliseconds, for each Z3 solver invocation when generating SQL data. " +
19851977
"If a query exceeds it, Z3 returns 'unknown' for that query instead of running unbounded. " +

core/src/main/kotlin/org/evomaster/core/search/service/Statistics.kt

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,6 @@ class Statistics : SearchListener {
104104
private val sqlZ3SeenQueryHashes = mutableSetOf<Int>()
105105
private var sqlZ3UniqueQueriesCount = 0
106106

107-
// Z3-based SQL data generation correctness distance statistics (only when measureSqlZ3Correctness=true)
108-
private var sqlZ3CorrectnessCheckCount = 0
109-
private var sqlZ3CorrectnessZeroDistanceCount = 0
110-
private var sqlZ3CorrectnessNonZeroDistanceCount = 0
111-
private val sqlZ3CorrectnessAvgDistance = IncrementalAverage()
112-
private var sqlZ3CorrectnessEvalFailureCount = 0
113-
114107
// mongo heuristic evaluation statistic
115108
private var mongoHeuristicEvaluationSuccessCount = 0
116109
private var mongoHeuristicEvaluationFailureCount = 0
@@ -308,22 +301,6 @@ class Statistics : SearchListener {
308301
internal fun getSqlZ3CacheHitCount() = sqlZ3CacheHitCount
309302
internal fun getSqlZ3CacheMissCount() = sqlZ3CacheMissCount
310303

311-
fun reportSqlZ3CorrectnessDistance(sqlDistance: Double, evaluationFailure: Boolean) {
312-
sqlZ3CorrectnessCheckCount++
313-
if (evaluationFailure) {
314-
// sqlDistance is a sentinel value (e.g. Double.MAX_VALUE) in this case,
315-
// and must not pollute the average of real distances
316-
sqlZ3CorrectnessEvalFailureCount++
317-
return
318-
}
319-
if (sqlDistance == 0.0) {
320-
sqlZ3CorrectnessZeroDistanceCount++
321-
} else {
322-
sqlZ3CorrectnessNonZeroDistanceCount++
323-
}
324-
sqlZ3CorrectnessAvgDistance.addValue(sqlDistance)
325-
}
326-
327304
fun getMongoHeuristicsEvaluationCount(): Int = mongoHeuristicEvaluationSuccessCount + mongoHeuristicEvaluationFailureCount
328305

329306
fun getSqlHeuristicsEvaluationCount(): Int = sqlHeuristicEvaluationSuccessCount + sqlHeuristicEvaluationFailureCount
@@ -500,15 +477,6 @@ class Statistics : SearchListener {
500477
add(Pair("sqlZ3AvgSmtlibSizeBytes", "%.1f".format(sqlZ3SmtlibSizeBytes.mean)))
501478
}
502479

503-
// correctness distance stats (only emitted when measureSqlZ3Correctness=true)
504-
if (config.measureSqlZ3Correctness) {
505-
add(Pair("sqlZ3CorrectnessChecks", "$sqlZ3CorrectnessCheckCount"))
506-
add(Pair("sqlZ3CorrectnessZeroDistance", "$sqlZ3CorrectnessZeroDistanceCount"))
507-
add(Pair("sqlZ3CorrectnessNonZero", "$sqlZ3CorrectnessNonZeroDistanceCount"))
508-
add(Pair("sqlZ3CorrectnessAvgDist", "%.4f".format(sqlZ3CorrectnessAvgDistance.mean)))
509-
add(Pair("sqlZ3CorrectnessEvalFailures", "$sqlZ3CorrectnessEvalFailureCount"))
510-
}
511-
512480
for(phase in ExecutionPhaseController.Phase.entries){
513481
add(Pair("phase_${phase.name}", "${epc.getPhaseDurationInSeconds(phase)}"))
514482
}

core/src/main/kotlin/org/evomaster/core/solver/SMTConditionVisitor.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ class SMTConditionVisitor(
3838
* @return The SMT-LIB column reference string.
3939
*/
4040
private fun getColumnReference(tableName: String, columnName: String): String {
41-
return "(${convertToAscii(columnName).uppercase()} ${convertToAscii(tableName).lowercase()}$rowIndex)"
41+
val rowConstant = SmtLibGenerator.rowConstantName(convertToAscii(tableName).lowercase(), rowIndex)
42+
return "(${convertToAscii(columnName).uppercase()} $rowConstant)"
4243
}
4344

4445
/**

core/src/main/kotlin/org/evomaster/core/solver/SMTLibZ3DbConstraintSolver.kt

Lines changed: 14 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,13 @@ import com.google.inject.Inject
55
import net.sf.jsqlparser.JSQLParserException
66
import net.sf.jsqlparser.parser.CCJSqlParserUtil
77
import net.sf.jsqlparser.statement.Statement
8-
import net.sf.jsqlparser.statement.insert.Insert
98
import org.apache.commons.io.FileUtils
109
import org.evomaster.client.java.controller.api.dto.database.schema.ColumnDto
1110
import org.evomaster.client.java.controller.api.dto.database.schema.DatabaseType
1211
import org.evomaster.client.java.controller.api.dto.database.schema.DbInfoDto
1312
import org.evomaster.client.java.controller.api.dto.database.schema.TableDto
1413
import org.evomaster.core.EMConfig
1514
import org.evomaster.core.logging.LoggingUtil
16-
import org.evomaster.client.java.sql.DataRow
17-
import org.evomaster.client.java.sql.QueryResult
18-
import org.evomaster.client.java.sql.QueryResultSet
19-
import org.evomaster.client.java.sql.heuristic.SqlHeuristicsCalculator
20-
import org.evomaster.client.java.sql.heuristic.TableColumnResolver
21-
import org.evomaster.client.java.sql.internal.SqlDistanceWithMetrics
2215
import org.evomaster.core.search.gene.BooleanGene
2316
import org.evomaster.core.search.gene.Gene
2417
import org.evomaster.core.search.gene.numeric.DoubleGene
@@ -71,11 +64,12 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
7164
// Schema is assumed stable within a single run, so only query + row count form the key.
7265
// Null until Z3 SQL generation is enabled in postConstruct — avoids allocating the map in runs where Z3 SQL generation is off.
7366
//
74-
// THREAD-SAFETY: this is an access-ordered LinkedHashMap (LRU), whose get() mutates internal order,
75-
// so it is NOT thread-safe. It relies on solve() being called from a single thread, which holds today
76-
// because the MIO search loop (and thus fitness evaluation / structure mutation) is single-threaded.
77-
// If fitness evaluation is ever parallelized, this cache must be synchronized or replaced with a
78-
// concurrent LRU, otherwise concurrent access would corrupt it (lost entries / ConcurrentModificationException).
67+
// THREAD-SAFETY: the backing map is an access-ordered LinkedHashMap (a bounded LRU, whose get() mutates
68+
// internal order), wrapped in Collections.synchronizedMap so every operation is guarded by the map's
69+
// intrinsic lock. This is the standard idiom for a thread-safe bounded LRU (a plain ConcurrentHashMap
70+
// cannot do access-order eviction). Compound get-then-put in solve() is intentionally not atomic: at
71+
// worst two threads recompute the same query concurrently, which is harmless (idempotent), and never
72+
// corrupts the map. This makes the cache safe for the parallelized fitness evaluation that is planned.
7973
private var z3ResultCache: MutableMap<Pair<String, Int>, Z3Result>? = null
8074

8175
companion object {
@@ -111,10 +105,11 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
111105
private fun postConstruct() {
112106
if (config.generateSqlDataWithZ3) {
113107
initializeExecutor()
114-
z3ResultCache = object : LinkedHashMap<Pair<String, Int>, Z3Result>(16, 0.75f, true) {
108+
val lru = object : LinkedHashMap<Pair<String, Int>, Z3Result>(16, 0.75f, true) {
115109
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Pair<String, Int>, Z3Result>?) =
116110
size > MAX_CACHE_SIZE
117111
}
112+
z3ResultCache = Collections.synchronizedMap(lru)
118113
}
119114
}
120115

@@ -210,33 +205,7 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
210205
Z3Result.Status.SAT -> {
211206
stats?.reportSqlZ3Sat(z3TimeMs)
212207
z3ResultCache?.set(cacheKey, z3Result)
213-
val sqlActions = toSqlActionList(schemaDto, z3Result.solution)
214-
if (::config.isInitialized && config.measureSqlZ3Correctness && queryStatement !is Insert) {
215-
/*
216-
* INSERT statements have no WHERE clause, so SqlHeuristicsCalculator has no
217-
* predicate to evaluate distance against and will always report a failure.
218-
* Correctness measurement only makes sense for queries that filter rows
219-
* (SELECT, DELETE, UPDATE). In the future this could be extended to verify
220-
* that the generated rows satisfy insertion preconditions such as FK constraints
221-
* or NOT NULL columns that Z3 SQL generation currently leaves unconstrained.
222-
*
223-
* Note: SqlHeuristicsCalculator is SELECT-oriented. For DELETE/UPDATE the distance
224-
* computation may fail and be reported as an evaluation failure (sqlDistanceEvaluationFailure)
225-
* rather than a real distance; such failures are counted separately and are excluded
226-
* from the average, so they do not distort the correctness metric.
227-
*/
228-
val distResult = computeCorrectnessDistance(sqlQuery, schemaDto, sqlActions)
229-
if (distResult.sqlDistanceEvaluationFailure) {
230-
LoggingUtil.getInfoLogger().warn("SQL-Z3: correctness evaluation failure for query '$sqlQuery'")
231-
} else if (distResult.sqlDistance != 0.0) {
232-
LoggingUtil.getInfoLogger().warn("SQL-Z3: non-zero correctness distance (${distResult.sqlDistance}) for query '$sqlQuery'")
233-
}
234-
statisticsRef?.get()?.reportSqlZ3CorrectnessDistance(
235-
distResult.sqlDistance,
236-
distResult.sqlDistanceEvaluationFailure
237-
)
238-
}
239-
sqlActions
208+
toSqlActionList(schemaDto, z3Result.solution)
240209
}
241210
Z3Result.Status.UNSAT -> {
242211
stats?.reportSqlZ3Unsat(z3TimeMs)
@@ -387,15 +356,15 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
387356
/**
388357
* Extracts the table name from a row-constant key by removing the trailing row index.
389358
*
390-
* Row constants are named "${smtName}${i}" (e.g. "users1", "users2"). The trailing digits are
391-
* stripped so this works for any number of rows (e.g. "users10" -> "users"), not just single-digit
392-
* indices. Note: this still assumes table names themselves do not end in a digit.
359+
* Row constants are named "${smtName}${SEP}${i}" (e.g. "users__1", "users__2"; see
360+
* [SmtLibGenerator.rowConstantName]). Splitting on the last separator recovers the table name
361+
* unambiguously even when the table name itself ends in digits (e.g. "inventory2026__1" -> "inventory2026").
393362
*
394363
* @param key The key containing the table name and index.
395364
* @return The extracted table name.
396365
*/
397366
private fun getTableName(key: String): String {
398-
return key.replace(Regex("\\d+$"), "")
367+
return key.substringBeforeLast(SmtLibGenerator.ROW_INDEX_SEPARATOR)
399368
}
400369

401370
/**
@@ -488,7 +457,7 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
488457
/**
489458
* Maps column types to ColumnDataType.
490459
*
491-
* FUTURE WORK: this recognizes only a small subset of SQL type spellings and falls back to
460+
* TODO: this recognizes only a small subset of SQL type spellings and falls back to
492461
* CHARACTER_VARYING for everything else. It is one of three independent type vocabularies that
493462
* interpret [ColumnDto.type] — the others being [SmtLibGenerator.TYPE_MAP] (SQL type -> SMT sort)
494463
* and [hasColumnType] (BOOLEAN/TIMESTAMP special-casing). These can silently disagree when a
@@ -550,60 +519,4 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
550519
}
551520

552521
private fun leadingBarResourcesFolder() = if (resourcesFolder.endsWith("/")) resourcesFolder else "$resourcesFolder/"
553-
554-
private fun computeCorrectnessDistance(
555-
sqlQuery: String,
556-
schemaDto: DbInfoDto,
557-
sqlActions: List<SqlAction>
558-
): SqlDistanceWithMetrics {
559-
val queryResultSet = toQueryResultSet(schemaDto, sqlActions)
560-
val calculator = SqlHeuristicsCalculator.SqlHeuristicsCalculatorBuilder()
561-
.withTableColumnResolver(TableColumnResolver(schemaDto))
562-
.withSourceQueryResultSet(queryResultSet)
563-
.build()
564-
return calculator.computeDistance(sqlQuery)
565-
}
566-
567-
private fun toQueryResultSet(schemaDto: DbInfoDto, sqlActions: List<SqlAction>): QueryResultSet {
568-
val queryResultSet = QueryResultSet()
569-
val byTable = sqlActions.groupBy { it.table.id.name }
570-
for ((tableName, actions) in byTable) {
571-
val columnNames = actions.first().seeTopGenes().map { it.name }
572-
val queryResult = QueryResult(columnNames, tableName)
573-
for (action in actions) {
574-
val values: List<Any?> = action.seeTopGenes().map { gene -> extractGeneValue(gene) }
575-
queryResult.addRow(DataRow(tableName, columnNames, values))
576-
}
577-
queryResultSet.addQueryResult(queryResult)
578-
}
579-
// Tables not present in Z3's SAT model (e.g. the optional side of a LEFT OUTER JOIN)
580-
// still need an (empty) QueryResult, otherwise SqlHeuristicsCalculator NPEs when it
581-
// looks them up unconditionally while walking the FROM/JOIN clause.
582-
for (table in schemaDto.tables) {
583-
if (table.id.name !in byTable.keys) {
584-
val columnNames = table.columns.map { it.name }
585-
queryResultSet.addQueryResult(QueryResult(columnNames, table.id.name))
586-
}
587-
}
588-
return queryResultSet
589-
}
590-
591-
private fun extractGeneValue(gene: Gene): Any? {
592-
val inner = if (gene is SqlPrimaryKeyGene) gene.gene else gene
593-
return when (inner) {
594-
is IntegerGene -> inner.value
595-
is LongGene -> inner.value
596-
is StringGene -> inner.value
597-
is DoubleGene -> inner.value
598-
is BooleanGene -> inner.value
599-
is ImmutableDataHolderGene -> inner.value
600-
else -> {
601-
LoggingUtil.getInfoLogger().warn(
602-
"SQL-Z3: extractGeneValue() fallback to raw string for unhandled gene type " +
603-
"${inner.javaClass.name} (outer: ${gene.javaClass.name}, name: ${gene.name})"
604-
)
605-
inner.getValueAsRawString()
606-
}
607-
}
608-
}
609522
}

0 commit comments

Comments
 (0)