From 717b7fd71eb5e4398a15a0adbab2b41a2692b5c9 Mon Sep 17 00:00:00 2001 From: aschenzle Date: Mon, 7 Sep 2026 11:07:36 -0700 Subject: [PATCH 1/3] Capture failed DynamoDB reads for insertion generation. --- .../api/dto/ExtraHeuristicsDto.java | 4 +- .../operations/DynamoDbInsertionKey.java | 31 ++++ .../operations/DynamoDbInsertionKeyTest.java | 23 +++ .../internal/db/dynamodb/DynamoDbHandler.java | 158 +++++++++++++++++- .../db/dynamodb/DynamoDbHandlerTest.java | 72 +++++++- .../InstrumentationController.java | 11 +- .../external/AgentController.java | 35 ++-- .../instrumentation/external/Command.java | 1 + .../external/ServerController.java | 7 +- .../core/database/dynamodb/DynamoDbAction.kt | 19 +-- 10 files changed, 326 insertions(+), 35 deletions(-) create mode 100644 client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKey.java create mode 100644 client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyTest.java diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/ExtraHeuristicsDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/ExtraHeuristicsDto.java index 3bd2ccf5d0..33866eb705 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/ExtraHeuristicsDto.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/ExtraHeuristicsDto.java @@ -1,6 +1,7 @@ package org.evomaster.client.java.controller.api.dto; import org.evomaster.client.java.controller.api.dto.database.execution.RedisExecutionsDto; +import org.evomaster.client.java.controller.api.dto.database.execution.DynamoDbExecutionsDto; import org.evomaster.client.java.controller.api.dto.database.execution.SqlExecutionsDto; import org.evomaster.client.java.controller.api.dto.database.execution.MongoExecutionsDto; import java.util.ArrayList; @@ -9,7 +10,6 @@ /** * Represents possible extra heuristics related to the code * execution and that do apply to all the reached testing targets. - * * Example: rewarding SQL "select" operations that return non-empty sets */ public class ExtraHeuristicsDto { @@ -24,4 +24,6 @@ public class ExtraHeuristicsDto { public MongoExecutionsDto mongoExecutionsDto; public RedisExecutionsDto redisExecutionsDto; + + public DynamoDbExecutionsDto dynamoDbExecutionsDto; } diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKey.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKey.java new file mode 100644 index 0000000000..4ce693a43e --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKey.java @@ -0,0 +1,31 @@ +package org.evomaster.client.java.controller.api.dto.database.operations; + +import java.util.List; + +/** + * Builds stable identifiers for inferred DynamoDB insertions. + */ +public final class DynamoDbInsertionKey { + + /** + * Prevents instantiation of this utility class. + */ + private DynamoDbInsertionKey() { + } + + /** + * Builds the insertion key from a table and its ordered scalar attributes. + * + * @param tableName target DynamoDB table + * @param attributes ordered attributes in the insertion + * @return stable key in the {@code table|name:type=value} format + */ + public static String fromAttributes(String tableName, List attributes) { + StringBuilder insertionKey = new StringBuilder(tableName); + for (DynamoDbAttributeValueDto attribute : attributes) { + insertionKey.append('|').append(attribute.attributeName) + .append(':').append(attribute.type).append('=').append(attribute.value); + } + return insertionKey.toString(); + } +} diff --git a/client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyTest.java b/client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyTest.java new file mode 100644 index 0000000000..339cc34a3f --- /dev/null +++ b/client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyTest.java @@ -0,0 +1,23 @@ +package org.evomaster.client.java.controller.api.dto.database.operations; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests the canonical representation of inferred DynamoDB insertion keys. + */ +public class DynamoDbInsertionKeyTest { + + @Test + public void testBuildsWorldCupPlayerInsertionKey() { + assertEquals("WorldCupPlayers|country:STRING=Argentina|fifaId:NUMBER=10|captain:BOOLEAN=true", + DynamoDbInsertionKey.fromAttributes("WorldCupPlayers", Arrays.asList( + new DynamoDbAttributeValueDto("country", DynamoDbScalarTypeDto.STRING, "Argentina"), + new DynamoDbAttributeValueDto("fifaId", DynamoDbScalarTypeDto.NUMBER, "10"), + new DynamoDbAttributeValueDto("captain", DynamoDbScalarTypeDto.BOOLEAN, "true") + ))); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandler.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandler.java index 439cb9b60c..54a7517678 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandler.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandler.java @@ -1,16 +1,28 @@ package org.evomaster.client.java.controller.internal.db.dynamodb; +import org.evomaster.client.java.controller.api.dto.database.execution.DynamoDbExecutionsDto; +import org.evomaster.client.java.controller.api.dto.database.execution.DynamoDbFailedQuery; +import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbAttributeValueDto; +import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbInsertionKey; +import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbScalarTypeDto; import org.evomaster.client.java.controller.dynamodb.DynamoDbRequestParser; import org.evomaster.client.java.controller.dynamodb.ParsedDynamoDbRequest; +import org.evomaster.client.java.controller.dynamodb.operations.AndOperation; +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +import org.evomaster.client.java.controller.dynamodb.operations.comparison.EqualsOperation; import org.evomaster.client.java.controller.internal.TaintHandlerExecutionTracer; import org.evomaster.client.java.instrumentation.DynamoDbCommand; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; import org.evomaster.client.java.utils.SimpleLogger; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * Processes DynamoDB commands captured from the SUT and computes database heuristics for them. @@ -19,12 +31,15 @@ public class DynamoDbHandler { private final List commands = new ArrayList<>(); private final List evaluatedCommands = new ArrayList<>(); + private final List failedQueries = new ArrayList<>(); + private final Set insertionKeys = new LinkedHashSet<>(); private final DynamoDbRequestParser requestParser = new DynamoDbRequestParser(); private final DynamoDbHeuristicsCalculator calculator = new DynamoDbHeuristicsCalculator(new TaintHandlerExecutionTracer()); private final DynamoDbTableDataAccessor tableDataAccessor = new DynamoDbTableDataAccessor(); private volatile boolean calculateHeuristics; + private volatile boolean extractDynamoDbExecution; private Object dynamoDbClient; /** @@ -32,6 +47,7 @@ public class DynamoDbHandler { */ public DynamoDbHandler() { calculateHeuristics = true; + extractDynamoDbExecution = true; } /** @@ -40,6 +56,8 @@ public DynamoDbHandler() { public void reset() { commands.clear(); evaluatedCommands.clear(); + failedQueries.clear(); + insertionKeys.clear(); } /** @@ -58,6 +76,22 @@ public void setCalculateHeuristics(boolean calculateHeuristics) { this.calculateHeuristics = calculateHeuristics; } + /** + * @return whether failed DynamoDB reads are extracted + */ + public boolean isExtractDynamoDbExecution() { + return extractDynamoDbExecution; + } + + /** + * Enables or disables extraction of failed DynamoDB reads. + * + * @param extractDynamoDbExecution new extraction state + */ + public void setExtractDynamoDbExecution(boolean extractDynamoDbExecution) { + this.extractDynamoDbExecution = extractDynamoDbExecution; + } + /** * Sets the SDK v2 client used to read table contents. * @@ -84,7 +118,7 @@ public void handle(DynamoDbCommand command) { * @return evaluated commands for the current action */ public List getEvaluatedDynamoDbCommands() { - if (!calculateHeuristics) { + if (!calculateHeuristics && !extractDynamoDbExecution) { commands.clear(); return Collections.emptyList(); } @@ -97,6 +131,15 @@ public List getEvaluatedDynamoDbCommands() { return new ArrayList<>(evaluatedCommands); } + /** + * @return failed reads captured for the current action + */ + public DynamoDbExecutionsDto getExecutionDto() { + DynamoDbExecutionsDto dto = new DynamoDbExecutionsDto(); + dto.failedQueries = new ArrayList<>(failedQueries); + return dto; + } + /** * Evaluates one successfully executed command and reuses table scans within the current batch. * @@ -131,14 +174,123 @@ private void evaluateCommand(DynamoDbCommand command, Map 0.0d) { + registerFailedQuery(command, tableName, parsed); + } } catch (RuntimeException e) { registerFailure(command, tableName, e); } } } + /** + * Registers one positive-distance DynamoDB read when its conditions can define an insertion item. + * + * @param command intercepted DynamoDB read + * @param tableName table read by the command + * @param parsed parsed request conditions + */ + private void registerFailedQuery(DynamoDbCommand command, String tableName, ParsedDynamoDbRequest parsed) { + if (command.getOperationName() != DynamoDbOperationNames.GET_ITEM + && command.getOperationName() != DynamoDbOperationNames.QUERY) { + return; + } + + Map attributes = new LinkedHashMap<>(); + if (!evaluateEqualitiesAsFlatAttributes(parsed.getKeyCondition(), attributes) + || !evaluateEqualitiesAsFlatAttributes(parsed.getFilterExpression(), attributes) + || attributes.isEmpty()) { + return; + } + + List insertionAttributes = new ArrayList<>(attributes.values()); + String insertionKey = DynamoDbInsertionKey.fromAttributes(tableName, insertionAttributes); + if (insertionKeys.add(insertionKey)) { + failedQueries.add(new DynamoDbFailedQuery(tableName, insertionAttributes)); + } + } + + /** + * Evaluates whether a condition can be represented as flat scalar insertion attributes. + *

+ * The condition must be {@code null}, a conjunction, or a top-level equality whose value is a string, + * number, or boolean. Equalities are iteratively flattened into {@code attributes}; the method returns + * {@code false} for unsupported predicates, nested document paths, or conflicting values for one attribute. + * For example, the resolved condition + * {@code country = "Argentina" AND (fifaId = 10 AND captain = true)} produces the flat attributes + * {@code country -> (STRING, Argentina)}, {@code fifaId -> (NUMBER, 10)}, and + * {@code captain -> (BOOLEAN, true)}. + * A syntactically supported condition can still fail: {@code country = "Argentina" AND country = "Brazil"} + * returns {@code false}, because one flat insertion item cannot assign two different values to {@code country}. + * + * @param operation condition to evaluate + * @param attributes inferred insertion attributes, populated when the condition is supported + * @return {@code true} if the condition is representable as flat scalar attributes + */ + private boolean evaluateEqualitiesAsFlatAttributes( + QueryOperation operation, + Map attributes) { + if (operation == null) { + return true; + } + + List pending = new ArrayList<>(); + pending.add(operation); + while (!pending.isEmpty()) { + QueryOperation current = pending.remove(pending.size() - 1); + if (current instanceof AndOperation) { + List conditions = ((AndOperation) current).getConditions(); + for (int i = conditions.size() - 1; i >= 0; i--) { + pending.add(conditions.get(i)); + } + continue; + } + if (!(current instanceof EqualsOperation)) { + return false; + } + + EqualsOperation equality = (EqualsOperation) current; + String name = equality.getFieldName(); + if (name == null || name.isEmpty() || name.contains(".") || name.contains("[")) { + return false; + } + DynamoDbAttributeValueDto attribute = toAttribute(name, equality.getValue()); + if (attribute == null) { + return false; + } + DynamoDbAttributeValueDto existing = attributes.get(name); + if (existing != null && (existing.type != attribute.type || !existing.value.equals(attribute.value))) { + return false; + } + attributes.put(name, attribute); + } + return true; + } + + /** + * Converts a supported scalar equality value to the DTO used for a DynamoDB insertion attribute. + * + * @param name attribute name + * @param value equality value + * @return the corresponding scalar attribute, or {@code null} when the value is unsupported + */ + private DynamoDbAttributeValueDto toAttribute(String name, Object value) { + if (value instanceof String) { + return new DynamoDbAttributeValueDto(name, DynamoDbScalarTypeDto.STRING, (String) value); + } + if (value instanceof Number) { + return new DynamoDbAttributeValueDto(name, DynamoDbScalarTypeDto.NUMBER, String.valueOf(value)); + } + if (value instanceof Boolean) { + return new DynamoDbAttributeValueDto(name, DynamoDbScalarTypeDto.BOOLEAN, String.valueOf(value)); + } + return null; + } + /** * Records a failed evaluation for every table referenced by a command. * diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandlerTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandlerTest.java index 6625010c91..f4a08be5fe 100644 --- a/client-java/controller/src/test/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandlerTest.java +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandlerTest.java @@ -1,5 +1,9 @@ package org.evomaster.client.java.controller.internal.db.dynamodb; +import org.evomaster.client.java.controller.api.dto.database.execution.DynamoDbExecutionsDto; +import org.evomaster.client.java.controller.api.dto.database.execution.DynamoDbFailedQuery; +import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbAttributeValueDto; +import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbScalarTypeDto; import org.evomaster.client.java.instrumentation.DynamoDbCommand; import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; import org.junit.jupiter.api.Test; @@ -101,6 +105,43 @@ public void testDisabledAndReset() { assertTrue(handler.getEvaluatedDynamoDbCommands().isEmpty()); } + @Test + public void testExtractsAndDeduplicatesNestedWorldCupPlayerQueries() { + DynamoDbHandler handler = enabledHandler(new SyncDynamoDbClient()); + handler.handle(nestedQueryCommand()); + handler.handle(nestedQueryCommand()); + + handler.getEvaluatedDynamoDbCommands(); + DynamoDbExecutionsDto execution = handler.getExecutionDto(); + + assertEquals(1, execution.failedQueries.size()); + DynamoDbFailedQuery failedQuery = execution.failedQueries.get(0); + assertEquals(TABLE, failedQuery.tableName); + assertAttribute(failedQuery.attributes, "country", DynamoDbScalarTypeDto.STRING, "Argentina"); + assertAttribute(failedQuery.attributes, "playerName", DynamoDbScalarTypeDto.STRING, "Lionel Scaloni"); + assertAttribute(failedQuery.attributes, "fifaId", DynamoDbScalarTypeDto.NUMBER, "10"); + assertAttribute(failedQuery.attributes, "captain", DynamoDbScalarTypeDto.BOOLEAN, "true"); + + handler.reset(); + assertTrue(handler.getExecutionDto().failedQueries.isEmpty()); + } + + @Test + public void testExtractionCanRunWithoutHeuristicCollection() { + DynamoDbHandler handler = enabledHandler(new SyncDynamoDbClient()); + handler.setCalculateHeuristics(false); + handler.handle(queryCommand("Lionel Scaloni")); + + assertTrue(handler.getEvaluatedDynamoDbCommands().isEmpty()); + assertEquals(1, handler.getExecutionDto().failedQueries.size()); + + handler.reset(); + handler.setExtractDynamoDbExecution(false); + handler.handle(queryCommand("Lionel Scaloni")); + handler.getEvaluatedDynamoDbCommands(); + assertTrue(handler.getExecutionDto().failedQueries.isEmpty()); + } + private DynamoDbHandler enabledHandler(Object client) { DynamoDbHandler handler = new DynamoDbHandler(); handler.setCalculateHeuristics(true); @@ -122,6 +163,35 @@ private DynamoDbCommand queryCommand(String playerName) { request, true, 1L); } + private DynamoDbCommand nestedQueryCommand() { + Map values = new HashMap<>(); + values.put(":country", AttributeValue.builder().s("Argentina").build()); + values.put(":player", AttributeValue.builder().s("Lionel Scaloni").build()); + values.put(":fifaId", AttributeValue.builder().n("10").build()); + values.put(":captain", AttributeValue.builder().bool(true).build()); + QueryRequest request = QueryRequest.builder() + .tableName(TABLE) + .keyConditionExpression("country = :country") + .filterExpression("playerName = :player AND (fifaId = :fifaId AND captain = :captain)") + .expressionAttributeValues(values) + .build(); + return new DynamoDbCommand(Collections.singletonList(TABLE), DynamoDbOperationNames.QUERY, + request, true, 1L); + } + + private void assertAttribute( + List attributes, + String name, + DynamoDbScalarTypeDto type, + String value) { + DynamoDbAttributeValueDto attribute = attributes.stream() + .filter(candidate -> name.equals(candidate.attributeName)) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing attribute " + name)); + assertEquals(type, attribute.type); + assertEquals(value, attribute.value); + } + private static Map item(String country, String playerName) { Map item = new HashMap<>(); item.put("country", AttributeValue.builder().s(country).build()); @@ -154,7 +224,7 @@ public ScanResponse scan(ScanRequest request) { } return ScanResponse.builder() .items(Collections.singletonList(item("Argentina", "Lionel Messi"))) - .lastEvaluatedKey(Collections.emptyMap()) + .lastEvaluatedKey(Collections.emptyMap()) .build(); } } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/InstrumentationController.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/InstrumentationController.java index 518a81be48..5e46cb232b 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/InstrumentationController.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/InstrumentationController.java @@ -1,7 +1,6 @@ package org.evomaster.client.java.instrumentation; import org.evomaster.client.java.instrumentation.cassandra.CassandraSchemaTracer; -import org.evomaster.client.java.instrumentation.object.ClassToSchema; import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; import org.evomaster.client.java.instrumentation.staticstate.ObjectiveRecorder; import org.evomaster.client.java.instrumentation.staticstate.UnitsInfoRecorder; @@ -64,6 +63,10 @@ public static void setExecutingInitRedis(boolean executingInitRedis){ ExecutionTracer.setExecutingInitRedis(executingInitRedis); } + public static void setExecutingInitDynamoDb(boolean executingInitDynamoDb){ + ExecutionTracer.setExecutingInitDynamoDB(executingInitDynamoDb); + } + public static void setExecutingAction(boolean executingAction){ ExecutionTracer.setExecutingAction(executingAction); } @@ -83,7 +86,7 @@ public static List getTargetInfos( Map objectives = ExecutionTracer.getInternalReferenceToObjectiveCoverage(); if(ids != null) { - ids.stream().forEach(id -> { + ids.forEach(id -> { String descriptiveId = ObjectiveRecorder.getDescriptiveId(id); @@ -103,7 +106,7 @@ public static List getTargetInfos( /* * If new targets were found, we add them even if not requested by EM */ - ObjectiveRecorder.getTargetsSeenFirstTime().stream().forEach(s -> { + ObjectiveRecorder.getTargetsSeenFirstTime().forEach(s -> { int mappedId = ObjectiveRecorder.getMappedId(s); @@ -137,7 +140,7 @@ public static List getTargetInfos( } return info; }) - .forEach(e -> list.add(e)); + .forEach(list::add); } if(fullyCovered){ diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/AgentController.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/AgentController.java index 79aa3b7ff2..0ab221dc86 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/AgentController.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/AgentController.java @@ -9,7 +9,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; -import java.util.Collection; import java.util.List; /** @@ -19,7 +18,6 @@ public class AgentController { private static Socket socket; - private static Thread thread; private static ObjectOutputStream out; private static ObjectInputStream in; @@ -35,32 +33,32 @@ public static void start(int port){ SimpleLogger.info("Connected to EvoMaster controller"); - thread = new Thread(() ->{ + Thread thread = new Thread(() -> { - while (! Thread.interrupted() && socket != null){ + while (!Thread.interrupted() && socket != null) { Object msg; try { msg = in.readObject(); } catch (IOException e) { - SimpleLogger.error("Failure in receiving message: "+e.getMessage()); + SimpleLogger.error("Failure in receiving message: " + e.getMessage()); return; } catch (ClassNotFoundException e) { - SimpleLogger.error("Configuration error: "+e.getMessage()); + SimpleLogger.error("Configuration error: " + e.getMessage()); return; } - if(msg == null || ! (msg instanceof Command)){ - SimpleLogger.error("Received wrong message type: "+msg); + if (!(msg instanceof Command)) { + SimpleLogger.error("Received wrong message type: " + msg); continue; } Command command = (Command) msg; long start = System.currentTimeMillis(); - SimpleLogger.debug("Handling command: "+command); + SimpleLogger.debug("Handling command: " + command); - switch(command){ + switch (command) { case NEW_SEARCH: InstrumentationController.resetForNewSearch(); sendCommand(Command.ACK); @@ -102,6 +100,10 @@ public static void start(int port){ handleExecutingInitRedis(); sendCommand(Command.ACK); break; + case EXECUTING_INIT_DYNAMODB: + handleExecutingInitDynamoDb(); + sendCommand(Command.ACK); + break; case EXECUTING_ACTION: handleExecutingAction(); sendCommand(Command.ACK); @@ -117,12 +119,12 @@ public static void start(int port){ handleExtractingSpecifiedDto(); break; default: - SimpleLogger.error("Unrecognized command: "+command); + SimpleLogger.error("Unrecognized command: " + command); return; } long delta = System.currentTimeMillis() - start; - SimpleLogger.debug("Command took "+delta+" ms"); + SimpleLogger.debug("Command took " + delta + " ms"); } }); @@ -208,6 +210,15 @@ private static void handleExecutingInitRedis() { } } + private static void handleExecutingInitDynamoDb() { + try { + Object msg = in.readObject(); + InstrumentationController.setExecutingInitDynamoDb((Boolean) msg); + } catch (Exception e){ + SimpleLogger.error("Failure in handling executing-init-dynamodb: " + e.getMessage()); + } + } + private static void handleExecutingAction() { try { Object msg = in.readObject(); diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/Command.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/Command.java index f7a7ba2ac6..892cbf211b 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/Command.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/Command.java @@ -21,6 +21,7 @@ public enum Command implements Serializable { EXECUTING_INIT_MONGO, EXECUTING_INIT_CASSANDRA, EXECUTING_INIT_REDIS, + EXECUTING_INIT_DYNAMODB, EXECUTING_ACTION, BOOT_TIME_INFO, EXTRACT_JVM_DTO, diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/ServerController.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/ServerController.java index 34b359b1f3..256218cf70 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/ServerController.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/ServerController.java @@ -122,8 +122,7 @@ public synchronized Object waitAndGetResponse() { } try { - Object obj = in.readObject(); - return obj; + return in.readObject(); } catch (IOException e) { SimpleLogger.error("IO exception while waiting for response", e); return null; @@ -212,6 +211,10 @@ public boolean setExecutingInitRedis(boolean executingInitRedis) { return sendWithDataAndExpectACK(Command.EXECUTING_INIT_REDIS, executingInitRedis); } + public boolean setExecutingInitDynamoDb(boolean executingInitDynamoDb) { + return sendWithDataAndExpectACK(Command.EXECUTING_INIT_DYNAMODB, executingInitDynamoDb); + } + public boolean setExecutingAction(boolean executingAction){ return sendWithDataAndExpectACK(Command.EXECUTING_ACTION, executingAction); } diff --git a/core/src/main/kotlin/org/evomaster/core/database/dynamodb/DynamoDbAction.kt b/core/src/main/kotlin/org/evomaster/core/database/dynamodb/DynamoDbAction.kt index ee28b2ca59..0680e9c0a7 100644 --- a/core/src/main/kotlin/org/evomaster/core/database/dynamodb/DynamoDbAction.kt +++ b/core/src/main/kotlin/org/evomaster/core/database/dynamodb/DynamoDbAction.kt @@ -1,5 +1,7 @@ package org.evomaster.core.database.dynamodb +import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbAttributeValueDto +import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbInsertionKey import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbScalarTypeDto import org.evomaster.core.search.action.Action import org.evomaster.core.search.action.EnvironmentAction @@ -29,12 +31,6 @@ class DynamoDbAction( val attributes: List ) : EnvironmentAction(listOf()) { - companion object { - private const val ATTRIBUTE_SEPARATOR = '|' - private const val TYPE_SEPARATOR = ':' - private const val VALUE_SEPARATOR = '=' - } - init { addChildren(attributes.map { it.gene }) } @@ -55,11 +51,10 @@ class DynamoDbAction( override fun getActionGroupKey(): String = DynamoDbAction::class.java.name /** Stable key used to avoid adding the same inferred insertion twice. */ - fun insertionKey(): String = buildString { - append(tableName) - attributes.forEach { - append(ATTRIBUTE_SEPARATOR).append(it.attributeName).append(TYPE_SEPARATOR).append(it.type) - .append(VALUE_SEPARATOR).append(it.gene.getValueAsRawString()) + fun insertionKey(): String = DynamoDbInsertionKey.fromAttributes( + tableName, + attributes.map { + DynamoDbAttributeValueDto(it.attributeName, it.type, it.gene.getValueAsRawString()) } - } + ) } From fac7e42146c21b1127c0b8dc71ec5d95cebd244d Mon Sep 17 00:00:00 2001 From: aschenzle Date: Tue, 8 Sep 2026 17:38:06 -0700 Subject: [PATCH 2/3] Address DynamoDB observation review comments --- ....java => DynamoDbInsertionKeyBuilder.java} | 4 ++-- ...a => DynamoDbInsertionKeyBuilderTest.java} | 4 ++-- .../internal/db/dynamodb/DynamoDbHandler.java | 22 +++++++++++++++++-- .../core/database/dynamodb/DynamoDbAction.kt | 4 ++-- 4 files changed, 26 insertions(+), 8 deletions(-) rename client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/{DynamoDbInsertionKey.java => DynamoDbInsertionKeyBuilder.java} (91%) rename client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/{DynamoDbInsertionKeyTest.java => DynamoDbInsertionKeyBuilderTest.java} (85%) diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKey.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyBuilder.java similarity index 91% rename from client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKey.java rename to client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyBuilder.java index 4ce693a43e..1cebad83ff 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKey.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyBuilder.java @@ -5,12 +5,12 @@ /** * Builds stable identifiers for inferred DynamoDB insertions. */ -public final class DynamoDbInsertionKey { +public final class DynamoDbInsertionKeyBuilder { /** * Prevents instantiation of this utility class. */ - private DynamoDbInsertionKey() { + private DynamoDbInsertionKeyBuilder() { } /** diff --git a/client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyTest.java b/client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyBuilderTest.java similarity index 85% rename from client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyTest.java rename to client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyBuilderTest.java index 339cc34a3f..cc80204bc6 100644 --- a/client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyTest.java +++ b/client-java/controller-api/src/test/java/org/evomaster/client/java/controller/api/dto/database/operations/DynamoDbInsertionKeyBuilderTest.java @@ -9,12 +9,12 @@ /** * Tests the canonical representation of inferred DynamoDB insertion keys. */ -public class DynamoDbInsertionKeyTest { +public class DynamoDbInsertionKeyBuilderTest { @Test public void testBuildsWorldCupPlayerInsertionKey() { assertEquals("WorldCupPlayers|country:STRING=Argentina|fifaId:NUMBER=10|captain:BOOLEAN=true", - DynamoDbInsertionKey.fromAttributes("WorldCupPlayers", Arrays.asList( + DynamoDbInsertionKeyBuilder.fromAttributes("WorldCupPlayers", Arrays.asList( new DynamoDbAttributeValueDto("country", DynamoDbScalarTypeDto.STRING, "Argentina"), new DynamoDbAttributeValueDto("fifaId", DynamoDbScalarTypeDto.NUMBER, "10"), new DynamoDbAttributeValueDto("captain", DynamoDbScalarTypeDto.BOOLEAN, "true") diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandler.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandler.java index 54a7517678..8d7fd19333 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandler.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbHandler.java @@ -3,7 +3,7 @@ import org.evomaster.client.java.controller.api.dto.database.execution.DynamoDbExecutionsDto; import org.evomaster.client.java.controller.api.dto.database.execution.DynamoDbFailedQuery; import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbAttributeValueDto; -import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbInsertionKey; +import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbInsertionKeyBuilder; import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbScalarTypeDto; import org.evomaster.client.java.controller.dynamodb.DynamoDbRequestParser; import org.evomaster.client.java.controller.dynamodb.ParsedDynamoDbRequest; @@ -29,17 +29,35 @@ */ public class DynamoDbHandler { + /** Commands captured from the SUT and awaiting evaluation. */ private final List commands = new ArrayList<>(); + + /** Heuristic results accumulated for the current action. */ private final List evaluatedCommands = new ArrayList<>(); + + /** Failed reads accumulated for insertion generation during the current action. */ private final List failedQueries = new ArrayList<>(); + + /** Canonical keys used to avoid reporting duplicate inferred insertions. */ private final Set insertionKeys = new LinkedHashSet<>(); + + /** Parser used to extract predicates and table names from captured requests. */ private final DynamoDbRequestParser requestParser = new DynamoDbRequestParser(); + + /** Calculator used to measure how close table items are to satisfying a request. */ private final DynamoDbHeuristicsCalculator calculator = new DynamoDbHeuristicsCalculator(new TaintHandlerExecutionTracer()); + + /** Accessor used to load table items through the configured DynamoDB client. */ private final DynamoDbTableDataAccessor tableDataAccessor = new DynamoDbTableDataAccessor(); + /** Whether captured commands should produce heuristic results. */ private volatile boolean calculateHeuristics; + + /** Whether failed reads should be captured for insertion generation. */ private volatile boolean extractDynamoDbExecution; + + /** SDK v2 synchronous or asynchronous DynamoDB client supplied by the SUT controller. */ private Object dynamoDbClient; /** @@ -208,7 +226,7 @@ private void registerFailedQuery(DynamoDbCommand command, String tableName, Pars } List insertionAttributes = new ArrayList<>(attributes.values()); - String insertionKey = DynamoDbInsertionKey.fromAttributes(tableName, insertionAttributes); + String insertionKey = DynamoDbInsertionKeyBuilder.fromAttributes(tableName, insertionAttributes); if (insertionKeys.add(insertionKey)) { failedQueries.add(new DynamoDbFailedQuery(tableName, insertionAttributes)); } diff --git a/core/src/main/kotlin/org/evomaster/core/database/dynamodb/DynamoDbAction.kt b/core/src/main/kotlin/org/evomaster/core/database/dynamodb/DynamoDbAction.kt index 0680e9c0a7..c4186e93c2 100644 --- a/core/src/main/kotlin/org/evomaster/core/database/dynamodb/DynamoDbAction.kt +++ b/core/src/main/kotlin/org/evomaster/core/database/dynamodb/DynamoDbAction.kt @@ -1,7 +1,7 @@ package org.evomaster.core.database.dynamodb import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbAttributeValueDto -import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbInsertionKey +import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbInsertionKeyBuilder import org.evomaster.client.java.controller.api.dto.database.operations.DynamoDbScalarTypeDto import org.evomaster.core.search.action.Action import org.evomaster.core.search.action.EnvironmentAction @@ -51,7 +51,7 @@ class DynamoDbAction( override fun getActionGroupKey(): String = DynamoDbAction::class.java.name /** Stable key used to avoid adding the same inferred insertion twice. */ - fun insertionKey(): String = DynamoDbInsertionKey.fromAttributes( + fun insertionKey(): String = DynamoDbInsertionKeyBuilder.fromAttributes( tableName, attributes.map { DynamoDbAttributeValueDto(it.attributeName, it.type, it.gene.getValueAsRawString()) From f66d5d71a0af7015ab5471d67ff01579c4b31410 Mon Sep 17 00:00:00 2001 From: aschenzle Date: Thu, 10 Sep 2026 10:29:35 -0700 Subject: [PATCH 3/3] Revert unrelated instrumentation refactors --- .../InstrumentationController.java | 7 +++--- .../external/AgentController.java | 22 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/InstrumentationController.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/InstrumentationController.java index 5e46cb232b..49c1d94b44 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/InstrumentationController.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/InstrumentationController.java @@ -1,6 +1,7 @@ package org.evomaster.client.java.instrumentation; import org.evomaster.client.java.instrumentation.cassandra.CassandraSchemaTracer; +import org.evomaster.client.java.instrumentation.object.ClassToSchema; import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; import org.evomaster.client.java.instrumentation.staticstate.ObjectiveRecorder; import org.evomaster.client.java.instrumentation.staticstate.UnitsInfoRecorder; @@ -86,7 +87,7 @@ public static List getTargetInfos( Map objectives = ExecutionTracer.getInternalReferenceToObjectiveCoverage(); if(ids != null) { - ids.forEach(id -> { + ids.stream().forEach(id -> { String descriptiveId = ObjectiveRecorder.getDescriptiveId(id); @@ -106,7 +107,7 @@ public static List getTargetInfos( /* * If new targets were found, we add them even if not requested by EM */ - ObjectiveRecorder.getTargetsSeenFirstTime().forEach(s -> { + ObjectiveRecorder.getTargetsSeenFirstTime().stream().forEach(s -> { int mappedId = ObjectiveRecorder.getMappedId(s); @@ -140,7 +141,7 @@ public static List getTargetInfos( } return info; }) - .forEach(list::add); + .forEach(e -> list.add(e)); } if(fullyCovered){ diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/AgentController.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/AgentController.java index 0ab221dc86..a9b728bf2c 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/AgentController.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/external/AgentController.java @@ -9,6 +9,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; +import java.util.Collection; import java.util.List; /** @@ -18,6 +19,7 @@ public class AgentController { private static Socket socket; + private static Thread thread; private static ObjectOutputStream out; private static ObjectInputStream in; @@ -33,32 +35,32 @@ public static void start(int port){ SimpleLogger.info("Connected to EvoMaster controller"); - Thread thread = new Thread(() -> { + thread = new Thread(() ->{ - while (!Thread.interrupted() && socket != null) { + while (! Thread.interrupted() && socket != null){ Object msg; try { msg = in.readObject(); } catch (IOException e) { - SimpleLogger.error("Failure in receiving message: " + e.getMessage()); + SimpleLogger.error("Failure in receiving message: "+e.getMessage()); return; } catch (ClassNotFoundException e) { - SimpleLogger.error("Configuration error: " + e.getMessage()); + SimpleLogger.error("Configuration error: "+e.getMessage()); return; } - if (!(msg instanceof Command)) { - SimpleLogger.error("Received wrong message type: " + msg); + if(msg == null || ! (msg instanceof Command)){ + SimpleLogger.error("Received wrong message type: "+msg); continue; } Command command = (Command) msg; long start = System.currentTimeMillis(); - SimpleLogger.debug("Handling command: " + command); + SimpleLogger.debug("Handling command: "+command); - switch (command) { + switch(command){ case NEW_SEARCH: InstrumentationController.resetForNewSearch(); sendCommand(Command.ACK); @@ -119,12 +121,12 @@ public static void start(int port){ handleExtractingSpecifiedDto(); break; default: - SimpleLogger.error("Unrecognized command: " + command); + SimpleLogger.error("Unrecognized command: "+command); return; } long delta = System.currentTimeMillis() - start; - SimpleLogger.debug("Command took " + delta + " ms"); + SimpleLogger.debug("Command took "+delta+" ms"); } });