Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -24,4 +24,6 @@ public class ExtraHeuristicsDto {
public MongoExecutionsDto mongoExecutionsDto;

public RedisExecutionsDto redisExecutionsDto;

public DynamoDbExecutionsDto dynamoDbExecutionsDto;
}
Original file line number Diff line number Diff line change
@@ -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 DynamoDbInsertionKeyBuilder {

/**
* Prevents instantiation of this utility class.
*/
private DynamoDbInsertionKeyBuilder() {
}

/**
* 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<DynamoDbAttributeValueDto> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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 DynamoDbInsertionKeyBuilderTest {

@Test
public void testBuildsWorldCupPlayerInsertionKey() {
assertEquals("WorldCupPlayers|country:STRING=Argentina|fifaId:NUMBER=10|captain:BOOLEAN=true",
DynamoDbInsertionKeyBuilder.fromAttributes("WorldCupPlayers", Arrays.asList(
new DynamoDbAttributeValueDto("country", DynamoDbScalarTypeDto.STRING, "Argentina"),
new DynamoDbAttributeValueDto("fifaId", DynamoDbScalarTypeDto.NUMBER, "10"),
new DynamoDbAttributeValueDto("captain", DynamoDbScalarTypeDto.BOOLEAN, "true")
)));
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,71 @@
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.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;
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.
*/
public class DynamoDbHandler {

/** Commands captured from the SUT and awaiting evaluation. */
private final List<DynamoDbCommand> commands = new ArrayList<>();

/** Heuristic results accumulated for the current action. */
private final List<DynamoDbCommandWithDistance> evaluatedCommands = new ArrayList<>();

/** Failed reads accumulated for insertion generation during the current action. */
private final List<DynamoDbFailedQuery> failedQueries = new ArrayList<>();

/** Canonical keys used to avoid reporting duplicate inferred insertions. */
private final Set<String> insertionKeys = new LinkedHashSet<>();
Comment thread
aschenzle marked this conversation as resolved.

/** 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;

/**
* Creates a handler with heuristic calculation enabled.
*/
public DynamoDbHandler() {
calculateHeuristics = true;
extractDynamoDbExecution = true;
}

/**
Expand All @@ -40,6 +74,8 @@ public DynamoDbHandler() {
public void reset() {
commands.clear();
evaluatedCommands.clear();
failedQueries.clear();
insertionKeys.clear();
}

/**
Expand All @@ -58,6 +94,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.
*
Expand All @@ -84,7 +136,7 @@ public void handle(DynamoDbCommand command) {
* @return evaluated commands for the current action
*/
public List<DynamoDbCommandWithDistance> getEvaluatedDynamoDbCommands() {
if (!calculateHeuristics) {
if (!calculateHeuristics && !extractDynamoDbExecution) {
commands.clear();
return Collections.emptyList();
}
Expand All @@ -97,6 +149,15 @@ public List<DynamoDbCommandWithDistance> 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.
*
Expand Down Expand Up @@ -131,14 +192,123 @@ private void evaluateCommand(DynamoDbCommand command, Map<String, List<Map<Strin
}
double distance = calculator.computeDistance(
parsed.getKeyCondition(), parsed.getFilterExpression(), items);
evaluatedCommands.add(new DynamoDbCommandWithDistance(command, tableName,
new DynamoDbDistanceWithMetrics(distance, items.size(), false)));
DynamoDbDistanceWithMetrics metrics = new DynamoDbDistanceWithMetrics(distance, items.size(), false);
if (calculateHeuristics) {
evaluatedCommands.add(new DynamoDbCommandWithDistance(command, tableName, metrics));
}
if (extractDynamoDbExecution && distance > 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<String, DynamoDbAttributeValueDto> attributes = new LinkedHashMap<>();
if (!evaluateEqualitiesAsFlatAttributes(parsed.getKeyCondition(), attributes)
|| !evaluateEqualitiesAsFlatAttributes(parsed.getFilterExpression(), attributes)
|| attributes.isEmpty()) {
return;
}

List<DynamoDbAttributeValueDto> insertionAttributes = new ArrayList<>(attributes.values());
String insertionKey = DynamoDbInsertionKeyBuilder.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.
* <p>
* 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<String, DynamoDbAttributeValueDto> attributes) {
if (operation == null) {
return true;
}

List<QueryOperation> pending = new ArrayList<>();
pending.add(operation);
while (!pending.isEmpty()) {
QueryOperation current = pending.remove(pending.size() - 1);
if (current instanceof AndOperation) {
List<QueryOperation> 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.
*
Expand Down
Loading
Loading