From dcef0916f4a5720adf4383701c11714fa249cfc2 Mon Sep 17 00:00:00 2001 From: hghianni Date: Wed, 17 May 2023 16:53:00 -0300 Subject: [PATCH 01/20] Early approach to supporting Mongo data generation --- .../controller/api/ControllerConstants.java | 2 + .../api/dto/ExtraHeuristicsDto.java | 4 +- .../dto/database/execution/FailedQuery.java | 32 +++++ .../database/execution/MongoExecutionDto.java | 9 ++ .../operations/MongoDatabaseCommandDto.java | 8 ++ .../operations/MongoInsertionDto.java | 11 ++ .../operations/MongoInsertionEntryDto.java | 6 + .../operations/MongoInsertionResultsDto.java | 8 ++ .../controller/EmbeddedSutController.java | 5 + .../controller/ExternalSutController.java | 8 ++ .../client/java/controller/SutHandler.java | 5 + .../controller/internal/EMController.java | 61 ++++++++++ .../controller/internal/SutController.java | 22 ++++ .../controller/internal/db/MongoHandler.java | 113 +++++++++++++++++- .../java/controller/mongo/MongoOperation.java | 18 +++ .../controller/mongo/MongoScriptRunner.java | 50 ++++++++ .../java/controller/mongo/dsl/MongoDsl.java | 100 ++++++++++++++++ .../mongo/dsl/MongoSequenceDsl.java | 12 ++ .../mongo/dsl/MongoStatementDsl.java | 33 +++++ .../methodreplacement/ReplacementList.java | 1 + .../MongoRepositoryClassReplacement.java | 48 ++++++++ .../instrumentation/external/Command.java | 2 +- .../external/ServerController.java | 4 + .../staticstate/ExecutionTracer.java | 4 + .../core/problem/rest/SamplerVerifierTest.kt | 8 +- .../rest/service/resource/ResourceTestBase.kt | 8 +- .../kotlin/org/evomaster/core/EMConfig.kt | 8 ++ .../core/database/DatabaseExecutor.kt | 10 +- .../core/mongo/MongoActionGeneBuilder.kt | 26 ++++ .../org/evomaster/core/mongo/MongoDbAction.kt | 52 ++++++++ .../core/mongo/MongoDbActionResult.kt | 36 ++++++ .../core/mongo/MongoDbActionTransformer.kt | 36 ++++++ .../evomaster/core/mongo/MongoExecution.kt | 13 ++ .../core/mongo/MongoInsertBuilder.kt | 9 ++ .../org/evomaster/core/output/MongoWriter.kt | 86 +++++++++++++ .../core/output/service/ApiTestCaseWriter.kt | 35 ++++-- .../core/output/service/TestSuiteWriter.kt | 7 ++ .../api/service/ApiWsStructureMutator.kt | 52 ++++++++ .../enterprise/EnterpriseIndividual.kt | 25 +++- .../enterprise/service/EnterpriseFitness.kt | 40 ++++++- .../enterprise/service/EnterpriseSampler.kt | 24 ++++ .../core/problem/graphql/GraphQLIndividual.kt | 2 + .../core/problem/rest/RestIndividual.kt | 7 +- .../rest/resource/RestResourceCalls.kt | 11 +- .../core/problem/rest/service/RestFitness.kt | 2 + .../rest/service/RestResourceFitness.kt | 3 + .../core/problem/rpc/RPCIndividual.kt | 2 + .../service/RemoteControllerImplementation.kt | 26 +++- .../org/evomaster/core/search/ActionFilter.kt | 5 + .../evomaster/core/search/EvaluatedAction.kt | 6 +- .../core/search/EvaluatedIndividual.kt | 9 +- .../org/evomaster/core/search/FitnessValue.kt | 23 ++++ .../evomaster/core/search/GroupsOfChildren.kt | 2 + .../org/evomaster/core/search/Individual.kt | 2 +- .../org/evomaster/core/search/Solution.kt | 5 + .../ImpactsOfIndividual.kt | 3 +- .../InitializationActionImpacts.kt | 3 +- .../core/database/SqlInsertBuilderTest.kt | 8 +- .../core/output/service/FakeController.kt | 6 + .../external/service/DummyController.kt | 8 +- .../rest/individual/RestIndividualTestBase.kt | 9 +- .../rest/resource/ResourceNodeWithDbTest.kt | 8 +- .../spring/rest/mongo/MongoController.java | 8 ++ 63 files changed, 1147 insertions(+), 52 deletions(-) create mode 100644 client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java create mode 100644 client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/MongoExecutionDto.java create mode 100644 client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoDatabaseCommandDto.java create mode 100644 client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java create mode 100644 client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionEntryDto.java create mode 100644 client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionResultsDto.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoStatementDsl.java create mode 100644 client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MongoRepositoryClassReplacement.java create mode 100644 core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt create mode 100644 core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt create mode 100644 core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionResult.kt create mode 100644 core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt create mode 100644 core/src/main/kotlin/org/evomaster/core/mongo/MongoExecution.kt create mode 100644 core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt create mode 100644 core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/ControllerConstants.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/ControllerConstants.java index d4403e4fe4..51be312251 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/ControllerConstants.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/ControllerConstants.java @@ -26,5 +26,7 @@ public class ControllerConstants { public static final String DATABASE_COMMAND = "/databaseCommand"; + public static final String MONGO_INSERTION = "/mongoInsertion"; + public static final String POST_SEARCH_ACTION = "/postSearchAction"; } 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 bcf1ac1b73..acc5f8d908 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,7 +1,7 @@ package org.evomaster.client.java.controller.api.dto; import org.evomaster.client.java.controller.api.dto.database.execution.ExecutionDto; - +import org.evomaster.client.java.controller.api.dto.database.execution.MongoExecutionDto; import java.util.ArrayList; import java.util.List; @@ -19,4 +19,6 @@ public class ExtraHeuristicsDto { public List heuristics = new ArrayList<>(); public ExecutionDto databaseExecutionDto; + + public MongoExecutionDto mongoExecutionDto; } diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java new file mode 100644 index 0000000000..06127ecaff --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java @@ -0,0 +1,32 @@ +package org.evomaster.client.java.controller.api.dto.database.execution; + +import java.util.Map; + +public class FailedQuery { + // Add database + public FailedQuery(String collection, Class documentsType, Map accessedFields) { + this.collection = collection; + this.documentsType = documentsType; + this.accessedFields = accessedFields; + } + + public FailedQuery(){ + this.collection = ""; + } + + private final String collection; + private Class documentsType; + private Map accessedFields; + + public String getCollection() { + return collection; + } + + public Map getAccessedFields() { + return accessedFields; + } + + public Class getDocumentsType() { + return documentsType; + } +} diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/MongoExecutionDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/MongoExecutionDto.java new file mode 100644 index 0000000000..fa4341dec3 --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/MongoExecutionDto.java @@ -0,0 +1,9 @@ +package org.evomaster.client.java.controller.api.dto.database.execution; + + +import java.util.ArrayList; +import java.util.List; + +public class MongoExecutionDto { + public List failedQueries = new ArrayList<>(); +} diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoDatabaseCommandDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoDatabaseCommandDto.java new file mode 100644 index 0000000000..f76beea4b0 --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoDatabaseCommandDto.java @@ -0,0 +1,8 @@ +package org.evomaster.client.java.controller.api.dto.database.operations; + +import java.util.ArrayList; +import java.util.List; + +public class MongoDatabaseCommandDto { + public List insertions = new ArrayList<>(); +} diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java new file mode 100644 index 0000000000..59b6b9f4c6 --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java @@ -0,0 +1,11 @@ +package org.evomaster.client.java.controller.api.dto.database.operations; + +import java.util.ArrayList; +import java.util.List; + +public class MongoInsertionDto { + + public String collectionName; + + public List data = new ArrayList<>(); +} diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionEntryDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionEntryDto.java new file mode 100644 index 0000000000..a23e0c3706 --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionEntryDto.java @@ -0,0 +1,6 @@ +package org.evomaster.client.java.controller.api.dto.database.operations; + +public class MongoInsertionEntryDto { + public String fieldName; + public String value; +} diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionResultsDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionResultsDto.java new file mode 100644 index 0000000000..899442d640 --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionResultsDto.java @@ -0,0 +1,8 @@ +package org.evomaster.client.java.controller.api.dto.database.operations; + +import java.util.ArrayList; +import java.util.List; + +public class MongoInsertionResultsDto { + public List executionResults = new ArrayList<>(); +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/EmbeddedSutController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/EmbeddedSutController.java index 297127c65c..e4897e0006 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/EmbeddedSutController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/EmbeddedSutController.java @@ -87,6 +87,11 @@ public final void setExecutingInitSql(boolean executingInitSql) { ExecutionTracer.setExecutingInitSql(executingInitSql); } + @Override + public final void setExecutingInitMongo(boolean executingInitMongo) { + ExecutionTracer.setExecutingInitMongo(executingInitMongo); + } + @Override public final void setExecutingAction(boolean executingAction){ ExecutionTracer.setExecutingAction(executingAction); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/ExternalSutController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/ExternalSutController.java index 0331d61a31..a1c921ce0f 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/ExternalSutController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/ExternalSutController.java @@ -460,6 +460,14 @@ public final void setExecutingInitSql(boolean executingInitSql) { ExecutionTracer.setExecutingInitSql(executingInitSql); } + @Override + public final void setExecutingInitMongo(boolean executingInitMongo) { + checkInstrumentation(); + serverController.setExecutingInitMongo(executingInitMongo); + // sync executingInitMongo on the local ExecutionTracer + ExecutionTracer.setExecutingInitMongo(executingInitMongo); + } + @Override public final void setExecutingAction(boolean executingAction){ checkInstrumentation(); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/SutHandler.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/SutHandler.java index 5b55a9cb70..a512d7b2fc 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/SutHandler.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/SutHandler.java @@ -2,6 +2,8 @@ import org.evomaster.client.java.controller.api.dto.database.operations.InsertionDto; import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto; import org.evomaster.client.java.controller.db.DbCleaner; import org.evomaster.client.java.controller.db.SqlScriptRunner; import org.evomaster.client.java.controller.db.SqlScriptRunnerCached; @@ -89,6 +91,7 @@ default void setupForGeneratedTest(){} */ InsertionResultsDto execInsertionsIntoDatabase(List insertions, InsertionResultsDto... previous); + MongoInsertionResultsDto execInsertionsIntoMongoDatabase(List insertions); /** *

@@ -177,6 +180,8 @@ default void extractRPCSchema(){} List getDbSpecifications(); + default Object getMongoConnection() {return null;} + /** *

diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java index f126e33a71..ce0c4fc6f4 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java @@ -5,7 +5,10 @@ import org.evomaster.client.java.controller.api.dto.*; import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto; import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoDatabaseCommandDto; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto; import org.evomaster.client.java.controller.api.dto.problem.*; +import org.evomaster.client.java.controller.mongo.MongoScriptRunner; import org.evomaster.client.java.controller.problem.*; import org.evomaster.client.java.controller.db.QueryResult; import org.evomaster.client.java.controller.db.SqlScriptRunner; @@ -720,4 +723,62 @@ going to be instrumented (as not in org.evomaster) sutController.setExecutingInitSql(false); } } + + @Path(ControllerConstants.MONGO_INSERTION) + @Consumes(Formats.JSON_V1) + @POST + public Response executeMongoInsertion(MongoDatabaseCommandDto dto, @Context HttpServletRequest httpServletRequest) { + + assert trackRequestSource(httpServletRequest); + + try { + + sutController.setExecutingInitMongo(true); + + SimpleLogger.debug("Received mongo database command"); + + Object connection = noKillSwitch(sutController::getMongoConnection); + if (connection == null) { + String msg = "No active database connection"; + SimpleLogger.warn(msg); + return Response.status(400).entity(WrappedResponseDto.withError(msg)).build(); + } + + if (dto.insertions == null || dto.insertions.isEmpty()) { + String msg = "No input command"; + SimpleLogger.warn(msg); + return Response.status(400).entity(WrappedResponseDto.withError(msg)).build(); + } + + if (dto.insertions.stream().anyMatch(i -> i.collectionName == null || i.collectionName.isEmpty())) { + String msg = "Insertion with no target collection"; + SimpleLogger.warn(msg); + return Response.status(400).entity(WrappedResponseDto.withError(msg)).build(); + } + + MongoInsertionResultsDto mongoInsertionResultsDto = null; + + + try { + mongoInsertionResultsDto = MongoScriptRunner.execInsert(connection, dto.insertions); + } catch (Exception e) { + String msg = "Failed to execute database command: " + e.getMessage(); + SimpleLogger.warn(msg); + return Response.status(400).entity(WrappedResponseDto.withError(msg)).build(); + } + + if (mongoInsertionResultsDto != null) { + return Response.status(200).entity(WrappedResponseDto.withData(mongoInsertionResultsDto)).build(); + } else { + return Response.status(204).entity(WrappedResponseDto.withNoData()).build(); + } + + } catch (RuntimeException e) { + String msg = "Thrown exception: " + e.getMessage(); + SimpleLogger.error(msg, e); + return Response.status(500).entity(WrappedResponseDto.withError(msg)).build(); + } finally { + sutController.setExecutingInitMongo(false); + } + } } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java index c8a26e195b..05a9b3f241 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java @@ -16,6 +16,8 @@ import org.evomaster.client.java.controller.api.dto.database.execution.ExecutionDto; import org.evomaster.client.java.controller.api.dto.database.operations.InsertionDto; import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto; import org.evomaster.client.java.controller.api.dto.database.schema.DbSchemaDto; import org.evomaster.client.java.controller.api.dto.database.schema.ExtraConstraintsDto; import org.evomaster.client.java.controller.api.dto.problem.RPCProblemDto; @@ -27,6 +29,7 @@ import org.evomaster.client.java.controller.internal.db.MongoHandler; import org.evomaster.client.java.controller.internal.db.SchemaExtractor; import org.evomaster.client.java.controller.internal.db.SqlHandler; +import org.evomaster.client.java.controller.mongo.MongoScriptRunner; import org.evomaster.client.java.controller.problem.ProblemInfo; import org.evomaster.client.java.controller.problem.RPCProblem; import org.evomaster.client.java.controller.problem.rpc.CustomizedNotNullAnnotationForRPCDto; @@ -225,6 +228,21 @@ public InsertionResultsDto execInsertionsIntoDatabase(List inserti } } + @Override + public MongoInsertionResultsDto execInsertionsIntoMongoDatabase(List insertions) { + + Object connection = getMongoConnection(); + if (connection == null) { + throw new IllegalStateException("No connection to mongo database"); + } + + try { + return MongoScriptRunner.execInsert(connection, insertions); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public int getActionIndex(){ return actionIndex; } @@ -371,6 +389,8 @@ public final void computeMongoHeuristics(ExtraHeuristicsDto dto){ )) .forEach(h -> dto.heuristics.add(h)); } + + if(mongoHandler.isExtractMongoExecution()){dto.mongoExecutionDto = mongoHandler.getExecutionDto();} } /** @@ -1089,6 +1109,8 @@ public final String getDatabaseDriverName(){ public abstract void setExecutingInitSql(boolean executingInitSql); + public abstract void setExecutingInitMongo(boolean executingInitMongo); + public abstract void setExecutingAction(boolean executingAction); public abstract BootTimeInfoDto getBootTimeInfoDto(); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java index 92c32c0f32..b87698702e 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java @@ -1,11 +1,19 @@ package org.evomaster.client.java.controller.internal.db; +import org.evomaster.client.java.controller.api.dto.database.execution.FailedQuery; +import org.evomaster.client.java.controller.api.dto.database.execution.MongoExecutionDto; import org.evomaster.client.java.controller.mongo.MongoHeuristicsCalculator; +import org.evomaster.client.java.controller.mongo.MongoOperation; +import org.evomaster.client.java.controller.mongo.QueryParser; +import org.evomaster.client.java.controller.mongo.operations.*; import org.evomaster.client.java.instrumentation.MongoInfo; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * Class used to act upon Mongo commands executed by the SUT @@ -32,9 +40,12 @@ public class MongoHandler { */ private volatile boolean calculateHeuristics; + private final List failedQueries; + public MongoHandler() { distances = new ArrayList<>(); operations = new ArrayList<>(); + failedQueries = new ArrayList<>(); extractMongoExecution = true; calculateHeuristics = true; } @@ -42,6 +53,7 @@ public MongoHandler() { public void reset() { operations.clear(); distances.clear(); + failedQueries.clear(); } public void handle(MongoInfo info) { @@ -62,8 +74,12 @@ public List getDistances() { dist = Double.MAX_VALUE; } distances.add(new MongoOperationDistance(mongoInfo.getQuery(), dist)); - }); + if (dist > 0 ) { + Object collection = mongoInfo.getCollection(); + failedQueries.add(new MongoOperation(collection, mongoInfo.getQuery())); + } + }); operations.clear(); return distances; @@ -95,6 +111,101 @@ private static Iterable getDocuments(Object collection) { } } + public MongoExecutionDto getExecutionDto(){ + MongoExecutionDto dto = new MongoExecutionDto(); + dto.failedQueries = failedQueries.stream().map(this::extractRelevantInfo).collect(Collectors.toList()); + return dto; + } + + private FailedQuery extractRelevantInfo(MongoOperation operation) { + QueryOperation query = new QueryParser().parse(operation.getQuery()); + Object collection = operation.getCollection(); + + Map accessedFields = extractFieldsInQuery(query); + Class documentsType = extractDocumentsType(collection); + + return new FailedQuery("operation.getCollection()", documentsType, accessedFields); + } + + private static Class extractDocumentsType(Object collection) { + try { + Class collectionClass = collection.getClass().getClassLoader().loadClass("com.mongodb.client.MongoCollection"); + return (Class) collectionClass.getMethod("getDocumentClass").invoke(collection); + + } catch (NoSuchMethodException | ClassNotFoundException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private static Map extractFieldsInQuery(QueryOperation operation) { + Map accessedFields = new HashMap<>(); + + if(operation instanceof ComparisonOperation) { + ComparisonOperation op = (ComparisonOperation) operation; + accessedFields.put(op.getFieldName(), op.getValue()); + } + + if(operation instanceof AndOperation) { + AndOperation op = (AndOperation) operation; + op.getConditions().forEach(cond -> accessedFields.putAll(extractFieldsInQuery(cond))); + } + + if(operation instanceof OrOperation) { + OrOperation op = (OrOperation) operation; + op.getConditions().forEach(cond -> accessedFields.putAll(extractFieldsInQuery(cond))); + } + + if(operation instanceof NorOperation) { + NorOperation op = (NorOperation) operation; + op.getConditions().forEach(cond -> accessedFields.putAll(extractFieldsInQuery(cond))); + } + + if(operation instanceof InOperation) { + InOperation op = (InOperation) operation; + accessedFields.put(op.getFieldName(), op.getValues().get(0)); + } + + if(operation instanceof NotInOperation) { + NotInOperation op = (NotInOperation) operation; + accessedFields.put(op.getFieldName(), op.getValues().get(0)); + } + + if(operation instanceof AllOperation) { + AllOperation op = (AllOperation) operation; + accessedFields.put(op.getFieldName(), op.getValues()); + } + + if(operation instanceof SizeOperation) { + SizeOperation op = (SizeOperation) operation; + accessedFields.put(op.getFieldName(), op.getValue()); + } + + if(operation instanceof ExistsOperation) { + ExistsOperation op = (ExistsOperation) operation; + accessedFields.put(op.getFieldName(), null); + } + + if(operation instanceof ModOperation) { + ModOperation op = (ModOperation) operation; + accessedFields.put(op.getFieldName(), op.getDivisor()); + } + + if(operation instanceof TypeOperation) { + TypeOperation op = (TypeOperation) operation; + accessedFields.put(op.getFieldName(), op.getType()); + } + + /* + if(operation instanceof ElemMatchOperation) { + ElemMatchOperation op = (ElemMatchOperation) operation; + accessedFields.put(op.getFieldName(), op.getValue()); + } + + */ + + return accessedFields; + } + public boolean isCalculateHeuristics() { return calculateHeuristics; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoOperation.java new file mode 100644 index 0000000000..aff9ec9367 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoOperation.java @@ -0,0 +1,18 @@ +package org.evomaster.client.java.controller.mongo; +public class MongoOperation { + private final Object query; + private final Object collection; + + public MongoOperation(Object collection, Object query) { + this.collection = collection; + this.query = query; + } + + public Object getCollection() { + return collection; + } + + public Object getQuery() { + return query; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java new file mode 100644 index 0000000000..255146e735 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java @@ -0,0 +1,50 @@ +package org.evomaster.client.java.controller.mongo; + +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Class used to execute Mongo commands + */ +public class MongoScriptRunner { + + /** + * Default constructor + */ + public MongoScriptRunner() { + } + + public static MongoInsertionResultsDto execInsert(Object conn, List insertions){ + + if (insertions == null || insertions.isEmpty()) { + throw new IllegalArgumentException("No data to insert"); + } + + List mongoResults = new ArrayList<>(Collections.nCopies(insertions.size(), false)); + + for (int i = 0; i < insertions.size(); i++) { + + MongoInsertionDto insDto = insertions.get(i); + + /* + try { + insDto.data.forEach(field -> conn.getDatabase("persons").getCollection(insDto.collectionName).insertOne(Document.parse(field.value))); + mongoResults.set(i, true); + } catch (Exception e) { + String msg = "Failed to execute insertion with index " + i + " with Mongo. Error: " + e.getMessage(); + throw new RuntimeException(msg, e); + } + + */ + } + + MongoInsertionResultsDto insertionResultsDto = new MongoInsertionResultsDto(); + insertionResultsDto.executionResults = mongoResults; + return insertionResultsDto; + } + +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java new file mode 100644 index 0000000000..889236447b --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java @@ -0,0 +1,100 @@ +package org.evomaster.client.java.controller.mongo.dsl; + +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionEntryDto; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * DSL (Domain Specific Language) for operations on + * the Mongo Database + */ +public class MongoDsl implements MongoSequenceDsl, MongoStatementDsl{ + + private List list = new ArrayList<>(); + + private final List previousInsertionDtos = new ArrayList<>(); + + private MongoDsl() { + } + + private MongoDsl(List... previous) { + if (previous != null && previous.length > 0){ + Arrays.stream(previous).forEach(previousInsertionDtos::addAll); + } + } + + /** + * @return a DSL object to create SQL operations + */ + public static MongoSequenceDsl mongo() { + + return new MongoDsl(); + } + + /** + * @param previous a DSL object which is executed in the front of this + * @return a DSL object to create SQL operations + */ + public static MongoSequenceDsl mongo(List... previous) { + + return new MongoDsl(previous); + } + + @Override + public MongoStatementDsl insertInto(String collectionName) { + + checkDsl(); + + if (collectionName == null || collectionName.isEmpty()) { + throw new IllegalArgumentException("Unspecified collection"); + } + + MongoInsertionDto dto = new MongoInsertionDto(); + dto.collectionName = collectionName; + list.add(dto); + + return this; + } + + @Override + public MongoStatementDsl d(String printableValue) { + + checkDsl(); + + MongoInsertionEntryDto entry = new MongoInsertionEntryDto(); + entry.value = printableValue; + + current().data.add(entry); + + return this; + } + + @Override + public MongoSequenceDsl and() { + return this; + } + + @Override + public List dtos() { + + List tmp = list; + list = null; + + return tmp; + } + + + private MongoInsertionDto current() { + return list.get(list.size() - 1); + } + + private void checkDsl() { + if (list == null) { + throw new IllegalStateException("DTO was already built for this object"); + } + } + +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java new file mode 100644 index 0000000000..806b915eba --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java @@ -0,0 +1,12 @@ +package org.evomaster.client.java.controller.mongo.dsl; + +public interface MongoSequenceDsl { + + /** + * An insertion operation on the Mongo Database (MongoDB) + * + * @param collectionName the target table in the DB + * @return a statement in which it can be specified the values to add + */ + MongoStatementDsl insertInto(String collectionName); +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoStatementDsl.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoStatementDsl.java new file mode 100644 index 0000000000..8f9e24437e --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoStatementDsl.java @@ -0,0 +1,33 @@ +package org.evomaster.client.java.controller.mongo.dsl; + +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto; + +import java.util.List; + +public interface MongoStatementDsl { + + /** + * Add a value to insert + * + * @param printableValue the value that is going to be inserted, as + * it would be printed as string. + * This means that 5 is represented with "5", + * whereas "5" with "'5'" + * @return the continuation of this statement, in which more values can be added + */ + MongoStatementDsl d(String printableValue); + + /** + * Close the current statement + * @return the sequence object on which new Mongo commands can be added + */ + MongoSequenceDsl and(); + + /** + * Build the DTOs (Data Transfer Object) from this DSL, + * closing it (ie, not usable any longer). + * @return a list of DTOs representing all the insertion SQL commands defined in this DSL. + */ + List dtos(); + +} diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java index f109975fe0..ddaf10f758 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java @@ -49,6 +49,7 @@ public static List getList() { new MatcherClassReplacement(), new MethodClassReplacement(), new MongoClassReplacement(), + new MongoRepositoryClassReplacement(), new OkHttpClient3BuilderClassReplacement(), new OkHttpClient3ClassReplacement(), new OkHttpClientClassReplacement(), diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MongoRepositoryClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MongoRepositoryClassReplacement.java new file mode 100644 index 0000000000..3f5b6220ec --- /dev/null +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MongoRepositoryClassReplacement.java @@ -0,0 +1,48 @@ +package org.evomaster.client.java.instrumentation.coverage.methodreplacement.classes; + +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement; +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast; +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass; +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.UsageFilter; +import org.evomaster.client.java.instrumentation.shared.ReplacementCategory; +import org.evomaster.client.java.instrumentation.shared.ReplacementType; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + + +public class MongoRepositoryClassReplacement extends ThirdPartyMethodReplacementClass { + private static final MongoRepositoryClassReplacement singleton = new MongoRepositoryClassReplacement(); + + @Override + protected String getNameOfThirdPartyTargetClass() { + return "org.springframework.data.mongodb.repository.support.SimpleMongoRepository"; + } + + // This is not working. It may be related to the fact that this constructor seems to be called using reflection. + // When a MongoRepository is created in Spring it uses a MongoCollection under the hood. + // The type of the collection is the default (Document) despite probably the repository was created of some custom type like Person. + // The fact that elements of the collection should be Person is stored in Spring (SimpleMongoRepository). + // The idea is to instrument the constructor of SimpleMongoRepository to obtain the actual type of the collection. + + // Change category to MONGO + @Replacement(replacingStatic = false, + replacingConstructor = true, + type = ReplacementType.TRACKER, id = "SimpleMongoRepository", + usageFilter = UsageFilter.ANY, + category = ReplacementCategory.SQL) + public static void SimpleMongoRepository( + Object mongoRepository, + @ThirdPartyCast(actualType = "org.springframework.data.mongodb.repository.query.MongoEntityInformation") Object metadata, + @ThirdPartyCast(actualType = "org.springframework.data.mongodb.repository.core.MongoOperations") Object mongoOperations) { + try { + Method method = getOriginal(singleton, "SimpleMongoRepository", mongoRepository); + method.invoke(mongoRepository, metadata, mongoOperations); + // Store info + } catch (IllegalAccessException e){ + throw new RuntimeException(e); + } catch (InvocationTargetException e){ + throw (RuntimeException) e.getCause(); + } + } +} 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 7f61fe8690..1c378218e1 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 @@ -8,5 +8,5 @@ */ public enum Command implements Serializable { - NEW_SEARCH, NEW_TEST, TARGETS_INFO, ACK, ACTION_INDEX, ADDITIONAL_INFO, UNITS_INFO, KILL_SWITCH, EXECUTING_INIT_SQL, EXECUTING_ACTION, BOOT_TIME_INFO, EXTRACT_JVM_DTO + NEW_SEARCH, NEW_TEST, TARGETS_INFO, ACK, ACTION_INDEX, ADDITIONAL_INFO, UNITS_INFO, KILL_SWITCH, EXECUTING_INIT_SQL, EXECUTING_INIT_MONGO, 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 9416d4a8be..2fb308227f 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 @@ -204,6 +204,10 @@ public boolean setExecutingInitSql(boolean executingInitSql) { return sendWithDataAndExpectACK(Command.EXECUTING_INIT_SQL, executingInitSql); } + public boolean setExecutingInitMongo(boolean executingInitMongo) { + return sendWithDataAndExpectACK(Command.EXECUTING_INIT_MONGO, executingInitMongo); + } + public boolean setExecutingAction(boolean executingAction){ return sendWithDataAndExpectACK(Command.EXECUTING_ACTION, executingAction); } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java index 7dca36f19a..4e73901a6d 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java @@ -189,6 +189,10 @@ public static void setExecutingInitSql(boolean executingInitSql) { ExecutionTracer.executingInitSql = executingInitSql; } + public static void setExecutingInitMongo(boolean executingInitMongo) { + ExecutionTracer.executingInitMongo = executingInitMongo; + } + public static boolean isExecutingAction() { return executingAction; } diff --git a/core-it/src/test/kotlin/org/evomaster/core/problem/rest/SamplerVerifierTest.kt b/core-it/src/test/kotlin/org/evomaster/core/problem/rest/SamplerVerifierTest.kt index 62e47b79c8..5b92b27325 100644 --- a/core-it/src/test/kotlin/org/evomaster/core/problem/rest/SamplerVerifierTest.kt +++ b/core-it/src/test/kotlin/org/evomaster/core/problem/rest/SamplerVerifierTest.kt @@ -6,9 +6,7 @@ import com.google.inject.Provides import com.google.inject.Singleton import com.netflix.governator.guice.LifecycleInjector import org.evomaster.client.java.controller.api.dto.* -import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto -import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto -import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto +import org.evomaster.client.java.controller.api.dto.database.operations.* import org.evomaster.client.java.controller.api.dto.problem.RestProblemDto import org.evomaster.core.BaseModule import org.evomaster.core.problem.rest.service.ResourceRestModule @@ -370,6 +368,10 @@ class SamplerVerifierTest { return null } + override fun executeMongoDatabaseInsertions(dto: MongoDatabaseCommandDto): MongoInsertionResultsDto? { + return null + } + override fun getSutInfo(): SutInfoDto? { return sutInfoDto } diff --git a/core-it/src/test/kotlin/org/evomaster/core/problem/rest/service/resource/ResourceTestBase.kt b/core-it/src/test/kotlin/org/evomaster/core/problem/rest/service/resource/ResourceTestBase.kt index 1f4d914e77..f93fde25de 100644 --- a/core-it/src/test/kotlin/org/evomaster/core/problem/rest/service/resource/ResourceTestBase.kt +++ b/core-it/src/test/kotlin/org/evomaster/core/problem/rest/service/resource/ResourceTestBase.kt @@ -3,9 +3,7 @@ package org.evomaster.core.problem.rest.service.resource import com.google.inject.Module import com.netflix.governator.lifecycle.LifecycleManager import com.netflix.governator.guice.LifecycleInjector -import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto -import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto -import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto +import org.evomaster.client.java.controller.api.dto.database.operations.* import org.evomaster.client.java.controller.db.SqlScriptRunner import org.evomaster.client.java.controller.internal.db.SchemaExtractor import org.evomaster.core.BaseModule @@ -84,6 +82,10 @@ abstract class ResourceTestBase : ExtractTestBaseH2(), ResourceBasedTestInterfac return null } + override fun executeMongoDatabaseInsertions(dto: MongoDatabaseCommandDto): MongoInsertionResultsDto? { + return null + } + override fun executeDatabaseCommandAndGetQueryResults(dto: DatabaseCommandDto): QueryResultDto? { return SqlScriptRunner.execCommand(connection, dto.command).toDto() } diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index 04b090cd98..81323e91b5 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -525,6 +525,8 @@ class EMConfig { fun shouldGenerateSqlData() = isMIO() && (generateSqlDataWithDSE || generateSqlDataWithSearch) + fun shouldGenerateMongoData() = generateMongoData + fun experimentalFeatures(): List { val properties = getConfigurationProperties() @@ -1046,6 +1048,9 @@ class EMConfig { @Cfg("Enable extracting SQL execution info") var extractSqlExecutionInfo = true + @Cfg("Enable extracting Mongo execution info") + var extractMongoExecutionInfo = true + @Experimental @Cfg("Enable EvoMaster to generate SQL data with direct accesses to the database. Use Dynamic Symbolic Execution") var generateSqlDataWithDSE = false @@ -1053,6 +1058,9 @@ class EMConfig { @Cfg("Enable EvoMaster to generate SQL data with direct accesses to the database. Use a search algorithm") var generateSqlDataWithSearch = true + @Cfg("Enable EvoMaster to generate Mongo data with direct accesses to the database") + var generateMongoData = true + @Cfg("When generating SQL data, how many new rows (max) to generate for each specific SQL Select") @Min(1.0) var maxSqlInitActionsPerMissingData = 5 diff --git a/core/src/main/kotlin/org/evomaster/core/database/DatabaseExecutor.kt b/core/src/main/kotlin/org/evomaster/core/database/DatabaseExecutor.kt index 1163c854dc..8ec33cb36d 100644 --- a/core/src/main/kotlin/org/evomaster/core/database/DatabaseExecutor.kt +++ b/core/src/main/kotlin/org/evomaster/core/database/DatabaseExecutor.kt @@ -1,8 +1,6 @@ package org.evomaster.core.database -import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto -import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto -import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto +import org.evomaster.client.java.controller.api.dto.database.operations.* interface DatabaseExecutor { @@ -24,4 +22,10 @@ interface DatabaseExecutor { * Return the result of whether it success (first) and new pks in such insertions (second), if any */ fun executeDatabaseInsertionsAndGetIdMapping(dto: DatabaseCommandDto): InsertionResultsDto? + + /** + * Execute a the given INSERT MONGO command (in DTO format). + * Return the result of whether it success + */ + fun executeMongoDatabaseInsertions(dto: MongoDatabaseCommandDto): MongoInsertionResultsDto? } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt new file mode 100644 index 0000000000..601c5336e9 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt @@ -0,0 +1,26 @@ +package org.evomaster.core.mongo + +import org.evomaster.core.search.gene.* +import org.evomaster.core.search.gene.collection.ArrayGene +import org.evomaster.core.search.gene.datetime.DateGene +import org.evomaster.core.search.gene.numeric.* +import org.evomaster.core.search.gene.string.StringGene +import java.util.Date + +class MongoActionGeneBuilder { + fun buildGene(fieldName: String, value: T): Gene { + return when (value) { + is Int -> IntegerGene(fieldName, min = Int.MIN_VALUE, max = Int.MAX_VALUE) + is Long -> LongGene(fieldName, min = Long.MIN_VALUE, max = Long.MAX_VALUE) + is Double -> DoubleGene(fieldName, min = Double.MIN_VALUE, max = Double.MAX_VALUE) + is String -> StringGene(name = fieldName, minLength = Int.MIN_VALUE) + is Boolean -> BooleanGene(name = fieldName) + //is Array<*> -> ArrayGene<*>(name = fieldName) + //is Object -> ObjectGene + //is Date -> DateGene + //is null -> NullGene + //Unhandled Types: Binary Data, Object Id, Regular Expression, Javascript, Timestamp, Decimal128, Min/max key + else -> throw IllegalArgumentException("Cannot handle: $fieldName.") + } + } +} diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt new file mode 100644 index 0000000000..c542e9e3b4 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt @@ -0,0 +1,52 @@ +package org.evomaster.core.mongo + +import org.evomaster.core.search.Action +import org.evomaster.core.search.gene.Gene +import org.evomaster.core.search.gene.ObjectGene +import java.util.* + +class MongoDbAction(val collection: String, val documentsType: Class<*>, val accessedFields: Map, computedGenes: List? = null) : Action(listOf()) { + + val genes: List = (computedGenes ?: computeGenes()) .also { addChildren(it) } + + private fun computeGenes(): List { + // I should be as specific as I can with the type the collection's documents should have. + // There are a few ways to get that info: + + // 1) Using , which is the result of collection.getDocumentsClass() + // Spring for example "ignore" this and store type info inside SampleMongoRepository + + // 2) Instrument the constructor of SampleMongoRepository and retrieve the info + // Probably can reuse something from GsonClassReplacement. + + // 3) Extract from the query the fields () used and type of each of them. This probably won't + // work fine as usually a subset of fields is used in a query. But is better than creating a + // Document. + + val genes = + if(documentsType.typeName == "org.bson.Document"){ + // 3) + accessedFields.map { MongoActionGeneBuilder().buildGene(it.key, it.value) } + }else{ + // 1) + documentsType.declaredFields.map { MongoActionGeneBuilder().buildGene(it.name, it.type) } + } + return Collections.singletonList(ObjectGene("BSON", genes)) + } + + override fun getName(): String { + return "MONGO_Find_${collection}_${accessedFields.map { it.key }.sorted().joinToString("_")}" + } + + override fun seeTopGenes(): List { + return genes + } + + override fun shouldCountForFitnessEvaluations(): Boolean { + return false + } + + override fun copyContent(): Action { + return MongoDbAction(collection, documentsType, accessedFields, genes.map(Gene::copy)) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionResult.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionResult.kt new file mode 100644 index 0000000000..09f68ce633 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionResult.kt @@ -0,0 +1,36 @@ +package org.evomaster.core.mongo +import org.evomaster.core.search.Action +import org.evomaster.core.search.ActionResult + +/** + * sql insert action execution result + */ +class MongoDbActionResult : ActionResult { + + constructor(stopping: Boolean = false) : super(stopping) + constructor(other: MongoDbActionResult): super(other) + + companion object{ + const val INSERT_MONGO_EXECUTE_SUCCESSFULLY = "INSERT_MONGO_EXECUTE_SUCCESSFULLY" + } + + override fun copy(): MongoDbActionResult { + return MongoDbActionResult(this) + } + + /** + * @param success specifies whether the INSERT SQL executed successfully + * + * NOTE THAT here for SELECT, the execution result is false by default. + */ + fun setInsertExecutionResult(success: Boolean) = addResultValue(INSERT_MONGO_EXECUTE_SUCCESSFULLY, success.toString()) + + /** + * @return whether the db action executed successfully + */ + fun getInsertExecutionResult() = getResultValue(INSERT_MONGO_EXECUTE_SUCCESSFULLY)?.toBoolean()?:false + + override fun matchedType(action: Action): Boolean { + return action is MongoDbAction + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt new file mode 100644 index 0000000000..2137357f2d --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt @@ -0,0 +1,36 @@ +package org.evomaster.core.mongo + +import org.evomaster.client.java.controller.api.dto.database.operations.* + +object MongoDbActionTransformer { + + fun transform(insertions: List) : MongoDatabaseCommandDto { + + val list = mutableListOf() + + for (i in 0 until insertions.size) { + + val action = insertions[i] + + val insertion = MongoInsertionDto().apply { collectionName = action.collection } + + val g = action.seeTopGenes().first() + val entry = MongoInsertionEntryDto() + + // If is printed as JSON there might a problem + // A Document(from Mongo) can be created from a JSON but some info might be lost + // Preferably is created from a EJSON (Extended JSON) as JSON can only directly represent a + // subset of the types supported by BSON. + // Maybe we can create a new OutputFormat + + entry.value = g.getValueAsPrintableString() + entry.fieldName = g.getVariableName() + + insertion.data.add(entry) + + list.add(insertion) + } + + return MongoDatabaseCommandDto().apply { this.insertions = list } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoExecution.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoExecution.kt new file mode 100644 index 0000000000..f4dbff3051 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoExecution.kt @@ -0,0 +1,13 @@ +package org.evomaster.core.mongo + +import org.evomaster.client.java.controller.api.dto.database.execution.FailedQuery +import org.evomaster.client.java.controller.api.dto.database.execution.MongoExecutionDto +class MongoExecution(val failedQueries: List) { + + companion object { + + fun fromDto(dto: MongoExecutionDto?): MongoExecution { + return MongoExecution(dto!!.failedQueries) + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt new file mode 100644 index 0000000000..8df7126243 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt @@ -0,0 +1,9 @@ +package org.evomaster.core.mongo + +class MongoInsertBuilder { + + fun createMongoInsertionAction(collection: String, documentsType: Class<*>, accessedFields: Map): List { + // FIX + return mutableListOf(MongoDbAction(collection, documentsType, accessedFields)) + } +} diff --git a/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt b/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt new file mode 100644 index 0000000000..b6e78002d1 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt @@ -0,0 +1,86 @@ +package org.evomaster.core.output + +import org.apache.commons.lang3.StringEscapeUtils +import org.evomaster.core.search.EvaluatedMongoDbAction +import org.evomaster.core.search.gene.ObjectGene + +/** + * Class used to generate the code in the test dealing with insertion of + * data into MONGO databases. + */ +object MongoWriter { + + /** + * generate mongo insert actions into test case based on [mongoDbInitialization] + * @param format is the format of tests to be generated + * @param mongoDbInitialization contains the db actions to be generated + * @param lines is used to save generated textual lines with respects to [mongoDbInitialization] + * @param groupIndex specifies an index of a group of this [mongoDbInitialization] + * @param insertionVars is a list of previous variable names of the db actions (Pair.first) and corresponding results (Pair.second) + * @param skipFailure specifies whether to skip failure tests + */ + fun handleMongoDbInitialization( + format: OutputFormat, + mongoDbInitialization: List, + lines: Lines, + groupIndex: String ="", + insertionVars: MutableList>, + skipFailure: Boolean) { + + //if (dbInitialization.isEmpty() || dbInitialization.none { !it.action.representExistingData && (!skipFailure || it.result.getInsertExecutionResult())}) { + //return + //} + + val insertionVar = "insertions${groupIndex}" + val insertionVarResult = "${insertionVar}result" + val previousVar = insertionVars.joinToString(", ") { it.first } + val previousVarResults = insertionVars.joinToString(", ") { it.second } + mongoDbInitialization + .filter { !skipFailure || it.result.getInsertExecutionResult()} + .forEachIndexed { index, evaluatedMongoDbAction -> + + lines.add(when { + index == 0 && format.isJava() -> "List $insertionVar = mongo($previousVar)" + index == 0 && format.isKotlin() -> "val $insertionVar = mongo($previousVar)" + else -> ".and()" + } + ".insertInto(\"${evaluatedMongoDbAction.action.collection}\")") + + if (index == 0) { + lines.indent() + } + + lines.indented { + evaluatedMongoDbAction.action.seeTopGenes() + .filter { it.isPrintable() } + .forEach { g -> + when (g) { + is ObjectGene -> { + val printableValue = StringEscapeUtils.escapeJava(g.getValueAsPrintableString()) + lines.add(".d(\"$printableValue\")") + } + + else -> { + } + } + } + + } + } + + lines.add(".dtos()") + lines.appendSemicolon(format) + + lines.deindent() + + lines.add(when{ + format.isJava() -> "MongoInsertionResultsDto " + format.isKotlin() -> "val " + else -> throw IllegalStateException("Not support mongo insertions generation for $format") + } + "$insertionVarResult = controller.execInsertionsIntoMongoDatabase(${if (previousVarResults.isBlank()) insertionVar else "$insertionVar, $previousVarResults"})") + lines.appendSemicolon(format) + + insertionVars.add(insertionVar to insertionVarResult) + + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/output/service/ApiTestCaseWriter.kt b/core/src/main/kotlin/org/evomaster/core/output/service/ApiTestCaseWriter.kt index 937c5d8340..7e8537c51d 100644 --- a/core/src/main/kotlin/org/evomaster/core/output/service/ApiTestCaseWriter.kt +++ b/core/src/main/kotlin/org/evomaster/core/output/service/ApiTestCaseWriter.kt @@ -4,12 +4,12 @@ import com.google.gson.Gson import com.google.gson.JsonSyntaxException import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionResult -import org.evomaster.core.output.CookieWriter -import org.evomaster.core.output.Lines -import org.evomaster.core.output.SqlWriter -import org.evomaster.core.output.TokenWriter +import org.evomaster.core.mongo.MongoDbAction +import org.evomaster.core.mongo.MongoDbActionResult +import org.evomaster.core.output.* import org.evomaster.core.search.EvaluatedDbAction import org.evomaster.core.search.EvaluatedIndividual +import org.evomaster.core.search.EvaluatedMongoDbAction import org.evomaster.core.search.gene.utils.GeneUtils abstract class ApiTestCaseWriter : TestCaseWriter() { @@ -33,20 +33,35 @@ abstract class ApiTestCaseWriter : TestCaseWriter() { TokenWriter.handleGettingTokens(format, ind, lines, baseUrlOfSut, this) //FIXME this doing initializations, not field declaration - val initializingActions = ind.individual.seeInitializingActions().filterIsInstance() - val initializingActionResults = (ind.seeResults(initializingActions)) - if (initializingActionResults.any { (it as? DbActionResult) == null }) + //REFACTOR TO HANDLE MULTIPLE DATABASES + val initializingSqlActions = ind.individual.seeInitializingActions().filterIsInstance() + val initializingSqlActionResults = (ind.seeResults(initializingSqlActions)) + if (initializingSqlActionResults.any { (it as? DbActionResult) == null }) throw IllegalStateException("the type of results are expected as DbActionResults") + val initializingMongoActions = ind.individual.seeInitializingActions().filterIsInstance() + val initializingMongoResults = (ind.seeResults(initializingMongoActions)) + if (initializingMongoResults.any { (it as? MongoDbActionResult) == null }) + throw IllegalStateException("the type of results are expected as MongoDbActionResults") - if (ind.individual.seeInitializingActions().isNotEmpty()) { + + if (initializingSqlActions.isNotEmpty()) { SqlWriter.handleDbInitialization( format, - initializingActions.indices.map { - EvaluatedDbAction(initializingActions[it], initializingActionResults[it] as DbActionResult) + initializingSqlActions.indices.map { + EvaluatedDbAction(initializingSqlActions[it], initializingSqlActions[it] as DbActionResult) }, lines, insertionVars = insertionVars, skipFailure = config.skipFailureSQLInTestFile) } + + if (initializingMongoActions.isNotEmpty()) { + MongoWriter.handleMongoDbInitialization( + format, + initializingMongoActions.indices.map { + EvaluatedMongoDbAction(initializingMongoActions[it], initializingMongoResults[it] as MongoDbActionResult) + }, + lines, insertionVars = insertionVars, skipFailure = config.skipFailureSQLInTestFile) + } } /** diff --git a/core/src/main/kotlin/org/evomaster/core/output/service/TestSuiteWriter.kt b/core/src/main/kotlin/org/evomaster/core/output/service/TestSuiteWriter.kt index 21ae6f7b07..5bb3aaa57a 100644 --- a/core/src/main/kotlin/org/evomaster/core/output/service/TestSuiteWriter.kt +++ b/core/src/main/kotlin/org/evomaster/core/output/service/TestSuiteWriter.kt @@ -2,6 +2,7 @@ package org.evomaster.core.output.service import com.google.inject.Inject import org.evomaster.client.java.controller.api.dto.database.operations.InsertionDto +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto import org.evomaster.core.EMConfig import org.evomaster.core.output.* import org.evomaster.core.output.service.TestWriterUtils.Companion.getWireMockVariableName @@ -367,6 +368,12 @@ class TestSuiteWriter { addImport(InsertionDto::class.qualifiedName!!, lines) } + if(solution.hasAnyMongoAction()) { + addImport("org.evomaster.client.java.controller.mongo.dsl.MongoDsl.mongo", lines, true) + addImport("org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto", lines) + addImport(MongoInsertionDto::class.qualifiedName!!, lines) + } + // TODO: BMR - this is temporarily added as WiP. Should we have a more targeted import (i.e. not import everything?) if (config.enableBasicAssertions) { diff --git a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt index 803f900dba..5d03a57350 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt @@ -1,11 +1,13 @@ package org.evomaster.core.problem.api.service import com.google.inject.Inject +import org.evomaster.client.java.controller.api.dto.database.execution.FailedQuery import org.evomaster.core.EMConfig import org.evomaster.core.Lazy import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionUtils import org.evomaster.core.database.SqlInsertBuilder +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.problem.api.ApiWsIndividual import org.evomaster.core.problem.enterprise.EnterpriseActionGroup import org.evomaster.core.problem.externalservice.httpws.service.HarvestActualHttpWsResponseHandler @@ -143,6 +145,39 @@ abstract class ApiWsStructureMutator : StructureMutator() { sampler: ApiWsSampler ) { addInitializingDbActions(individual, mutatedGenes, sampler) + addInitializingMongoDbActions(individual, mutatedGenes, sampler) + } + + private fun addInitializingMongoDbActions( + individual: EvaluatedIndividual<*>, + mutatedGenes: MutatedGeneSpecification?, + sampler: ApiWsSampler + ) { + if (!config.shouldGenerateMongoData()) { + return + } + + val ind = individual.individual as? T + ?: throw IllegalArgumentException("Invalid individual type") + + val fw = individual.fitness.getViewOfAggregatedFailedFind() + + if (fw.isEmpty()) { + return + } + + val old = mutableListOf().plus(ind.seeInitializingActions().filterIsInstance()) + + val addedInsertions = handleFailedFind(ind, fw, mutatedGenes, sampler) + + // update impact based on added genes + if (mutatedGenes != null && config.isEnabledArchiveGeneSelection()) { + individual.updateImpactGeneDueToAddedInitializationGenes( + mutatedGenes, + old, + addedInsertions + ) + } } private fun addInitializingDbActions( @@ -279,6 +314,23 @@ abstract class ApiWsStructureMutator : StructureMutator() { return addedInsertions } + private fun handleFailedFind( + ind: T, + ff: List, + mutatedGenes: MutatedGeneSpecification?, sampler: ApiWsSampler + ): MutableList>? { + + val addedInsertions = if (mutatedGenes != null) mutableListOf>() else null + + ff.forEach { + val insertions = sampler.sampleMongoInsertion(it.collection, it.documentsType, it.accessedFields) + ind.addInitializingMongoDbActions(actions = insertions) + addedInsertions?.add(insertions) + } + + return addedInsertions + } + private fun findMissing(fw: Map>, dbactions: List): Map> { return fw.filter { e -> diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt index e6f7a09af8..cff492e57c 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt @@ -3,6 +3,7 @@ package org.evomaster.core.problem.enterprise import org.evomaster.core.Lazy import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionUtils +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.problem.api.ApiWsIndividual import org.evomaster.core.problem.externalservice.ApiExternalServiceAction import org.evomaster.core.problem.graphql.GraphQLAction @@ -12,6 +13,7 @@ import org.evomaster.core.search.service.Randomness import org.evomaster.core.search.tracer.TrackOperator import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.util.* /** @@ -74,7 +76,8 @@ abstract class EnterpriseIndividual( } //TODO in future ll need to refactor to handle multiple databases and NoSQL ones - val db = ChildGroup(GroupsOfChildren.INITIALIZATION_SQL,{e -> e is ActionComponent && e.flatten().all { a -> a is DbAction }}, + //CHANGE: This is momentary for testing. Needs refactor to handle multiple databases + val db = ChildGroup(GroupsOfChildren.INITIALIZATION_MONGO,{e -> e is ActionComponent && e.flatten().all { a -> a is MongoDbAction }}, if(sizeDb==0) -1 else 0 , if(sizeDb==0) -1 else sizeDb-1 ) @@ -101,10 +104,12 @@ abstract class EnterpriseIndividual( ActionFilter.MAIN_EXECUTABLE -> groupsView()!!.getAllInGroup(GroupsOfChildren.MAIN) .flatMap { (it as ActionComponent).flatten() } .filter { it !is DbAction && it !is ApiExternalServiceAction } - ActionFilter.INIT -> groupsView()!!.getAllInGroup(GroupsOfChildren.INITIALIZATION_SQL).flatMap { (it as ActionComponent).flatten() } + //CHANGE: This is momentary for testing. Needs refactor to handle multiple databases + ActionFilter.INIT -> groupsView()!!.getAllInGroup(GroupsOfChildren.INITIALIZATION_MONGO).flatMap { (it as ActionComponent).flatten() } // WARNING: this can still return DbAction and External ones... ActionFilter.NO_INIT -> groupsView()!!.getAllInGroup(GroupsOfChildren.MAIN).flatMap { (it as ActionComponent).flatten() } ActionFilter.ONLY_SQL -> seeAllActions().filterIsInstance() + ActionFilter.ONLY_MONGO -> seeAllActions().filterIsInstance() ActionFilter.NO_SQL -> seeAllActions().filter { it !is DbAction } ActionFilter.ONLY_EXTERNAL_SERVICE -> seeAllActions().filterIsInstance() ActionFilter.NO_EXTERNAL_SERVICE -> seeAllActions().filter { it !is ApiExternalServiceAction } @@ -145,6 +150,8 @@ abstract class EnterpriseIndividual( */ fun seeDbActions() : List = seeActions(ActionFilter.ONLY_SQL) as List + fun seeMongoDbActions() : List = seeActions(ActionFilter.ONLY_MONGO) as List + /** * return a list of all external service actions in [this] individual * that include all the initializing actions among the main actions @@ -194,9 +201,15 @@ abstract class EnterpriseIndividual( private fun getLastIndexOfDbActionToAdd(): Int = groupsView()!!.endIndexForGroupInsertionInclusive(GroupsOfChildren.INITIALIZATION_SQL) + private fun getLastIndexOfMongoDbActionToAdd(): Int = + groupsView()!!.endIndexForGroupInsertionInclusive(GroupsOfChildren.INITIALIZATION_MONGO) + private fun getFirstIndexOfDbActionToAdd(): Int = groupsView()!!.startIndexForGroupInsertionInclusive(GroupsOfChildren.INITIALIZATION_SQL) + private fun getFirstIndexOfMongoDbActionToAdd(): Int = + groupsView()!!.startIndexForGroupInsertionInclusive(GroupsOfChildren.INITIALIZATION_MONGO) + /** * add [actions] at [relativePosition] * if [relativePosition] = -1, append the [actions] at the end @@ -209,6 +222,14 @@ abstract class EnterpriseIndividual( } } + fun addInitializingMongoDbActions(relativePosition: Int=-1, actions: List){ + if (relativePosition < 0) { + addChildrenToGroup(getLastIndexOfMongoDbActionToAdd(), actions, GroupsOfChildren.INITIALIZATION_MONGO) + } else{ + addChildrenToGroup(getFirstIndexOfMongoDbActionToAdd()+relativePosition, actions, GroupsOfChildren.INITIALIZATION_MONGO) + } + } + private fun resetInitializingActions(actions: List){ killChildren { it is DbAction } // TODO: Can be merged with DbAction later diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseFitness.kt index c0c25e5901..9465f35bcc 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseFitness.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseFitness.kt @@ -11,7 +11,10 @@ import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionResult import org.evomaster.core.database.DbActionTransformer import org.evomaster.core.logging.LoggingUtil -import org.evomaster.core.problem.api.service.ApiWsFitness +import org.evomaster.core.mongo.MongoDbAction +import org.evomaster.core.mongo.MongoDbActionResult +import org.evomaster.core.mongo.MongoDbActionTransformer +import org.evomaster.core.mongo.MongoExecution import org.evomaster.core.remote.service.RemoteController import org.evomaster.core.search.Action import org.evomaster.core.search.ActionResult @@ -125,6 +128,31 @@ abstract class EnterpriseFitness : FitnessFunction() where T : Individual return true } + fun doMongoDbCalls(allDbActions: List, actionResults: MutableList) : Boolean { + + if (allDbActions.isEmpty()) { + return true + } + + val mongoDbResults = (allDbActions.indices).map { MongoDbActionResult() } + actionResults.addAll(mongoDbResults) + + val dto = try { + MongoDbActionTransformer.transform(allDbActions) + }catch (e : IllegalArgumentException){ + throw e + } + + val mongoResults = rc.executeMongoDatabaseInsertions(dto) + val executedResults = mongoResults?.executionResults + + executedResults?.forEachIndexed { index, b -> + mongoDbResults[index].setInsertExecutionResult(b) + } + + return true + } + protected fun registerNewAction(action: Action, index: Int){ rc.registerNewAction(getActionDto(action, index)) } @@ -241,5 +269,15 @@ abstract class EnterpriseFitness : FitnessFunction() where T : Individual if (toMinimize.isNotEmpty()) fv.setExtraToMinimize(i, toMinimize) } } + + if (configuration.extractMongoExecutionInfo) { + + for (i in 0 until dto.extraHeuristics.size) { + val extra = dto.extraHeuristics[i] + fv.setMongoExecution(i, MongoExecution.fromDto(extra.mongoExecutionDto)) + } + + fv.aggregateMongoDatabaseData() + } } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt index 8c36f48150..7f74be10e1 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt @@ -4,6 +4,8 @@ import com.google.inject.Inject import org.evomaster.client.java.controller.api.dto.SutInfoDto import org.evomaster.core.database.DbAction import org.evomaster.core.database.SqlInsertBuilder +import org.evomaster.core.mongo.MongoDbAction +import org.evomaster.core.mongo.MongoInsertBuilder import org.evomaster.core.output.OutputFormat import org.evomaster.core.remote.SutProblemException import org.evomaster.core.remote.service.RemoteController @@ -76,6 +78,28 @@ abstract class EnterpriseSampler : Sampler() where T : Individual { return actions } + fun sampleMongoInsertion(collection: String, documentsType: Class<*>, accessedFields: Map): List { + + // Should I use something like this? + //val extraConstraints = randomness.nextBoolean(apc.getExtraSqlDbConstraintsProbability()) + + val actions = MongoInsertBuilder().createMongoInsertionAction(collection, documentsType, accessedFields) + ?: throw IllegalStateException("No MongoDB schema is available") + actions.flatMap{it.seeTopGenes()}.forEach{it.doInitialize(randomness)} + + /* + if (log.isTraceEnabled){ + log.trace("at sampleMongoInsertion, {} insertions are added, and they are {}", actions.size, + actions.joinToString(",") { + if (it is MongoDbAction) it.getResolvedName() else it.getName() + }) + } + + */ + + return actions + } + fun canInsertInto(tableName: String) : Boolean { //TODO might need to refactor/remove once we deal with VIEWs return sqlInsertBuilder?.isTable(tableName) ?: false diff --git a/core/src/main/kotlin/org/evomaster/core/problem/graphql/GraphQLIndividual.kt b/core/src/main/kotlin/org/evomaster/core/problem/graphql/GraphQLIndividual.kt index 1cd3bda269..56db3c0c10 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/graphql/GraphQLIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/graphql/GraphQLIndividual.kt @@ -2,6 +2,7 @@ package org.evomaster.core.problem.graphql import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionUtils +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.problem.api.ApiWsIndividual import org.evomaster.core.problem.enterprise.EnterpriseActionGroup import org.evomaster.core.problem.externalservice.ApiExternalServiceAction @@ -41,6 +42,7 @@ class GraphQLIndividual( GeneFilter.ALL -> seeAllActions().flatMap(Action::seeTopGenes) GeneFilter.NO_SQL -> seeActions(ActionFilter.NO_SQL).flatMap(Action::seeTopGenes) GeneFilter.ONLY_SQL -> seeDbActions().flatMap(DbAction::seeTopGenes) + GeneFilter.ONLY_MONGO -> seeMongoDbActions().flatMap(MongoDbAction::seeTopGenes) GeneFilter.ONLY_EXTERNAL_SERVICE -> seeExternalServiceActions().flatMap(ApiExternalServiceAction::seeTopGenes) } } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/RestIndividual.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/RestIndividual.kt index 8624edee77..913c8a1ae6 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/RestIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/RestIndividual.kt @@ -2,6 +2,7 @@ package org.evomaster.core.problem.rest import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionUtils +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.problem.api.ApiWsIndividual import org.evomaster.core.problem.externalservice.ApiExternalServiceAction import org.evomaster.core.problem.rest.resource.RestResourceCalls @@ -33,7 +34,7 @@ class RestIndividual( ): ApiWsIndividual(trackOperator, index, allActions, childTypeVerifier = { RestResourceCalls::class.java.isAssignableFrom(it) - || DbAction::class.java.isAssignableFrom(it) + || DbAction::class.java.isAssignableFrom(it) || MongoDbAction::class.java.isAssignableFrom(it) }, groups) { companion object{ @@ -76,7 +77,8 @@ class RestIndividual( index, children.map { it.copy() }.toMutableList() as MutableList, mainSize = groupsView()!!.sizeOfGroup(GroupsOfChildren.MAIN), - dbSize = groupsView()!!.sizeOfGroup(GroupsOfChildren.INITIALIZATION_SQL) + //CHANGE: This is momentary for testing. Needs refactor to handle multiple databases + dbSize = groupsView()!!.sizeOfGroup(GroupsOfChildren.INITIALIZATION_MONGO) ) } @@ -104,6 +106,7 @@ class RestIndividual( GeneFilter.ALL -> seeAllActions().flatMap(Action::seeTopGenes) GeneFilter.NO_SQL -> seeActions(ActionFilter.NO_SQL).flatMap(Action::seeTopGenes) GeneFilter.ONLY_SQL -> seeDbActions().flatMap(DbAction::seeTopGenes) + GeneFilter.ONLY_MONGO -> seeMongoDbActions().flatMap(MongoDbAction::seeTopGenes) GeneFilter.ONLY_EXTERNAL_SERVICE -> seeExternalServiceActions().flatMap(ApiExternalServiceAction::seeTopGenes) } } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/resource/RestResourceCalls.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/resource/RestResourceCalls.kt index e86fbb6cb9..3f7945656a 100755 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/resource/RestResourceCalls.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/resource/RestResourceCalls.kt @@ -3,6 +3,7 @@ package org.evomaster.core.problem.rest.resource import org.evomaster.core.Lazy import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionUtils +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.problem.rest.RestCallAction import org.evomaster.core.problem.rest.RestIndividual import org.evomaster.core.problem.api.param.Param @@ -37,7 +38,7 @@ class RestResourceCalls( randomness: Randomness? = null ) : ActionTree( children, - { k -> DbAction::class.java.isAssignableFrom(k) || EnterpriseActionGroup::class.java.isAssignableFrom(k) } + { k -> DbAction::class.java.isAssignableFrom(k) || MongoDbAction::class.java.isAssignableFrom(k) || EnterpriseActionGroup::class.java.isAssignableFrom(k) } ) { constructor( @@ -77,6 +78,10 @@ class RestResourceCalls( get() { return children.flatMap { it.flatten() }.filterIsInstance() } + private val mongoDbActions: List + get() { + return children.flatMap { it.flatten() }.filterIsInstance() + } private val externalServiceActions: List get() { @@ -170,11 +175,13 @@ class RestResourceCalls( fun seeActions(filter: ActionFilter): List { return when (filter) { ActionFilter.ALL -> dbActions.plus(externalServiceActions).plus(mainActions) - ActionFilter.INIT, ActionFilter.ONLY_SQL -> dbActions + ActionFilter.INIT -> dbActions.plus(mongoDbActions) + ActionFilter.ONLY_SQL -> dbActions ActionFilter.NO_INIT, ActionFilter.NO_SQL -> externalServiceActions.plus(mainActions) ActionFilter.MAIN_EXECUTABLE -> mainActions ActionFilter.ONLY_EXTERNAL_SERVICE -> externalServiceActions ActionFilter.NO_EXTERNAL_SERVICE -> dbActions.plus(mainActions) + ActionFilter.ONLY_MONGO -> mongoDbActions } } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestFitness.kt index 8b1f44bda9..126570443d 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestFitness.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestFitness.kt @@ -1,6 +1,7 @@ package org.evomaster.core.problem.rest.service import org.evomaster.core.database.DbAction +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.problem.rest.RestCallAction import org.evomaster.core.problem.rest.RestCallResult import org.evomaster.core.problem.rest.RestIndividual @@ -34,6 +35,7 @@ open class RestFitness : AbstractRestFitness() { val actionResults: MutableList = mutableListOf() doDbCalls(individual.seeInitializingActions().filterIsInstance(), actionResults = actionResults) + doMongoDbCalls(individual.seeInitializingActions().filterIsInstance(), actionResults = actionResults) val fv = FitnessValue(individual.size().toDouble()) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestResourceFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestResourceFitness.kt index f9a32bb2a4..0d9ca43cea 100755 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestResourceFitness.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/RestResourceFitness.kt @@ -3,6 +3,7 @@ package org.evomaster.core.problem.rest.service import com.google.inject.Inject import org.evomaster.core.database.DbAction +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.problem.enterprise.EnterpriseActionGroup import org.evomaster.core.problem.externalservice.httpws.HttpExternalServiceAction import org.evomaster.core.problem.externalservice.httpws.HttpExternalServiceRequest @@ -61,6 +62,8 @@ class RestResourceFitness : AbstractRestFitness() { actionResults ) + doMongoDbCalls(individual.seeInitializingActions().filterIsInstance(), actionResults) + val cookies = getCookies(individual) val tokens = getTokens(individual) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rpc/RPCIndividual.kt b/core/src/main/kotlin/org/evomaster/core/problem/rpc/RPCIndividual.kt index 96ae923894..4bc03e1a36 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rpc/RPCIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rpc/RPCIndividual.kt @@ -3,6 +3,7 @@ package org.evomaster.core.problem.rpc import org.evomaster.core.Lazy import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionUtils +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.problem.api.ApiWsIndividual import org.evomaster.core.problem.enterprise.EnterpriseActionGroup import org.evomaster.core.problem.externalservice.ApiExternalServiceAction @@ -64,6 +65,7 @@ class RPCIndividual( return when (filter) { GeneFilter.ALL -> seeAllActions().flatMap(Action::seeTopGenes) GeneFilter.NO_SQL -> seeActions(ActionFilter.NO_SQL).flatMap(Action::seeTopGenes) + GeneFilter.ONLY_MONGO -> seeMongoDbActions().flatMap(MongoDbAction::seeTopGenes) GeneFilter.ONLY_SQL -> seeDbActions().flatMap(DbAction::seeTopGenes) GeneFilter.ONLY_EXTERNAL_SERVICE -> seeExternalServiceActions().flatMap(ApiExternalServiceAction::seeTopGenes) } diff --git a/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt b/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt index 901a47c286..1b1f4f1fd0 100644 --- a/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt +++ b/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt @@ -4,9 +4,7 @@ import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException import com.google.inject.Inject import org.evomaster.client.java.controller.api.ControllerConstants import org.evomaster.client.java.controller.api.dto.* -import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto -import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto -import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto +import org.evomaster.client.java.controller.api.dto.database.operations.* import org.evomaster.core.EMConfig import org.evomaster.core.logging.LoggingUtil import org.evomaster.core.remote.NoRemoteConnectionException @@ -420,6 +418,10 @@ class RemoteControllerImplementation() : RemoteController{ return executeDatabaseCommandAndGetResults(dto, object : GenericType>() {}) } + override fun executeMongoDatabaseInsertions(dto: MongoDatabaseCommandDto): MongoInsertionResultsDto? { + return executeMongoDatabaseCommandAndGetResults(dto, object : GenericType>() {}) + } + private fun executeDatabaseCommandAndGetResults(dto: DatabaseCommandDto, type: GenericType>): T?{ val response = makeHttpCall { @@ -438,6 +440,24 @@ class RemoteControllerImplementation() : RemoteController{ return dto?.data } + private fun executeMongoDatabaseCommandAndGetResults(dto: MongoDatabaseCommandDto, type: GenericType>): T? { + + val response = makeHttpCall { + getWebTarget() + .path(ControllerConstants.MONGO_INSERTION) + .request() + .post(Entity.entity(dto, MediaType.APPLICATION_JSON_TYPE)) + } + + val dto = getDtoFromResponse(response, type) + + if (!checkResponse(response, dto, "Failed to execute database command")) { + return null + } + + return dto?.data + } + private fun wasSuccess(response: Response?): Boolean { return response?.statusInfo?.family?.equals(Response.Status.Family.SUCCESSFUL) ?: false diff --git a/core/src/main/kotlin/org/evomaster/core/search/ActionFilter.kt b/core/src/main/kotlin/org/evomaster/core/search/ActionFilter.kt index 3beb8301b8..bf97763a7c 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/ActionFilter.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/ActionFilter.kt @@ -26,6 +26,11 @@ enum class ActionFilter { */ ONLY_SQL, + /** + * actions which are MONGO-related actions + */ + ONLY_MONGO, + /** * actions which are not SQL-related actions */ diff --git a/core/src/main/kotlin/org/evomaster/core/search/EvaluatedAction.kt b/core/src/main/kotlin/org/evomaster/core/search/EvaluatedAction.kt index 86dc4fa535..f3253c5b09 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/EvaluatedAction.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/EvaluatedAction.kt @@ -2,6 +2,8 @@ package org.evomaster.core.search import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionResult +import org.evomaster.core.mongo.MongoDbAction +import org.evomaster.core.mongo.MongoDbActionResult open class EvaluatedAction(open val action: Action, open val result: ActionResult) @@ -9,4 +11,6 @@ open class EvaluatedAction(open val action: Action, open val result: ActionResul /** * specialized evaluated db action */ -class EvaluatedDbAction(override val action: DbAction, override val result: DbActionResult) : EvaluatedAction(action, result) \ No newline at end of file +class EvaluatedDbAction(override val action: DbAction, override val result: DbActionResult) : EvaluatedAction(action, result) + +class EvaluatedMongoDbAction(override val action: MongoDbAction, override val result: MongoDbActionResult) : EvaluatedAction(action, result) \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/search/EvaluatedIndividual.kt b/core/src/main/kotlin/org/evomaster/core/search/EvaluatedIndividual.kt index bf382a1bf9..36960f9cfa 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/EvaluatedIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/EvaluatedIndividual.kt @@ -11,6 +11,8 @@ import org.evomaster.core.Lazy import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionResult import org.evomaster.core.logging.LoggingUtil +import org.evomaster.core.mongo.MongoDbAction +import org.evomaster.core.mongo.MongoDbActionResult import org.evomaster.core.problem.externalservice.ApiExternalServiceAction import org.evomaster.core.problem.rest.RestCallAction import org.evomaster.core.problem.rest.RestCallResult @@ -712,7 +714,8 @@ class EvaluatedIndividual( /* if there exist other types of action (ie, not DbAction), this might need to be extended */ - action = individual.seeInitializingActions().filterIsInstance().find { it.seeTopGenes().contains(gene) } + //CHANGE: This is momentary for testing. Needs refactor to handle multiple databases + action = individual.seeInitializingActions().filterIsInstance().find { it.seeTopGenes().contains(gene) } if (action != null) { return impactInfo.getGene( @@ -764,7 +767,7 @@ class EvaluatedIndividual( val allExistingData = individual.seeInitializingActions().filter { it is DbAction && it.representExistingData } val diff = individual.seeInitializingActions() - .filter { !old.contains(it) && it is DbAction && !it.representExistingData } + .filter { !old.contains(it) && ((it is DbAction && !it.representExistingData) || it is MongoDbAction) } if (allExistingData.isNotEmpty()) impactInfo.updateExistingSQLData(allExistingData.size) @@ -802,7 +805,7 @@ class EvaluatedIndividual( Lazy.assert { individual.seeInitializingActions() - .filter { it is DbAction && !it.representExistingData }.size == impactInfo.getSizeOfActionImpacts(true) + .filter { (it is DbAction && !it.representExistingData) || it is MongoDbAction }.size == impactInfo.getSizeOfActionImpacts(true) } } 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 b0b4081e82..4a67ed9af1 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt @@ -1,10 +1,13 @@ package org.evomaster.core.search import org.evomaster.client.java.controller.api.dto.BootTimeInfoDto +import org.evomaster.client.java.controller.api.dto.database.execution.FailedQuery +import org.evomaster.client.java.controller.api.dto.database.execution.MongoExecutionDto import org.evomaster.core.EMConfig import org.evomaster.core.database.DatabaseExecution import org.evomaster.core.EMConfig.SecondaryObjectiveStrategy.* import org.evomaster.core.Lazy +import org.evomaster.core.mongo.MongoExecution import org.evomaster.core.problem.externalservice.httpws.HttpWsExternalService import org.evomaster.core.problem.externalservice.httpws.HttpExternalServiceRequest import org.evomaster.core.search.service.IdMapper @@ -73,12 +76,20 @@ class FitnessValue( */ val databaseExecutions: MutableMap = mutableMapOf() + val mongoExecutions: MutableMap = mutableMapOf() + /** * When SUT does SQL commands using WHERE, keep track of when those "fails" (ie evaluate * to false), in particular the tables and columns in them involved */ private val aggregatedFailedWhere: MutableMap> = mutableMapOf() + /** + * When SUT does MONGO commands using FIND, keep track of when those "fails" (ie evaluate + * to false), in particular the collection and fields in them involved + */ + private val aggregatedFailedFind: MutableList = mutableListOf() + /** * To keep track of accessed external services prevent from adding them again * TODO: This is not completed, not need to consider for review for now @@ -104,7 +115,9 @@ class FitnessValue( copy.targets.putAll(this.targets) copy.extraToMinimize.putAll(this.extraToMinimize) copy.databaseExecutions.putAll(this.databaseExecutions) //note: DatabaseExecution supposed to be immutable + copy.mongoExecutions.putAll(this.mongoExecutions) copy.aggregateDatabaseData() + copy.aggregateMongoDatabaseData() copy.executionTimeMs = executionTimeMs copy.accessedExternalServiceRequests.putAll(this.accessedExternalServiceRequests) copy.accessedDefaultWM.putAll(this.accessedDefaultWM.toMap()) @@ -124,6 +137,10 @@ class FitnessValue( {x -> x.failedWhere} )) } + fun aggregateMongoDatabaseData(){ + aggregatedFailedFind.clear() + mongoExecutions.values.map { aggregatedFailedFind.addAll(it.failedQueries) } + } fun setExtraToMinimize(actionIndex: Int, list: List) { extraToMinimize[actionIndex] = list.sorted() @@ -133,6 +150,10 @@ class FitnessValue( databaseExecutions[actionIndex] = databaseExecution } + fun setMongoExecution(actionIndex: Int, mongoExecution: MongoExecution){ + mongoExecutions[actionIndex] = mongoExecution + } + fun isAnyDatabaseExecutionInfo() = databaseExecutions.isNotEmpty() fun getViewOfData(): Map { @@ -141,6 +162,8 @@ class FitnessValue( fun getViewOfAggregatedFailedWhere() = aggregatedFailedWhere + fun getViewOfAggregatedFailedFind() = aggregatedFailedFind + fun doesCover(target: Int): Boolean { return targets[target]?.distance == MAX_VALUE } diff --git a/core/src/main/kotlin/org/evomaster/core/search/GroupsOfChildren.kt b/core/src/main/kotlin/org/evomaster/core/search/GroupsOfChildren.kt index 10628cf74c..af18fec076 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/GroupsOfChildren.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/GroupsOfChildren.kt @@ -25,6 +25,8 @@ class GroupsOfChildren( const val INITIALIZATION_SQL = "INITIALIZATION_SQL" + const val INITIALIZATION_MONGO = "INITIALIZATION_MONGO" + const val EXTERNAL_SERVICES = "EXTERNAL_SERVICES" const val RESOURCE_SQL = "RESOURCE_SQL" diff --git a/core/src/main/kotlin/org/evomaster/core/search/Individual.kt b/core/src/main/kotlin/org/evomaster/core/search/Individual.kt index e1dbd721ce..4667e81c92 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/Individual.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/Individual.kt @@ -159,7 +159,7 @@ abstract class Individual(override var trackOperator: TrackOperator? = null, throw IllegalStateException("${this::class.java.simpleName}: copyContent() IS NOT IMPLEMENTED") } - enum class GeneFilter { ALL, NO_SQL, ONLY_SQL, ONLY_EXTERNAL_SERVICE } + enum class GeneFilter { ALL, NO_SQL, ONLY_SQL, ONLY_MONGO, ONLY_EXTERNAL_SERVICE } /** * Return a view of all the Genes in this chromosome/individual diff --git a/core/src/main/kotlin/org/evomaster/core/search/Solution.kt b/core/src/main/kotlin/org/evomaster/core/search/Solution.kt index 70f6e2fef2..d3ce42c052 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/Solution.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/Solution.kt @@ -1,6 +1,7 @@ package org.evomaster.core.search import org.evomaster.core.database.DbAction +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.output.Termination import org.evomaster.core.problem.externalservice.httpws.HttpExternalServiceAction @@ -51,4 +52,8 @@ where T : Individual { fun hasAnySqlAction() : Boolean{ return individuals.any { ind -> ind.individual.seeAllActions().any { a -> a is DbAction}} } + + fun hasAnyMongoAction() : Boolean{ + return individuals.any { ind -> ind.individual.seeAllActions().any { a -> a is MongoDbAction}} + } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/search/impact/impactinfocollection/ImpactsOfIndividual.kt b/core/src/main/kotlin/org/evomaster/core/search/impact/impactinfocollection/ImpactsOfIndividual.kt index 52ca8395ec..19b2dc8c94 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/impact/impactinfocollection/ImpactsOfIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/impact/impactinfocollection/ImpactsOfIndividual.kt @@ -1,6 +1,7 @@ package org.evomaster.core.search.impact.impactinfocollection import org.evomaster.core.database.DbAction +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.search.Action import org.evomaster.core.search.FitnessValue import org.evomaster.core.search.Individual @@ -186,7 +187,7 @@ open class ImpactsOfIndividual( * thus, we need to synchronize the action impacts based on the [individual] */ fun syncBasedOnIndividual(individual: Individual) { - val initActions = individual.seeInitializingActions().filterIsInstance() + val initActions = individual.seeInitializingActions().filter { it is DbAction || it is MongoDbAction } //for initialization due to db action fixing val diff = initActions.size - initActionImpacts.getOriginalSize() if (diff < 0) { //truncation diff --git a/core/src/main/kotlin/org/evomaster/core/search/impact/impactinfocollection/InitializationActionImpacts.kt b/core/src/main/kotlin/org/evomaster/core/search/impact/impactinfocollection/InitializationActionImpacts.kt index 6e0a18cd32..78c3ddcf2a 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/impact/impactinfocollection/InitializationActionImpacts.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/impact/impactinfocollection/InitializationActionImpacts.kt @@ -2,6 +2,7 @@ package org.evomaster.core.search.impact.impactinfocollection import org.evomaster.core.Lazy import org.evomaster.core.database.DbAction +import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.search.Action import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -194,7 +195,7 @@ class InitializationActionImpacts(val abstract: Boolean, val enableImpactOnDupli } val original = completeSequence.size - val seq = list.filterIsInstance().filter{ !it.representExistingData } + val seq = list.filter{(it is DbAction && !it.representExistingData) || it is MongoDbAction } if (seq.size > original) { log.warn("there are more db actions after the truncation") return diff --git a/core/src/test/kotlin/org/evomaster/core/database/SqlInsertBuilderTest.kt b/core/src/test/kotlin/org/evomaster/core/database/SqlInsertBuilderTest.kt index 047680f516..6960c432e5 100644 --- a/core/src/test/kotlin/org/evomaster/core/database/SqlInsertBuilderTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/database/SqlInsertBuilderTest.kt @@ -1,8 +1,6 @@ package org.evomaster.core.database -import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto -import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto -import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto +import org.evomaster.client.java.controller.api.dto.database.operations.* import org.evomaster.client.java.controller.db.SqlScriptRunner import org.evomaster.client.java.controller.internal.db.SchemaExtractor import org.evomaster.core.search.gene.* @@ -414,6 +412,10 @@ class SqlInsertBuilderTest { return null } + override fun executeMongoDatabaseInsertions(dto: MongoDatabaseCommandDto): MongoInsertionResultsDto? { + return null + } + override fun executeDatabaseCommandAndGetQueryResults(dto: DatabaseCommandDto): QueryResultDto? { return SqlScriptRunner.execCommand(connection, dto.command).toDto() } diff --git a/core/src/test/kotlin/org/evomaster/core/output/service/FakeController.kt b/core/src/test/kotlin/org/evomaster/core/output/service/FakeController.kt index 9aad16f63f..db3fcebcbc 100644 --- a/core/src/test/kotlin/org/evomaster/core/output/service/FakeController.kt +++ b/core/src/test/kotlin/org/evomaster/core/output/service/FakeController.kt @@ -3,6 +3,8 @@ package org.evomaster.core.output.service import org.evomaster.client.java.controller.SutHandler import org.evomaster.client.java.controller.api.dto.database.operations.InsertionDto import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto import org.evomaster.client.java.controller.internal.db.DbSpecification @@ -21,6 +23,10 @@ class FakeController : SutHandler { return null } + override fun execInsertionsIntoMongoDatabase(insertions: MutableList?): MongoInsertionResultsDto? { + return null + } + override fun getDbSpecifications(): MutableList? { return null } diff --git a/core/src/test/kotlin/org/evomaster/core/problem/external/service/DummyController.kt b/core/src/test/kotlin/org/evomaster/core/problem/external/service/DummyController.kt index 4978955577..755ed8eb14 100644 --- a/core/src/test/kotlin/org/evomaster/core/problem/external/service/DummyController.kt +++ b/core/src/test/kotlin/org/evomaster/core/problem/external/service/DummyController.kt @@ -1,9 +1,7 @@ package org.evomaster.core.problem.external.service import org.evomaster.client.java.controller.api.dto.* -import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto -import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto -import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto +import org.evomaster.client.java.controller.api.dto.database.operations.* import org.evomaster.core.remote.service.RemoteController class DummyController: RemoteController { @@ -71,4 +69,8 @@ class DummyController: RemoteController { TODO("Not yet implemented") } + override fun executeMongoDatabaseInsertions(dto: MongoDatabaseCommandDto): MongoInsertionResultsDto? { + TODO("Not yet implemented") + } + } \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/problem/rest/individual/RestIndividualTestBase.kt b/core/src/test/kotlin/org/evomaster/core/problem/rest/individual/RestIndividualTestBase.kt index c44bb7d033..9974a134e5 100644 --- a/core/src/test/kotlin/org/evomaster/core/problem/rest/individual/RestIndividualTestBase.kt +++ b/core/src/test/kotlin/org/evomaster/core/problem/rest/individual/RestIndividualTestBase.kt @@ -19,10 +19,7 @@ import io.swagger.v3.oas.models.responses.ApiResponse import io.swagger.v3.oas.models.responses.ApiResponses import org.evomaster.client.java.controller.api.dto.* import org.evomaster.client.java.controller.api.dto.database.execution.ExecutionDto -import org.evomaster.client.java.controller.api.dto.database.operations.DataRowDto -import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto -import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto -import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto +import org.evomaster.client.java.controller.api.dto.database.operations.* import org.evomaster.client.java.controller.api.dto.problem.RestProblemDto import org.evomaster.client.java.controller.db.SqlScriptRunner import org.evomaster.client.java.controller.internal.db.SchemaExtractor @@ -663,6 +660,10 @@ abstract class RestIndividualTestBase { return null } + override fun executeMongoDatabaseInsertions(dto: MongoDatabaseCommandDto): MongoInsertionResultsDto? { + return null + } + } @AfterEach diff --git a/core/src/test/kotlin/org/evomaster/core/problem/rest/resource/ResourceNodeWithDbTest.kt b/core/src/test/kotlin/org/evomaster/core/problem/rest/resource/ResourceNodeWithDbTest.kt index 8f2cba320e..57b0b64db0 100644 --- a/core/src/test/kotlin/org/evomaster/core/problem/rest/resource/ResourceNodeWithDbTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/problem/rest/resource/ResourceNodeWithDbTest.kt @@ -1,9 +1,7 @@ package org.evomaster.core.problem.rest.resource import io.swagger.parser.OpenAPIParser -import org.evomaster.client.java.controller.api.dto.database.operations.DatabaseCommandDto -import org.evomaster.client.java.controller.api.dto.database.operations.InsertionResultsDto -import org.evomaster.client.java.controller.api.dto.database.operations.QueryResultDto +import org.evomaster.client.java.controller.api.dto.database.operations.* import org.evomaster.client.java.controller.db.SqlScriptRunner import org.evomaster.client.java.controller.internal.db.SchemaExtractor import org.evomaster.core.EMConfig @@ -273,6 +271,10 @@ class ResourceNodeWithDbTest { return null } + override fun executeMongoDatabaseInsertions(dto: MongoDatabaseCommandDto): MongoInsertionResultsDto? { + return null + } + override fun executeDatabaseCommandAndGetQueryResults(dto: DatabaseCommandDto): QueryResultDto? { return SqlScriptRunner.execCommand(connection, dto.command).toDto() } diff --git a/e2e-tests/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/MongoController.java b/e2e-tests/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/MongoController.java index 912c220815..0f0405bde4 100644 --- a/e2e-tests/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/MongoController.java +++ b/e2e-tests/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/MongoController.java @@ -51,6 +51,9 @@ public String startSut() { "spring.data.mongodb.database=" + databaseName ); + // CHANGE: Is necessary to crete the collection first? + mongoClient.getDatabase("persons").createCollection("person"); + ctx = app.run(); return "http://localhost:" + getSutPort(); @@ -102,4 +105,9 @@ protected int getSutPort() { .getPropertySources().get("server.ports").getSource()) .get("local.server.port"); } + + @Override + public MongoClient getMongoConnection() { + return mongoClient; + } } From 5adf79538515fb1a86f7a8a30565580e776019c4 Mon Sep 17 00:00:00 2001 From: hghianni Date: Tue, 30 May 2023 22:48:06 -0300 Subject: [PATCH 02/20] Take into account database name when inserting data --- .../dto/database/execution/FailedQuery.java | 12 ++--- .../operations/MongoInsertionDto.java | 2 + .../controller/internal/db/MongoHandler.java | 14 +++++- .../controller/mongo/MongoScriptRunner.java | 10 ++-- .../java/controller/mongo/dsl/MongoDsl.java | 10 ++-- .../mongo/dsl/MongoSequenceDsl.java | 2 +- .../org/evomaster/core/mongo/MongoDbAction.kt | 4 +- .../core/mongo/MongoDbActionTransformer.kt | 14 +++--- .../core/mongo/MongoInsertBuilder.kt | 5 +- .../org/evomaster/core/output/MongoWriter.kt | 2 +- .../api/service/ApiWsStructureMutator.kt | 2 +- .../enterprise/service/EnterpriseSampler.kt | 4 +- .../MongoPersonsWithoutPostApp.java | 20 ++++++++ .../com/mongo/personswithoutpost/Person.java | 13 ++++++ .../personswithoutpost/PersonRepository.java | 9 ++++ .../PersonWithoutPostController.java | 21 +++++++++ .../MongoPersonsWithoutPostAppController.java | 14 ++++++ .../rest/mongo/foo/MongoEMGenerationTest.java | 46 +++++++++++++++++++ 18 files changed, 174 insertions(+), 30 deletions(-) create mode 100644 e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/MongoPersonsWithoutPostApp.java create mode 100644 e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/Person.java create mode 100644 e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/PersonRepository.java create mode 100644 e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/PersonWithoutPostController.java create mode 100644 e2e-tests/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/MongoPersonsWithoutPostAppController.java create mode 100644 e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java index 06127ecaff..98be5552ba 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java @@ -4,28 +4,28 @@ public class FailedQuery { // Add database - public FailedQuery(String collection, Class documentsType, Map accessedFields) { + public FailedQuery(String database, String collection, Class documentsType, Map accessedFields) { + this.database = database; this.collection = collection; this.documentsType = documentsType; this.accessedFields = accessedFields; } public FailedQuery(){ + this.database = ""; this.collection = ""; } + private final String database; private final String collection; private Class documentsType; private Map accessedFields; - public String getCollection() { - return collection; - } - + public String getDatabase() {return database;} + public String getCollection() {return collection;} public Map getAccessedFields() { return accessedFields; } - public Class getDocumentsType() { return documentsType; } diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java index 59b6b9f4c6..d0e7896a4a 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java @@ -5,6 +5,8 @@ public class MongoInsertionDto { + public String databaseName; + public String collectionName; public List data = new ArrayList<>(); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java index b87698702e..6fbacf2ba9 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java @@ -124,7 +124,19 @@ private FailedQuery extractRelevantInfo(MongoOperation operation) { Map accessedFields = extractFieldsInQuery(query); Class documentsType = extractDocumentsType(collection); - return new FailedQuery("operation.getCollection()", documentsType, accessedFields); + String collectionName; + String databaseName; + + try { + Class collectionClass = collection.getClass().getClassLoader().loadClass("com.mongodb.client.MongoCollection"); + Object namespace = collectionClass.getMethod("getNamespace").invoke(collection); + collectionName = (String) namespace.getClass().getMethod("getCollectionName").invoke(namespace); + databaseName = (String) namespace.getClass().getMethod("getDatabaseName").invoke(namespace); + } catch (ClassNotFoundException |IllegalAccessException | InvocationTargetException | NoSuchMethodException e){ + throw new RuntimeException(e); + } + + return new FailedQuery(databaseName, collectionName, documentsType, accessedFields); } private static Class extractDocumentsType(Object collection) { diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java index 255146e735..d4855e61ae 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java @@ -30,16 +30,18 @@ public static MongoInsertionResultsDto execInsert(Object conn, List conn.getDatabase("persons").getCollection(insDto.collectionName).insertOne(Document.parse(field.value))); + Class documentClass = Class.forName("org.bson.Document"); + Object document = documentClass.getDeclaredConstructor().newInstance(); + document = document.getClass().getMethod("parse", String.class).invoke(document, insDto.data.get(0).value); + Object database = conn.getClass().getMethod("getDatabase", String.class).invoke(conn,insDto.databaseName); + Object collection = database.getClass().getMethod("getCollection", String.class).invoke(database, insDto.collectionName); + Class.forName("com.mongodb.client.MongoCollection").getMethod("insertOne", Object.class).invoke(collection, document); mongoResults.set(i, true); } catch (Exception e) { String msg = "Failed to execute insertion with index " + i + " with Mongo. Error: " + e.getMessage(); throw new RuntimeException(msg, e); } - - */ } MongoInsertionResultsDto insertionResultsDto = new MongoInsertionResultsDto(); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java index 889236447b..83c8162678 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java @@ -36,23 +36,27 @@ public static MongoSequenceDsl mongo() { /** * @param previous a DSL object which is executed in the front of this - * @return a DSL object to create SQL operations + * @return a DSL object to create MONGO operations */ public static MongoSequenceDsl mongo(List... previous) { - return new MongoDsl(previous); } @Override - public MongoStatementDsl insertInto(String collectionName) { + public MongoStatementDsl insertInto(String databaseName, String collectionName) { checkDsl(); + if (databaseName == null || databaseName.isEmpty()) { + throw new IllegalArgumentException("Unspecified database"); + } + if (collectionName == null || collectionName.isEmpty()) { throw new IllegalArgumentException("Unspecified collection"); } MongoInsertionDto dto = new MongoInsertionDto(); + dto.databaseName = databaseName; dto.collectionName = collectionName; list.add(dto); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java index 806b915eba..a69afb0cc6 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java @@ -8,5 +8,5 @@ public interface MongoSequenceDsl { * @param collectionName the target table in the DB * @return a statement in which it can be specified the values to add */ - MongoStatementDsl insertInto(String collectionName); + MongoStatementDsl insertInto(String databaseName, String collectionName); } diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt index c542e9e3b4..e29dfb17f2 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt @@ -5,7 +5,7 @@ import org.evomaster.core.search.gene.Gene import org.evomaster.core.search.gene.ObjectGene import java.util.* -class MongoDbAction(val collection: String, val documentsType: Class<*>, val accessedFields: Map, computedGenes: List? = null) : Action(listOf()) { +class MongoDbAction(val database: String, val collection: String, val documentsType: Class<*>, val accessedFields: Map, computedGenes: List? = null) : Action(listOf()) { val genes: List = (computedGenes ?: computeGenes()) .also { addChildren(it) } @@ -47,6 +47,6 @@ class MongoDbAction(val collection: String, val documentsType: Class<*>, val acc } override fun copyContent(): Action { - return MongoDbAction(collection, documentsType, accessedFields, genes.map(Gene::copy)) + return MongoDbAction(database, collection, documentsType, accessedFields, genes.map(Gene::copy)) } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt index 2137357f2d..99b904f687 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt @@ -8,22 +8,24 @@ object MongoDbActionTransformer { val list = mutableListOf() - for (i in 0 until insertions.size) { + for (element in insertions) { - val action = insertions[i] + val insertion = MongoInsertionDto().apply { + databaseName = element.database + collectionName = element.collection } - val insertion = MongoInsertionDto().apply { collectionName = action.collection } - - val g = action.seeTopGenes().first() + val g = element.seeTopGenes().first() val entry = MongoInsertionEntryDto() // If is printed as JSON there might a problem // A Document(from Mongo) can be created from a JSON but some info might be lost - // Preferably is created from a EJSON (Extended JSON) as JSON can only directly represent a + // Preferably is created from an EJSON (Extended JSON) as JSON can only directly represent a // subset of the types supported by BSON. // Maybe we can create a new OutputFormat entry.value = g.getValueAsPrintableString() + + // This is ignored for now entry.fieldName = g.getVariableName() insertion.data.add(entry) diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt index 8df7126243..32aa37835d 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt @@ -2,8 +2,7 @@ package org.evomaster.core.mongo class MongoInsertBuilder { - fun createMongoInsertionAction(collection: String, documentsType: Class<*>, accessedFields: Map): List { - // FIX - return mutableListOf(MongoDbAction(collection, documentsType, accessedFields)) + fun createMongoInsertionAction(database: String, collection: String, documentsType: Class<*>, accessedFields: Map): List { + return mutableListOf(MongoDbAction(database, collection, documentsType, accessedFields)) } } diff --git a/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt b/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt index b6e78002d1..d7fc99f124 100644 --- a/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt +++ b/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt @@ -43,7 +43,7 @@ object MongoWriter { index == 0 && format.isJava() -> "List $insertionVar = mongo($previousVar)" index == 0 && format.isKotlin() -> "val $insertionVar = mongo($previousVar)" else -> ".and()" - } + ".insertInto(\"${evaluatedMongoDbAction.action.collection}\")") + } + ".insertInto(\"${evaluatedMongoDbAction.action.database}\"" + ", " + "\"${evaluatedMongoDbAction.action.collection}\")") if (index == 0) { lines.indent() diff --git a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt index 5d03a57350..ed2f1feba8 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt @@ -323,7 +323,7 @@ abstract class ApiWsStructureMutator : StructureMutator() { val addedInsertions = if (mutatedGenes != null) mutableListOf>() else null ff.forEach { - val insertions = sampler.sampleMongoInsertion(it.collection, it.documentsType, it.accessedFields) + val insertions = sampler.sampleMongoInsertion(it.database, it.collection, it.documentsType, it.accessedFields) ind.addInitializingMongoDbActions(actions = insertions) addedInsertions?.add(insertions) } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt index 7f74be10e1..8340d803a6 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt @@ -78,12 +78,12 @@ abstract class EnterpriseSampler : Sampler() where T : Individual { return actions } - fun sampleMongoInsertion(collection: String, documentsType: Class<*>, accessedFields: Map): List { + fun sampleMongoInsertion(database: String, collection: String, documentsType: Class<*>, accessedFields: Map): List { // Should I use something like this? //val extraConstraints = randomness.nextBoolean(apc.getExtraSqlDbConstraintsProbability()) - val actions = MongoInsertBuilder().createMongoInsertionAction(collection, documentsType, accessedFields) + val actions = MongoInsertBuilder().createMongoInsertionAction(database, collection, documentsType, accessedFields) ?: throw IllegalStateException("No MongoDB schema is available") actions.flatMap{it.seeTopGenes()}.forEach{it.doInitialize(randomness)} diff --git a/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/MongoPersonsWithoutPostApp.java b/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/MongoPersonsWithoutPostApp.java new file mode 100644 index 0000000000..3be81e80f6 --- /dev/null +++ b/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/MongoPersonsWithoutPostApp.java @@ -0,0 +1,20 @@ +package com.mongo.personswithoutpost; + +import com.mongo.SwaggerConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@EnableSwagger2 +@SpringBootApplication(exclude = SecurityAutoConfiguration.class) +public class MongoPersonsWithoutPostApp extends SwaggerConfiguration { + public MongoPersonsWithoutPostApp() { + super("persons"); + } + + public static void main(String[] args) { + SpringApplication.run(MongoPersonsWithoutPostApp.class, args); + } + +} \ No newline at end of file diff --git a/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/Person.java b/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/Person.java new file mode 100644 index 0000000000..dd6fd35209 --- /dev/null +++ b/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/Person.java @@ -0,0 +1,13 @@ +package com.mongo.personswithoutpost; + +import org.springframework.data.annotation.Id; + +public class Person { + @Id + public String id; + public Integer age; + public Person(Integer age) { + this.age = age; + } +} + diff --git a/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/PersonRepository.java b/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/PersonRepository.java new file mode 100644 index 0000000000..817427b6f0 --- /dev/null +++ b/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/PersonRepository.java @@ -0,0 +1,9 @@ +package com.mongo.personswithoutpost; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.List; + +public interface PersonRepository extends MongoRepository { + List findByAge(Integer age); +} \ No newline at end of file diff --git a/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/PersonWithoutPostController.java b/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/PersonWithoutPostController.java new file mode 100644 index 0000000000..d1e1ed3ec7 --- /dev/null +++ b/e2e-tests/spring-rest-mongo/src/main/java/com/mongo/personswithoutpost/PersonWithoutPostController.java @@ -0,0 +1,21 @@ +package com.mongo.personswithoutpost; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping(path = "/persons") +public class PersonWithoutPostController { + + @Autowired + private PersonRepository persons; + + @GetMapping("list18") + public ResponseEntity find18s() { + int status = (persons.findByAge(18).isEmpty()) ? 400 : 200 ; + return ResponseEntity.status(status).build(); + } +} + + diff --git a/e2e-tests/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/MongoPersonsWithoutPostAppController.java b/e2e-tests/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/MongoPersonsWithoutPostAppController.java new file mode 100644 index 0000000000..f964fa0246 --- /dev/null +++ b/e2e-tests/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/MongoPersonsWithoutPostAppController.java @@ -0,0 +1,14 @@ +package com.foo.spring.rest.mongo; + +import com.mongo.personswithoutpost.MongoPersonsWithoutPostApp; + +public class MongoPersonsWithoutPostAppController extends MongoController{ + public MongoPersonsWithoutPostAppController() { + super("persons", MongoPersonsWithoutPostApp.class); + } + + @Override + public String getPackagePrefixesToCover() { + return "com.mongo.persons"; + } +} diff --git a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java new file mode 100644 index 0000000000..14218ebbb3 --- /dev/null +++ b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java @@ -0,0 +1,46 @@ +package org.evomaster.e2etests.spring.rest.mongo.foo; + +import com.foo.spring.rest.mongo.MongoPersonsAppController; +import com.foo.spring.rest.mongo.MongoPersonsWithoutPostAppController; +import org.evomaster.core.problem.rest.HttpVerb; +import org.evomaster.core.problem.rest.RestIndividual; +import org.evomaster.core.search.Solution; +import org.evomaster.e2etests.utils.RestTestBase; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class + + +MongoEMGenerationTest extends RestTestBase { + + @BeforeAll + public static void initClass() throws Exception { + RestTestBase.initClass(new MongoPersonsWithoutPostAppController()); + } + + @Test + public void testRunEM() throws Throwable { + + runTestHandlingFlakyAndCompilation( + "MongoEMGeneration", + "org.foo.spring.rest.mongo.MongoEMGeneration", + 10000, + (args) -> { + args.add("--enableWeightBasedMutationRateSelectionForGene"); + args.add("false"); + args.add("--heuristicsForMongo"); + args.add("true"); + args.add("--instrumentMR_MONGO"); + args.add("true"); + + Solution solution = initAndRun(args); + + assertTrue(solution.getIndividuals().size() >= 1); + assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/persons/list18", null); + assertHasAtLeastOne(solution, HttpVerb.GET, 400, "/persons/list18", null); + }); + } +} \ No newline at end of file From d9b4417a1e8ae84fc06981f04b616f6937d117ce Mon Sep 17 00:00:00 2001 From: hghianni Date: Tue, 20 Jun 2023 00:02:44 -0300 Subject: [PATCH 03/20] Continue with the repository type handling --- .../dto/database/execution/FailedQuery.java | 7 +- .../controller/internal/EMController.java | 1 + .../controller/internal/SutController.java | 22 ++++- .../controller/internal/db/MongoHandler.java | 53 ++++++++--- .../java/instrumentation/AdditionalInfo.java | 10 +++ .../instrumentation/MongoCollectionInfo.java | 20 +++++ .../methodreplacement/ReplacementList.java | 2 +- .../ThirdPartyMethodReplacementClass.java | 9 ++ ...ongoEntityInformationClassReplacement.java | 87 +++++++++++++++++++ .../MongoRepositoryClassReplacement.java | 48 ---------- .../staticstate/ExecutionTracer.java | 5 ++ .../core/mongo/MongoActionGeneBuilder.kt | 54 +++++++++--- .../org/evomaster/core/mongo/MongoDbAction.kt | 35 +++----- .../core/mongo/MongoInsertBuilder.kt | 2 +- .../enterprise/service/EnterpriseSampler.kt | 2 +- .../core/search/gene/numeric/DoubleGene.kt | 3 +- .../core/search/gene/numeric/IntegerGene.kt | 3 +- .../core/search/gene/numeric/LongGene.kt | 3 +- .../core/search/gene/utils/GeneUtils.kt | 3 +- 19 files changed, 260 insertions(+), 109 deletions(-) create mode 100644 client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoCollectionInfo.java create mode 100644 client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MappingMongoEntityInformationClassReplacement.java delete mode 100644 client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MongoRepositoryClassReplacement.java diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java index 98be5552ba..5a955ce8d1 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java @@ -3,8 +3,7 @@ import java.util.Map; public class FailedQuery { - // Add database - public FailedQuery(String database, String collection, Class documentsType, Map accessedFields) { + public FailedQuery(String database, String collection, Class documentsType, Map > accessedFields) { this.database = database; this.collection = collection; this.documentsType = documentsType; @@ -19,11 +18,11 @@ public FailedQuery(){ private final String database; private final String collection; private Class documentsType; - private Map accessedFields; + private Map> accessedFields; public String getDatabase() {return database;} public String getCollection() {return collection;} - public Map getAccessedFields() { + public Map> getAccessedFields() { return accessedFields; } public Class getDocumentsType() { diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java index b47d41e59c..7c04329ec4 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java @@ -369,6 +369,7 @@ public Response runSut(SutRunDto dto, @Context HttpServletRequest httpServletReq return Response.status(500).entity(WrappedResponseDto.withError(msg)).build(); } noKillSwitch(() -> sutController.initSqlHandler()); + noKillSwitch(() -> sutController.initMongoHandler()); } else { //TODO as starting should be blocking, need to check //if initialized, and wait if not diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java index 4cbe6f180d..ca5c61d28d 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java @@ -275,6 +275,16 @@ public final void initSqlHandler() { sqlHandler.setSchema(getSqlDatabaseSchema()); } + public final void initMongoHandler() { + // This is needed because the replacement use to get this info occurs during the start of the SUT. + + List list = getAdditionalInfoList(); + if(!list.isEmpty()) { + AdditionalInfo last = list.get(list.size() - 1); + last.getMongoCollectionInfoData().forEach(mongoHandler::handle); + } + } + /** * TODO further handle multiple connections @@ -364,9 +374,9 @@ private void computeSQLHeuristics(ExtraHeuristicsDto dto) { } public final void computeMongoHeuristics(ExtraHeuristicsDto dto){ - if(mongoHandler.isCalculateHeuristics()){ + List list = getAdditionalInfoList(); - List list = getAdditionalInfoList(); + if(mongoHandler.isCalculateHeuristics()){ if(!list.isEmpty()) { AdditionalInfo last = list.get(list.size() - 1); last.getMongoInfoData().forEach(it -> { @@ -390,7 +400,13 @@ public final void computeMongoHeuristics(ExtraHeuristicsDto dto){ .forEach(h -> dto.heuristics.add(h)); } - if(mongoHandler.isExtractMongoExecution()){dto.mongoExecutionDto = mongoHandler.getExecutionDto();} + if(mongoHandler.isExtractMongoExecution()){ + if(!list.isEmpty()) { + AdditionalInfo last = list.get(list.size() - 1); + last.getMongoCollectionInfoData().forEach(mongoHandler::handle); + } + dto.mongoExecutionDto = mongoHandler.getExecutionDto(); + } } /** diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java index 6fbacf2ba9..5827beab02 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java @@ -6,6 +6,7 @@ import org.evomaster.client.java.controller.mongo.MongoOperation; import org.evomaster.client.java.controller.mongo.QueryParser; import org.evomaster.client.java.controller.mongo.operations.*; +import org.evomaster.client.java.instrumentation.MongoCollectionInfo; import org.evomaster.client.java.instrumentation.MongoInfo; import java.lang.reflect.InvocationTargetException; @@ -42,10 +43,13 @@ public class MongoHandler { private final List failedQueries; + private final HashMap> collectionInfo; + public MongoHandler() { distances = new ArrayList<>(); operations = new ArrayList<>(); failedQueries = new ArrayList<>(); + collectionInfo = new HashMap<>(); extractMongoExecution = true; calculateHeuristics = true; } @@ -54,6 +58,7 @@ public void reset() { operations.clear(); distances.clear(); failedQueries.clear(); + // collectionInfo is not cleared to avoid losing the info as it's retrieved when SUT is started } public void handle(MongoInfo info) { @@ -64,6 +69,14 @@ public void handle(MongoInfo info) { operations.add(info); } + public void handle(MongoCollectionInfo info) { + if (!extractMongoExecution) { + return; + } + + collectionInfo.put(info.getCollectionName(), info.getDocumentsType()); + } + public List getDistances() { operations.stream().filter(info -> info.getQuery() != null).forEach(mongoInfo -> { @@ -121,8 +134,8 @@ private FailedQuery extractRelevantInfo(MongoOperation operation) { QueryOperation query = new QueryParser().parse(operation.getQuery()); Object collection = operation.getCollection(); - Map accessedFields = extractFieldsInQuery(query); - Class documentsType = extractDocumentsType(collection); + Map> accessedFields = extractFieldsInQuery(query); + Class documentsType; String collectionName; String databaseName; @@ -136,6 +149,24 @@ private FailedQuery extractRelevantInfo(MongoOperation operation) { throw new RuntimeException(e); } + // I should be as specific as I can with the type the collection's documents should have. + // There are a few ways to get that info: + + // 1) Using collection.getDocumentsClass(). + // Spring for example "ignore" this and store type info inside repository. + + // 2) When using Spring, retrieving the type of the repository associated with the collection. + // The MongoEntityInformation replacement makes this possible. + + // 3) Extract from the query the fields used and type of each of them. This probably won't + // work as expected as usually a subset of fields is used in a query. + + if(collectionInfo.containsKey(collectionName)){ + documentsType = collectionInfo.get(collectionName); + }else{ + documentsType = extractDocumentsType(collection); + } + return new FailedQuery(databaseName, collectionName, documentsType, accessedFields); } @@ -149,12 +180,12 @@ private static Class extractDocumentsType(Object collection) { } } - private static Map extractFieldsInQuery(QueryOperation operation) { - Map accessedFields = new HashMap<>(); + private static Map> extractFieldsInQuery(QueryOperation operation) { + Map> accessedFields = new HashMap<>(); if(operation instanceof ComparisonOperation) { ComparisonOperation op = (ComparisonOperation) operation; - accessedFields.put(op.getFieldName(), op.getValue()); + accessedFields.put(op.getFieldName(), op.getValue().getClass()); } if(operation instanceof AndOperation) { @@ -174,22 +205,22 @@ private static Map extractFieldsInQuery(QueryOperation operation if(operation instanceof InOperation) { InOperation op = (InOperation) operation; - accessedFields.put(op.getFieldName(), op.getValues().get(0)); + accessedFields.put(op.getFieldName(), op.getValues().get(0).getClass()); } if(operation instanceof NotInOperation) { NotInOperation op = (NotInOperation) operation; - accessedFields.put(op.getFieldName(), op.getValues().get(0)); + accessedFields.put(op.getFieldName(), op.getValues().get(0).getClass()); } if(operation instanceof AllOperation) { AllOperation op = (AllOperation) operation; - accessedFields.put(op.getFieldName(), op.getValues()); + accessedFields.put(op.getFieldName(), op.getValues().getClass()); } if(operation instanceof SizeOperation) { SizeOperation op = (SizeOperation) operation; - accessedFields.put(op.getFieldName(), op.getValue()); + accessedFields.put(op.getFieldName(), op.getValue().getClass()); } if(operation instanceof ExistsOperation) { @@ -199,12 +230,12 @@ private static Map extractFieldsInQuery(QueryOperation operation if(operation instanceof ModOperation) { ModOperation op = (ModOperation) operation; - accessedFields.put(op.getFieldName(), op.getDivisor()); + accessedFields.put(op.getFieldName(), op.getDivisor().getClass()); } if(operation instanceof TypeOperation) { TypeOperation op = (TypeOperation) operation; - accessedFields.put(op.getFieldName(), op.getType()); + accessedFields.put(op.getFieldName(), op.getType().getClass()); } /* diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java index bfdb1d0b8c..667a6d8028 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java @@ -104,6 +104,8 @@ public StatementDescription(String line, String method) { private final Set mongoInfoData = new CopyOnWriteArraySet<>(); + private final Set mongoCollectionInfoData = new CopyOnWriteArraySet<>(); + public Set getSqlInfoData(){ return Collections.unmodifiableSet(sqlInfoData); } @@ -112,6 +114,10 @@ public Set getMongoInfoData(){ return Collections.unmodifiableSet(mongoInfoData); } + public Set getMongoCollectionInfoData(){ + return Collections.unmodifiableSet(mongoCollectionInfoData); + } + public void addSqlInfo(SqlInfo info){ sqlInfoData.add(info); } @@ -120,6 +126,10 @@ public void addMongoInfo(MongoInfo info){ mongoInfoData.add(info); } + public void addMongoCollectionInfo(MongoCollectionInfo info){ + mongoCollectionInfoData.add(info); + } + public Set getParsedDtoNamesView(){ return Collections.unmodifiableSet(parsedDtoNames); } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoCollectionInfo.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoCollectionInfo.java new file mode 100644 index 0000000000..63f72f750c --- /dev/null +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoCollectionInfo.java @@ -0,0 +1,20 @@ +package org.evomaster.client.java.instrumentation; + +import java.io.Serializable; + +/** + * Info about the type of documents in the collection. + */ +public class MongoCollectionInfo implements Serializable { + private final String collectionName; + private final Class documentsType; + + public MongoCollectionInfo(String collectionName, Class collectionType) { + this.collectionName = collectionName; + this.documentsType = collectionType; + } + + public String getCollectionName() {return collectionName;} + + public Class getDocumentsType() {return documentsType;} +} diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java index d3772b4dd9..4dce7ecb60 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java @@ -49,7 +49,7 @@ public static List getList() { new MatcherClassReplacement(), new MethodClassReplacement(), new MongoCollectionClassReplacement(), - new MongoRepositoryClassReplacement(), + new MappingMongoEntityInformationClassReplacement(), new OkHttpClient3BuilderClassReplacement(), new OkHttpClient3ClassReplacement(), new OkHttpClientClassReplacement(), diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ThirdPartyMethodReplacementClass.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ThirdPartyMethodReplacementClass.java index d245406a81..efa8514701 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ThirdPartyMethodReplacementClass.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ThirdPartyMethodReplacementClass.java @@ -163,6 +163,15 @@ private void initConstructors(ClassLoader loader, StateInfo info) { } Class[] reducedInputs = Arrays.copyOfRange(inputs, start, end); + Annotation[][] annotations = m.getParameterAnnotations(); + + for (int i = start; i < end; i++) { + if (annotations[i].length > 0) { + Class klazz = ReplacementUtils.getCastedToThirdParty(loader,annotations[i]); + if (klazz != null) + reducedInputs[i - start] = klazz; + } + } Constructor targetConstructor = null; try { diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MappingMongoEntityInformationClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MappingMongoEntityInformationClassReplacement.java new file mode 100644 index 0000000000..9020836f3c --- /dev/null +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MappingMongoEntityInformationClassReplacement.java @@ -0,0 +1,87 @@ +package org.evomaster.client.java.instrumentation.coverage.methodreplacement.classes; + +import org.evomaster.client.java.instrumentation.MongoCollectionInfo; +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement; +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast; +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass; +import org.evomaster.client.java.instrumentation.shared.ReplacementCategory; +import org.evomaster.client.java.instrumentation.shared.ReplacementType; +import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + + +public class MappingMongoEntityInformationClassReplacement extends ThirdPartyMethodReplacementClass { + private static final MappingMongoEntityInformationClassReplacement singleton = new MappingMongoEntityInformationClassReplacement(); + private static ThreadLocal instance = new ThreadLocal<>(); + + @Override + protected String getNameOfThirdPartyTargetClass() { + return "org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation"; + } + + public static Object consumeInstance() { + Object mappingMongoEntityInformation = instance.get(); + if (mappingMongoEntityInformation == null) { + throw new IllegalStateException("No instance to consume"); + } + instance.set(null); + return mappingMongoEntityInformation; + } + + private static void addInstance(Object x) { + Object mappingMongoEntityInformation = instance.get(); + if (mappingMongoEntityInformation != null) { + throw new IllegalStateException("Previous instance was not consumed"); + } + instance.set(x); + } + + @Replacement( + replacingConstructor = true, + type = ReplacementType.TRACKER, + category = ReplacementCategory.SQL, + id = "constructorEntity" + ) + public static void MappingMongoEntityInformation(@ThirdPartyCast(actualType = "org.springframework.data.mongodb.core.mapping.MongoPersistentEntity") Object entity) { + handleMappingMongoEntityInformationConstructor("constructorEntity", Collections.singletonList(entity)); + } + + @Replacement( + replacingConstructor = true, + type = ReplacementType.TRACKER, + category = ReplacementCategory.SQL, + id = "constructorEntityCustomCollectionName" + ) + public static void MappingMongoEntityInformation(@ThirdPartyCast(actualType = "org.springframework.data.mongodb.core.mapping.MongoPersistentEntity") Object entity, String customCollectionName) { + handleMappingMongoEntityInformationConstructor("constructorEntityCustomCollectionName", Arrays.asList(entity, customCollectionName)); + } + + @Replacement( + replacingConstructor = true, + type = ReplacementType.TRACKER, + category = ReplacementCategory.SQL, + id = "constructorEntityFallbackIdType" + ) + public static void MappingMongoEntityInformation(@ThirdPartyCast(actualType = "org.springframework.data.mongodb.core.mapping.MongoPersistentEntity") Object entity, Class fallbackIdType) { + handleMappingMongoEntityInformationConstructor("constructorEntityFallbackIdType", Arrays.asList(entity, fallbackIdType)); + } + + private static void handleMappingMongoEntityInformationConstructor(String id, List args) { + Constructor original = getOriginalConstructor(singleton, id); + + try { + Object mappingMongoEntityInformation = original.newInstance(args.toArray()); + addInstance(mappingMongoEntityInformation); + String collectionName = (String) mappingMongoEntityInformation.getClass().getMethod("getCollectionName").invoke(mappingMongoEntityInformation); + Class repositoryType = (Class) mappingMongoEntityInformation.getClass().getMethod("getJavaType").invoke(mappingMongoEntityInformation); + ExecutionTracer.addMongoCollectionInfo(new MongoCollectionInfo(collectionName, repositoryType)); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } +} diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MongoRepositoryClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MongoRepositoryClassReplacement.java deleted file mode 100644 index 3f5b6220ec..0000000000 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MongoRepositoryClassReplacement.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.evomaster.client.java.instrumentation.coverage.methodreplacement.classes; - -import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement; -import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast; -import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass; -import org.evomaster.client.java.instrumentation.coverage.methodreplacement.UsageFilter; -import org.evomaster.client.java.instrumentation.shared.ReplacementCategory; -import org.evomaster.client.java.instrumentation.shared.ReplacementType; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - - -public class MongoRepositoryClassReplacement extends ThirdPartyMethodReplacementClass { - private static final MongoRepositoryClassReplacement singleton = new MongoRepositoryClassReplacement(); - - @Override - protected String getNameOfThirdPartyTargetClass() { - return "org.springframework.data.mongodb.repository.support.SimpleMongoRepository"; - } - - // This is not working. It may be related to the fact that this constructor seems to be called using reflection. - // When a MongoRepository is created in Spring it uses a MongoCollection under the hood. - // The type of the collection is the default (Document) despite probably the repository was created of some custom type like Person. - // The fact that elements of the collection should be Person is stored in Spring (SimpleMongoRepository). - // The idea is to instrument the constructor of SimpleMongoRepository to obtain the actual type of the collection. - - // Change category to MONGO - @Replacement(replacingStatic = false, - replacingConstructor = true, - type = ReplacementType.TRACKER, id = "SimpleMongoRepository", - usageFilter = UsageFilter.ANY, - category = ReplacementCategory.SQL) - public static void SimpleMongoRepository( - Object mongoRepository, - @ThirdPartyCast(actualType = "org.springframework.data.mongodb.repository.query.MongoEntityInformation") Object metadata, - @ThirdPartyCast(actualType = "org.springframework.data.mongodb.repository.core.MongoOperations") Object mongoOperations) { - try { - Method method = getOriginal(singleton, "SimpleMongoRepository", mongoRepository); - method.invoke(mongoRepository, metadata, mongoOperations); - // Store info - } catch (IllegalAccessException e){ - throw new RuntimeException(e); - } catch (InvocationTargetException e){ - throw (RuntimeException) e.getCause(); - } - } -} diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java index 4e73901a6d..c27c224350 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java @@ -421,6 +421,11 @@ public static void addMongoInfo(MongoInfo info){ getCurrentAdditionalInfo().addMongoInfo(info); } + public static void addMongoCollectionInfo(MongoCollectionInfo info){ + if (!executingInitMongo) + getCurrentAdditionalInfo().addMongoCollectionInfo(info); + } + public static void markLastExecutedStatement(String lastLine, String lastMethod) { getCurrentAdditionalInfo().pushLastExecutedStatement(lastLine, lastMethod); } diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt index 601c5336e9..8542695715 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt @@ -4,23 +4,49 @@ import org.evomaster.core.search.gene.* import org.evomaster.core.search.gene.collection.ArrayGene import org.evomaster.core.search.gene.datetime.DateGene import org.evomaster.core.search.gene.numeric.* +import org.evomaster.core.search.gene.optional.NullableGene import org.evomaster.core.search.gene.string.StringGene -import java.util.Date class MongoActionGeneBuilder { - fun buildGene(fieldName: String, value: T): Gene { - return when (value) { - is Int -> IntegerGene(fieldName, min = Int.MIN_VALUE, max = Int.MAX_VALUE) - is Long -> LongGene(fieldName, min = Long.MIN_VALUE, max = Long.MAX_VALUE) - is Double -> DoubleGene(fieldName, min = Double.MIN_VALUE, max = Double.MAX_VALUE) - is String -> StringGene(name = fieldName, minLength = Int.MIN_VALUE) - is Boolean -> BooleanGene(name = fieldName) - //is Array<*> -> ArrayGene<*>(name = fieldName) - //is Object -> ObjectGene - //is Date -> DateGene - //is null -> NullGene - //Unhandled Types: Binary Data, Object Id, Regular Expression, Javascript, Timestamp, Decimal128, Min/max key - else -> throw IllegalArgumentException("Cannot handle: $fieldName.") + fun buildGene(fieldName: String, valueType: Class): Gene { + val typeName = valueType.simpleName; + + if(typeName == "String"){ + return StringGene(name = fieldName) + } + + if(typeName == "int" || typeName == "Integer"){ + return IntegerGene(fieldName, min = Int.MIN_VALUE, max = Int.MAX_VALUE) + } + + if(typeName == "long" || typeName == "Long"){ + return LongGene(fieldName, min = Long.MIN_VALUE, max = Long.MAX_VALUE) + } + + if(typeName == "double" || typeName == "Double"){ + return DoubleGene(fieldName, min = Double.MIN_VALUE, max = Double.MAX_VALUE) + } + + if(typeName == "boolean" || typeName == "Boolean") { + return BooleanGene(name = fieldName) } + + if(typeName == "Date") { + return DateGene(name = fieldName) + } + + + /* + if(valueType == List<*>::javaClass ) { + return ArrayGene(name = fieldName, template = IntegerGene(fieldName, min = Int.MIN_VALUE, max = Int.MAX_VALUE)) + } + + */ + + //Unhandled Types: Null, Document, Binary Data, Object Id, Regular Expression, Javascript, Timestamp, Decimal128, Min/max key + + return ObjectGene(fieldName, valueType.fields.map { field -> buildGene(field.name, field.type) }) + + //throw IllegalArgumentException("Cannot handle: $fieldName.") } } diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt index e29dfb17f2..c24cdefa8d 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt @@ -5,32 +5,23 @@ import org.evomaster.core.search.gene.Gene import org.evomaster.core.search.gene.ObjectGene import java.util.* -class MongoDbAction(val database: String, val collection: String, val documentsType: Class<*>, val accessedFields: Map, computedGenes: List? = null) : Action(listOf()) { +class MongoDbAction( + val database: String, + val collection: String, + val documentsType: Class<*>, + val accessedFields: Map>, + computedGenes: List? = null +) : Action(listOf()) { - val genes: List = (computedGenes ?: computeGenes()) .also { addChildren(it) } + val genes: List = (computedGenes ?: computeGenes()).also { addChildren(it) } private fun computeGenes(): List { - // I should be as specific as I can with the type the collection's documents should have. - // There are a few ways to get that info: - - // 1) Using , which is the result of collection.getDocumentsClass() - // Spring for example "ignore" this and store type info inside SampleMongoRepository - - // 2) Instrument the constructor of SampleMongoRepository and retrieve the info - // Probably can reuse something from GsonClassReplacement. - - // 3) Extract from the query the fields () used and type of each of them. This probably won't - // work fine as usually a subset of fields is used in a query. But is better than creating a - // Document. - val genes = - if(documentsType.typeName == "org.bson.Document"){ - // 3) - accessedFields.map { MongoActionGeneBuilder().buildGene(it.key, it.value) } - }else{ - // 1) - documentsType.declaredFields.map { MongoActionGeneBuilder().buildGene(it.name, it.type) } - } + if (documentsType.typeName == "org.bson.Document") { + accessedFields.map { MongoActionGeneBuilder().buildGene(it.key, it.value) } + } else { + documentsType.declaredFields.map { MongoActionGeneBuilder().buildGene(it.name, it.type) } + } return Collections.singletonList(ObjectGene("BSON", genes)) } diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt index 32aa37835d..743e43545c 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt @@ -2,7 +2,7 @@ package org.evomaster.core.mongo class MongoInsertBuilder { - fun createMongoInsertionAction(database: String, collection: String, documentsType: Class<*>, accessedFields: Map): List { + fun createMongoInsertionAction(database: String, collection: String, documentsType: Class<*>, accessedFields: Map>): List { return mutableListOf(MongoDbAction(database, collection, documentsType, accessedFields)) } } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt index 8340d803a6..0a3f3cb5e9 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt @@ -78,7 +78,7 @@ abstract class EnterpriseSampler : Sampler() where T : Individual { return actions } - fun sampleMongoInsertion(database: String, collection: String, documentsType: Class<*>, accessedFields: Map): List { + fun sampleMongoInsertion(database: String, collection: String, documentsType: Class<*>, accessedFields: Map>): List { // Should I use something like this? //val extraConstraints = randomness.nextBoolean(apc.getExtraSqlDbConstraintsProbability()) diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/DoubleGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/DoubleGene.kt index 936e1e2a81..826aa45352 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/DoubleGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/DoubleGene.kt @@ -82,7 +82,8 @@ class DoubleGene(name: String, } override fun getValueAsPrintableString(previousGenes: List, mode: GeneUtils.EscapeMode?, targetFormat: OutputFormat?, extraCheck: Boolean): String { - return getFormattedValue().toString() + val stringValue = getFormattedValue().toString() + return if(mode==GeneUtils.EscapeMode.EJSON) "{ \"\$numberInt\": \"$stringValue\" }" else stringValue } override fun copyValueFrom(other: Gene): Boolean { diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/IntegerGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/IntegerGene.kt index d6472bc101..8afd998639 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/IntegerGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/IntegerGene.kt @@ -143,7 +143,8 @@ class IntegerGene( targetFormat: OutputFormat?, extraCheck: Boolean ): String { - return value.toString() + val stringValue = value.toString() + return if(mode==GeneUtils.EscapeMode.EJSON) "{ \"\$numberInt\": \"$stringValue\" }" else stringValue } diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/LongGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/LongGene.kt index 1402aae1e4..d9028b97bf 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/LongGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/LongGene.kt @@ -68,7 +68,8 @@ class LongGene( } override fun getValueAsPrintableString(previousGenes: List, mode: GeneUtils.EscapeMode?, targetFormat: OutputFormat?, extraCheck: Boolean): String { - return value.toString() + val stringValue = value.toString() + return if(mode==GeneUtils.EscapeMode.EJSON) "{ \"\$numberLong\": \"$stringValue\" }" else stringValue } override fun copyValueFrom(other: Gene): Boolean { diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/utils/GeneUtils.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/utils/GeneUtils.kt index c36a169ea2..7747b918e5 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/utils/GeneUtils.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/utils/GeneUtils.kt @@ -54,6 +54,7 @@ object GeneUtils { SQL, ASSERTION, EXPECTATION, + EJSON, JSON, TEXT, XML, @@ -172,7 +173,7 @@ object GeneUtils { EscapeMode.SQL -> applySqlEscapes(string, format) EscapeMode.ASSERTION -> applyAssertionEscapes(string, format) EscapeMode.EXPECTATION -> applyExpectationEscapes(string, format) - EscapeMode.JSON -> applyJsonEscapes(string, format) + EscapeMode.JSON, EscapeMode.EJSON-> applyJsonEscapes(string, format) EscapeMode.TEXT -> applyTextEscapes(string, format) EscapeMode.NONE, EscapeMode.X_WWW_FORM_URLENCODED, From c93a04d519ee153af86f47c42c53e3506b1c24b4 Mon Sep 17 00:00:00 2001 From: hghianni Date: Sun, 25 Jun 2023 23:55:01 -0300 Subject: [PATCH 04/20] Add tests, refactors, docs --- .../dto/database/execution/FailedQuery.java | 8 +- .../operations/MongoInsertionDto.java | 5 +- .../operations/MongoInsertionEntryDto.java | 6 - .../controller/internal/EMController.java | 6 +- .../controller/internal/SutController.java | 2 +- .../controller/internal/db/MongoHandler.java | 181 +++++---------- .../controller/mongo/MongoScriptRunner.java | 37 +++- .../java/controller/mongo/dsl/MongoDsl.java | 14 +- .../mongo/dsl/MongoSequenceDsl.java | 3 +- .../mongo/dsl/MongoStatementDsl.java | 2 +- .../mongo/MongoScriptRunnerTest.java | 46 ++++ client-java/instrumentation/pom.xml | 5 + ...ongoEntityInformationClassReplacement.java | 25 ++- .../MappingMongoEntityOperationsImpl.java | 13 ++ .../MappingMongoEntityInstrumentedTest.java | 49 +++++ .../mongo/MappingMongoEntityOperations.java | 9 + .../mongo/MongoPersistentEntityMock.java | 208 ++++++++++++++++++ core/pom.xml | 6 + .../kotlin/org/evomaster/core/EMConfig.kt | 3 +- .../core/mongo/MongoActionGeneBuilder.kt | 101 +++++++-- .../org/evomaster/core/mongo/MongoDbAction.kt | 27 ++- .../core/mongo/MongoDbActionResult.kt | 16 +- .../core/mongo/MongoDbActionTransformer.kt | 36 +-- .../core/mongo/MongoInsertBuilder.kt | 5 +- .../org/evomaster/core/output/MongoWriter.kt | 6 +- .../api/service/ApiWsStructureMutator.kt | 6 +- .../enterprise/EnterpriseIndividual.kt | 5 +- .../enterprise/service/EnterpriseSampler.kt | 24 +- .../service/RemoteControllerImplementation.kt | 2 +- .../core/search/EvaluatedIndividual.kt | 34 +-- .../evomaster/core/search/gene/ObjectGene.kt | 2 +- .../core/search/gene/datetime/DateGene.kt | 2 +- .../core/search/gene/numeric/DoubleGene.kt | 2 +- .../core/search/gene/numeric/IntegerGene.kt | 2 +- .../core/search/gene/numeric/LongGene.kt | 2 +- .../core/mongo/MongoActionGeneBuilderTest.kt | 82 +++++++ .../evomaster/core/mongo/MongoActionTest.kt | 31 +++ .../mongo/MongoDbActionTransformerTest.kt | 36 +++ .../core/mongo/MongoInsertBuilderTest.kt | 21 ++ .../search/gene/mongo/EJSONOutputModeTest.kt | 73 ++++++ docs/options.md | 2 + .../foo/EvoMasterSampleGenerationTest.kt | 193 ++++++++++++++++ .../rest/mongo/foo/MongoEMGenerationTest.java | 8 +- 43 files changed, 1046 insertions(+), 300 deletions(-) delete mode 100644 client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionEntryDto.java create mode 100644 client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoScriptRunnerTest.java rename client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/{classes => thirdpartyclasses}/MappingMongoEntityInformationClassReplacement.java (79%) create mode 100644 client-java/instrumentation/src/test/java/com/foo/somedifferentpackage/examples/methodreplacement/MappingMongoEntityOperationsImpl.java create mode 100644 client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MappingMongoEntityInstrumentedTest.java create mode 100644 client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MappingMongoEntityOperations.java create mode 100644 client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MongoPersistentEntityMock.java create mode 100644 core/src/test/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilderTest.kt create mode 100644 core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt create mode 100644 core/src/test/kotlin/org/evomaster/core/mongo/MongoDbActionTransformerTest.kt create mode 100644 core/src/test/kotlin/org/evomaster/core/mongo/MongoInsertBuilderTest.kt create mode 100644 core/src/test/kotlin/org/evomaster/core/search/gene/mongo/EJSONOutputModeTest.kt create mode 100644 e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java index 5a955ce8d1..f4a2ef6be7 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/FailedQuery.java @@ -1,13 +1,11 @@ package org.evomaster.client.java.controller.api.dto.database.execution; -import java.util.Map; public class FailedQuery { - public FailedQuery(String database, String collection, Class documentsType, Map > accessedFields) { + public FailedQuery(String database, String collection, Class documentsType) { this.database = database; this.collection = collection; this.documentsType = documentsType; - this.accessedFields = accessedFields; } public FailedQuery(){ @@ -18,13 +16,9 @@ public FailedQuery(){ private final String database; private final String collection; private Class documentsType; - private Map> accessedFields; public String getDatabase() {return database;} public String getCollection() {return collection;} - public Map> getAccessedFields() { - return accessedFields; - } public Class getDocumentsType() { return documentsType; } diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java index d0e7896a4a..9fe5274807 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionDto.java @@ -1,13 +1,10 @@ package org.evomaster.client.java.controller.api.dto.database.operations; -import java.util.ArrayList; -import java.util.List; - public class MongoInsertionDto { public String databaseName; public String collectionName; - public List data = new ArrayList<>(); + public String data; } diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionEntryDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionEntryDto.java deleted file mode 100644 index a23e0c3706..0000000000 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/operations/MongoInsertionEntryDto.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.evomaster.client.java.controller.api.dto.database.operations; - -public class MongoInsertionEntryDto { - public String fieldName; - public String value; -} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java index 7c04329ec4..3377585789 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java @@ -790,8 +790,8 @@ public Response executeMongoInsertion(MongoDatabaseCommandDto dto, @Context Http return Response.status(400).entity(WrappedResponseDto.withError(msg)).build(); } - if (dto.insertions.stream().anyMatch(i -> i.collectionName == null || i.collectionName.isEmpty())) { - String msg = "Insertion with no target collection"; + if (dto.insertions.stream().anyMatch(i -> i.collectionName.isEmpty() || i.databaseName.isEmpty())) { + String msg = "Insertion with no target collection or database"; SimpleLogger.warn(msg); return Response.status(400).entity(WrappedResponseDto.withError(msg)).build(); } @@ -800,7 +800,7 @@ public Response executeMongoInsertion(MongoDatabaseCommandDto dto, @Context Http try { - mongoInsertionResultsDto = MongoScriptRunner.execInsert(connection, dto.insertions); + mongoInsertionResultsDto = MongoScriptRunner.executeInsert(connection, dto.insertions); } catch (Exception e) { String msg = "Failed to execute database command: " + e.getMessage(); SimpleLogger.warn(msg); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java index ca5c61d28d..e082349228 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java @@ -237,7 +237,7 @@ public MongoInsertionResultsDto execInsertionsIntoMongoDatabase(List failedQueries; - private final HashMap> collectionInfo; + /** + * Info about types of the documents of collections + */ + private final Map> collectionInfo; public MongoHandler() { distances = new ArrayList<>(); @@ -58,23 +62,31 @@ public void reset() { operations.clear(); distances.clear(); failedQueries.clear(); - // collectionInfo is not cleared to avoid losing the info as it's retrieved when SUT is started + // collectionInfo is not cleared to avoid losing the info as it's retrieved while SUT is starting } - public void handle(MongoInfo info) { - if (!extractMongoExecution) { - return; - } + public boolean isCalculateHeuristics() { + return calculateHeuristics; + } - operations.add(info); + public boolean isExtractMongoExecution() { + return extractMongoExecution; } - public void handle(MongoCollectionInfo info) { - if (!extractMongoExecution) { - return; - } + public void setCalculateHeuristics(boolean calculateHeuristics) { + this.calculateHeuristics = calculateHeuristics; + } + + public void setExtractMongoExecution(boolean extractMongoExecution) { + this.extractMongoExecution = extractMongoExecution; + } + + public void handle(MongoInfo info) { + if (extractMongoExecution) operations.add(info); + } - collectionInfo.put(info.getCollectionName(), info.getDocumentsType()); + public void handle(MongoCollectionInfo info) { + if (extractMongoExecution) collectionInfo.put(info.getCollectionName(), info.getDocumentsType()); } public List getDistances() { @@ -88,7 +100,7 @@ public List getDistances() { } distances.add(new MongoOperationDistance(mongoInfo.getQuery(), dist)); - if (dist > 0 ) { + if (dist > 0) { Object collection = mongoInfo.getCollection(); failedQueries.add(new MongoOperation(collection, mongoInfo.getQuery())); } @@ -98,6 +110,12 @@ public List getDistances() { return distances; } + public MongoExecutionDto getExecutionDto() { + MongoExecutionDto dto = new MongoExecutionDto(); + dto.failedQueries = failedQueries.stream().map(this::extractRelevantInfo).collect(Collectors.toList()); + return dto; + } + private double computeDistance(MongoInfo info) { Object collection = info.getCollection(); Iterable documents = getDocuments(collection); @@ -124,144 +142,43 @@ private static Iterable getDocuments(Object collection) { } } - public MongoExecutionDto getExecutionDto(){ - MongoExecutionDto dto = new MongoExecutionDto(); - dto.failedQueries = failedQueries.stream().map(this::extractRelevantInfo).collect(Collectors.toList()); - return dto; - } - private FailedQuery extractRelevantInfo(MongoOperation operation) { - QueryOperation query = new QueryParser().parse(operation.getQuery()); Object collection = operation.getCollection(); - Map> accessedFields = extractFieldsInQuery(query); - Class documentsType; - - String collectionName; String databaseName; + String collectionName; + Class documentsType; try { Class collectionClass = collection.getClass().getClassLoader().loadClass("com.mongodb.client.MongoCollection"); Object namespace = collectionClass.getMethod("getNamespace").invoke(collection); - collectionName = (String) namespace.getClass().getMethod("getCollectionName").invoke(namespace); databaseName = (String) namespace.getClass().getMethod("getDatabaseName").invoke(namespace); - } catch (ClassNotFoundException |IllegalAccessException | InvocationTargetException | NoSuchMethodException e){ - throw new RuntimeException(e); + collectionName = (String) namespace.getClass().getMethod("getCollectionName").invoke(namespace); + } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + throw new RuntimeException("Failed to retrieve collection name or database name", e); } - // I should be as specific as I can with the type the collection's documents should have. - // There are a few ways to get that info: - - // 1) Using collection.getDocumentsClass(). - // Spring for example "ignore" this and store type info inside repository. - - // 2) When using Spring, retrieving the type of the repository associated with the collection. - // The MongoEntityInformation replacement makes this possible. - - // 3) Extract from the query the fields used and type of each of them. This probably won't - // work as expected as usually a subset of fields is used in a query. - - if(collectionInfo.containsKey(collectionName)){ + if (collectionTypeIsRegistered(collectionName)) { documentsType = collectionInfo.get(collectionName); - }else{ + } else { documentsType = extractDocumentsType(collection); } - return new FailedQuery(databaseName, collectionName, documentsType, accessedFields); + return new FailedQuery(databaseName, collectionName, documentsType); + } + + private boolean collectionTypeIsRegistered(String collectionName) { + return collectionInfo.containsKey(collectionName); } private static Class extractDocumentsType(Object collection) { try { Class collectionClass = collection.getClass().getClassLoader().loadClass("com.mongodb.client.MongoCollection"); return (Class) collectionClass.getMethod("getDocumentClass").invoke(collection); - - } catch (NoSuchMethodException | ClassNotFoundException | InvocationTargetException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - private static Map> extractFieldsInQuery(QueryOperation operation) { - Map> accessedFields = new HashMap<>(); - - if(operation instanceof ComparisonOperation) { - ComparisonOperation op = (ComparisonOperation) operation; - accessedFields.put(op.getFieldName(), op.getValue().getClass()); - } - - if(operation instanceof AndOperation) { - AndOperation op = (AndOperation) operation; - op.getConditions().forEach(cond -> accessedFields.putAll(extractFieldsInQuery(cond))); - } - - if(operation instanceof OrOperation) { - OrOperation op = (OrOperation) operation; - op.getConditions().forEach(cond -> accessedFields.putAll(extractFieldsInQuery(cond))); - } - - if(operation instanceof NorOperation) { - NorOperation op = (NorOperation) operation; - op.getConditions().forEach(cond -> accessedFields.putAll(extractFieldsInQuery(cond))); - } - - if(operation instanceof InOperation) { - InOperation op = (InOperation) operation; - accessedFields.put(op.getFieldName(), op.getValues().get(0).getClass()); - } - - if(operation instanceof NotInOperation) { - NotInOperation op = (NotInOperation) operation; - accessedFields.put(op.getFieldName(), op.getValues().get(0).getClass()); - } - - if(operation instanceof AllOperation) { - AllOperation op = (AllOperation) operation; - accessedFields.put(op.getFieldName(), op.getValues().getClass()); - } - - if(operation instanceof SizeOperation) { - SizeOperation op = (SizeOperation) operation; - accessedFields.put(op.getFieldName(), op.getValue().getClass()); - } - - if(operation instanceof ExistsOperation) { - ExistsOperation op = (ExistsOperation) operation; - accessedFields.put(op.getFieldName(), null); - } - - if(operation instanceof ModOperation) { - ModOperation op = (ModOperation) operation; - accessedFields.put(op.getFieldName(), op.getDivisor().getClass()); - } - - if(operation instanceof TypeOperation) { - TypeOperation op = (TypeOperation) operation; - accessedFields.put(op.getFieldName(), op.getType().getClass()); + } catch (NoSuchMethodException | ClassNotFoundException | InvocationTargetException | + IllegalAccessException e) { + throw new RuntimeException("Failed to retrieve document's type from collection", e); } - - /* - if(operation instanceof ElemMatchOperation) { - ElemMatchOperation op = (ElemMatchOperation) operation; - accessedFields.put(op.getFieldName(), op.getValue()); - } - - */ - - return accessedFields; - } - - public boolean isCalculateHeuristics() { - return calculateHeuristics; - } - - public boolean isExtractMongoExecution() { - return extractMongoExecution; - } - - public void setCalculateHeuristics(boolean calculateHeuristics) { - this.calculateHeuristics = calculateHeuristics; - } - - public void setExtractMongoExecution(boolean extractMongoExecution) { - this.extractMongoExecution = extractMongoExecution; } } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java index d4855e61ae..cd6e266b31 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java @@ -3,6 +3,7 @@ import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto; import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -15,10 +16,17 @@ public class MongoScriptRunner { /** * Default constructor */ - public MongoScriptRunner() { - } + public MongoScriptRunner() {} - public static MongoInsertionResultsDto execInsert(Object conn, List insertions){ + /** + * Execute the different Mongo insertions. + * + * @param connection a connection to the database (MongoClient) + * @param insertions the Mongo insertions to execute + * + * @return a MongoInsertionResultsDto + */ + public static MongoInsertionResultsDto executeInsert(Object connection, List insertions) { if (insertions == null || insertions.isEmpty()) { throw new IllegalArgumentException("No data to insert"); @@ -28,15 +36,11 @@ public static MongoInsertionResultsDto execInsert(Object conn, List documentClass = Class.forName("org.bson.Document"); - Object document = documentClass.getDeclaredConstructor().newInstance(); - document = document.getClass().getMethod("parse", String.class).invoke(document, insDto.data.get(0).value); - Object database = conn.getClass().getMethod("getDatabase", String.class).invoke(conn,insDto.databaseName); - Object collection = database.getClass().getMethod("getCollection", String.class).invoke(database, insDto.collectionName); - Class.forName("com.mongodb.client.MongoCollection").getMethod("insertOne", Object.class).invoke(collection, document); + Object document = parseEJSON(insertionDto.data); + insertDocument(connection, insertionDto.databaseName, insertionDto.collectionName, document); mongoResults.set(i, true); } catch (Exception e) { String msg = "Failed to execute insertion with index " + i + " with Mongo. Error: " + e.getMessage(); @@ -49,4 +53,17 @@ public static MongoInsertionResultsDto execInsert(Object conn, List documentClass = Class.forName("org.bson.Document"); + Object document = Class.forName("org.bson.Document").getDeclaredConstructor().newInstance(); + document = documentClass.getMethod("parse", String.class).invoke(document, documentAsEJSON); + return document; + } + } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java index 83c8162678..dece6570ef 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoDsl.java @@ -1,7 +1,6 @@ package org.evomaster.client.java.controller.mongo.dsl; import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto; -import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionEntryDto; import java.util.ArrayList; import java.util.Arrays; @@ -11,7 +10,7 @@ * DSL (Domain Specific Language) for operations on * the Mongo Database */ -public class MongoDsl implements MongoSequenceDsl, MongoStatementDsl{ +public class MongoDsl implements MongoSequenceDsl, MongoStatementDsl{ private List list = new ArrayList<>(); @@ -27,10 +26,9 @@ private MongoDsl(List... previous) { } /** - * @return a DSL object to create SQL operations + * @return a DSL object to create MONGO operations */ public static MongoSequenceDsl mongo() { - return new MongoDsl(); } @@ -65,14 +63,8 @@ public MongoStatementDsl insertInto(String databaseName, String collectionName) @Override public MongoStatementDsl d(String printableValue) { - checkDsl(); - - MongoInsertionEntryDto entry = new MongoInsertionEntryDto(); - entry.value = printableValue; - - current().data.add(entry); - + current().data = printableValue; return this; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java index a69afb0cc6..99bee8820b 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoSequenceDsl.java @@ -5,7 +5,8 @@ public interface MongoSequenceDsl { /** * An insertion operation on the Mongo Database (MongoDB) * - * @param collectionName the target table in the DB + * @param databaseName the target database in the MongoDB + * @param collectionName the target collection in the MongoDB * @return a statement in which it can be specified the values to add */ MongoStatementDsl insertInto(String databaseName, String collectionName); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoStatementDsl.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoStatementDsl.java index 8f9e24437e..464843f4a2 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoStatementDsl.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/dsl/MongoStatementDsl.java @@ -26,7 +26,7 @@ public interface MongoStatementDsl { /** * Build the DTOs (Data Transfer Object) from this DSL, * closing it (ie, not usable any longer). - * @return a list of DTOs representing all the insertion SQL commands defined in this DSL. + * @return a list of DTOs representing all the insertion MONGO commands defined in this DSL. */ List dtos(); diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoScriptRunnerTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoScriptRunnerTest.java new file mode 100644 index 0000000000..e06163fa25 --- /dev/null +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoScriptRunnerTest.java @@ -0,0 +1,46 @@ +package org.evomaster.client.java.controller.mongo; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto; +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; + +// Is this test possible? Docker should be running I think +public class MongoScriptRunnerTest { + + private static MongoClient connection; + private static final int MONGODB_PORT = 27017; + private static final GenericContainer mongodb = new GenericContainer<>("mongo:6.0") + .withExposedPorts(MONGODB_PORT); + + @BeforeAll + public static void initClass() throws Exception { + mongodb.start(); + int port = mongodb.getMappedPort(MONGODB_PORT); + + connection = MongoClients.create("mongodb://localhost:" + port + "/" + "aDatabase"); + } + + @Test + public void testInsert() { + assertFalse(connection.getDatabase("aDatabase").getCollection("aCollection").find().cursor().hasNext()); + MongoInsertionDto insertionDto = new MongoInsertionDto(); + insertionDto.databaseName = "aDatabase"; + insertionDto.collectionName = "aCollection"; + insertionDto.data = "{\"aField\":\"aString\"}"; + MongoInsertionResultsDto resultsDto = MongoScriptRunner.executeInsert(getConnection(), Collections.singletonList(insertionDto)); + assertTrue(resultsDto.executionResults.get(0)); + assertTrue(connection.getDatabase("aDatabase").getCollection("aCollection").find().cursor().hasNext()); + } + + public Object getConnection() { + return connection; + } +} diff --git a/client-java/instrumentation/pom.xml b/client-java/instrumentation/pom.xml index 4e7b023b3f..01ce07607e 100644 --- a/client-java/instrumentation/pom.xml +++ b/client-java/instrumentation/pom.xml @@ -149,6 +149,11 @@ mongodb-driver-sync test + + org.springframework.boot + spring-boot-starter-data-mongodb + test + diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MappingMongoEntityInformationClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java similarity index 79% rename from client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MappingMongoEntityInformationClassReplacement.java rename to client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java index 9020836f3c..5ddc9d4f8c 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/classes/MappingMongoEntityInformationClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java @@ -1,4 +1,4 @@ -package org.evomaster.client.java.instrumentation.coverage.methodreplacement.classes; +package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses; import org.evomaster.client.java.instrumentation.MongoCollectionInfo; import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement; @@ -14,7 +14,13 @@ import java.util.Collections; import java.util.List; - +/** + * When a MongoRepository is created in Spring, a MongoCollection is used under the hood. + * But information about the type of the repository's documents is not transferred to the collection. + * That info is retained on Spring side. + * So the intention of this replacement is to retrieve that type info. + * This will allow us to create and insert documents of the correct type in the collection (and the repository). + */ public class MappingMongoEntityInformationClassReplacement extends ThirdPartyMethodReplacementClass { private static final MappingMongoEntityInformationClassReplacement singleton = new MappingMongoEntityInformationClassReplacement(); private static ThreadLocal instance = new ThreadLocal<>(); @@ -44,8 +50,9 @@ private static void addInstance(Object x) { @Replacement( replacingConstructor = true, type = ReplacementType.TRACKER, - category = ReplacementCategory.SQL, - id = "constructorEntity" + category = ReplacementCategory.MONGO, + id = "constructorEntity", + castTo = "org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation" ) public static void MappingMongoEntityInformation(@ThirdPartyCast(actualType = "org.springframework.data.mongodb.core.mapping.MongoPersistentEntity") Object entity) { handleMappingMongoEntityInformationConstructor("constructorEntity", Collections.singletonList(entity)); @@ -54,8 +61,9 @@ public static void MappingMongoEntityInformation(@ThirdPartyCast(actualType = "o @Replacement( replacingConstructor = true, type = ReplacementType.TRACKER, - category = ReplacementCategory.SQL, - id = "constructorEntityCustomCollectionName" + category = ReplacementCategory.MONGO, + id = "constructorEntityCustomCollectionName", + castTo = "org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation" ) public static void MappingMongoEntityInformation(@ThirdPartyCast(actualType = "org.springframework.data.mongodb.core.mapping.MongoPersistentEntity") Object entity, String customCollectionName) { handleMappingMongoEntityInformationConstructor("constructorEntityCustomCollectionName", Arrays.asList(entity, customCollectionName)); @@ -64,8 +72,9 @@ public static void MappingMongoEntityInformation(@ThirdPartyCast(actualType = "o @Replacement( replacingConstructor = true, type = ReplacementType.TRACKER, - category = ReplacementCategory.SQL, - id = "constructorEntityFallbackIdType" + category = ReplacementCategory.MONGO, + id = "constructorEntityFallbackIdType", + castTo = "org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation" ) public static void MappingMongoEntityInformation(@ThirdPartyCast(actualType = "org.springframework.data.mongodb.core.mapping.MongoPersistentEntity") Object entity, Class fallbackIdType) { handleMappingMongoEntityInformationConstructor("constructorEntityFallbackIdType", Arrays.asList(entity, fallbackIdType)); diff --git a/client-java/instrumentation/src/test/java/com/foo/somedifferentpackage/examples/methodreplacement/MappingMongoEntityOperationsImpl.java b/client-java/instrumentation/src/test/java/com/foo/somedifferentpackage/examples/methodreplacement/MappingMongoEntityOperationsImpl.java new file mode 100644 index 0000000000..6abef79a52 --- /dev/null +++ b/client-java/instrumentation/src/test/java/com/foo/somedifferentpackage/examples/methodreplacement/MappingMongoEntityOperationsImpl.java @@ -0,0 +1,13 @@ +package com.foo.somedifferentpackage.examples.methodreplacement; + +import org.evomaster.client.java.instrumentation.example.mongo.MappingMongoEntityOperations; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation; +import org.springframework.lang.Nullable; + +public class MappingMongoEntityOperationsImpl implements MappingMongoEntityOperations { + @Override + public MappingMongoEntityInformation callMappingMongoEntityInformation(MongoPersistentEntity entity, @Nullable Class fallbackIdType){ + return new MappingMongoEntityInformation<>(entity, fallbackIdType); + } +} diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MappingMongoEntityInstrumentedTest.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MappingMongoEntityInstrumentedTest.java new file mode 100644 index 0000000000..6a6fe47831 --- /dev/null +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MappingMongoEntityInstrumentedTest.java @@ -0,0 +1,49 @@ +package org.evomaster.client.java.instrumentation.example.mongo; + +import com.foo.somedifferentpackage.examples.methodreplacement.MappingMongoEntityOperationsImpl; +import org.evomaster.client.java.instrumentation.InstrumentingClassLoader; +import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation; + +import static org.junit.jupiter.api.Assertions.*; + + +public class MappingMongoEntityInstrumentedTest { + + public class SomeType { + public String someField; + } + + protected MappingMongoEntityOperations getInstance() throws Exception { + + InstrumentingClassLoader cl = new InstrumentingClassLoader("com.foo"); + + return (MappingMongoEntityOperations) + cl.loadClass(MappingMongoEntityOperationsImpl.class.getName()) + .newInstance(); + } + + @BeforeEach + public void init(){ + ExecutionTracer.reset(); + assertEquals(0 , ExecutionTracer.getNumberOfObjectives()); + } + + @AfterEach + public void checkInstrumentation(){ + assertTrue(ExecutionTracer.getNumberOfObjectives() > 0); + } + + @Test + public void testConstructor() throws Exception { + MappingMongoEntityOperations mongoInstrumented = getInstance(); + MongoPersistentEntity entity = new MongoPersistentEntityMock<>(); + MappingMongoEntityInformation mappingMongoEntityInformation = mongoInstrumented.callMappingMongoEntityInformation(entity, SomeType.class); + assertNotNull(mappingMongoEntityInformation); + } +} + diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MappingMongoEntityOperations.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MappingMongoEntityOperations.java new file mode 100644 index 0000000000..af5e844398 --- /dev/null +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MappingMongoEntityOperations.java @@ -0,0 +1,9 @@ +package org.evomaster.client.java.instrumentation.example.mongo; + +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation; +import org.springframework.lang.Nullable; + +public interface MappingMongoEntityOperations { + MappingMongoEntityInformation callMappingMongoEntityInformation(MongoPersistentEntity entity, @Nullable Class fallbackIdType); +} diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MongoPersistentEntityMock.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MongoPersistentEntityMock.java new file mode 100644 index 0000000000..b4f6062eae --- /dev/null +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/example/mongo/MongoPersistentEntityMock.java @@ -0,0 +1,208 @@ +package org.evomaster.client.java.instrumentation.example.mongo; + +import org.jetbrains.annotations.NotNull; +import org.springframework.data.mapping.*; +import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.data.mongodb.core.mapping.ShardKey; +import org.springframework.data.mongodb.core.query.Collation; +import org.springframework.data.spel.EvaluationContextProvider; +import org.springframework.data.util.TypeInformation; + +import java.lang.annotation.Annotation; +import java.util.Iterator; + +public class MongoPersistentEntityMock implements MongoPersistentEntity{ + + @Override + public String getCollection() { + return null; + } + + @Override + public String getLanguage() { + return null; + } + + @Override + public MongoPersistentProperty getTextScoreProperty() { + return null; + } + + @Override + public boolean hasTextScoreProperty() { + return false; + } + + @Override + public Collation getCollation() { + return null; + } + + @Override + public ShardKey getShardKey() { + return null; + } + + @Override + public void addPersistentProperty(MongoPersistentProperty mongoPersistentProperty) { + + } + + @Override + public void addAssociation(Association association) { + + } + + @Override + public void verify() throws MappingException { + + } + + @Override + public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory persistentPropertyAccessorFactory) { + + } + + @Override + public void setEvaluationContextProvider(EvaluationContextProvider evaluationContextProvider) { + + } + + @Override + public String getName() { + return null; + } + + @Override + public PreferredConstructor getPersistenceConstructor() { + return null; + } + + @Override + public boolean isConstructorArgument(PersistentProperty persistentProperty) { + return false; + } + + @Override + public boolean isIdProperty(PersistentProperty persistentProperty) { + return false; + } + + @Override + public boolean isVersionProperty(PersistentProperty persistentProperty) { + return false; + } + + @Override + public MongoPersistentProperty getIdProperty() { + return null; + } + + @Override + public MongoPersistentProperty getVersionProperty() { + return null; + } + + @Override + public MongoPersistentProperty getPersistentProperty(String s) { + return null; + } + + @Override + public Iterable getPersistentProperties(Class aClass) { + return null; + } + + @Override + public boolean hasIdProperty() { + return false; + } + + @Override + public boolean hasVersionProperty() { + return false; + } + + @Override + public Class getType() { + return null; + } + + @Override + public Alias getTypeAlias() { + return null; + } + + @Override + public TypeInformation getTypeInformation() { + return null; + } + + @Override + public void doWithProperties(PropertyHandler propertyHandler) { + + } + + @Override + public void doWithProperties(SimplePropertyHandler simplePropertyHandler) { + + } + + @Override + public void doWithAssociations(AssociationHandler associationHandler) { + + } + + @Override + public void doWithAssociations(SimpleAssociationHandler simpleAssociationHandler) { + + } + + @Override + public A findAnnotation(Class aClass) { + return null; + } + + @Override + public boolean isAnnotationPresent(Class aClass) { + return false; + } + + @Override + public PersistentPropertyAccessor getPropertyAccessor(B b) { + return null; + } + + @Override + public PersistentPropertyPathAccessor getPropertyPathAccessor(B b) { + return null; + } + + @Override + public IdentifierAccessor getIdentifierAccessor(Object o) { + return null; + } + + @Override + public boolean isNew(Object o) { + return false; + } + + @Override + public boolean isImmutable() { + return false; + } + + @Override + public boolean requiresPropertyPopulation() { + return false; + } + + @NotNull + @Override + public Iterator iterator() { + return null; + } +} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 331d928a2c..4cbe6c6654 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -222,6 +222,12 @@ mockserver-client-java-no-dependencies test + + + org.mongodb + bson + test + diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index 757dabdd4a..0b699e9804 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -1061,8 +1061,9 @@ class EMConfig { @Cfg("Enable EvoMaster to generate SQL data with direct accesses to the database. Use a search algorithm") var generateSqlDataWithSearch = true + @Experimental @Cfg("Enable EvoMaster to generate Mongo data with direct accesses to the database") - var generateMongoData = true + var generateMongoData = false @Cfg("When generating SQL data, how many new rows (max) to generate for each specific SQL Select") @Min(1.0) diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt index 8542695715..ddffb67343 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt @@ -4,49 +4,110 @@ import org.evomaster.core.search.gene.* import org.evomaster.core.search.gene.collection.ArrayGene import org.evomaster.core.search.gene.datetime.DateGene import org.evomaster.core.search.gene.numeric.* -import org.evomaster.core.search.gene.optional.NullableGene import org.evomaster.core.search.gene.string.StringGene +import org.slf4j.Logger +import org.slf4j.LoggerFactory +// Default Mapping of Java Classes to BSON types: +// https://mongodb.github.io/mongo-java-driver/3.6/javadoc/?org/bson/codecs/BsonTypeClassMap.html class MongoActionGeneBuilder { - fun buildGene(fieldName: String, valueType: Class): Gene { - val typeName = valueType.simpleName; + private val log: Logger = LoggerFactory.getLogger(MongoActionGeneBuilder::class.java) - if(typeName == "String"){ + fun buildGene(fieldName: String, valueType: Class): Gene? { + val typeName = valueType.name; + + if(typeName == "java.lang.String"){ return StringGene(name = fieldName) } - if(typeName == "int" || typeName == "Integer"){ - return IntegerGene(fieldName, min = Int.MIN_VALUE, max = Int.MAX_VALUE) + if(typeName == "int" || typeName == "java.lang.Integer"){ + return IntegerGene(name = fieldName, min = Int.MIN_VALUE, max = Int.MAX_VALUE) } - if(typeName == "long" || typeName == "Long"){ - return LongGene(fieldName, min = Long.MIN_VALUE, max = Long.MAX_VALUE) + if(typeName == "long" || typeName == "java.lang.Long"){ + return LongGene(name = fieldName, min = Long.MIN_VALUE, max = Long.MAX_VALUE) } - if(typeName == "double" || typeName == "Double"){ - return DoubleGene(fieldName, min = Double.MIN_VALUE, max = Double.MAX_VALUE) + if(typeName == "double" || typeName == "java.lang.Double"){ + return DoubleGene(name = fieldName, min = Double.MIN_VALUE, max = Double.MAX_VALUE) } - if(typeName == "boolean" || typeName == "Boolean") { + if(typeName == "boolean" || typeName == "java.lang.Boolean") { return BooleanGene(name = fieldName) } - if(typeName == "Date") { - return DateGene(name = fieldName) + if(typeName == "java.util.Date") { + return DateGene(name = fieldName, onlyValidDates = true) + } + + if(isAListImplementation(valueType)){ + val elementsGene = buildGene("", valueType.componentType) + return if(elementsGene != null) ArrayGene(name = fieldName, template = elementsGene) else null + } + + if(typeName == "org.bson.types.Decimal128") { + return unhandledValueType(fieldName) + } + + if(typeName == "org.bson.types.Binary") { + return unhandledValueType(fieldName) + } + + if(typeName == "org.bson.types.ObjectId") { + return unhandledValueType(fieldName) + } + + if(typeName == "org.bson.types.RegularExpression") { + return unhandledValueType(fieldName) + } + + // Deprecated + if(typeName == "org.bson.types.Symbol") { + return unhandledValueType(fieldName) } + // Deprecated + if(typeName == "org.bson.types.DBPointer") { + return unhandledValueType(fieldName) + } - /* - if(valueType == List<*>::javaClass ) { - return ArrayGene(name = fieldName, template = IntegerGene(fieldName, min = Int.MIN_VALUE, max = Int.MAX_VALUE)) + if(typeName == "org.bson.types.MaxKey") { + return unhandledValueType(fieldName) } - */ + if(typeName == "org.bson.types.MinKey") { + return unhandledValueType(fieldName) + } - //Unhandled Types: Null, Document, Binary Data, Object Id, Regular Expression, Javascript, Timestamp, Decimal128, Min/max key + if(typeName == "org.bson.types.Code") { + return unhandledValueType(fieldName) + } - return ObjectGene(fieldName, valueType.fields.map { field -> buildGene(field.name, field.type) }) + // Deprecated + if(typeName == "org.bson.types.CodeWithScope") { + return unhandledValueType(fieldName) + } - //throw IllegalArgumentException("Cannot handle: $fieldName.") + if(typeName == "org.bson.types.BSONTimestamp") { + return unhandledValueType(fieldName) + } + + // Deprecated + if(typeName == "org.bson.types.Undefined") { + return unhandledValueType(fieldName) + } + + if(typeName == "org.bson.Document") { + return ObjectGene(fieldName, listOf()) + } + + return ObjectGene(fieldName, valueType.fields.mapNotNull { field -> buildGene(field.name, field.type) }) } + + private fun unhandledValueType(fieldName: String): Gene? { + log.warn(("Cannot convert field: $fieldName to gene")) + return null + } + + private fun isAListImplementation(valueType: Class) = valueType.interfaces.any { i -> i.typeName == "java.util.List" } } diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt index c24cdefa8d..a8a6656179 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt @@ -3,30 +3,41 @@ package org.evomaster.core.mongo import org.evomaster.core.search.Action import org.evomaster.core.search.gene.Gene import org.evomaster.core.search.gene.ObjectGene +import java.lang.reflect.Modifier import java.util.* class MongoDbAction( + /** + * The database to insert document into + */ val database: String, + /** + * The collection to insert document into + */ val collection: String, + /** + * The type of the new document. Should map the type of the documents of the collection + */ val documentsType: Class<*>, - val accessedFields: Map>, computedGenes: List? = null ) : Action(listOf()) { - val genes: List = (computedGenes ?: computeGenes()).also { addChildren(it) } + private val genes: List = (computedGenes ?: computeGenes()).also { addChildren(it) } private fun computeGenes(): List { val genes = - if (documentsType.typeName == "org.bson.Document") { - accessedFields.map { MongoActionGeneBuilder().buildGene(it.key, it.value) } + if (documentsType.name == "org.bson.Document") { + listOf() } else { - documentsType.declaredFields.map { MongoActionGeneBuilder().buildGene(it.name, it.type) } + getFieldsFromType().mapNotNull { MongoActionGeneBuilder().buildGene(it.name, it.type) } } return Collections.singletonList(ObjectGene("BSON", genes)) } override fun getName(): String { - return "MONGO_Find_${collection}_${accessedFields.map { it.key }.sorted().joinToString("_")}" + return "MONGO_Insert_${database}_${collection}_${ + getFieldsFromType().map { it.name }.sorted().joinToString("_") + }" } override fun seeTopGenes(): List { @@ -38,6 +49,8 @@ class MongoDbAction( } override fun copyContent(): Action { - return MongoDbAction(database, collection, documentsType, accessedFields, genes.map(Gene::copy)) + return MongoDbAction(database, collection, documentsType, genes.map(Gene::copy)) } + + private fun getFieldsFromType() = documentsType.declaredFields.filter { Modifier.isPublic(it.modifiers) } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionResult.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionResult.kt index 09f68ce633..a2a31848e3 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionResult.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionResult.kt @@ -1,4 +1,5 @@ package org.evomaster.core.mongo + import org.evomaster.core.search.Action import org.evomaster.core.search.ActionResult @@ -8,9 +9,9 @@ import org.evomaster.core.search.ActionResult class MongoDbActionResult : ActionResult { constructor(stopping: Boolean = false) : super(stopping) - constructor(other: MongoDbActionResult): super(other) + constructor(other: MongoDbActionResult) : super(other) - companion object{ + companion object { const val INSERT_MONGO_EXECUTE_SUCCESSFULLY = "INSERT_MONGO_EXECUTE_SUCCESSFULLY" } @@ -19,16 +20,15 @@ class MongoDbActionResult : ActionResult { } /** - * @param success specifies whether the INSERT SQL executed successfully - * - * NOTE THAT here for SELECT, the execution result is false by default. + * @param success specifies whether the INSERT MONGO executed successfully */ - fun setInsertExecutionResult(success: Boolean) = addResultValue(INSERT_MONGO_EXECUTE_SUCCESSFULLY, success.toString()) + fun setInsertExecutionResult(success: Boolean) = + addResultValue(INSERT_MONGO_EXECUTE_SUCCESSFULLY, success.toString()) /** - * @return whether the db action executed successfully + * @return whether the MongoDB action executed successfully */ - fun getInsertExecutionResult() = getResultValue(INSERT_MONGO_EXECUTE_SUCCESSFULLY)?.toBoolean()?:false + fun getInsertExecutionResult() = getResultValue(INSERT_MONGO_EXECUTE_SUCCESSFULLY)?.toBoolean() ?: false override fun matchedType(action: Action): Boolean { return action is MongoDbAction diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt index 99b904f687..2377164b2e 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbActionTransformer.kt @@ -1,38 +1,26 @@ package org.evomaster.core.mongo import org.evomaster.client.java.controller.api.dto.database.operations.* +import org.evomaster.core.search.gene.utils.GeneUtils object MongoDbActionTransformer { - fun transform(insertions: List) : MongoDatabaseCommandDto { + fun transform(actions: List) : MongoDatabaseCommandDto { - val list = mutableListOf() + val insertionDtos = mutableListOf() - for (element in insertions) { + for (action in actions) { + val genes = action.seeTopGenes().first() - val insertion = MongoInsertionDto().apply { - databaseName = element.database - collectionName = element.collection } + val insertionDto = MongoInsertionDto().apply { + databaseName = action.database + collectionName = action.collection + data = genes.getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON) + } - val g = element.seeTopGenes().first() - val entry = MongoInsertionEntryDto() - - // If is printed as JSON there might a problem - // A Document(from Mongo) can be created from a JSON but some info might be lost - // Preferably is created from an EJSON (Extended JSON) as JSON can only directly represent a - // subset of the types supported by BSON. - // Maybe we can create a new OutputFormat - - entry.value = g.getValueAsPrintableString() - - // This is ignored for now - entry.fieldName = g.getVariableName() - - insertion.data.add(entry) - - list.add(insertion) + insertionDtos.add(insertionDto) } - return MongoDatabaseCommandDto().apply { this.insertions = list } + return MongoDatabaseCommandDto().apply { this.insertions = insertionDtos } } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt index 743e43545c..fc916af983 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt @@ -1,8 +1,7 @@ package org.evomaster.core.mongo class MongoInsertBuilder { - - fun createMongoInsertionAction(database: String, collection: String, documentsType: Class<*>, accessedFields: Map>): List { - return mutableListOf(MongoDbAction(database, collection, documentsType, accessedFields)) + fun createMongoInsertionAction(database: String, collection: String, documentsType: Class<*>): MongoDbAction{ + return MongoDbAction(database, collection, documentsType) } } diff --git a/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt b/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt index d7fc99f124..1bcac528d7 100644 --- a/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt +++ b/core/src/main/kotlin/org/evomaster/core/output/MongoWriter.kt @@ -23,13 +23,11 @@ object MongoWriter { format: OutputFormat, mongoDbInitialization: List, lines: Lines, - groupIndex: String ="", + groupIndex: String = "", insertionVars: MutableList>, skipFailure: Boolean) { - //if (dbInitialization.isEmpty() || dbInitialization.none { !it.action.representExistingData && (!skipFailure || it.result.getInsertExecutionResult())}) { - //return - //} + if (mongoDbInitialization.isEmpty()) return val insertionVar = "insertions${groupIndex}" val insertionVarResult = "${insertionVar}result" diff --git a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt index ed2f1feba8..554db1eea6 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/api/service/ApiWsStructureMutator.kt @@ -323,9 +323,9 @@ abstract class ApiWsStructureMutator : StructureMutator() { val addedInsertions = if (mutatedGenes != null) mutableListOf>() else null ff.forEach { - val insertions = sampler.sampleMongoInsertion(it.database, it.collection, it.documentsType, it.accessedFields) - ind.addInitializingMongoDbActions(actions = insertions) - addedInsertions?.add(insertions) + val insertion = listOf(sampler.sampleMongoInsertion(it.database, it.collection, it.documentsType)) + ind.addInitializingMongoDbActions(actions = insertion) + addedInsertions?.add(insertion) } return addedInsertions diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt index b9783c43d1..5057774c9f 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt @@ -105,9 +105,8 @@ abstract class EnterpriseIndividual( ActionFilter.MAIN_EXECUTABLE -> groupsView()!!.getAllInGroup(GroupsOfChildren.MAIN) .flatMap { (it as ActionComponent).flatten() } .filter { it !is DbAction && it !is ApiExternalServiceAction } - //CHANGE: This is momentary for testing. Needs refactor to handle multiple databases - ActionFilter.INIT -> groupsView()!!.getAllInGroup(GroupsOfChildren.INITIALIZATION_MONGO).flatMap { (it as ActionComponent).flatten() } - // WARNING: this can still return DbAction and External ones... + ActionFilter.INIT -> seeAllActions().filter { it is DbAction || it is MongoDbAction} + // WARNING: this can still return DbAction, MongoDbAction and External ones... ActionFilter.NO_INIT -> groupsView()!!.getAllInGroup(GroupsOfChildren.MAIN).flatMap { (it as ActionComponent).flatten() } ActionFilter.ONLY_SQL -> seeAllActions().filterIsInstance() ActionFilter.ONLY_MONGO -> seeAllActions().filterIsInstance() diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt index 0a3f3cb5e9..648d976620 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt @@ -78,26 +78,10 @@ abstract class EnterpriseSampler : Sampler() where T : Individual { return actions } - fun sampleMongoInsertion(database: String, collection: String, documentsType: Class<*>, accessedFields: Map>): List { - - // Should I use something like this? - //val extraConstraints = randomness.nextBoolean(apc.getExtraSqlDbConstraintsProbability()) - - val actions = MongoInsertBuilder().createMongoInsertionAction(database, collection, documentsType, accessedFields) - ?: throw IllegalStateException("No MongoDB schema is available") - actions.flatMap{it.seeTopGenes()}.forEach{it.doInitialize(randomness)} - - /* - if (log.isTraceEnabled){ - log.trace("at sampleMongoInsertion, {} insertions are added, and they are {}", actions.size, - actions.joinToString(",") { - if (it is MongoDbAction) it.getResolvedName() else it.getName() - }) - } - - */ - - return actions + fun sampleMongoInsertion(database: String, collection: String, documentsType: Class<*>): MongoDbAction { + val action = MongoInsertBuilder().createMongoInsertionAction(database, collection, documentsType) + action.seeTopGenes().forEach{it.doInitialize(randomness)} + return action } fun canInsertInto(tableName: String) : Boolean { diff --git a/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt b/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt index a0a99ed014..375f83728a 100644 --- a/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt +++ b/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt @@ -456,7 +456,7 @@ class RemoteControllerImplementation() : RemoteController{ val dto = getDtoFromResponse(response, type) - if (!checkResponse(response, dto, "Failed to execute database command")) { + if (!checkResponse(response, dto, "Failed to execute MongoDB insertion")) { return null } diff --git a/core/src/main/kotlin/org/evomaster/core/search/EvaluatedIndividual.kt b/core/src/main/kotlin/org/evomaster/core/search/EvaluatedIndividual.kt index 36960f9cfa..f4493f57a0 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/EvaluatedIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/EvaluatedIndividual.kt @@ -12,7 +12,6 @@ import org.evomaster.core.database.DbAction import org.evomaster.core.database.DbActionResult import org.evomaster.core.logging.LoggingUtil import org.evomaster.core.mongo.MongoDbAction -import org.evomaster.core.mongo.MongoDbActionResult import org.evomaster.core.problem.externalservice.ApiExternalServiceAction import org.evomaster.core.problem.rest.RestCallAction import org.evomaster.core.problem.rest.RestCallResult @@ -24,6 +23,8 @@ import org.evomaster.core.search.service.mutator.EvaluatedMutation import org.evomaster.core.search.tracer.TrackingHistory import org.slf4j.Logger import org.slf4j.LoggerFactory +import javax.security.sasl.AuthorizeCallback +import kotlin.reflect.KClass /** * EvaluatedIndividual allows to tracking its evolution. @@ -711,21 +712,21 @@ class EvaluatedIndividual( ) } - /* - if there exist other types of action (ie, not DbAction), this might need to be extended - */ - //CHANGE: This is momentary for testing. Needs refactor to handle multiple databases - action = individual.seeInitializingActions().filterIsInstance().find { it.seeTopGenes().contains(gene) } + initializingActionClasses().forEach { initializingActionClass -> + action = individual.seeInitializingActions().filter { initializingActionClass.isInstance(it)} + .find { it.seeTopGenes().contains(gene) } - if (action != null) { - return impactInfo.getGene( - localId = null, - fixedIndexedAction = true, - actionName = action.getName(), - actionIndex = individual.seeInitializingActions().indexOf(action), - geneId = id, - fromInitialization = true - ) + if (action != null) { + action as Action + return impactInfo.getGene( + localId = null, + fixedIndexedAction = true, + actionName = action!!.getName(), + actionIndex = individual.seeInitializingActions().indexOf(action), + geneId = id, + fromInitialization = true + ) + } } return impactInfo.getGene( @@ -890,4 +891,7 @@ class EvaluatedIndividual( } return !invalid } + private fun initializingActionClasses(): List> { + return listOf(MongoDbAction::class, DbAction::class) + } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt index 366632fde0..3c0077bbe2 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt @@ -331,7 +331,7 @@ class ObjectGene( } .filter { it.isPrintable() } - if (shouldPrintAsJSON(mode)) { + if (shouldPrintAsJSON(mode) || mode == GeneUtils.EscapeMode.EJSON) { buffer.append("{") includedFields.map { diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/datetime/DateGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/datetime/DateGene.kt index 1a836f1752..f8a1ebaeae 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/datetime/DateGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/datetime/DateGene.kt @@ -114,7 +114,7 @@ class DateGene( targetFormat: OutputFormat?, extraCheck: Boolean ): String { - return "\"${getValueAsRawString()}\"" + return if(mode == GeneUtils.EscapeMode.EJSON) "{\"\$date\":\"${getValueAsRawString()}\"}" else "\"${getValueAsRawString()}\"" } override fun getValueAsRawString(): String { diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/DoubleGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/DoubleGene.kt index 826aa45352..f2e556b1bf 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/DoubleGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/DoubleGene.kt @@ -83,7 +83,7 @@ class DoubleGene(name: String, override fun getValueAsPrintableString(previousGenes: List, mode: GeneUtils.EscapeMode?, targetFormat: OutputFormat?, extraCheck: Boolean): String { val stringValue = getFormattedValue().toString() - return if(mode==GeneUtils.EscapeMode.EJSON) "{ \"\$numberInt\": \"$stringValue\" }" else stringValue + return if(mode==GeneUtils.EscapeMode.EJSON) "{\"\$numberDouble\":\"$stringValue\"}" else stringValue } override fun copyValueFrom(other: Gene): Boolean { diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/IntegerGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/IntegerGene.kt index 8afd998639..48b4f149fb 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/IntegerGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/IntegerGene.kt @@ -144,7 +144,7 @@ class IntegerGene( extraCheck: Boolean ): String { val stringValue = value.toString() - return if(mode==GeneUtils.EscapeMode.EJSON) "{ \"\$numberInt\": \"$stringValue\" }" else stringValue + return if(mode==GeneUtils.EscapeMode.EJSON) "{\"\$numberInt\":\"$stringValue\"}" else stringValue } diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/LongGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/LongGene.kt index d9028b97bf..5819706fe0 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/LongGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/numeric/LongGene.kt @@ -69,7 +69,7 @@ class LongGene( override fun getValueAsPrintableString(previousGenes: List, mode: GeneUtils.EscapeMode?, targetFormat: OutputFormat?, extraCheck: Boolean): String { val stringValue = value.toString() - return if(mode==GeneUtils.EscapeMode.EJSON) "{ \"\$numberLong\": \"$stringValue\" }" else stringValue + return if(mode==GeneUtils.EscapeMode.EJSON) "{\"\$numberLong\":\"$stringValue\"}" else stringValue } override fun copyValueFrom(other: Gene): Boolean { diff --git a/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilderTest.kt b/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilderTest.kt new file mode 100644 index 0000000000..264c0c00c3 --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilderTest.kt @@ -0,0 +1,82 @@ +package org.evomaster.core.mongo + +import org.evomaster.core.search.gene.BooleanGene +import org.evomaster.core.search.gene.ObjectGene +import org.evomaster.core.search.gene.collection.ArrayGene +import org.evomaster.core.search.gene.datetime.DateGene +import org.evomaster.core.search.gene.numeric.DoubleGene +import org.evomaster.core.search.gene.numeric.IntegerGene +import org.evomaster.core.search.gene.numeric.LongGene +import org.evomaster.core.search.gene.string.StringGene +import org.junit.jupiter.api.Test +import java.util.* + +class MongoActionGeneBuilderTest { + + inner class Object { + var someField: Int = 0 + } + + @Test + fun testStringField() { + val gene = MongoActionGeneBuilder().buildGene("someField", String::class.java) + assert(StringGene::class.isInstance(gene)) + } + + @Test + fun testIntegerField() { + val gene = MongoActionGeneBuilder().buildGene("someField", Integer::class.java) + assert(IntegerGene::class.isInstance(gene)) + } + + @Test + fun testLongField() { + val gene = MongoActionGeneBuilder().buildGene("someField", Long::class.java) + assert(LongGene::class.isInstance(gene)) + } + + @Test + fun testDoubleField() { + val gene = MongoActionGeneBuilder().buildGene("someField", Double::class.java) + assert(DoubleGene::class.isInstance(gene)) + } + + @Test + fun testDateField() { + val gene = MongoActionGeneBuilder().buildGene("someField", Date::class.java) + assert(DateGene::class.isInstance(gene)) + } + + @Test + fun testBooleanField() { + val gene = MongoActionGeneBuilder().buildGene("someField", Boolean::class.java) + assert(BooleanGene::class.isInstance(gene)) + } + + @Test + fun testObjectField() { + val gene = MongoActionGeneBuilder().buildGene("someField", Object::class.java) + assert(ObjectGene::class.isInstance(gene)) + } + + @Test + fun testUnhandledTypeField() { + val gene = MongoActionGeneBuilder().buildGene("someField", Class.forName("org.bson.types.Decimal128")) + assert(gene == null) + } + + @Test + fun testDocumentField() { + val gene = MongoActionGeneBuilder().buildGene("someField", Class.forName("org.bson.Document")) + assert(ObjectGene::class.isInstance(gene)) + } + + /* + @Test + fun testListField() { + val gene = MongoActionGeneBuilder().buildGene("someField", ArrayList()::class.java) + assert(ArrayGene::class.isInstance(gene)) + } + + */ +} \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt b/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt new file mode 100644 index 0000000000..07752dc6cc --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt @@ -0,0 +1,31 @@ +package org.evomaster.core.mongo + +import org.evomaster.core.search.gene.ObjectGene +import org.evomaster.core.search.gene.numeric.IntegerGene +import org.junit.jupiter.api.Test + +class MongoActionTest { + + inner class Object { + @JvmField + var someField: Int = 0 + } + + @Test + fun testGenesWhenDocument() { + val action = MongoDbAction("someDatabase", "someCollection", Class.forName("org.bson.Document")) + val gene = action.seeTopGenes().first() + assert(ObjectGene::class.isInstance(gene)) + gene as ObjectGene + assert(gene.fields.isEmpty()) + } + + @Test + fun testGenesWhenNotDocument() { + val action = MongoDbAction("someDatabase", "someCollection", Object::class.java) + val gene = action.seeTopGenes().first() + assert(ObjectGene::class.isInstance(gene)) + gene as ObjectGene + assert(IntegerGene::class.isInstance(gene.fields.first())) + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/mongo/MongoDbActionTransformerTest.kt b/core/src/test/kotlin/org/evomaster/core/mongo/MongoDbActionTransformerTest.kt new file mode 100644 index 0000000000..e8b8537ef6 --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/mongo/MongoDbActionTransformerTest.kt @@ -0,0 +1,36 @@ +package org.evomaster.core.mongo + +import org.evomaster.core.search.gene.utils.GeneUtils +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class MongoDbActionTransformerTest { + + class CustomType(field: Int) { + val aField = field + } + + @Test + fun testEmpty() { + val actions = listOf() + val dto = MongoDbActionTransformer.transform(actions) + assertTrue(dto.insertions.isEmpty()) + } + + @Test + fun testNotEmpty() { + val database = "aDatabase" + val collection = "aCollection" + val action = MongoDbAction(database, collection, CustomType::class.java) + val actions = listOf(action) + val dto = MongoDbActionTransformer.transform(actions) + assertFalse(dto.insertions.isEmpty()) + assertTrue(dto.insertions[0].databaseName == action.database) + assertTrue(dto.insertions[0].collectionName == action.collection) + assertTrue( + dto.insertions[0].data == action.seeTopGenes().first() + .getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON) + ) + } + +} \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/mongo/MongoInsertBuilderTest.kt b/core/src/test/kotlin/org/evomaster/core/mongo/MongoInsertBuilderTest.kt new file mode 100644 index 0000000000..5795bfb4a8 --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/mongo/MongoInsertBuilderTest.kt @@ -0,0 +1,21 @@ +package org.evomaster.core.mongo + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +class MongoInsertBuilderTest { + class CustomType(field: Int) { + val aField = field + } + @Test + fun testInsert() { + val database = "aDatabase" + val collection = "aCollection" + val documentsType = CustomType::class.java + val builder = MongoInsertBuilder() + val action = builder.createMongoInsertionAction(database, collection, documentsType) + + assertEquals(database, action.database) + assertEquals(collection, action.collection) + assertEquals(documentsType, action.documentsType) + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/mongo/EJSONOutputModeTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/mongo/EJSONOutputModeTest.kt new file mode 100644 index 0000000000..a786b87e36 --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/mongo/EJSONOutputModeTest.kt @@ -0,0 +1,73 @@ +package org.evomaster.core.search.gene.mongo + +import org.evomaster.core.search.gene.BooleanGene +import org.evomaster.core.search.gene.ObjectGene +import org.evomaster.core.search.gene.collection.ArrayGene +import org.evomaster.core.search.gene.datetime.DateGene +import org.evomaster.core.search.gene.numeric.DoubleGene +import org.evomaster.core.search.gene.numeric.IntegerGene +import org.evomaster.core.search.gene.numeric.LongGene +import org.evomaster.core.search.gene.string.StringGene +import org.evomaster.core.search.gene.utils.GeneUtils +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class EJSONOutputModeTest { + @Test + fun testStringGene() { + val gene = StringGene("someField", value = "someValue") + assertEquals("\"someValue\"", gene.getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON)) + } + + @Test + fun testIntegerGene() { + val gene = IntegerGene("someField", value = 1) + assertEquals("{\"\$numberInt\":\"1\"}", gene.getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON)) + } + + @Test + fun testDoubleGene() { + val gene = DoubleGene("someField", value = 1.0) + assertEquals("{\"\$numberDouble\":\"1.0\"}", gene.getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON)) + } + + @Test + fun testLongGene() { + val gene = LongGene("someField", value = 1L) + assertEquals("{\"\$numberLong\":\"1\"}", gene.getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON)) + } + + @Test + fun testBooleanGene() { + val gene = BooleanGene("Boolean", value = false) + assertEquals( + "false", + gene.getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON) + ) + } + + @Test + fun testDateGene() { + val gene = DateGene("someField") + assertEquals("{\"\$date\":\"2016-03-12\"}", gene.getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON)) + } + + @Test + fun testObjectGene() { + val gene = ObjectGene("Object", listOf(LongGene("someField", value = 1L))) + assertEquals( + "{\"someField\":{\"\$numberLong\":\"1\"}}", + gene.getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON) + ) + } + + @Test + fun testArrayGene() { + val gene = ArrayGene("Array", template = LongGene("Long", 1L), elements = mutableListOf(LongGene("Long", 1L))) + assertEquals( + "[{\"\$numberLong\":\"1\"}]", + gene.getValueAsPrintableString(mode = GeneUtils.EscapeMode.EJSON) + ) + } +} + diff --git a/docs/options.md b/docs/options.md index c0e6849dd7..301a061d40 100644 --- a/docs/options.md +++ b/docs/options.md @@ -79,6 +79,7 @@ There are 3 types of options: |`extraHeader`| __Boolean__. Add an extra HTTP header, to analyze how it is used/read by the SUT. Needed to discover new headers that were not specified in the schema. *Default value*: `true`.| |`extraHeuristicsFile`| __String__. Where the extra heuristics file (if any) is going to be written (in CSV format). *Default value*: `extra_heuristics.csv`.| |`extraQueryParam`| __Boolean__. Add an extra query param, to analyze how it is used/read by the SUT. Needed to discover new query params that were not specified in the schema. *Default value*: `true`.| +|`extractMongoExecutionInfo`| __Boolean__. Enable extracting Mongo execution info. *Default value*: `true`.| |`extractSqlExecutionInfo`| __Boolean__. Enable extracting SQL execution info. *Default value*: `true`.| |`feedbackDirectedSampling`| __Enum__. Specify whether when we sample from archive we do look at the most promising targets for which we have had a recent improvement. *Valid values*: `NONE, LAST, FOCUSED_QUICKEST`. *Default value*: `LAST`.| |`focusedSearchActivationTime`| __Double__. The percentage of passed search before starting a more focused, less exploratory one. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.5`.| @@ -181,6 +182,7 @@ There are 3 types of options: |`externalRequestResponseSelectionStrategy`| __Enum__. Harvested external request response selection strategy. *Valid values*: `EXACT, CLOSEST_SAME_DOMAIN, CLOSEST_SAME_PATH, RANDOM`. *Default value*: `EXACT`.| |`externalServiceIP`| __String__. User provided external service IP. *Constraints*: `regex ^127\.((25[0-5]\|2[0-4][0-9]\|[01]?[0-9][0-9]?)\.){2}(25[0-5]\|2[0-4][0-9]\|[01]?[0-9][0-9]?)$`. *Default value*: `127.0.0.2`.| |`externalServiceIPSelectionStrategy`| __Enum__. Specify a method to select the first external service spoof IP address. *Valid values*: `NONE, DEFAULT, USER, RANDOM`. *Default value*: `NONE`.| +|`generateMongoData`| __Boolean__. Enable EvoMaster to generate Mongo data with direct accesses to the database. *Default value*: `false`.| |`generateSqlDataWithDSE`| __Boolean__. Enable EvoMaster to generate SQL data with direct accesses to the database. Use Dynamic Symbolic Execution. *Default value*: `false`.| |`heuristicsForMongo`| __Boolean__. Tracking of Mongo commands to improve test generation. *Default value*: `false`.| |`impactAfterMutationFile`| __String__. Specify a path to save collected impact info after each mutation during search, only useful for debugging. *Default value*: `impactSnapshot.csv`.| diff --git a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt new file mode 100644 index 0000000000..aaad8f12ca --- /dev/null +++ b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt @@ -0,0 +1,193 @@ +package org.evomaster.e2etests.spring.rest.mongo.foo + + +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.Timeout +import org.junit.jupiter.api.Assertions.* +import java.util.List +import org.evomaster.client.java.controller.api.EMTestUtils.* +import org.evomaster.client.java.controller.SutHandler +import io.restassured.RestAssured +import io.restassured.RestAssured.given +import io.restassured.response.ValidatableResponse +import org.evomaster.client.java.controller.mongo.dsl.MongoDsl.mongo +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto +import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto +import org.hamcrest.Matchers.* +import io.restassured.config.JsonConfig +import io.restassured.path.json.config.JsonPathConfig +import io.restassured.config.RedirectConfig.redirectConfig +import org.evomaster.client.java.controller.contentMatchers.NumberMatcher.* +import org.evomaster.client.java.controller.contentMatchers.StringMatcher.* +import org.evomaster.client.java.controller.contentMatchers.SubStringMatcher.* +import org.evomaster.client.java.controller.expect.ExpectationHandler.expectationHandler +import org.evomaster.client.java.controller.expect.ExpectationHandler +import io.restassured.path.json.JsonPath +import java.util.Arrays + + + + +/** + * This file was automatically generated by EvoMaster on 2023-06-26T22:42:10.888704-03:00\[America/Argentina/Buenos_Aires\] + * + * The generated test suite contains 3 tests + * + * Covered targets: 26 + * + * Used time: 0h 1m 55s + * + * Needed budget for current results: 100% + * + * + */ +internal class MongoEMGeneration { + + + companion object { + private val controller : SutHandler = com.foo.spring.rest.mongo.MongoPersonsWithoutPostAppController() + private lateinit var baseUrlOfSut: String + /** [ems] - expectations master switch - is the variable that activates/deactivates expectations individual test cases + * by default, expectations are turned off. The variable needs to be set to [true] to enable expectations + */ + private val ems = false + /** + * sco - supported code oracle - checking that the response status code is among those supported according to the schema + */ + private val sco = false + + + @BeforeAll + @JvmStatic + fun initClass() { + controller.setupForGeneratedTest() + baseUrlOfSut = controller.startSut() + assertNotNull(baseUrlOfSut) + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails() + RestAssured.useRelaxedHTTPSValidation() + RestAssured.urlEncodingEnabled = false + RestAssured.config = RestAssured.config() + .jsonConfig(JsonConfig.jsonConfig().numberReturnType(JsonPathConfig.NumberReturnType.DOUBLE)) + .redirect(redirectConfig().followRedirects(false)) + } + + + @AfterAll + @JvmStatic + fun tearDown() { + controller.stopSut() + } + } + + + @BeforeEach + fun initTest() { + controller.resetStateOfSUT() + } + + + + + @Test @Timeout(60) + fun test_0() { + val expectationHandler: ExpectationHandler = expectationHandler() + + val res_0: ValidatableResponse = given().accept("*/*") + .get("${baseUrlOfSut}/v2/api-docs") + .then() + .statusCode(200) + .assertThat() + .contentType("application/json") + .body("'swagger'", containsString("2.0")) + .body("'info'.'description'", containsString("Some description")) + .body("'info'.'version'", containsString("1.0")) + .body("'info'.'title'", containsString("API")) + .body("'basePath'", containsString("/")) + .body("'tags'.size()", equalTo(1)) + .body("'tags'[0].'name'", containsString("person-without-post-controller")) + .body("'tags'[0].'description'", containsString("Person Without Post Controller")) + .body("'paths'.'/persons/list18'.'get'.'tags'.size()", equalTo(1)) + .body("'paths'.'/persons/list18'.'get'.'tags'", hasItems("person-without-post-controller")) + .body("'paths'.'/persons/list18'.'get'.'summary'", containsString("find18s")) + .body("'paths'.'/persons/list18'.'get'.'operationId'", containsString("find18sUsingGET")) + .body("'paths'.'/persons/list18'.'get'.'produces'.size()", equalTo(1)) + .body("'paths'.'/persons/list18'.'get'.'produces'", hasItems("*/*")) + .body("'paths'.'/persons/list18'.'get'.'responses'.'200'.'description'", containsString("OK")) + .body("'paths'.'/persons/list18'.'get'.'responses'.'401'.'description'", containsString("Unauthorized")) + .body("'paths'.'/persons/list18'.'get'.'responses'.'403'.'description'", containsString("Forbidden")) + .body("'paths'.'/persons/list18'.'get'.'responses'.'404'.'description'", containsString("Not Found")) + .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'200'.'description'", containsString("OK")) + .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'401'.'description'", containsString("Unauthorized")) + .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'403'.'description'", containsString("Forbidden")) + .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'404'.'description'", containsString("Not Found")) + + expectationHandler.expect(ems) + /* + Note: No supported codes appear to be defined. https://swagger.io/docs/specification/describing-responses/. + This is somewhat unexpected, so the code below is likely to lead to a failed expectation + */ + .that(sco, listOf().contains(res_0.extract().statusCode())) + } + + + @Test @Timeout(60) + fun test_1() { + val insertions = mongo().insertInto("persons", "person") + .d("{\"age\":16}") + .and().insertInto("persons", "person") + .d("{\"age\":-512}") + .and().insertInto("persons", "person") + .d("{\"age\":18}") + .and().insertInto("persons", "person") + .d("{\"age\":0}") + .and().insertInto("persons", "person") + .d("{\"age\":131072}") + .and().insertInto("persons", "person") + .d("{\"age\":-2048}") + .and().insertInto("persons", "person") + .d("{\"age\":301793848}") + .and().insertInto("persons", "person") + .d("{\"age\":-16252321}") + .and().insertInto("persons", "person") + .d("{\"age\":692}") + .and().insertInto("persons", "person") + .d("{\"age\":332656646}") + .and().insertInto("persons", "person") + .d("{\"age\":67109689}") + .and().insertInto("persons", "person") + .d("{\"age\":90362404}") + .dtos() + val insertionsresult = controller.execInsertionsIntoMongoDatabase(insertions) + + given().accept("*/*") + .header("x-EMextraHeader123", "") + .get("${baseUrlOfSut}/persons/list18?EMextraParam123=_EM_0_XYZ_") + .then() + .statusCode(200) + .assertThat() + .body(isEmptyOrNullString()) + + } + + + @Test @Timeout(60) + fun test_2() { + val expectationHandler: ExpectationHandler = expectationHandler() + + val res_0: ValidatableResponse = given().accept("*/*") + .header("x-EMextraHeader123", "") + .get("${baseUrlOfSut}/persons/list18?EMextraParam123=_EM_0_XYZ_") + .then() + .statusCode(400) + .assertThat() + .body(isEmptyOrNullString()) + + expectationHandler.expect(ems) + .that(sco, listOf(200, 401, 403, 404).contains(res_0.extract().statusCode())) + } + + +} diff --git a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java index 14218ebbb3..2d4545f48b 100644 --- a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java +++ b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java @@ -1,7 +1,7 @@ package org.evomaster.e2etests.spring.rest.mongo.foo; -import com.foo.spring.rest.mongo.MongoPersonsAppController; import com.foo.spring.rest.mongo.MongoPersonsWithoutPostAppController; +import org.evomaster.core.EMConfig; import org.evomaster.core.problem.rest.HttpVerb; import org.evomaster.core.problem.rest.RestIndividual; import org.evomaster.core.search.Solution; @@ -18,7 +18,9 @@ @BeforeAll public static void initClass() throws Exception { - RestTestBase.initClass(new MongoPersonsWithoutPostAppController()); + EMConfig config = new EMConfig(); + config.setInstrumentMR_MONGO(true); + RestTestBase.initClass(new MongoPersonsWithoutPostAppController(), config); } @Test @@ -35,6 +37,8 @@ public void testRunEM() throws Throwable { args.add("true"); args.add("--instrumentMR_MONGO"); args.add("true"); + args.add("--generateMongoData"); + args.add("true"); Solution solution = initAndRun(args); From 3bd7250cd1b03485804da340a8a1667a9433162f Mon Sep 17 00:00:00 2001 From: hghianni Date: Wed, 28 Jun 2023 14:27:29 -0300 Subject: [PATCH 05/20] Momentary fix to handle multiple databases --- .../controller/mongo/MongoScriptRunner.java | 3 ++- .../org/evomaster/core/mongo/MongoExecution.kt | 4 ++-- .../problem/enterprise/EnterpriseIndividual.kt | 18 ++++++++++++++---- .../core/problem/rest/RestIndividual.kt | 2 +- .../org/evomaster/core/search/FitnessValue.kt | 2 +- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java index cd6e266b31..b2510c9046 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoScriptRunner.java @@ -2,6 +2,7 @@ import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto; import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto; +import org.evomaster.client.java.utils.SimpleLogger; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -23,7 +24,6 @@ public MongoScriptRunner() {} * * @param connection a connection to the database (MongoClient) * @param insertions the Mongo insertions to execute - * * @return a MongoInsertionResultsDto */ public static MongoInsertionResultsDto executeInsert(Object connection, List insertions) { @@ -42,6 +42,7 @@ public static MongoInsertionResultsDto executeInsert(Object connection, List) { +class MongoExecution(val failedQueries: MutableList?) { companion object { fun fromDto(dto: MongoExecutionDto?): MongoExecution { - return MongoExecution(dto!!.failedQueries) + return MongoExecution(dto?.failedQueries) } } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt index 5057774c9f..101d65d143 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt @@ -77,15 +77,25 @@ abstract class EnterpriseIndividual( } //TODO in future ll need to refactor to handle multiple databases and NoSQL ones - //CHANGE: This is momentary for testing. Needs refactor to handle multiple databases - val db = ChildGroup(GroupsOfChildren.INITIALIZATION_MONGO,{e -> e is ActionComponent && e.flatten().all { a -> a is MongoDbAction }}, - if(sizeDb==0) -1 else 0 , if(sizeDb==0) -1 else sizeDb-1 + //CHANGE: This is momentary. Needs refactor to handle multiple databases + + val startIndexSQL = children.indexOfFirst { a -> a is DbAction } + val endIndexSQL = children.indexOfLast { a -> a is DbAction } + val startIndexMongo = children.indexOfFirst { a -> a is MongoDbAction } + val endIndexMongo = children.indexOfLast { a -> a is MongoDbAction } + + val db = ChildGroup(GroupsOfChildren.INITIALIZATION_SQL,{e -> e is ActionComponent && e.flatten().all { a -> a is DbAction }}, + if(sizeDb==0) -1 else startIndexSQL , if(sizeDb==0) -1 else endIndexSQL + ) + + val mongodb = ChildGroup(GroupsOfChildren.INITIALIZATION_MONGO,{e -> e is ActionComponent && e.flatten().all { a -> a is MongoDbAction }}, + if(sizeDb==0) -1 else startIndexMongo , if(sizeDb==0) -1 else endIndexMongo ) val main = ChildGroup(GroupsOfChildren.MAIN, {e -> e !is DbAction && e !is ApiExternalServiceAction }, if(sizeMain == 0) -1 else sizeDb, if(sizeMain == 0) -1 else sizeDb + sizeMain - 1) - return GroupsOfChildren(children, listOf(db, main)) + return GroupsOfChildren(children, listOf(db, mongodb, main)) } } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/RestIndividual.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/RestIndividual.kt index 9c9c45ae4f..8e00d1187b 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/RestIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/RestIndividual.kt @@ -79,7 +79,7 @@ class RestIndividual( children.map { it.copy() }.toMutableList() as MutableList, mainSize = groupsView()!!.sizeOfGroup(GroupsOfChildren.MAIN), //CHANGE: This is momentary for testing. Needs refactor to handle multiple databases - dbSize = groupsView()!!.sizeOfGroup(GroupsOfChildren.INITIALIZATION_MONGO) + dbSize = groupsView()!!.sizeOfGroup(GroupsOfChildren.INITIALIZATION_MONGO ) + groupsView()!!.sizeOfGroup(GroupsOfChildren.INITIALIZATION_SQL ) ) } 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 66006fa51c..1c17d5cdb0 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/FitnessValue.kt @@ -144,7 +144,7 @@ class FitnessValue( } fun aggregateMongoDatabaseData(){ aggregatedFailedFind.clear() - mongoExecutions.values.map { aggregatedFailedFind.addAll(it.failedQueries) } + mongoExecutions.values.map { it.failedQueries?.let { it1 -> aggregatedFailedFind.addAll(it1) } } } fun setExtraToMinimize(actionIndex: Int, list: List) { From cf8f90093ae90311a56c172480e4bc7b04c15592 Mon Sep 17 00:00:00 2001 From: hghianni Date: Wed, 28 Jun 2023 15:34:53 -0300 Subject: [PATCH 06/20] Change option extractMongoExecutionInfo to be false by default --- core/src/main/kotlin/org/evomaster/core/EMConfig.kt | 3 ++- .../e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index 191121ce42..a5a91800e1 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -1051,8 +1051,9 @@ class EMConfig { @Cfg("Enable extracting SQL execution info") var extractSqlExecutionInfo = true + @Experimental @Cfg("Enable extracting Mongo execution info") - var extractMongoExecutionInfo = true + var extractMongoExecutionInfo = false @Experimental @Cfg("Enable EvoMaster to generate SQL data with direct accesses to the database. Use Dynamic Symbolic Execution") diff --git a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java index 2d4545f48b..dd0fba65bd 100644 --- a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java +++ b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java @@ -39,6 +39,8 @@ public void testRunEM() throws Throwable { args.add("true"); args.add("--generateMongoData"); args.add("true"); + args.add("--extractMongoExecutionInfo"); + args.add("true"); Solution solution = initAndRun(args); From dac92645309bb09bccdc6f2f1ff708287e034008 Mon Sep 17 00:00:00 2001 From: hghianni Date: Wed, 28 Jun 2023 15:35:37 -0300 Subject: [PATCH 07/20] Solve failing tests --- .../org/evomaster/core/output/service/ApiTestCaseWriter.kt | 2 +- .../evomaster/core/problem/enterprise/EnterpriseIndividual.kt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/output/service/ApiTestCaseWriter.kt b/core/src/main/kotlin/org/evomaster/core/output/service/ApiTestCaseWriter.kt index 7e8537c51d..b5635e1460 100644 --- a/core/src/main/kotlin/org/evomaster/core/output/service/ApiTestCaseWriter.kt +++ b/core/src/main/kotlin/org/evomaster/core/output/service/ApiTestCaseWriter.kt @@ -49,7 +49,7 @@ abstract class ApiTestCaseWriter : TestCaseWriter() { SqlWriter.handleDbInitialization( format, initializingSqlActions.indices.map { - EvaluatedDbAction(initializingSqlActions[it], initializingSqlActions[it] as DbActionResult) + EvaluatedDbAction(initializingSqlActions[it], initializingSqlActionResults[it] as DbActionResult) }, lines, insertionVars = insertionVars, skipFailure = config.skipFailureSQLInTestFile) } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt index 101d65d143..2285f28459 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/EnterpriseIndividual.kt @@ -115,7 +115,9 @@ abstract class EnterpriseIndividual( ActionFilter.MAIN_EXECUTABLE -> groupsView()!!.getAllInGroup(GroupsOfChildren.MAIN) .flatMap { (it as ActionComponent).flatten() } .filter { it !is DbAction && it !is ApiExternalServiceAction } - ActionFilter.INIT -> seeAllActions().filter { it is DbAction || it is MongoDbAction} + ActionFilter.INIT -> groupsView()!!.getAllInGroup(GroupsOfChildren.INITIALIZATION_SQL) + .flatMap { (it as ActionComponent).flatten() } + groupsView()!!.getAllInGroup(GroupsOfChildren.INITIALIZATION_MONGO) + .flatMap { (it as ActionComponent).flatten() } // WARNING: this can still return DbAction, MongoDbAction and External ones... ActionFilter.NO_INIT -> groupsView()!!.getAllInGroup(GroupsOfChildren.MAIN).flatMap { (it as ActionComponent).flatten() } ActionFilter.ONLY_SQL -> seeAllActions().filterIsInstance() From d39a3d6556660947f2f60b77ca549b7c2d993461 Mon Sep 17 00:00:00 2001 From: hghianni Date: Wed, 28 Jun 2023 16:36:29 -0300 Subject: [PATCH 08/20] Update options file --- docs/options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/options.md b/docs/options.md index 08ee2789c4..cce3d6f87b 100644 --- a/docs/options.md +++ b/docs/options.md @@ -79,7 +79,6 @@ There are 3 types of options: |`extraHeader`| __Boolean__. Add an extra HTTP header, to analyze how it is used/read by the SUT. Needed to discover new headers that were not specified in the schema. *Default value*: `true`.| |`extraHeuristicsFile`| __String__. Where the extra heuristics file (if any) is going to be written (in CSV format). *Default value*: `extra_heuristics.csv`.| |`extraQueryParam`| __Boolean__. Add an extra query param, to analyze how it is used/read by the SUT. Needed to discover new query params that were not specified in the schema. *Default value*: `true`.| -|`extractMongoExecutionInfo`| __Boolean__. Enable extracting Mongo execution info. *Default value*: `true`.| |`extractSqlExecutionInfo`| __Boolean__. Enable extracting SQL execution info. *Default value*: `true`.| |`feedbackDirectedSampling`| __Enum__. Specify whether when we sample from archive we do look at the most promising targets for which we have had a recent improvement. *Valid values*: `NONE, LAST, FOCUSED_QUICKEST`. *Default value*: `LAST`.| |`focusedSearchActivationTime`| __Double__. The percentage of passed search before starting a more focused, less exploratory one. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.5`.| @@ -182,6 +181,7 @@ There are 3 types of options: |`externalRequestResponseSelectionStrategy`| __Enum__. Harvested external request response selection strategy. *Valid values*: `EXACT, CLOSEST_SAME_DOMAIN, CLOSEST_SAME_PATH, RANDOM`. *Default value*: `EXACT`.| |`externalServiceIP`| __String__. User provided external service IP. *Constraints*: `regex ^127\.((25[0-5]\|2[0-4][0-9]\|[01]?[0-9][0-9]?)\.){2}(25[0-5]\|2[0-4][0-9]\|[01]?[0-9][0-9]?)$`. *Default value*: `127.0.0.2`.| |`externalServiceIPSelectionStrategy`| __Enum__. Specify a method to select the first external service spoof IP address. *Valid values*: `NONE, DEFAULT, USER, RANDOM`. *Default value*: `NONE`.| +|`extractMongoExecutionInfo`| __Boolean__. Enable extracting Mongo execution info. *Default value*: `false`.| |`generateMongoData`| __Boolean__. Enable EvoMaster to generate Mongo data with direct accesses to the database. *Default value*: `false`.| |`generateSqlDataWithDSE`| __Boolean__. Enable EvoMaster to generate SQL data with direct accesses to the database. Use Dynamic Symbolic Execution. *Default value*: `false`.| |`heuristicsForMongo`| __Boolean__. Tracking of Mongo commands to improve test generation. *Default value*: `false`.| From ea421846db3ebd8b9376266d27bebbc2200cf17c Mon Sep 17 00:00:00 2001 From: hghianni Date: Wed, 28 Jun 2023 19:20:55 -0300 Subject: [PATCH 09/20] Decrease iterations of MongoEMGenerationTest to avoid timeout --- .../e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java index dd0fba65bd..6b4f9f766e 100644 --- a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java +++ b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java @@ -29,7 +29,7 @@ public void testRunEM() throws Throwable { runTestHandlingFlakyAndCompilation( "MongoEMGeneration", "org.foo.spring.rest.mongo.MongoEMGeneration", - 10000, + 1000, (args) -> { args.add("--enableWeightBasedMutationRateSelectionForGene"); args.add("false"); From d06f9c3378088ec4956bd81c050b2a5f1fcd8059 Mon Sep 17 00:00:00 2001 From: hghianni Date: Thu, 29 Jun 2023 14:16:48 -0300 Subject: [PATCH 10/20] Change failed queries to be only the queries executed in an empty collection --- .../controller/internal/db/MongoHandler.java | 8 +- .../foo/EvoMasterSampleGenerationTest.kt | 172 ++++++++---------- .../rest/mongo/foo/MongoEMFitnessTest.java | 5 +- .../rest/mongo/foo/MongoEMGenerationTest.java | 5 +- 4 files changed, 80 insertions(+), 110 deletions(-) diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java index 3128f75ad4..4ef3d7b357 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java @@ -99,11 +99,6 @@ public List getDistances() { dist = Double.MAX_VALUE; } distances.add(new MongoOperationDistance(mongoInfo.getQuery(), dist)); - - if (dist > 0) { - Object collection = mongoInfo.getCollection(); - failedQueries.add(new MongoOperation(collection, mongoInfo.getQuery())); - } }); operations.clear(); @@ -119,6 +114,9 @@ public MongoExecutionDto getExecutionDto() { private double computeDistance(MongoInfo info) { Object collection = info.getCollection(); Iterable documents = getDocuments(collection); + boolean collectionIsEmpty = !documents.iterator().hasNext(); + + if (collectionIsEmpty) failedQueries.add(new MongoOperation(collection, info.getQuery())); MongoHeuristicsCalculator calculator = new MongoHeuristicsCalculator(); diff --git a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt index aaad8f12ca..dc96979d22 100644 --- a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt +++ b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt @@ -1,4 +1,4 @@ -package org.evomaster.e2etests.spring.rest.mongo.foo +package org.foo.spring.rest.mongo import org.junit.jupiter.api.AfterAll @@ -32,34 +32,34 @@ import java.util.Arrays /** - * This file was automatically generated by EvoMaster on 2023-06-26T22:42:10.888704-03:00\[America/Argentina/Buenos_Aires\] - * + * This file was automatically generated by EvoMaster on 2023-06-29T13:40:21.894829-03:00\[America/Argentina/Buenos_Aires\] + * * The generated test suite contains 3 tests - * - * Covered targets: 26 - * - * Used time: 0h 1m 55s - * + * + * Covered targets: 29 + * + * Used time: 0h 0m 10s + * * Needed budget for current results: 100% - * - * + * + * */ internal class MongoEMGeneration { - + companion object { private val controller : SutHandler = com.foo.spring.rest.mongo.MongoPersonsWithoutPostAppController() private lateinit var baseUrlOfSut: String /** [ems] - expectations master switch - is the variable that activates/deactivates expectations individual test cases - * by default, expectations are turned off. The variable needs to be set to [true] to enable expectations - */ + * by default, expectations are turned off. The variable needs to be set to [true] to enable expectations + */ private val ems = false /** - * sco - supported code oracle - checking that the response status code is among those supported according to the schema - */ + * sco - supported code oracle - checking that the response status code is among those supported according to the schema + */ private val sco = false - - + + @BeforeAll @JvmStatic fun initClass() { @@ -73,57 +73,57 @@ internal class MongoEMGeneration { .jsonConfig(JsonConfig.jsonConfig().numberReturnType(JsonPathConfig.NumberReturnType.DOUBLE)) .redirect(redirectConfig().followRedirects(false)) } - - + + @AfterAll @JvmStatic fun tearDown() { controller.stopSut() } } - - + + @BeforeEach fun initTest() { controller.resetStateOfSUT() } - - - - + + + + @Test @Timeout(60) fun test_0() { val expectationHandler: ExpectationHandler = expectationHandler() - + val res_0: ValidatableResponse = given().accept("*/*") - .get("${baseUrlOfSut}/v2/api-docs") - .then() - .statusCode(200) - .assertThat() - .contentType("application/json") - .body("'swagger'", containsString("2.0")) - .body("'info'.'description'", containsString("Some description")) - .body("'info'.'version'", containsString("1.0")) - .body("'info'.'title'", containsString("API")) - .body("'basePath'", containsString("/")) - .body("'tags'.size()", equalTo(1)) - .body("'tags'[0].'name'", containsString("person-without-post-controller")) - .body("'tags'[0].'description'", containsString("Person Without Post Controller")) - .body("'paths'.'/persons/list18'.'get'.'tags'.size()", equalTo(1)) - .body("'paths'.'/persons/list18'.'get'.'tags'", hasItems("person-without-post-controller")) - .body("'paths'.'/persons/list18'.'get'.'summary'", containsString("find18s")) - .body("'paths'.'/persons/list18'.'get'.'operationId'", containsString("find18sUsingGET")) - .body("'paths'.'/persons/list18'.'get'.'produces'.size()", equalTo(1)) - .body("'paths'.'/persons/list18'.'get'.'produces'", hasItems("*/*")) - .body("'paths'.'/persons/list18'.'get'.'responses'.'200'.'description'", containsString("OK")) - .body("'paths'.'/persons/list18'.'get'.'responses'.'401'.'description'", containsString("Unauthorized")) - .body("'paths'.'/persons/list18'.'get'.'responses'.'403'.'description'", containsString("Forbidden")) - .body("'paths'.'/persons/list18'.'get'.'responses'.'404'.'description'", containsString("Not Found")) - .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'200'.'description'", containsString("OK")) - .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'401'.'description'", containsString("Unauthorized")) - .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'403'.'description'", containsString("Forbidden")) - .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'404'.'description'", containsString("Not Found")) - + .get("${baseUrlOfSut}/v2/api-docs") + .then() + .statusCode(200) + .assertThat() + .contentType("application/json") + .body("'swagger'", containsString("2.0")) + .body("'info'.'description'", containsString("Some description")) + .body("'info'.'version'", containsString("1.0")) + .body("'info'.'title'", containsString("API")) + .body("'basePath'", containsString("/")) + .body("'tags'.size()", equalTo(1)) + .body("'tags'[0].'name'", containsString("person-without-post-controller")) + .body("'tags'[0].'description'", containsString("Person Without Post Controller")) + .body("'paths'.'/persons/list18'.'get'.'tags'.size()", equalTo(1)) + .body("'paths'.'/persons/list18'.'get'.'tags'", hasItems("person-without-post-controller")) + .body("'paths'.'/persons/list18'.'get'.'summary'", containsString("find18s")) + .body("'paths'.'/persons/list18'.'get'.'operationId'", containsString("find18sUsingGET")) + .body("'paths'.'/persons/list18'.'get'.'produces'.size()", equalTo(1)) + .body("'paths'.'/persons/list18'.'get'.'produces'", hasItems("*/*")) + .body("'paths'.'/persons/list18'.'get'.'responses'.'200'.'description'", containsString("OK")) + .body("'paths'.'/persons/list18'.'get'.'responses'.'401'.'description'", containsString("Unauthorized")) + .body("'paths'.'/persons/list18'.'get'.'responses'.'403'.'description'", containsString("Forbidden")) + .body("'paths'.'/persons/list18'.'get'.'responses'.'404'.'description'", containsString("Not Found")) + .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'200'.'description'", containsString("OK")) + .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'401'.'description'", containsString("Unauthorized")) + .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'403'.'description'", containsString("Forbidden")) + .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'404'.'description'", containsString("Not Found")) + expectationHandler.expect(ems) /* Note: No supported codes appear to be defined. https://swagger.io/docs/specification/describing-responses/. @@ -131,60 +131,38 @@ internal class MongoEMGeneration { */ .that(sco, listOf().contains(res_0.extract().statusCode())) } - - + + @Test @Timeout(60) fun test_1() { val insertions = mongo().insertInto("persons", "person") - .d("{\"age\":16}") - .and().insertInto("persons", "person") - .d("{\"age\":-512}") - .and().insertInto("persons", "person") - .d("{\"age\":18}") - .and().insertInto("persons", "person") - .d("{\"age\":0}") - .and().insertInto("persons", "person") - .d("{\"age\":131072}") - .and().insertInto("persons", "person") - .d("{\"age\":-2048}") - .and().insertInto("persons", "person") - .d("{\"age\":301793848}") - .and().insertInto("persons", "person") - .d("{\"age\":-16252321}") - .and().insertInto("persons", "person") - .d("{\"age\":692}") - .and().insertInto("persons", "person") - .d("{\"age\":332656646}") - .and().insertInto("persons", "person") - .d("{\"age\":67109689}") - .and().insertInto("persons", "person") - .d("{\"age\":90362404}") + .d("{\"id\":\"IMU_V4ERbZxCSaw\", \"age\":18}") .dtos() val insertionsresult = controller.execInsertionsIntoMongoDatabase(insertions) - + given().accept("*/*") - .header("x-EMextraHeader123", "") - .get("${baseUrlOfSut}/persons/list18?EMextraParam123=_EM_0_XYZ_") - .then() - .statusCode(200) - .assertThat() - .body(isEmptyOrNullString()) - + .header("x-EMextraHeader123", "") + .get("${baseUrlOfSut}/persons/list18") + .then() + .statusCode(200) + .assertThat() + .body(isEmptyOrNullString()) + } - - + + @Test @Timeout(60) fun test_2() { val expectationHandler: ExpectationHandler = expectationHandler() - + val res_0: ValidatableResponse = given().accept("*/*") - .header("x-EMextraHeader123", "") - .get("${baseUrlOfSut}/persons/list18?EMextraParam123=_EM_0_XYZ_") - .then() - .statusCode(400) - .assertThat() - .body(isEmptyOrNullString()) - + .header("x-EMextraHeader123", "") + .get("${baseUrlOfSut}/persons/list18?EMextraParam123=_EM_0_XYZ_") + .then() + .statusCode(400) + .assertThat() + .body(isEmptyOrNullString()) + expectationHandler.expect(ems) .that(sco, listOf(200, 401, 403, 404).contains(res_0.extract().statusCode())) } diff --git a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMFitnessTest.java b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMFitnessTest.java index 6267c23c89..126af94500 100644 --- a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMFitnessTest.java +++ b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMFitnessTest.java @@ -10,10 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -public class - - -MongoEMFitnessTest extends RestTestBase { +public class MongoEMFitnessTest extends RestTestBase { @BeforeAll public static void initClass() throws Exception { diff --git a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java index 6b4f9f766e..1396201c73 100644 --- a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java +++ b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/MongoEMGenerationTest.java @@ -11,10 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -public class - - -MongoEMGenerationTest extends RestTestBase { +public class MongoEMGenerationTest extends RestTestBase { @BeforeAll public static void initClass() throws Exception { From 55303990fa7998879fb2b94990b528ece703768c Mon Sep 17 00:00:00 2001 From: hghianni Date: Fri, 30 Jun 2023 12:10:44 -0300 Subject: [PATCH 11/20] Change surefire config to debug why MongoEMGenerationTest fails --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index adc73d12a3..7c85370580 100644 --- a/pom.xml +++ b/pom.xml @@ -968,7 +968,7 @@ 3.0.0-M5 1 - true + false 2 false false - + \ No newline at end of file diff --git a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt b/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt deleted file mode 100644 index dc96979d22..0000000000 --- a/e2e-tests/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/foo/EvoMasterSampleGenerationTest.kt +++ /dev/null @@ -1,171 +0,0 @@ -package org.foo.spring.rest.mongo - - -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.Timeout -import org.junit.jupiter.api.Assertions.* -import java.util.List -import org.evomaster.client.java.controller.api.EMTestUtils.* -import org.evomaster.client.java.controller.SutHandler -import io.restassured.RestAssured -import io.restassured.RestAssured.given -import io.restassured.response.ValidatableResponse -import org.evomaster.client.java.controller.mongo.dsl.MongoDsl.mongo -import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto -import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionDto -import org.hamcrest.Matchers.* -import io.restassured.config.JsonConfig -import io.restassured.path.json.config.JsonPathConfig -import io.restassured.config.RedirectConfig.redirectConfig -import org.evomaster.client.java.controller.contentMatchers.NumberMatcher.* -import org.evomaster.client.java.controller.contentMatchers.StringMatcher.* -import org.evomaster.client.java.controller.contentMatchers.SubStringMatcher.* -import org.evomaster.client.java.controller.expect.ExpectationHandler.expectationHandler -import org.evomaster.client.java.controller.expect.ExpectationHandler -import io.restassured.path.json.JsonPath -import java.util.Arrays - - - - -/** - * This file was automatically generated by EvoMaster on 2023-06-29T13:40:21.894829-03:00\[America/Argentina/Buenos_Aires\] - * - * The generated test suite contains 3 tests - * - * Covered targets: 29 - * - * Used time: 0h 0m 10s - * - * Needed budget for current results: 100% - * - * - */ -internal class MongoEMGeneration { - - - companion object { - private val controller : SutHandler = com.foo.spring.rest.mongo.MongoPersonsWithoutPostAppController() - private lateinit var baseUrlOfSut: String - /** [ems] - expectations master switch - is the variable that activates/deactivates expectations individual test cases - * by default, expectations are turned off. The variable needs to be set to [true] to enable expectations - */ - private val ems = false - /** - * sco - supported code oracle - checking that the response status code is among those supported according to the schema - */ - private val sco = false - - - @BeforeAll - @JvmStatic - fun initClass() { - controller.setupForGeneratedTest() - baseUrlOfSut = controller.startSut() - assertNotNull(baseUrlOfSut) - RestAssured.enableLoggingOfRequestAndResponseIfValidationFails() - RestAssured.useRelaxedHTTPSValidation() - RestAssured.urlEncodingEnabled = false - RestAssured.config = RestAssured.config() - .jsonConfig(JsonConfig.jsonConfig().numberReturnType(JsonPathConfig.NumberReturnType.DOUBLE)) - .redirect(redirectConfig().followRedirects(false)) - } - - - @AfterAll - @JvmStatic - fun tearDown() { - controller.stopSut() - } - } - - - @BeforeEach - fun initTest() { - controller.resetStateOfSUT() - } - - - - - @Test @Timeout(60) - fun test_0() { - val expectationHandler: ExpectationHandler = expectationHandler() - - val res_0: ValidatableResponse = given().accept("*/*") - .get("${baseUrlOfSut}/v2/api-docs") - .then() - .statusCode(200) - .assertThat() - .contentType("application/json") - .body("'swagger'", containsString("2.0")) - .body("'info'.'description'", containsString("Some description")) - .body("'info'.'version'", containsString("1.0")) - .body("'info'.'title'", containsString("API")) - .body("'basePath'", containsString("/")) - .body("'tags'.size()", equalTo(1)) - .body("'tags'[0].'name'", containsString("person-without-post-controller")) - .body("'tags'[0].'description'", containsString("Person Without Post Controller")) - .body("'paths'.'/persons/list18'.'get'.'tags'.size()", equalTo(1)) - .body("'paths'.'/persons/list18'.'get'.'tags'", hasItems("person-without-post-controller")) - .body("'paths'.'/persons/list18'.'get'.'summary'", containsString("find18s")) - .body("'paths'.'/persons/list18'.'get'.'operationId'", containsString("find18sUsingGET")) - .body("'paths'.'/persons/list18'.'get'.'produces'.size()", equalTo(1)) - .body("'paths'.'/persons/list18'.'get'.'produces'", hasItems("*/*")) - .body("'paths'.'/persons/list18'.'get'.'responses'.'200'.'description'", containsString("OK")) - .body("'paths'.'/persons/list18'.'get'.'responses'.'401'.'description'", containsString("Unauthorized")) - .body("'paths'.'/persons/list18'.'get'.'responses'.'403'.'description'", containsString("Forbidden")) - .body("'paths'.'/persons/list18'.'get'.'responses'.'404'.'description'", containsString("Not Found")) - .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'200'.'description'", containsString("OK")) - .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'401'.'description'", containsString("Unauthorized")) - .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'403'.'description'", containsString("Forbidden")) - .body("'paths'.'/persons/list18'.'get'.'responsesObject'.'404'.'description'", containsString("Not Found")) - - expectationHandler.expect(ems) - /* - Note: No supported codes appear to be defined. https://swagger.io/docs/specification/describing-responses/. - This is somewhat unexpected, so the code below is likely to lead to a failed expectation - */ - .that(sco, listOf().contains(res_0.extract().statusCode())) - } - - - @Test @Timeout(60) - fun test_1() { - val insertions = mongo().insertInto("persons", "person") - .d("{\"id\":\"IMU_V4ERbZxCSaw\", \"age\":18}") - .dtos() - val insertionsresult = controller.execInsertionsIntoMongoDatabase(insertions) - - given().accept("*/*") - .header("x-EMextraHeader123", "") - .get("${baseUrlOfSut}/persons/list18") - .then() - .statusCode(200) - .assertThat() - .body(isEmptyOrNullString()) - - } - - - @Test @Timeout(60) - fun test_2() { - val expectationHandler: ExpectationHandler = expectationHandler() - - val res_0: ValidatableResponse = given().accept("*/*") - .header("x-EMextraHeader123", "") - .get("${baseUrlOfSut}/persons/list18?EMextraParam123=_EM_0_XYZ_") - .then() - .statusCode(400) - .assertThat() - .body(isEmptyOrNullString()) - - expectationHandler.expect(ems) - .that(sco, listOf(200, 401, 403, 404).contains(res_0.extract().statusCode())) - } - - -} From 074b90f19a24fbe6106e8dc1639f2da203828062 Mon Sep 17 00:00:00 2001 From: hghianni Date: Tue, 11 Jul 2023 16:59:28 -0300 Subject: [PATCH 15/20] Adapt to work with External controllers - Converting document's type to OpenApi instead of storing it as Java class. - Extract required info from collection in replacement class as MongoCollection is not serializable --- .../database/execution/MongoFailedQuery.java | 7 +- .../controller/internal/db/MongoHandler.java | 60 ++-------- .../mongo/MongoHeuristicsCalculator.java | 9 +- .../java/controller/mongo/MongoOperation.java | 51 ++++---- .../mongo/selectors/ImplicitSelector.java | 2 +- .../mongo/selectors/NotSelector.java | 2 +- .../controller/mongo/utils/BsonHelper.java | 43 +------ .../mongo/MongoHeuristicCalculatorTest.java | 90 +++++++------- .../mongo/MongoScriptRunnerTest.java | 1 - .../InstrumentationController.java | 4 + .../instrumentation/MongoCollectionInfo.java | 8 +- .../java/instrumentation/MongoInfo.java | 46 ++++++- ...ongoEntityInformationClassReplacement.java | 4 +- .../MongoCollectionClassReplacement.java | 61 +++++++++- .../external/AgentController.java | 14 +++ .../core/mongo/MongoActionGeneBuilder.kt | 113 ------------------ .../org/evomaster/core/mongo/MongoDbAction.kt | 36 ++---- .../core/mongo/MongoInsertBuilder.kt | 2 +- .../enterprise/service/EnterpriseSampler.kt | 2 +- .../core/mongo/MongoActionGeneBuilderTest.kt | 82 ------------- .../evomaster/core/mongo/MongoActionTest.kt | 27 ++--- .../mongo/MongoDbActionTransformerTest.kt | 7 +- .../core/mongo/MongoInsertBuilderTest.kt | 5 +- 23 files changed, 252 insertions(+), 424 deletions(-) delete mode 100644 core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt delete mode 100644 core/src/test/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilderTest.kt diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/MongoFailedQuery.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/MongoFailedQuery.java index be68a8ea3d..340a935219 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/MongoFailedQuery.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/database/execution/MongoFailedQuery.java @@ -13,9 +13,9 @@ public class MongoFailedQuery { /** * The type of the new document. Should map the type of the documents of the collection. */ - private Class documentsType; + private String documentsType; - public MongoFailedQuery(String database, String collection, Class documentsType) { + public MongoFailedQuery(String database, String collection, String documentsType) { this.database = database; this.collection = collection; this.documentsType = documentsType; @@ -24,11 +24,12 @@ public MongoFailedQuery(String database, String collection, Class documentsTy public MongoFailedQuery(){ this.database = ""; this.collection = ""; + this.documentsType = ""; } public String getDatabase() {return database;} public String getCollection() {return collection;} - public Class getDocumentsType() { + public String getDocumentsType() { return documentsType; } } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java index d133d99438..8178a9c6b5 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/MongoHandler.java @@ -7,7 +7,6 @@ import org.evomaster.client.java.instrumentation.MongoCollectionInfo; import org.evomaster.client.java.instrumentation.MongoInfo; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -45,9 +44,10 @@ public class MongoHandler { private final List failedQueries; /** - * Info about types of the documents of collections + * Info about types of the documents of the repository extracted from Spring framework. + * Documents of the collection will be mapped to the Repository type */ - private final Map> collectionInfo; + private final Map collectionInfo; public MongoHandler() { distances = new ArrayList<>(); @@ -112,11 +112,11 @@ public MongoExecutionDto getExecutionDto() { } private double computeDistance(MongoInfo info) { - Object collection = info.getCollection(); - Iterable documents = getDocuments(collection); + Iterable documents = info.getDocuments(); boolean collectionIsEmpty = !documents.iterator().hasNext(); - if (collectionIsEmpty) failedQueries.add(new MongoOperation(collection, info.getQuery())); + if (collectionIsEmpty) + failedQueries.add(new MongoOperation(info.getCollectionName(), info.getQuery(), info.getDatabaseName(), info.getDocumentsType())); MongoHeuristicsCalculator calculator = new MongoHeuristicsCalculator(); @@ -130,53 +130,19 @@ private double computeDistance(MongoInfo info) { return min; } - private static Iterable getDocuments(Object collection) { - try { - Class collectionClass = collection.getClass().getClassLoader().loadClass("com.mongodb.client.MongoCollection"); - return (Iterable) collectionClass.getMethod("find").invoke(collection); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | - ClassNotFoundException e) { - throw new RuntimeException("Failed to retrieve all documents from a mongo collection", e); - } - } - private MongoFailedQuery extractRelevantInfo(MongoOperation operation) { - Object collection = operation.getCollection(); - - String databaseName; - String collectionName; - Class documentsType; - - try { - Class collectionClass = collection.getClass().getClassLoader().loadClass("com.mongodb.client.MongoCollection"); - Object namespace = collectionClass.getMethod("getNamespace").invoke(collection); - databaseName = (String) namespace.getClass().getMethod("getDatabaseName").invoke(namespace); - collectionName = (String) namespace.getClass().getMethod("getCollectionName").invoke(namespace); - } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | - NoSuchMethodException e) { - throw new RuntimeException("Failed to retrieve collection name or database name", e); - } - - if (collectionTypeIsRegistered(collectionName)) { - documentsType = collectionInfo.get(collectionName); + String documentsType; + if (collectionTypeIsRegistered(operation.getCollectionName())) { + // We have to which class the documents of the collection will be mapped to + documentsType = collectionInfo.get(operation.getCollectionName()); } else { - documentsType = extractDocumentsType(collection); + // Just using the documents type provided by the MongoCollection method + documentsType = operation.getDocumentsType(); } - - return new MongoFailedQuery(databaseName, collectionName, documentsType); + return new MongoFailedQuery(operation.getDatabaseName(), operation.getCollectionName(), documentsType); } private boolean collectionTypeIsRegistered(String collectionName) { return collectionInfo.containsKey(collectionName); } - - private static Class extractDocumentsType(Object collection) { - try { - Class collectionClass = collection.getClass().getClassLoader().loadClass("com.mongodb.client.MongoCollection"); - return (Class) collectionClass.getMethod("getDocumentClass").invoke(collection); - } catch (NoSuchMethodException | ClassNotFoundException | InvocationTargetException | - IllegalAccessException e) { - throw new RuntimeException("Failed to retrieve document's type from collection", e); - } - } } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculator.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculator.java index 18c0ede42f..cbfbf38960 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculator.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoHeuristicsCalculator.java @@ -28,8 +28,7 @@ public double computeExpression(Object query, Object doc) { } private QueryOperation getOperation(Object query) { - Object queryDocument = convertToQueryDocument(query); - return new QueryParser().parse(queryDocument); + return new QueryParser().parse(query); } private double calculateDistance(QueryOperation operation, Object doc) { @@ -199,7 +198,7 @@ private double calculateDistanceForElemMatch(ElemMatchOperation operation, Objec List val = (List) actualValue; return val.stream() .mapToDouble(elem -> { - Object newDoc = newDocument(); + Object newDoc = newDocument(doc); appendToDocument(newDoc, operation.getFieldName(), elem); return calculateDistance(operation.getCondition(), newDoc); }) @@ -383,6 +382,10 @@ private double compareValues(Object val1, Object val2) { return (double) DistanceHelper.getLeftAlignmentDistance((String) val1, (String) val2); } + if (val1 instanceof Boolean && val2 instanceof Boolean) { + return val1 == val2 ? 0d : 1d; + } + if (val1 instanceof List && val2 instanceof List) { // Modify return Double.MAX_VALUE; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoOperation.java index b9bc4ab4cf..9b9415fa5a 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/MongoOperation.java @@ -2,49 +2,56 @@ public class MongoOperation { /** + * Executed FIND query * Should be an implementation of class {@link MongoOperation#queryClass} */ private final Object query; + + /** + * Name of the collection that the operation was applied to + */ + private final String collectionName; + /** - * Should be an implementation of class {@link MongoOperation#collectionClass} + * Name of the database that the operation was applied to */ - private final Object collection; + private final String databaseName; - private final String collectionClass = "com.mongodb.client.MongoCollection"; + /** + * Type of the documents of the collection + */ + private final String documentsType; private final String queryClass = "org.bson.conversions.Bson"; - public MongoOperation(Object collection, Object query) { - if (!isImplementationOfMongoCollection(collection)) - throw new java.lang.IllegalArgumentException("collection must be of type " + collectionClass); - if (!isImplementationOfBson(query)) - throw new java.lang.IllegalArgumentException("query must be of type " + queryClass); - this.collection = collection; + public MongoOperation(String collectionName, Object query, String databaseName, String documentsType) { + if (!isImplementationOfBson(query)) { + throw new IllegalArgumentException("query must be of type " + queryClass); + } + this.collectionName = collectionName; + this.databaseName = databaseName; + this.documentsType = documentsType; this.query = query; } - public Object getCollection() { - return collection; + public String getCollectionName() { + return collectionName; } - public Object getQuery() { - return query; + public String getDatabaseName() { + return databaseName; } - private boolean isImplementationOfMongoCollection(Object collection) { - return implementsInterface(collection, collectionClass); + public Object getQuery() { + return query; } - private boolean isImplementationOfBson(Object query) { - return implementsInterface(query, queryClass); - } + public String getDocumentsType() {return documentsType;} - private boolean implementsInterface(Object obj, String interfaceName) { + private boolean isImplementationOfBson(Object obj) { Class[] interfaces = obj.getClass().getInterfaces(); for (Class intf : interfaces) { - if (intf.getName().equals(interfaceName)) { - return true; - } + if (intf.getName().equals(queryClass)) return true; } return false; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/selectors/ImplicitSelector.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/selectors/ImplicitSelector.java index fade591a04..f04f82bdca 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/selectors/ImplicitSelector.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/selectors/ImplicitSelector.java @@ -31,7 +31,7 @@ protected List parseConditions(Object query) { Set fields = keySet(query); ArrayList conditions = new ArrayList<>(); fields.forEach(fieldName -> { - Object newQuery = newDocument(); + Object newQuery = newDocument(query); appendToDocument(newQuery, fieldName, getValue(query, fieldName)); conditions.add(new QueryParser().parse(newQuery)); }); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/selectors/NotSelector.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/selectors/NotSelector.java index ea5822f482..ad2a79c739 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/selectors/NotSelector.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/selectors/NotSelector.java @@ -14,7 +14,7 @@ protected QueryOperation parseValue(String fieldName, Object value) { if (isDocument(value)) { // This is necessary for query parser to work correctly as the syntax for not is different // The field is at the beginning instead - Object docWithRemovedNot = newDocument(); + Object docWithRemovedNot = newDocument(value); appendToDocument(docWithRemovedNot, fieldName, value); QueryOperation condition = new QueryParser().parse(docWithRemovedNot); return new NotOperation(fieldName, condition); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/utils/BsonHelper.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/utils/BsonHelper.java index 64c79fa57b..85ae9bb44c 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/utils/BsonHelper.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/mongo/utils/BsonHelper.java @@ -5,9 +5,9 @@ import java.util.Set; public class BsonHelper { - public static Object newDocument() { + public static Object newDocument(Object document) { try { - return documentClass.getConstructor().newInstance(); + return document.getClass().getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); @@ -56,26 +56,7 @@ public static Set documentKeys(Object document) { } public static Boolean isDocument(Object value) { - return documentClass.isInstance(value); - } - - public static Object convertToQueryDocument(Object query) { - try { - Method toBsonDocument = query.getClass().getMethod("toBsonDocument"); - Object bsonDocument = toBsonDocument.invoke(query); - - Object documentCodec = documentCodecClass.getDeclaredConstructor().newInstance(); - Method asBsonReader = bsonDocumentClass.getMethod("asBsonReader"); - Method builder = decoderContextClass.getMethod("builder"); - Object builderInstance = builder.invoke(null); - Method build = builderInstance.getClass().getMethod("build"); - - Method decode = documentCodecClass.getMethod("decode", bsonReaderClass, decoderContextClass); - return decode.invoke(documentCodec, asBsonReader.invoke(bsonDocument), build.invoke(builderInstance)); - } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | - InvocationTargetException e) { - throw new RuntimeException(e); - } + return value.getClass().getName().equals("org.bson.Document"); } public static String getType(Object bsonType) { @@ -116,22 +97,4 @@ public static Object getTypeFromAlias(String alias) { throw new RuntimeException(e); } } - - private static final Class documentClass = getClass("org.bson.Document"); - - private static final Class bsonDocumentClass = getClass("org.bson.BsonDocument"); - - private static final Class documentCodecClass = getClass("org.bson.codecs.DocumentCodec"); - - private static final Class decoderContextClass = getClass("org.bson.codecs.DecoderContext"); - - private static final Class bsonReaderClass = getClass("org.bson.BsonReader"); - - private static Class getClass(String className) { - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } } diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/internal/db/mongo/MongoHeuristicCalculatorTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/internal/db/mongo/MongoHeuristicCalculatorTest.java index 12881ba504..0336f0d10e 100644 --- a/client-java/controller/src/test/java/org/evomaster/client/java/controller/internal/db/mongo/MongoHeuristicCalculatorTest.java +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/internal/db/mongo/MongoHeuristicCalculatorTest.java @@ -4,6 +4,8 @@ import org.bson.BsonDocument; import org.bson.BsonType; import org.bson.Document; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.DocumentCodec; import org.bson.conversions.Bson; import org.evomaster.client.java.controller.mongo.MongoHeuristicsCalculator; import org.junit.jupiter.api.Test; @@ -21,8 +23,8 @@ public void testEquals() { Document doc = new Document().append("age", 10); Bson bsonTrue = Filters.eq("age", 10); Bson bsonFalse = Filters.eq("age", 26); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(16.0, distanceNotMatch); } @@ -33,9 +35,9 @@ public void testNotEquals() { Bson bsonTrue1 = Filters.ne("age", 26); Bson bsonTrue2 = Filters.ne("some-field", 26); Bson bsonFalse = Filters.ne("age", 10); - Double distanceMatch1 = new MongoHeuristicsCalculator().computeExpression(bsonTrue1, doc); - Double distanceMatch2 = new MongoHeuristicsCalculator().computeExpression(bsonTrue2, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch1 = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue1), doc); + Double distanceMatch2 = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue2), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch1); assertEquals(0.0, distanceMatch2); assertEquals(1.0, distanceNotMatch); @@ -46,8 +48,8 @@ public void testGreaterThan() { Document doc = new Document().append("age", 10); Bson bsonTrue = Filters.gt("age", 5); Bson bsonFalse = Filters.gt("age", 13); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(4.0, distanceNotMatch); } @@ -57,8 +59,8 @@ public void testGreaterThanEquals() { Document doc = new Document().append("age", 10); Bson bsonTrue = Filters.gte("age", 5); Bson bsonFalse = Filters.gte("age", 13); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(3.0, distanceNotMatch); } @@ -68,8 +70,8 @@ public void testLessThan() { Document doc = new Document().append("age", 10); Bson bsonTrue = Filters.lt("age", 11); Bson bsonFalse = Filters.lt("age", 7); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(4.0, distanceNotMatch); } @@ -79,8 +81,8 @@ public void testLessThanEquals() { Document doc = new Document().append("age", 10); Bson bsonTrue = Filters.lte("age", 11); Bson bsonFalse = Filters.lte("age", 7); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(3.0, distanceNotMatch); } @@ -90,8 +92,8 @@ public void testOr() { Document doc = new Document().append("age", 10); Bson bsonTrue = Filters.or(Filters.gt("age", 9), Filters.lt("age", 20)); Bson bsonFalse = Filters.or(Filters.gt("age", 17), Filters.lt("age", 8)); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(3.0, distanceNotMatch); } @@ -101,8 +103,8 @@ public void testAnd() { Document doc = new Document().append("age", 10); Bson bsonTrue = Filters.and(Filters.gt("age", 9), Filters.lt("age", 20)); Bson bsonFalse = Filters.and(Filters.gt("age", 10), Filters.lt("age", 8)); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(4.0, distanceNotMatch); } @@ -111,8 +113,8 @@ public void testImplicitAnd() { Document doc = new Document().append("age", 10).append("kg", 50); Bson bsonTrue = BsonDocument.parse("{age: 10, kg: {$gt: 40}}"); Bson bsonFalse = BsonDocument.parse("{age: 9, kg: {$gt: 40}}"); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(1.0, distanceNotMatch); } @@ -122,8 +124,8 @@ public void testIn() { Document doc = new Document().append("age", 10); Bson bsonTrue = Filters.in("age", new ArrayList<>(Arrays.asList(1, 10, 8))); Bson bsonFalse = Filters.in("age", new ArrayList<>(Arrays.asList(1, 15))); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(5.0, distanceNotMatch); } @@ -133,8 +135,8 @@ public void testNotIn() { Document doc = new Document().append("age", 10); Bson bsonTrue = Filters.nin("age", new ArrayList<>(Arrays.asList(1, 8))); Bson bsonFalse = Filters.nin("age", new ArrayList<>(Arrays.asList(1, 10))); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(1.0, distanceNotMatch); } @@ -144,8 +146,8 @@ public void testAll() { Document doc = new Document().append("employees", new ArrayList<>(Arrays.asList(1, 5, 6))); Bson bsonTrue = Filters.all("employees", new ArrayList<>(Arrays.asList(1, 5, 6))); Bson bsonFalse = Filters.all("employees", new ArrayList<>(Arrays.asList(1, 7, 8))); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(3.0, distanceNotMatch); } @@ -155,8 +157,8 @@ public void testSize() { Document doc = new Document().append("employees", new ArrayList<>(Arrays.asList(1, 5, 6))); Bson bsonTrue = Filters.size("employees", 3); Bson bsonFalse = Filters.size("employees", 5); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(2.0, distanceNotMatch); } @@ -166,8 +168,8 @@ public void testMod() { Document doc = new Document().append("age", 20); Bson bsonTrue = Filters.mod("age", 3, 2); Bson bsonFalse = Filters.mod("age", 3, 0); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(2.0, distanceNotMatch); } @@ -177,8 +179,8 @@ public void testNot() { Document doc = new Document().append("age", 20); Bson bsonTrue = Filters.not(Filters.gt("age", 30)); Bson bsonFalse = Filters.not(Filters.gt("age", 10)); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(10.0, distanceNotMatch); } @@ -188,8 +190,8 @@ public void testExistsTrueVersion() { Document doc = new Document().append("age", 20); Bson bsonTrue = Filters.exists("age", true); Bson bsonFalse = Filters.exists("name", true); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(65563.0, distanceNotMatch); } @@ -199,8 +201,8 @@ public void testExistsFalseVersion() { Document doc = new Document().append("age", 20); Bson bsonTrue = Filters.exists("name", false); Bson bsonFalse = Filters.exists("age", false); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(1.0, distanceNotMatch); } @@ -210,8 +212,8 @@ public void testTypeExplicitVersion() { Document doc = new Document().append("age", 20); Bson bsonTrue = Filters.type("age", BsonType.INT32); Bson bsonFalse = Filters.type("age", BsonType.DOUBLE); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(65551.0, distanceNotMatch); } @@ -222,8 +224,8 @@ public void testTypeAliasVersion() { Document doc = new Document().append("age", 20); Bson bsonTrue = Filters.type("age", BsonType.INT32.name()); Bson bsonFalse = Filters.type("age", BsonType.DOUBLE.name()); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(65551.0, distanceNotMatch); } @@ -233,9 +235,15 @@ public void testElemMatch() { Document doc = new Document().append("years", new ArrayList<>(Arrays.asList(2002, 2010))); Bson bsonTrue = Filters.elemMatch("years", Filters.gt("years", 2009)); Bson bsonFalse = Filters.elemMatch("years", Filters.lt("years", 2001)); - Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(bsonTrue, doc); - Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(bsonFalse, doc); + Double distanceMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonTrue), doc); + Double distanceNotMatch = new MongoHeuristicsCalculator().computeExpression(convertToDocument(bsonFalse), doc); assertEquals(0.0, distanceMatch); assertEquals(2.0, distanceNotMatch); } + + private static Document convertToDocument(Bson filter){ + BsonDocument bsonDocument = filter.toBsonDocument(); + DocumentCodec documentCodec = new DocumentCodec(); + return documentCodec.decode(bsonDocument.asBsonReader(), DecoderContext.builder().build()); + } } \ No newline at end of file diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoScriptRunnerTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoScriptRunnerTest.java index e06163fa25..22c23df227 100644 --- a/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoScriptRunnerTest.java +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/mongo/MongoScriptRunnerTest.java @@ -12,7 +12,6 @@ import static org.junit.jupiter.api.Assertions.*; -// Is this test possible? Docker should be running I think public class MongoScriptRunnerTest { private static MongoClient connection; 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 719a1189b4..db91e2232e 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 @@ -47,6 +47,10 @@ public static void setExecutingInitSql(boolean executingInitSql){ ExecutionTracer.setExecutingInitSql(executingInitSql); } + public static void setExecutingInitMongo(boolean executingInitMongo){ + ExecutionTracer.setExecutingInitMongo(executingInitMongo); + } + public static void setExecutingAction(boolean executingAction){ ExecutionTracer.setExecutingAction(executingAction); } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoCollectionInfo.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoCollectionInfo.java index 63f72f750c..7c5a786c76 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoCollectionInfo.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoCollectionInfo.java @@ -7,14 +7,14 @@ */ public class MongoCollectionInfo implements Serializable { private final String collectionName; - private final Class documentsType; + private final String documentsType; - public MongoCollectionInfo(String collectionName, Class collectionType) { + public MongoCollectionInfo(String collectionName, String documentsType) { this.collectionName = collectionName; - this.documentsType = collectionType; + this.documentsType = documentsType; } public String getCollectionName() {return collectionName;} - public Class getDocumentsType() {return documentsType;} + public String getDocumentsType() {return documentsType;} } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoInfo.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoInfo.java index c4e5a2fece..a6239f6b4b 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoInfo.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/MongoInfo.java @@ -6,13 +6,38 @@ * Info related to MONGO command execution. */ public class MongoInfo implements Serializable { - private final Object collection; + /** + * Name of the collection that the operation was applied to + */ + private final String collectionName; + + /** + * Name of the database that the operation was applied to + */ + private final String databaseName; + + /** + * Type of the documents of the collection + */ + private final String documentsType; + + /** + * Documents in the collection at the moment of the operation + */ + private final Iterable documents; + + /** + * Executed FIND query + */ private final Object bson; private final boolean successfullyExecuted; private final long executionTime; - public MongoInfo(Object collection, Object bson, boolean successfullyExecuted, long executionTime) { - this.collection = collection; + public MongoInfo(String collectionName, String databaseName, String documentsType, Iterable documents, Object bson, boolean successfullyExecuted, long executionTime) { + this.collectionName = collectionName; + this.databaseName = databaseName; + this.documentsType = documentsType; + this.documents = documents; this.bson = bson; this.successfullyExecuted = successfullyExecuted; this.executionTime = executionTime; @@ -21,7 +46,18 @@ public MongoInfo(Object collection, Object bson, boolean successfullyExecuted, l public Object getQuery() { return bson; } - public Object getCollection() { - return collection; + + public String getCollectionName() { + return collectionName; + } + + public Iterable getDocuments() { + return documents; + } + + public String getDocumentsType() {return documentsType;} + + public String getDatabaseName() { + return databaseName; } } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java index 5ddc9d4f8c..556dc69fef 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java @@ -4,6 +4,7 @@ import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement; import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast; import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass; +import org.evomaster.client.java.instrumentation.object.ClassToSchema; import org.evomaster.client.java.instrumentation.shared.ReplacementCategory; import org.evomaster.client.java.instrumentation.shared.ReplacementType; import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; @@ -88,7 +89,8 @@ private static void handleMappingMongoEntityInformationConstructor(String id, Li addInstance(mappingMongoEntityInformation); String collectionName = (String) mappingMongoEntityInformation.getClass().getMethod("getCollectionName").invoke(mappingMongoEntityInformation); Class repositoryType = (Class) mappingMongoEntityInformation.getClass().getMethod("getJavaType").invoke(mappingMongoEntityInformation); - ExecutionTracer.addMongoCollectionInfo(new MongoCollectionInfo(collectionName, repositoryType)); + String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(repositoryType); + ExecutionTracer.addMongoCollectionInfo(new MongoCollectionInfo(collectionName, schema)); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java index 169880915e..e0dc82fe40 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java @@ -5,12 +5,14 @@ import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast; import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass; import org.evomaster.client.java.instrumentation.coverage.methodreplacement.UsageFilter; +import org.evomaster.client.java.instrumentation.object.ClassToSchema; import org.evomaster.client.java.instrumentation.shared.ReplacementCategory; import org.evomaster.client.java.instrumentation.shared.ReplacementType; import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -62,9 +64,9 @@ private static Object handleFind(String id, Object mongoCollection, List long end = System.currentTimeMillis(); handleMongo(mongoCollection, query, true, end - start); return result; - } catch (IllegalAccessException e){ + } catch (IllegalAccessException e) { throw new RuntimeException(e); - } catch (InvocationTargetException e){ + } catch (InvocationTargetException e) { throw (RuntimeException) e.getCause(); } } @@ -74,7 +76,60 @@ private static Method retrieveFindMethod(String id, Object mongoCollection) { } private static void handleMongo(Object mongoCollection, Object bson, boolean successfullyExecuted, long executionTime) { - MongoInfo info = new MongoInfo(mongoCollection, bson, successfullyExecuted, executionTime); + String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(extractDocumentsType(mongoCollection)); + MongoInfo info = new MongoInfo(getCollectionName(mongoCollection), getDatabaseName(mongoCollection), schema, getDocuments(mongoCollection), bson, successfullyExecuted, executionTime); ExecutionTracer.addMongoInfo(info); } + + + private static Iterable getDocuments(Object collection) { + // Need to convert result of getDocuments which a FindIterable instance as it is not Serializable + List documentsAsList = new ArrayList<>(); + try { + Class collectionClass = getCollectionClass(collection); + Iterable findIterable = (Iterable) collectionClass.getMethod("find").invoke(collection); + findIterable.forEach(documentsAsList::add); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | + ClassNotFoundException e) { + throw new RuntimeException("Failed to retrieve all documents from a mongo collection", e); + } + return documentsAsList; + } + + private static Class extractDocumentsType(Object collection) { + try { + Class collectionClass = getCollectionClass(collection); + return (Class) collectionClass.getMethod("getDocumentClass").invoke(collection); + } catch (NoSuchMethodException | ClassNotFoundException | InvocationTargetException | + IllegalAccessException e) { + throw new RuntimeException("Failed to retrieve document's type from collection", e); + } + } + + private static String getDatabaseName(Object collection) { + try { + Class collectionClass = getCollectionClass(collection); + Object namespace = collectionClass.getMethod("getNamespace").invoke(collection); + return (String) namespace.getClass().getMethod("getDatabaseName").invoke(namespace); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | + ClassNotFoundException e) { + throw new RuntimeException("Failed to retrieve name of the database in which collection is", e); + } + } + + private static String getCollectionName(Object collection) { + try { + Class collectionClass = getCollectionClass(collection); + Object namespace = collectionClass.getMethod("getNamespace").invoke(collection); + return (String) namespace.getClass().getMethod("getCollectionName").invoke(namespace); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | + ClassNotFoundException e) { + throw new RuntimeException("Failed to retrieve collection name", e); + } + } + + private static Class getCollectionClass(Object collection) throws ClassNotFoundException { + // collection is an implementation of interface MongoCollection + return collection.getClass().getInterfaces()[0]; + } } 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 aeaaccbfe3..9f6f9acebc 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 @@ -93,6 +93,10 @@ public static void start(int port){ handleExecutingInitSql(); sendCommand(Command.ACK); break; + case EXECUTING_INIT_MONGO: + handleExecutingInitMongo(); + sendCommand(Command.ACK); + break; case EXECUTING_ACTION: handleExecutingAction(); sendCommand(Command.ACK); @@ -165,6 +169,16 @@ private static void handleExecutingInitSql() { } } + private static void handleExecutingInitMongo() { + try { + Object msg = in.readObject(); + Boolean executingInitMongo = (Boolean) msg; + InstrumentationController.setExecutingInitMongo(executingInitMongo); + } catch (Exception e){ + SimpleLogger.error("Failure in handling executing-init-mongo: "+e.getMessage()); + } + } + private static void handleExecutingAction() { try { Object msg = in.readObject(); diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt deleted file mode 100644 index ddffb67343..0000000000 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilder.kt +++ /dev/null @@ -1,113 +0,0 @@ -package org.evomaster.core.mongo - -import org.evomaster.core.search.gene.* -import org.evomaster.core.search.gene.collection.ArrayGene -import org.evomaster.core.search.gene.datetime.DateGene -import org.evomaster.core.search.gene.numeric.* -import org.evomaster.core.search.gene.string.StringGene -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -// Default Mapping of Java Classes to BSON types: -// https://mongodb.github.io/mongo-java-driver/3.6/javadoc/?org/bson/codecs/BsonTypeClassMap.html -class MongoActionGeneBuilder { - private val log: Logger = LoggerFactory.getLogger(MongoActionGeneBuilder::class.java) - - fun buildGene(fieldName: String, valueType: Class): Gene? { - val typeName = valueType.name; - - if(typeName == "java.lang.String"){ - return StringGene(name = fieldName) - } - - if(typeName == "int" || typeName == "java.lang.Integer"){ - return IntegerGene(name = fieldName, min = Int.MIN_VALUE, max = Int.MAX_VALUE) - } - - if(typeName == "long" || typeName == "java.lang.Long"){ - return LongGene(name = fieldName, min = Long.MIN_VALUE, max = Long.MAX_VALUE) - } - - if(typeName == "double" || typeName == "java.lang.Double"){ - return DoubleGene(name = fieldName, min = Double.MIN_VALUE, max = Double.MAX_VALUE) - } - - if(typeName == "boolean" || typeName == "java.lang.Boolean") { - return BooleanGene(name = fieldName) - } - - if(typeName == "java.util.Date") { - return DateGene(name = fieldName, onlyValidDates = true) - } - - if(isAListImplementation(valueType)){ - val elementsGene = buildGene("", valueType.componentType) - return if(elementsGene != null) ArrayGene(name = fieldName, template = elementsGene) else null - } - - if(typeName == "org.bson.types.Decimal128") { - return unhandledValueType(fieldName) - } - - if(typeName == "org.bson.types.Binary") { - return unhandledValueType(fieldName) - } - - if(typeName == "org.bson.types.ObjectId") { - return unhandledValueType(fieldName) - } - - if(typeName == "org.bson.types.RegularExpression") { - return unhandledValueType(fieldName) - } - - // Deprecated - if(typeName == "org.bson.types.Symbol") { - return unhandledValueType(fieldName) - } - - // Deprecated - if(typeName == "org.bson.types.DBPointer") { - return unhandledValueType(fieldName) - } - - if(typeName == "org.bson.types.MaxKey") { - return unhandledValueType(fieldName) - } - - if(typeName == "org.bson.types.MinKey") { - return unhandledValueType(fieldName) - } - - if(typeName == "org.bson.types.Code") { - return unhandledValueType(fieldName) - } - - // Deprecated - if(typeName == "org.bson.types.CodeWithScope") { - return unhandledValueType(fieldName) - } - - if(typeName == "org.bson.types.BSONTimestamp") { - return unhandledValueType(fieldName) - } - - // Deprecated - if(typeName == "org.bson.types.Undefined") { - return unhandledValueType(fieldName) - } - - if(typeName == "org.bson.Document") { - return ObjectGene(fieldName, listOf()) - } - - return ObjectGene(fieldName, valueType.fields.mapNotNull { field -> buildGene(field.name, field.type) }) - } - - private fun unhandledValueType(fieldName: String): Gene? { - log.warn(("Cannot convert field: $fieldName to gene")) - return null - } - - private fun isAListImplementation(valueType: Class) = valueType.interfaces.any { i -> i.typeName == "java.util.List" } -} diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt index 036f87b5e4..7a67f2cc88 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoDbAction.kt @@ -1,9 +1,8 @@ package org.evomaster.core.mongo +import org.evomaster.core.problem.rest.RestActionBuilderV3.createObjectGenesForDTOs import org.evomaster.core.search.Action import org.evomaster.core.search.gene.Gene -import org.evomaster.core.search.gene.ObjectGene -import java.lang.reflect.Modifier import java.util.* class MongoDbAction( @@ -18,38 +17,23 @@ class MongoDbAction( /** * The type of the new document. Should map the type of the documents of the collection */ - val documentsType: Class<*>, + val documentsType: String, computedGenes: List? = null ) : Action(listOf()) { private val genes: List = (computedGenes ?: computeGenes()).also { addChildren(it) } private fun computeGenes(): List { - val genes = - if (documentsType.name == "org.bson.Document") { - /* There are two different scenarios here: - 1) The collection has no type restriction. - 2) For some reason, it was not possible to determine the document's type. - - In case 1) any insertion to the collection would be valid. - In case 2) we don't have enough information. - - In both cases, we don't have fields from which to build genes. - One possibility would be to extract the fields used in the query, - but there is no guarantee that it would work in case 2) - (the fields used in the query may be different or a subset of the actual type of the collection). - */ - listOf() - } else { - getFieldsFromType().mapNotNull { MongoActionGeneBuilder().buildGene(it.name, it.type) } - } - return Collections.singletonList(ObjectGene("BSON", genes)) + val documentsTypeName = documentsType.substringBefore(":").drop(1).dropLast(1) + return Collections.singletonList( + createObjectGenesForDTOs( + documentsTypeName, documentsType, false + ) + ) } override fun getName(): String { - return "MONGO_Insert_${database}_${collection}_${ - getFieldsFromType().map { it.name }.sorted().joinToString("_") - }" + return "MONGO_Insert_${database}_${collection}_${documentsType}" } override fun seeTopGenes(): List { @@ -63,6 +47,4 @@ class MongoDbAction( override fun copyContent(): Action { return MongoDbAction(database, collection, documentsType, genes.map(Gene::copy)) } - - private fun getFieldsFromType() = documentsType.declaredFields.filter { Modifier.isPublic(it.modifiers) } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt index fc916af983..1bc4fecb4a 100644 --- a/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt +++ b/core/src/main/kotlin/org/evomaster/core/mongo/MongoInsertBuilder.kt @@ -1,7 +1,7 @@ package org.evomaster.core.mongo class MongoInsertBuilder { - fun createMongoInsertionAction(database: String, collection: String, documentsType: Class<*>): MongoDbAction{ + fun createMongoInsertionAction(database: String, collection: String, documentsType: String): MongoDbAction{ return MongoDbAction(database, collection, documentsType) } } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt index 648d976620..fb5e2a7aa0 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt @@ -78,7 +78,7 @@ abstract class EnterpriseSampler : Sampler() where T : Individual { return actions } - fun sampleMongoInsertion(database: String, collection: String, documentsType: Class<*>): MongoDbAction { + fun sampleMongoInsertion(database: String, collection: String, documentsType: String): MongoDbAction { val action = MongoInsertBuilder().createMongoInsertionAction(database, collection, documentsType) action.seeTopGenes().forEach{it.doInitialize(randomness)} return action diff --git a/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilderTest.kt b/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilderTest.kt deleted file mode 100644 index 264c0c00c3..0000000000 --- a/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionGeneBuilderTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -package org.evomaster.core.mongo - -import org.evomaster.core.search.gene.BooleanGene -import org.evomaster.core.search.gene.ObjectGene -import org.evomaster.core.search.gene.collection.ArrayGene -import org.evomaster.core.search.gene.datetime.DateGene -import org.evomaster.core.search.gene.numeric.DoubleGene -import org.evomaster.core.search.gene.numeric.IntegerGene -import org.evomaster.core.search.gene.numeric.LongGene -import org.evomaster.core.search.gene.string.StringGene -import org.junit.jupiter.api.Test -import java.util.* - -class MongoActionGeneBuilderTest { - - inner class Object { - var someField: Int = 0 - } - - @Test - fun testStringField() { - val gene = MongoActionGeneBuilder().buildGene("someField", String::class.java) - assert(StringGene::class.isInstance(gene)) - } - - @Test - fun testIntegerField() { - val gene = MongoActionGeneBuilder().buildGene("someField", Integer::class.java) - assert(IntegerGene::class.isInstance(gene)) - } - - @Test - fun testLongField() { - val gene = MongoActionGeneBuilder().buildGene("someField", Long::class.java) - assert(LongGene::class.isInstance(gene)) - } - - @Test - fun testDoubleField() { - val gene = MongoActionGeneBuilder().buildGene("someField", Double::class.java) - assert(DoubleGene::class.isInstance(gene)) - } - - @Test - fun testDateField() { - val gene = MongoActionGeneBuilder().buildGene("someField", Date::class.java) - assert(DateGene::class.isInstance(gene)) - } - - @Test - fun testBooleanField() { - val gene = MongoActionGeneBuilder().buildGene("someField", Boolean::class.java) - assert(BooleanGene::class.isInstance(gene)) - } - - @Test - fun testObjectField() { - val gene = MongoActionGeneBuilder().buildGene("someField", Object::class.java) - assert(ObjectGene::class.isInstance(gene)) - } - - @Test - fun testUnhandledTypeField() { - val gene = MongoActionGeneBuilder().buildGene("someField", Class.forName("org.bson.types.Decimal128")) - assert(gene == null) - } - - @Test - fun testDocumentField() { - val gene = MongoActionGeneBuilder().buildGene("someField", Class.forName("org.bson.Document")) - assert(ObjectGene::class.isInstance(gene)) - } - - /* - @Test - fun testListField() { - val gene = MongoActionGeneBuilder().buildGene("someField", ArrayList()::class.java) - assert(ArrayGene::class.isInstance(gene)) - } - - */ -} \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt b/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt index 07752dc6cc..1b3c8ebe82 100644 --- a/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt @@ -1,31 +1,20 @@ package org.evomaster.core.mongo import org.evomaster.core.search.gene.ObjectGene -import org.evomaster.core.search.gene.numeric.IntegerGene +import org.evomaster.core.search.gene.optional.OptionalGene import org.junit.jupiter.api.Test class MongoActionTest { - - inner class Object { - @JvmField - var someField: Int = 0 - } - - @Test - fun testGenesWhenDocument() { - val action = MongoDbAction("someDatabase", "someCollection", Class.forName("org.bson.Document")) - val gene = action.seeTopGenes().first() - assert(ObjectGene::class.isInstance(gene)) - gene as ObjectGene - assert(gene.fields.isEmpty()) - } - @Test - fun testGenesWhenNotDocument() { - val action = MongoDbAction("someDatabase", "someCollection", Object::class.java) + fun testGenes() { + val action = MongoDbAction( + "someDatabase", + "someCollection", + "\"CustomType\":{\"CustomType\":{\"type\":\"object\", \"properties\": {\"aField\":{\"type\":\"integer\"}}}}" + ) val gene = action.seeTopGenes().first() assert(ObjectGene::class.isInstance(gene)) gene as ObjectGene - assert(IntegerGene::class.isInstance(gene.fields.first())) + assert(OptionalGene::class.isInstance(gene.fields.first())) } } \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/mongo/MongoDbActionTransformerTest.kt b/core/src/test/kotlin/org/evomaster/core/mongo/MongoDbActionTransformerTest.kt index e8b8537ef6..a003009d83 100644 --- a/core/src/test/kotlin/org/evomaster/core/mongo/MongoDbActionTransformerTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/mongo/MongoDbActionTransformerTest.kt @@ -6,10 +6,6 @@ import org.junit.jupiter.api.Test class MongoDbActionTransformerTest { - class CustomType(field: Int) { - val aField = field - } - @Test fun testEmpty() { val actions = listOf() @@ -21,7 +17,8 @@ class MongoDbActionTransformerTest { fun testNotEmpty() { val database = "aDatabase" val collection = "aCollection" - val action = MongoDbAction(database, collection, CustomType::class.java) + val documentsType = "\"CustomType\":{\"CustomType\":{\"type\":\"object\", \"properties\": {\"aField\":{\"type\":\"string\"}}}}" + val action = MongoDbAction(database, collection, documentsType) val actions = listOf(action) val dto = MongoDbActionTransformer.transform(actions) assertFalse(dto.insertions.isEmpty()) diff --git a/core/src/test/kotlin/org/evomaster/core/mongo/MongoInsertBuilderTest.kt b/core/src/test/kotlin/org/evomaster/core/mongo/MongoInsertBuilderTest.kt index 5795bfb4a8..035684b0ed 100644 --- a/core/src/test/kotlin/org/evomaster/core/mongo/MongoInsertBuilderTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/mongo/MongoInsertBuilderTest.kt @@ -3,14 +3,11 @@ package org.evomaster.core.mongo import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test class MongoInsertBuilderTest { - class CustomType(field: Int) { - val aField = field - } @Test fun testInsert() { val database = "aDatabase" val collection = "aCollection" - val documentsType = CustomType::class.java + val documentsType ="\"CustomType\":{\"CustomType\":{\"type\":\"object\", \"properties\": {\"aField\":{\"type\":\"string\"}}}}" val builder = MongoInsertBuilder() val action = builder.createMongoInsertionAction(database, collection, documentsType) From 82d745fe9bcbcc7a45ba31ed290f422fc18f0998 Mon Sep 17 00:00:00 2001 From: hghianni Date: Fri, 14 Jul 2023 13:50:35 -0300 Subject: [PATCH 16/20] Force fields of type of collection to be not optional --- ...gMongoEntityInformationClassReplacement.java | 2 ++ .../MongoCollectionClassReplacement.java | 2 ++ .../instrumentation/object/ClassToSchema.java | 17 ++++++++++++++--- .../org/evomaster/core/mongo/MongoActionTest.kt | 5 +++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java index 556dc69fef..22fe79c0dd 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java @@ -89,7 +89,9 @@ private static void handleMappingMongoEntityInformationConstructor(String id, Li addInstance(mappingMongoEntityInformation); String collectionName = (String) mappingMongoEntityInformation.getClass().getMethod("getCollectionName").invoke(mappingMongoEntityInformation); Class repositoryType = (Class) mappingMongoEntityInformation.getClass().getMethod("getJavaType").invoke(mappingMongoEntityInformation); + ClassToSchema.setObjectFieldsRequired(true); String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(repositoryType); + ClassToSchema.setObjectFieldsRequired(false); ExecutionTracer.addMongoCollectionInfo(new MongoCollectionInfo(collectionName, schema)); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java index e0dc82fe40..3a021b317d 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java @@ -76,7 +76,9 @@ private static Method retrieveFindMethod(String id, Object mongoCollection) { } private static void handleMongo(Object mongoCollection, Object bson, boolean successfullyExecuted, long executionTime) { + ClassToSchema.setObjectFieldsRequired(true); String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(extractDocumentsType(mongoCollection)); + ClassToSchema.setObjectFieldsRequired(false); MongoInfo info = new MongoInfo(getCollectionName(mongoCollection), getDatabaseName(mongoCollection), schema, getDocuments(mongoCollection), bson, successfullyExecuted, executionTime); ExecutionTracer.addMongoInfo(info); } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java index 419749ed5e..5cdfb4516a 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java @@ -67,6 +67,8 @@ public class ClassToSchema { private static final String fieldRefPostfix = "\"}"; + private static boolean objectFieldsRequired = false; + public static void registerSchemaIfNeeded(Class valueType) { if (valueType == null) { @@ -305,6 +307,7 @@ private static String getSchema(Type type, Boolean useRefObject, List> List properties = new ArrayList<>(); + List propertiesNames = new ArrayList<>(); //general object, let's look at its fields Class target = klass; @@ -320,11 +323,12 @@ private static String getSchema(Type type, Boolean useRefObject, List> }else fieldSchema = getOrDeriveSchema(fieldName, f.getGenericType(), true, nested); properties.add(fieldSchema); + propertiesNames.add("\"" + fieldName + "\""); } target = target.getSuperclass(); } - return fieldObjectSchema(properties); + return fieldObjectSchema(properties, propertiesNames); } private static boolean shouldAddToSchema(Field field) { @@ -420,9 +424,12 @@ private static String fieldStringKeyMapSchema(Class klass, ParameterizedType return "{\"type\":\"object\", \"additionalProperties\":" + value + "}"; } - private static String fieldObjectSchema(List properties) { + private static String fieldObjectSchema(List properties, List propertiesNames) { String p = properties.stream().collect(Collectors.joining(",")); - + String r = propertiesNames.stream().collect(Collectors.joining(",")); + if(objectFieldsRequired){ + return "{\"type\":\"object\", \"properties\": {" + p + "}, \"required\": [" + r + "]}"; + } return "{\"type\":\"object\", \"properties\": {" + p + "}}"; } @@ -455,4 +462,8 @@ private static String getNameEnumConstant(Object object) { return object.toString(); } } + + public static void setObjectFieldsRequired(boolean objectFieldsRequired){ + ClassToSchema.objectFieldsRequired = objectFieldsRequired; + } } diff --git a/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt b/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt index 1b3c8ebe82..2d2e18e810 100644 --- a/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/mongo/MongoActionTest.kt @@ -1,6 +1,7 @@ package org.evomaster.core.mongo import org.evomaster.core.search.gene.ObjectGene +import org.evomaster.core.search.gene.numeric.IntegerGene import org.evomaster.core.search.gene.optional.OptionalGene import org.junit.jupiter.api.Test @@ -10,11 +11,11 @@ class MongoActionTest { val action = MongoDbAction( "someDatabase", "someCollection", - "\"CustomType\":{\"CustomType\":{\"type\":\"object\", \"properties\": {\"aField\":{\"type\":\"integer\"}}}}" + "\"CustomType\":{\"CustomType\":{\"type\":\"object\", \"properties\": {\"aField\":{\"type\":\"integer\"}}, \"required\": [\"aField\"]}}" ) val gene = action.seeTopGenes().first() assert(ObjectGene::class.isInstance(gene)) gene as ObjectGene - assert(OptionalGene::class.isInstance(gene.fields.first())) + assert(IntegerGene::class.isInstance(gene.fields.first())) } } \ No newline at end of file From 6ff1c8185b522122143a58fa54ba9821895cf58f Mon Sep 17 00:00:00 2001 From: hghianni Date: Fri, 14 Jul 2023 13:54:12 -0300 Subject: [PATCH 17/20] Handle Spring field annotation --- .../client/java/instrumentation/object/ClassToSchema.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java index 5cdfb4516a..7d0a858b19 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java @@ -371,7 +371,8 @@ private static String getName(Field field) { for (Annotation a : field.getAnnotations()) { String name = a.annotationType().getName(); if (name.equals("com.fasterxml.jackson.annotation.JsonProperty") - || name.equals("com.google.gson.annotations.SerializedName")) { + || name.equals("com.google.gson.annotations.SerializedName") + || name.equals("org.springframework.data.mongodb.core.mapping.Field")) { try { Method m = a.annotationType().getMethod("value"); String value = (String) m.invoke(a); From 60ac39fb6b8ca0135ac2284bd6b31050e4daaf64 Mon Sep 17 00:00:00 2001 From: hghianni Date: Sun, 30 Jul 2023 20:22:24 -0300 Subject: [PATCH 18/20] Address comments --- .../java/instrumentation/ExtractJvmClass.java | 2 +- ...ongoEntityInformationClassReplacement.java | 4 +- .../MongoCollectionClassReplacement.java | 4 +- .../instrumentation/object/ClassToSchema.java | 126 +++++++++--------- 4 files changed, 69 insertions(+), 67 deletions(-) diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/ExtractJvmClass.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/ExtractJvmClass.java index b4df58457d..96f991e2bd 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/ExtractJvmClass.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/ExtractJvmClass.java @@ -23,7 +23,7 @@ public static Map extractAsSchema(List jvmDtoNames) { continue; } clazz = Class.forName(dtoName); - schemas.putAll(ClassToSchema.getOrDeriveSchemaAndNestedClasses(clazz)); + schemas.putAll(ClassToSchema.getOrDeriveSchemaAndNestedClasses(clazz, false)); } catch (ClassNotFoundException e) { SimpleLogger.uniqueWarn("Fail to extract Jvm DTO as schema:"+e.getMessage()); } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java index 22fe79c0dd..58bf00e7ee 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MappingMongoEntityInformationClassReplacement.java @@ -89,9 +89,7 @@ private static void handleMappingMongoEntityInformationConstructor(String id, Li addInstance(mappingMongoEntityInformation); String collectionName = (String) mappingMongoEntityInformation.getClass().getMethod("getCollectionName").invoke(mappingMongoEntityInformation); Class repositoryType = (Class) mappingMongoEntityInformation.getClass().getMethod("getJavaType").invoke(mappingMongoEntityInformation); - ClassToSchema.setObjectFieldsRequired(true); - String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(repositoryType); - ClassToSchema.setObjectFieldsRequired(false); + String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(repositoryType, true); ExecutionTracer.addMongoCollectionInfo(new MongoCollectionInfo(collectionName, schema)); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java index 3a021b317d..d23cc62fe0 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java @@ -76,9 +76,7 @@ private static Method retrieveFindMethod(String id, Object mongoCollection) { } private static void handleMongo(Object mongoCollection, Object bson, boolean successfullyExecuted, long executionTime) { - ClassToSchema.setObjectFieldsRequired(true); - String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(extractDocumentsType(mongoCollection)); - ClassToSchema.setObjectFieldsRequired(false); + String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(extractDocumentsType(mongoCollection), true); MongoInfo info = new MongoInfo(getCollectionName(mongoCollection), getDatabaseName(mongoCollection), schema, getDocuments(mongoCollection), bson, successfullyExecuted, executionTime); ExecutionTracer.addMongoInfo(info); } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java index 7d0a858b19..ce3e238517 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java @@ -67,23 +67,21 @@ public class ClassToSchema { private static final String fieldRefPostfix = "\"}"; - private static boolean objectFieldsRequired = false; - public static void registerSchemaIfNeeded(Class valueType) { if (valueType == null) { return; } - if(valueType.getName().startsWith("io.swagger.")){ + if (valueType.getName().startsWith("io.swagger.")) { //no point in dealing with this. //also it happens in E2E, where it leads to a infinite recursion return; } - try{ + try { String name = valueType.getName(); - if (!UnitsInfoRecorder.isDtoSchemaRegister(name)){ + if (!UnitsInfoRecorder.isDtoSchemaRegister(name)) { List> embedded = new ArrayList<>(); String schema = ClassToSchema.getOrDeriveSchema(valueType, embedded); UnitsInfoRecorder.registerNewParsedDto(name, schema); @@ -93,8 +91,8 @@ public static void registerSchemaIfNeeded(Class valueType) { } } - }catch (Exception e){ - SimpleLogger.warn("Fail to get schema for Class:"+valueType.getName(), e); + } catch (Exception e) { + SimpleLogger.warn("Fail to get schema for Class:" + valueType.getName(), e); /* fail with tests */ @@ -126,13 +124,18 @@ public static void registerSchemaIfNeeded(Class valueType) { * "org.evomaster.client.java.instrumentation.object.dtos.CycleDtoB":{"type":"object", "properties": {"cycleBId":{"type":"string"},"cycleDtoA":{"$ref":"#/components/schemas/org.evomaster.client.java.instrumentation.object.dtos.CycleDtoA"}}}} * */ - public static String getOrDeriveSchemaWithItsRef(Class klass){ - if (!cacheSchemaWithItsRef.containsKey(klass)){ + + public static String getOrDeriveSchemaWithItsRef(Class klass) { + return getOrDeriveSchemaWithItsRef(klass, false); + } + + public static String getOrDeriveSchemaWithItsRef(Class klass, boolean objectFieldsRequired) { + if (!cacheSchemaWithItsRef.containsKey(klass)) { StringBuilder sb = new StringBuilder(); - Map map = getOrDeriveSchemaAndNestedClasses(klass); + Map map = getOrDeriveSchemaAndNestedClasses(klass, objectFieldsRequired); sb.append("{"); sb.append(map.get(klass.getName())); - map.keySet().stream().filter(s-> !s.equals(klass.getName())).forEach(s-> + map.keySet().stream().filter(s -> !s.equals(klass.getName())).forEach(s -> sb.append(",").append(map.get(s))); sb.append("}"); @@ -142,12 +145,15 @@ public static String getOrDeriveSchemaWithItsRef(Class klass){ return cacheSchemaWithItsRef.get(klass); } + public static String getOrDeriveNonNestedSchema(Class klass, boolean objectFieldsRequired) { + return getOrDeriveSchema(klass, Collections.emptyList(), objectFieldsRequired); + } + /** - * * @return a schema representation of the class in the form "name: {...}" */ public static String getOrDeriveNonNestedSchema(Class klass) { - return getOrDeriveSchema(klass, Collections.emptyList()); + return getOrDeriveNonNestedSchema(klass, false); } @@ -157,14 +163,18 @@ public static String getOrDeriveNonNestedSchema(Class klass) { * like a field entry in an OpenAPI object definition */ public static String getOrDeriveSchema(Class klass, List> nested) { - if (!cacheSchema.containsKey(klass)){ - cacheSchema.put(klass, getOrDeriveSchema(klass.getName(), klass, false, nested)); + return getOrDeriveSchema(klass, nested, false); + } + + public static String getOrDeriveSchema(Class klass, List> nested, boolean objectFieldsRequired) { + if (!cacheSchema.containsKey(klass)) { + cacheSchema.put(klass, getOrDeriveSchema(klass.getName(), klass, false, nested, objectFieldsRequired)); } return cacheSchema.get(klass); } - private static String getOrDeriveSchema(String name, Type type, Boolean useRefObject, List> nested) { + private static String getOrDeriveSchema(String name, Type type, Boolean useRefObject, List> nested, boolean objectFieldsRequired) { // TODO might handle collection and map in the cache later if (cacheSchema.containsKey(type) && !useRefObject && !isCollectionOrMap(type)) { @@ -172,7 +182,7 @@ private static String getOrDeriveSchema(String name, Type type, Boolean useRefOb } - String schema = getSchema(type, useRefObject, nested, false); + String schema = getSchema(type, useRefObject, nested, false, objectFieldsRequired); String namedSchema = named(name, schema); @@ -185,21 +195,21 @@ private static String getOrDeriveSchema(String name, Type type, Boolean useRefOb return namedSchema; } - private static boolean isCollectionOrMap(Type type){ + private static boolean isCollectionOrMap(Type type) { if (!(type instanceof Class)) return false; Class kclazz = (Class) type; return kclazz.isArray() || List.class.isAssignableFrom(kclazz) || Set.class.isAssignableFrom(kclazz) || Map.class.isAssignableFrom(kclazz); } - public static Map getOrDeriveSchemaAndNestedClasses(Class klass) { - if (!cacheMapOfDtoAndItsRefToSchemas.containsKey(klass)){ + public static Map getOrDeriveSchemaAndNestedClasses(Class klass, boolean objectFieldsRequired) { + if (!cacheMapOfDtoAndItsRefToSchemas.containsKey(klass)) { List> nested = new ArrayList<>(); registerSchemaIfNeeded(klass); - findAllNestedClassAndRegisterThemIfNeeded(klass, nested); + findAllNestedClassAndRegisterThemIfNeeded(klass, nested, objectFieldsRequired); Map map = new LinkedHashMap<>(); - for (Class nkclass : nested){ - map.putIfAbsent(nkclass.getName(), getOrDeriveNonNestedSchema(nkclass)); + for (Class nkclass : nested) { + map.putIfAbsent(nkclass.getName(), getOrDeriveNonNestedSchema(nkclass, objectFieldsRequired)); } cacheMapOfDtoAndItsRefToSchemas.put(klass, map); @@ -207,14 +217,14 @@ public static Map getOrDeriveSchemaAndNestedClasses(Class kla return cacheMapOfDtoAndItsRefToSchemas.get(klass); } - private static void findAllNestedClassAndRegisterThemIfNeeded(Class klass, List> nested){ - if (!nested.contains(klass)){ + private static void findAllNestedClassAndRegisterThemIfNeeded(Class klass, List> nested, boolean objectFieldsRequired) { + if (!nested.contains(klass)) { List> innerNested = new ArrayList<>(); - getSchema(klass, false, innerNested, true); + getSchema(klass, false, innerNested, true, objectFieldsRequired); nested.add(klass); - List> toAdd = innerNested.stream().filter(s-> !nested.contains(s)).collect(Collectors.toList()); + List> toAdd = innerNested.stream().filter(s -> !nested.contains(s)).collect(Collectors.toList()); if (toAdd.isEmpty()) return; - toAdd.forEach(a-> findAllNestedClassAndRegisterThemIfNeeded(a, nested)); + toAdd.forEach(a -> findAllNestedClassAndRegisterThemIfNeeded(a, nested, objectFieldsRequired)); } } @@ -224,12 +234,12 @@ private static String named(String name, String jsonObject) { /** - * * @param useRefObject represents whether to represent the object with ref - * @param nested is a list of nested classes - * @param allNested represents whether to add all nested into [nested] + * @param nested is a list of nested classes + * @param allNested represents whether to add all nested into [nested] + * @param objectFieldsRequired represents whether set fields of objects as required */ - private static String getSchema(Type type, Boolean useRefObject, List> nested, boolean allNested) { + private static String getSchema(Type type, Boolean useRefObject, List> nested, boolean allNested, boolean objectFieldsRequired) { Class klass = null; if (type instanceof Class) { @@ -241,8 +251,8 @@ private static String getSchema(Type type, Boolean useRefObject, List> } if (klass != null) { - if (klass.isEnum()){ - String [] items = Arrays.stream(klass.getEnumConstants()).map(e-> getNameEnumConstant(e)).toArray(String[]::new); + if (klass.isEnum()) { + String[] items = Arrays.stream(klass.getEnumConstants()).map(e -> getNameEnumConstant(e)).toArray(String[]::new); return fieldEnumSchema(items); } @@ -282,24 +292,24 @@ private static String getSchema(Type type, Boolean useRefObject, List> if ((klass != null && (klass.isArray() || List.class.isAssignableFrom(klass) || Set.class.isAssignableFrom(klass))) || (pType != null && (List.class.isAssignableFrom((Class) pType.getRawType()) || Set.class.isAssignableFrom((Class) pType.getRawType())))) { - return fieldArraySchema(klass, pType, nested, allNested); + return fieldArraySchema(klass, pType, nested, allNested, objectFieldsRequired); } //TOOD Map - if ((klass != null && Map.class.isAssignableFrom(klass))|| pType!=null && Map.class.isAssignableFrom((Class) pType.getRawType())){ - if (pType!=null && pType.getActualTypeArguments().length > 0){ + if ((klass != null && Map.class.isAssignableFrom(klass)) || pType != null && Map.class.isAssignableFrom((Class) pType.getRawType())) { + if (pType != null && pType.getActualTypeArguments().length > 0) { Type keyType = pType.getActualTypeArguments()[0]; - if (keyType != String.class){ + if (keyType != String.class) { throw new IllegalStateException("only support Map with String key"); } } - return fieldStringKeyMapSchema(klass, pType, nested, allNested); + return fieldStringKeyMapSchema(klass, pType, nested, allNested, objectFieldsRequired); } - if (useRefObject){ + if (useRefObject) { // register this class - if ((allNested || !UnitsInfoRecorder.isDtoSchemaRegister(klass.getName())) && !nested.contains(klass)){ + if ((allNested || !UnitsInfoRecorder.isDtoSchemaRegister(klass.getName())) && !nested.contains(klass)) { nested.add(klass); } return fieldObjectRefSchema(klass.getName()); @@ -318,17 +328,17 @@ private static String getSchema(Type type, Boolean useRefObject, List> } String fieldName = getName(f); String fieldSchema = null; - if (allNested){ - fieldSchema = named(fieldName, getSchema(f.getGenericType(), true, nested, true)); - }else - fieldSchema = getOrDeriveSchema(fieldName, f.getGenericType(), true, nested); + if (allNested) { + fieldSchema = named(fieldName, getSchema(f.getGenericType(), true, nested, true, objectFieldsRequired)); + } else + fieldSchema = getOrDeriveSchema(fieldName, f.getGenericType(), true, nested, objectFieldsRequired); properties.add(fieldSchema); propertiesNames.add("\"" + fieldName + "\""); } target = target.getSuperclass(); } - return fieldObjectSchema(properties, propertiesNames); + return fieldObjectSchema(properties, propertiesNames, objectFieldsRequired); } private static boolean shouldAddToSchema(Field field) { @@ -388,47 +398,47 @@ private static String getName(Field field) { return field.getName(); } - private static String fieldArraySchema(Class klass, ParameterizedType pType, List> embedded, boolean allEmbedded) { + private static String fieldArraySchema(Class klass, ParameterizedType pType, List> embedded, boolean allEmbedded, boolean objectFieldsRequired) { String item; if (klass != null) { if (klass.isArray()) { - item = getSchema(klass.getComponentType(), true, embedded, allEmbedded); + item = getSchema(klass.getComponentType(), true, embedded, allEmbedded, objectFieldsRequired); } else { /* This would happen if we have non-generic List or Set? What to do? I guess can just use String */ - item = getSchema(String.class,true, embedded, allEmbedded); + item = getSchema(String.class, true, embedded, allEmbedded, objectFieldsRequired); } } else { //either List<> or Set<> Type generic = pType.getActualTypeArguments()[0]; - item = getSchema(generic,true, embedded, allEmbedded); + item = getSchema(generic, true, embedded, allEmbedded, objectFieldsRequired); } return "{\"type\":\"array\", \"items\":" + item + "}"; } - private static String fieldStringKeyMapSchema(Class klass, ParameterizedType pType, List> embedded, boolean allEmbedded) { + private static String fieldStringKeyMapSchema(Class klass, ParameterizedType pType, List> embedded, boolean allEmbedded, boolean objectFieldsRequired) { String value; if (klass != null) { - value = getSchema(String.class,true, embedded, allEmbedded); + value = getSchema(String.class, true, embedded, allEmbedded, objectFieldsRequired); } else { Type generic = pType.getActualTypeArguments()[1]; - value = getSchema(generic,true, embedded, allEmbedded); + value = getSchema(generic, true, embedded, allEmbedded, objectFieldsRequired); } return "{\"type\":\"object\", \"additionalProperties\":" + value + "}"; } - private static String fieldObjectSchema(List properties, List propertiesNames) { + private static String fieldObjectSchema(List properties, List propertiesNames, boolean objectFieldsRequired) { String p = properties.stream().collect(Collectors.joining(",")); String r = propertiesNames.stream().collect(Collectors.joining(",")); - if(objectFieldsRequired){ + if (objectFieldsRequired) { return "{\"type\":\"object\", \"properties\": {" + p + "}, \"required\": [" + r + "]}"; } return "{\"type\":\"object\", \"properties\": {" + p + "}}"; @@ -447,7 +457,7 @@ private static String fieldSchema(String type, String format) { } private static String fieldEnumSchema(String[] items) { - return "{\"type\":\"string\", \"enum\":["+ Arrays.stream(items).map(s-> "\""+s+"\"").collect(Collectors.joining(",")) +"]}"; + return "{\"type\":\"string\", \"enum\":[" + Arrays.stream(items).map(s -> "\"" + s + "\"").collect(Collectors.joining(",")) + "]}"; } /* @@ -463,8 +473,4 @@ private static String getNameEnumConstant(Object object) { return object.toString(); } } - - public static void setObjectFieldsRequired(boolean objectFieldsRequired){ - ClassToSchema.objectFieldsRequired = objectFieldsRequired; - } } From 803d808329d4e3372a2170c9b3a67c0de946d110 Mon Sep 17 00:00:00 2001 From: hghianni Date: Sun, 30 Jul 2023 22:02:10 -0300 Subject: [PATCH 19/20] Add test & fix --- .../java/instrumentation/object/ClassToSchema.java | 9 +++++++-- .../instrumentation/object/ClassToSchemaTest.java | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java index ce3e238517..53978a5cbc 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/object/ClassToSchema.java @@ -67,7 +67,12 @@ public class ClassToSchema { private static final String fieldRefPostfix = "\"}"; + public static void registerSchemaIfNeeded(Class valueType) { + registerSchemaIfNeeded(valueType, false); + } + + public static void registerSchemaIfNeeded(Class valueType, boolean objectFieldsRequired) { if (valueType == null) { return; @@ -83,7 +88,7 @@ public static void registerSchemaIfNeeded(Class valueType) { String name = valueType.getName(); if (!UnitsInfoRecorder.isDtoSchemaRegister(name)) { List> embedded = new ArrayList<>(); - String schema = ClassToSchema.getOrDeriveSchema(valueType, embedded); + String schema = ClassToSchema.getOrDeriveSchema(valueType, embedded, objectFieldsRequired); UnitsInfoRecorder.registerNewParsedDto(name, schema); ExecutionTracer.addParsedDtoName(name); if (!embedded.isEmpty()){ @@ -205,7 +210,7 @@ private static boolean isCollectionOrMap(Type type) { public static Map getOrDeriveSchemaAndNestedClasses(Class klass, boolean objectFieldsRequired) { if (!cacheMapOfDtoAndItsRefToSchemas.containsKey(klass)) { List> nested = new ArrayList<>(); - registerSchemaIfNeeded(klass); + registerSchemaIfNeeded(klass, objectFieldsRequired); findAllNestedClassAndRegisterThemIfNeeded(klass, nested, objectFieldsRequired); Map map = new LinkedHashMap<>(); for (Class nkclass : nested) { diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/ClassToSchemaTest.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/ClassToSchemaTest.java index 60948c3bc4..b5439e77bc 100644 --- a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/ClassToSchemaTest.java +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/ClassToSchemaTest.java @@ -208,6 +208,19 @@ public void testDtoEnum(){ verifyEnumOfFieldInProperties(obj, "bar", new String[]{"ONE", "TWO", "THREE"}); } + @Test + public void testObjectRequiredFields(){ + + String schema = ClassToSchema.getOrDeriveNonNestedSchema(DtoBase.class, true); + JsonObject json = parse(schema); + + JsonObject obj = json.get(DtoBase.class.getName()).getAsJsonObject(); + assertNotNull(obj); + assertNotNull(obj.get("required")); + assertEquals(1, obj.get("required").getAsJsonArray().size()); + assertEquals("foo", obj.get("required").getAsJsonArray().get(0).getAsString()); + } + private void checkDtoArray(JsonObject obj){ assertEquals(5, obj.get("properties").getAsJsonObject().entrySet().size()); From d05664fa940e3d8e059f087f46bb057497b27547 Mon Sep 17 00:00:00 2001 From: hghianni Date: Sun, 30 Jul 2023 22:13:42 -0300 Subject: [PATCH 20/20] Use new dto in test --- .../instrumentation/object/ClassToSchemaTest.java | 4 ++-- .../java/instrumentation/object/dtos/DtoObj.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/dtos/DtoObj.java diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/ClassToSchemaTest.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/ClassToSchemaTest.java index b5439e77bc..390a756774 100644 --- a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/ClassToSchemaTest.java +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/ClassToSchemaTest.java @@ -211,10 +211,10 @@ public void testDtoEnum(){ @Test public void testObjectRequiredFields(){ - String schema = ClassToSchema.getOrDeriveNonNestedSchema(DtoBase.class, true); + String schema = ClassToSchema.getOrDeriveNonNestedSchema(DtoObj.class, true); JsonObject json = parse(schema); - JsonObject obj = json.get(DtoBase.class.getName()).getAsJsonObject(); + JsonObject obj = json.get(DtoObj.class.getName()).getAsJsonObject(); assertNotNull(obj); assertNotNull(obj.get("required")); assertEquals(1, obj.get("required").getAsJsonArray().size()); diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/dtos/DtoObj.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/dtos/DtoObj.java new file mode 100644 index 0000000000..8b645efefb --- /dev/null +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/object/dtos/DtoObj.java @@ -0,0 +1,14 @@ +package org.evomaster.client.java.instrumentation.object.dtos; + +public class DtoObj { + + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } +}