Skip to content

Commit c36024f

Browse files
committed
Add comments
1 parent 27d4730 commit c36024f

3 files changed

Lines changed: 40 additions & 16 deletions

File tree

core-extra/solver/src/main/java/org/evomaster/solver/smtlib/SMTResultParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public class SMTResultParser {
1818
/**
1919
* Parses the Z3 solver response and extracts variable values.
2020
*
21+
* FRAGILITY: parsing is regex- and position-based and assumes Z3's textual get-value layout
22+
* (one struct per line, constructor name split on '-', values split on whitespace). It therefore
23+
* assumes string values contain no spaces, hyphens, quotes or parentheses; such values would be
24+
* mis-split. Hardening the parser (or requesting values in a more robust format) is future work.
25+
*
2126
* @param z3Response the raw response from Z3 solver
2227
* @return a {@link Z3Solution} mapping variable names to the {@link SMTLibValue} objects Z3 assigned to them
2328
*/

core-extra/solver/src/test/java/org/evomaster/solver/Z3DockerExecutorTest.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
import java.nio.file.Path;
1414
import java.nio.file.Paths;
1515
import java.nio.file.StandardCopyOption;
16-
import java.util.HashMap;
17-
import java.util.Map;
1816

1917
import static org.junit.jupiter.api.Assertions.*;
2018

@@ -95,21 +93,31 @@ public void composedTypes() {
9593
assertEquals(Z3Result.Status.SAT, result.getStatus(), "Response should be SAT for composed_types.smt");
9694

9795
assertTrue(result.getSolution().containsKey("users1"), "Response should contain users1");
98-
Map<String, SMTLibValue> users1Expected = new HashMap<>();
99-
users1Expected.put("ID", new LongValue(3L));
100-
users1Expected.put("NAME", new StringValue("Alice"));
101-
users1Expected.put("AGE", new LongValue(31L));
102-
users1Expected.put("POINTS", new LongValue(7L));
103-
104-
assertEquals(new StructValue(users1Expected), result.getSolution().get("users1"), "The value for users1 is incorrect");
105-
10696
assertTrue(result.getSolution().containsKey("users2"), "Response should contain users2");
107-
Map<String, SMTLibValue> users2Expected = new HashMap<>();
108-
users2Expected.put("ID", new LongValue(3L));
109-
users2Expected.put("NAME", new StringValue("Bob"));
110-
users2Expected.put("AGE", new LongValue(31L));
111-
users2Expected.put("POINTS", new LongValue(7L));
112-
assertEquals(new StructValue(users2Expected), result.getSolution().get("users2"), "The value for users2 is incorrect");
97+
98+
// NOTE: assert the individual field VALUES, not StructValue equality: StructValue.equals only
99+
// compares the set of field names, so a whole-struct assertEquals would pass regardless of the
100+
// actual values Z3 returned. We assert the fully-constrained fields exactly (NAME, POINTS) and
101+
// the underconstrained fields against their SMT-LIB constraints (AGE range, distinct IDs).
102+
StructValue users1 = (StructValue) result.getSolution().get("users1");
103+
StructValue users2 = (StructValue) result.getSolution().get("users2");
104+
105+
// NAME and POINTS are pinned by the SMT-LIB (NAME = "Alice"/"Bob", POINTS = 7), so deterministic.
106+
assertEquals(new StringValue("Alice"), users1.getField("NAME"), "NAME users1");
107+
assertEquals(new StringValue("Bob"), users2.getField("NAME"), "NAME users2");
108+
assertEquals(new LongValue(7L), users1.getField("POINTS"), "POINTS users1");
109+
assertEquals(new LongValue(7L), users2.getField("POINTS"), "POINTS users2");
110+
111+
// AGE is constrained to (30, 100): assert the constraint holds rather than a specific value.
112+
long age1 = ((LongValue) users1.getField("AGE")).getValue();
113+
long age2 = ((LongValue) users2.getField("AGE")).getValue();
114+
assertTrue(age1 > 30 && age1 < 100, "AGE users1 out of range: " + age1);
115+
assertTrue(age2 > 30 && age2 < 100, "AGE users2 out of range: " + age2);
116+
117+
// IDs must be distinct (the 'distinct' assertion over the PK).
118+
long id1 = ((LongValue) users1.getField("ID")).getValue();
119+
long id2 = ((LongValue) users2.getField("ID")).getValue();
120+
assertNotEquals(id1, id2, "user IDs must be distinct");
113121
}
114122

115123
/**

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
7070
// Memoization cache: (sqlQuery, numberOfRows) -> Z3Result (SAT or UNSAT only; errors are not cached)
7171
// Schema is assumed stable within a single run, so only query + row count form the key.
7272
// Null until Z3 SQL generation is enabled in postConstruct — avoids allocating the map in runs where Z3 SQL generation is off.
73+
//
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).
7379
private var z3ResultCache: MutableMap<Pair<String, Int>, Z3Result>? = null
7480

7581
companion object {
@@ -206,6 +212,11 @@ class SMTLibZ3DbConstraintSolver() : DbConstraintSolver {
206212
* (SELECT, DELETE, UPDATE). In the future this could be extended to verify
207213
* that the generated rows satisfy insertion preconditions such as FK constraints
208214
* or NOT NULL columns that Z3 SQL generation currently leaves unconstrained.
215+
*
216+
* Note: SqlHeuristicsCalculator is SELECT-oriented. For DELETE/UPDATE the distance
217+
* computation may fail and be reported as an evaluation failure (sqlDistanceEvaluationFailure)
218+
* rather than a real distance; such failures are counted separately and are excluded
219+
* from the average, so they do not distort the correctness metric.
209220
*/
210221
val distResult = computeCorrectnessDistance(sqlQuery, schemaDto, sqlActions)
211222
if (distResult.sqlDistanceEvaluationFailure) {

0 commit comments

Comments
 (0)