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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions client-java/instrumentation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
<version>${springboot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
<version>${springboot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ public StatementDescription(String line, String method) {

private final Set<MongoCollectionSchema> mongoCollectionSchemaData = new CopyOnWriteArraySet<>();

private final Set<CassandraTableSchema> cassandraTableSchemaData = new CopyOnWriteArraySet<>();

public Set<ExecutedSqlCommand> getSqlInfoData(){
return Collections.unmodifiableSet(executedSqlCommandData);
}
Expand Down Expand Up @@ -150,6 +152,10 @@ public Set<MongoCollectionSchema> getMongoCollectionTypeData(){
return Collections.unmodifiableSet(mongoCollectionSchemaData);
}

public Set<CassandraTableSchema> getCassandraTableTypeData(){
return Collections.unmodifiableSet(cassandraTableSchemaData);
}

public void addSqlInfo(ExecutedSqlCommand info){
executedSqlCommandData.add(info);
}
Expand Down Expand Up @@ -182,6 +188,10 @@ public void addMongoCollectionType(MongoCollectionSchema mongoCollectionSchema){
mongoCollectionSchemaData.add(mongoCollectionSchema);
}

public void addCassandraTableType(CassandraTableSchema cassandraTableSchema){
cassandraTableSchemaData.add(cassandraTableSchema);
}

public Set<String> getParsedDtoNamesView(){
return Collections.unmodifiableSet(parsedDtoNames);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.evomaster.client.java.instrumentation;

import java.io.Serializable;
import java.util.Objects;

/**
* Schema of rows in a Cassandra table.
*/
public class CassandraTableSchema implements Serializable {
private final String tableName;
private final String tableSchema;

public CassandraTableSchema(String tableName, String tableSchema) {
this.tableName = tableName;
this.tableSchema = tableSchema;
}

public String getTableName() {
return tableName;
}

public String getTableSchema() {
return tableSchema;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CassandraTableSchema that = (CassandraTableSchema) o;
return Objects.equals(tableName, that.tableName) && Objects.equals(tableSchema, that.tableSchema);
}

@Override
public int hashCode() {
return Objects.hash(tableName, tableSchema);
}

@Override
public String toString() {
return "CassandraTableSchema{" +
"tableName='" + tableName + '\'' +
", tableSchema='" + tableSchema + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static List<MethodReplacementClass> getList() {
new Base64DecoderClassReplacement(),
new BooleanClassReplacement(),
new ByteClassReplacement(),
new CassandraTemplateClassReplacement(),
new CharacterClassReplacement(),
new CollectionClassReplacement(),
new CqlSessionClassReplacement(),
Expand All @@ -54,6 +55,7 @@ public static List<MethodReplacementClass> getList() {
new LocalTimeClassReplacement(),
new LongClassReplacement(),
new MapClassReplacement(),
new MappingCassandraEntityInformationClassReplacement(),
new MatcherClassReplacement(),
new MessageBodyReaderClassReplacement(),
new MethodClassReplacement(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses;

import org.evomaster.client.java.instrumentation.CassandraTableSchema;
import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement;
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.Collections;
import java.util.List;

/**
* The intention of this replacement is the same as {@link MappingCassandraEntityInformationClassReplacement}:
* retrieve the entity-type-to-table mapping. But that constructor replacement only fires when Spring Data
* instantiates a {@code CassandraRepository}; a SUT calling {@code CassandraTemplate} directly (bypassing
* the repository layer) needs this replacement to have that mapping recorded too.
*/
public class CassandraTemplateClassReplacement extends ThirdPartyMethodReplacementClass {

private static final CassandraTemplateClassReplacement singleton = new CassandraTemplateClassReplacement();

@Override
protected String getNameOfThirdPartyTargetClass() {
return "org.springframework.data.cassandra.core.CassandraTemplate";
}

private static final String INSERT_ID = "insert";
private static final String SELECT_ONE_ID = "selectOneString";
private static final String SELECT_ID = "selectString";

@Replacement(replacingStatic = false,
type = ReplacementType.TRACKER,
id = INSERT_ID,
usageFilter = UsageFilter.ANY,
category = ReplacementCategory.CASSANDRA)
public static <T> T insert(Object cassandraTemplate, T entity) {
try {
addCassandraTableType(cassandraTemplate, entity.getClass());

Method insertMethod = getOriginal(singleton, INSERT_ID, cassandraTemplate);
Object result = insertMethod.invoke(cassandraTemplate, entity);
return (T) result;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw (RuntimeException) e.getCause();
}
}

@Replacement(replacingStatic = false,
type = ReplacementType.TRACKER,
id = SELECT_ONE_ID,
usageFilter = UsageFilter.ANY,
category = ReplacementCategory.CASSANDRA)
public static <T> T selectOne(Object cassandraTemplate, String cql, Class<T> entityClass) {
try {
addCassandraTableType(cassandraTemplate, entityClass);

Method selectOneMethod = getOriginal(singleton, SELECT_ONE_ID, cassandraTemplate);
Object result = selectOneMethod.invoke(cassandraTemplate, cql, entityClass);
return (T) result;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw (RuntimeException) e.getCause();
}
}

@Replacement(replacingStatic = false,
type = ReplacementType.TRACKER,
id = SELECT_ID,
usageFilter = UsageFilter.ANY,
category = ReplacementCategory.CASSANDRA)
public static <T> List<T> select(Object cassandraTemplate, String cql, Class<T> entityClass) {
try {
addCassandraTableType(cassandraTemplate, entityClass);

Method selectMethod = getOriginal(singleton, SELECT_ID, cassandraTemplate);
Object result = selectMethod.invoke(cassandraTemplate, cql, entityClass);
return (List<T>) result;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw (RuntimeException) e.getCause();
}
}

private static void addCassandraTableType(Object cassandraTemplate, Class<?> entityClass) {
try {
Object tableNameId = cassandraTemplate.getClass().getMethod("getTableName", Class.class)
.invoke(cassandraTemplate, entityClass);
String tableName = (String) tableNameId.getClass().getMethod("asInternal").invoke(tableNameId);

String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(entityClass, true, Collections.emptyList());
ExecutionTracer.addCassandraTableType(new CassandraTableSchema(tableName, schema));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses;

import org.evomaster.client.java.instrumentation.CassandraTableSchema;
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;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;

/**
* When a Cassandra repository is created in Spring, a CqlSession is used under the hood.
* But information about the type of the repository's rows is not transferred to the session.
* 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 rows of the correct type in the table (and the repository).
*/
public class MappingCassandraEntityInformationClassReplacement extends ThirdPartyMethodReplacementClass {
private static final MappingCassandraEntityInformationClassReplacement singleton = new MappingCassandraEntityInformationClassReplacement();
private static ThreadLocal<Object> instance = new ThreadLocal<>();

public static final String CONSTRUCTOR_ENTITY_CONVERTER_ID = "constructorEntityConverter";

@Override
protected String getNameOfThirdPartyTargetClass() {
return "org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation";
}

public static Object consumeInstance() {
Object mappingCassandraEntityInformation = instance.get();
if (mappingCassandraEntityInformation == null) {
throw new IllegalStateException("No instance to consume");
}
instance.set(null);
return mappingCassandraEntityInformation;
}

private static void addInstance(Object x) {
Object mappingCassandraEntityInformation = instance.get();
if (mappingCassandraEntityInformation != null) {
throw new IllegalStateException("Previous instance was not consumed");
}
instance.set(x);
}

@Replacement(
replacingConstructor = true,
type = ReplacementType.TRACKER,
category = ReplacementCategory.CASSANDRA,
id = CONSTRUCTOR_ENTITY_CONVERTER_ID,
castTo = "org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation"
)
public static void MappingCassandraEntityInformation(
@ThirdPartyCast(actualType = "org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity") Object entity,
@ThirdPartyCast(actualType = "org.springframework.data.cassandra.core.convert.CassandraConverter") Object converter) {
Constructor original = getOriginalConstructor(singleton, CONSTRUCTOR_ENTITY_CONVERTER_ID);

try {
Object mappingCassandraEntityInformation = original.newInstance(entity, converter);
addInstance(mappingCassandraEntityInformation);

Object tableNameId = mappingCassandraEntityInformation.getClass().getMethod("getTableName").invoke(mappingCassandraEntityInformation);
String tableName = (String) tableNameId.getClass().getMethod("asInternal").invoke(tableNameId);

Class<?> rowType = (Class<?>) mappingCassandraEntityInformation.getClass().getMethod("getJavaType").invoke(mappingCassandraEntityInformation);
String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(rowType, true, Collections.emptyList());

ExecutionTracer.addCassandraTableType(new CassandraTableSchema(tableName, schema));
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,12 @@ public static void addMongoCollectionType(MongoCollectionSchema mongoCollectionS
}
}

public static void addCassandraTableType(CassandraTableSchema cassandraTableSchema){
if (!executingInitCassandra) {
getCurrentAdditionalInfo().addCassandraTableType(cassandraTableSchema);
}
}


public static void markLastExecutedStatement(String lastLine, String lastMethod) {
getCurrentAdditionalInfo().pushLastExecutedStatement(lastLine, lastMethod);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.foo.somedifferentpackage.examples.methodreplacement;

import org.evomaster.client.java.instrumentation.example.cassandra.MappingCassandraEntityOperations;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation;

public class MappingCassandraEntityOperationsImpl implements MappingCassandraEntityOperations {
@Override
public <T, ID> MappingCassandraEntityInformation<?, ?> callMappingCassandraEntityInformation(CassandraPersistentEntity<T> entity, CassandraConverter converter) {
return new MappingCassandraEntityInformation<>(entity, converter);
}
}
Loading
Loading