Skip to content

Commit 82530a6

Browse files
Add instrumentation of MappingCassandraEntityInformationClass
1 parent f61c578 commit 82530a6

9 files changed

Lines changed: 233 additions & 0 deletions

File tree

client-java/instrumentation/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@
6767
<version>${springboot.version}</version>
6868
<scope>test</scope>
6969
</dependency>
70+
<dependency>
71+
<groupId>org.springframework.boot</groupId>
72+
<artifactId>spring-boot-starter-data-cassandra</artifactId>
73+
<version>${springboot.version}</version>
74+
<scope>test</scope>
75+
</dependency>
7076
<dependency>
7177
<groupId>org.springframework.boot</groupId>
7278
<artifactId>spring-boot-starter-data-redis</artifactId>

client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ public StatementDescription(String line, String method) {
118118

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

121+
private final Set<CassandraTableSchema> cassandraTableSchemaData = new CopyOnWriteArraySet<>();
122+
121123
public Set<ExecutedSqlCommand> getSqlInfoData(){
122124
return Collections.unmodifiableSet(executedSqlCommandData);
123125
}
@@ -150,6 +152,10 @@ public Set<MongoCollectionSchema> getMongoCollectionTypeData(){
150152
return Collections.unmodifiableSet(mongoCollectionSchemaData);
151153
}
152154

155+
public Set<CassandraTableSchema> getCassandraTableTypeData(){
156+
return Collections.unmodifiableSet(cassandraTableSchemaData);
157+
}
158+
153159
public void addSqlInfo(ExecutedSqlCommand info){
154160
executedSqlCommandData.add(info);
155161
}
@@ -182,6 +188,10 @@ public void addMongoCollectionType(MongoCollectionSchema mongoCollectionSchema){
182188
mongoCollectionSchemaData.add(mongoCollectionSchema);
183189
}
184190

191+
public void addCassandraTableType(CassandraTableSchema cassandraTableSchema){
192+
cassandraTableSchemaData.add(cassandraTableSchema);
193+
}
194+
185195
public Set<String> getParsedDtoNamesView(){
186196
return Collections.unmodifiableSet(parsedDtoNames);
187197
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.evomaster.client.java.instrumentation;
2+
3+
import java.io.Serializable;
4+
import java.util.Objects;
5+
6+
/**
7+
* Schema of rows in a Cassandra table.
8+
*/
9+
public class CassandraTableSchema implements Serializable {
10+
private final String tableName;
11+
private final String tableSchema;
12+
13+
public CassandraTableSchema(String tableName, String tableSchema) {
14+
this.tableName = tableName;
15+
this.tableSchema = tableSchema;
16+
}
17+
18+
public String getTableName() {
19+
return tableName;
20+
}
21+
22+
public String getTableSchema() {
23+
return tableSchema;
24+
}
25+
26+
@Override
27+
public boolean equals(Object o) {
28+
if (this == o) return true;
29+
if (o == null || getClass() != o.getClass()) return false;
30+
CassandraTableSchema that = (CassandraTableSchema) o;
31+
return Objects.equals(tableName, that.tableName) && Objects.equals(tableSchema, that.tableSchema);
32+
}
33+
34+
@Override
35+
public int hashCode() {
36+
return Objects.hash(tableName, tableSchema);
37+
}
38+
39+
@Override
40+
public String toString() {
41+
return "CassandraTableSchema{" +
42+
"tableName='" + tableName + '\'' +
43+
", tableSchema='" + tableSchema + '\'' +
44+
'}';
45+
}
46+
}

client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public static List<MethodReplacementClass> getList() {
5454
new LocalTimeClassReplacement(),
5555
new LongClassReplacement(),
5656
new MapClassReplacement(),
57+
new MappingCassandraEntityInformationClassReplacement(),
5758
new MatcherClassReplacement(),
5859
new MessageBodyReaderClassReplacement(),
5960
new MethodClassReplacement(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses;
2+
3+
import org.evomaster.client.java.instrumentation.CassandraTableSchema;
4+
import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement;
5+
import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast;
6+
import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass;
7+
import org.evomaster.client.java.instrumentation.object.ClassToSchema;
8+
import org.evomaster.client.java.instrumentation.shared.ReplacementCategory;
9+
import org.evomaster.client.java.instrumentation.shared.ReplacementType;
10+
import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer;
11+
12+
import java.lang.reflect.Constructor;
13+
import java.lang.reflect.InvocationTargetException;
14+
import java.util.Arrays;
15+
import java.util.Collections;
16+
import java.util.List;
17+
18+
/**
19+
* When a Cassandra repository is created in Spring, a CqlSession is used under the hood.
20+
* But information about the type of the repository's rows is not transferred to the session.
21+
* That info is retained on Spring side.
22+
* So the intention of this replacement is to retrieve that type info.
23+
* This will allow us to create and insert rows of the correct type in the table (and the repository).
24+
*/
25+
public class MappingCassandraEntityInformationClassReplacement extends ThirdPartyMethodReplacementClass {
26+
private static final MappingCassandraEntityInformationClassReplacement singleton = new MappingCassandraEntityInformationClassReplacement();
27+
private static ThreadLocal<Object> instance = new ThreadLocal<>();
28+
29+
@Override
30+
protected String getNameOfThirdPartyTargetClass() {
31+
return "org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation";
32+
}
33+
34+
public static Object consumeInstance() {
35+
Object mappingCassandraEntityInformation = instance.get();
36+
if (mappingCassandraEntityInformation == null) {
37+
throw new IllegalStateException("No instance to consume");
38+
}
39+
instance.set(null);
40+
return mappingCassandraEntityInformation;
41+
}
42+
43+
private static void addInstance(Object x) {
44+
Object mappingCassandraEntityInformation = instance.get();
45+
if (mappingCassandraEntityInformation != null) {
46+
throw new IllegalStateException("Previous instance was not consumed");
47+
}
48+
instance.set(x);
49+
}
50+
51+
@Replacement(
52+
replacingConstructor = true,
53+
type = ReplacementType.TRACKER,
54+
category = ReplacementCategory.CASSANDRA,
55+
id = "constructorEntityConverter",
56+
castTo = "org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation"
57+
)
58+
public static void MappingCassandraEntityInformation(
59+
@ThirdPartyCast(actualType = "org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity") Object entity,
60+
@ThirdPartyCast(actualType = "org.springframework.data.cassandra.core.convert.CassandraConverter") Object converter) {
61+
handleMappingCassandraEntityInformationConstructor("constructorEntityConverter", Arrays.asList(entity, converter));
62+
}
63+
64+
private static void handleMappingCassandraEntityInformationConstructor(String id, List<Object> args) {
65+
Constructor original = getOriginalConstructor(singleton, id);
66+
67+
try {
68+
Object mappingCassandraEntityInformation = original.newInstance(args.toArray());
69+
addInstance(mappingCassandraEntityInformation);
70+
71+
Object tableNameId = mappingCassandraEntityInformation.getClass().getMethod("getTableName").invoke(mappingCassandraEntityInformation);
72+
String tableName = (String) tableNameId.getClass().getMethod("asInternal").invoke(tableNameId);
73+
74+
Class<?> rowType = (Class<?>) mappingCassandraEntityInformation.getClass().getMethod("getJavaType").invoke(mappingCassandraEntityInformation);
75+
String schema = ClassToSchema.getOrDeriveSchemaWithItsRef(rowType, true, Collections.emptyList());
76+
77+
ExecutionTracer.addCassandraTableType(new CassandraTableSchema(tableName, schema));
78+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
79+
throw new RuntimeException(e);
80+
}
81+
}
82+
}

client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,12 @@ public static void addMongoCollectionType(MongoCollectionSchema mongoCollectionS
478478
}
479479
}
480480

481+
public static void addCassandraTableType(CassandraTableSchema cassandraTableSchema){
482+
if (!executingInitCassandra) {
483+
getCurrentAdditionalInfo().addCassandraTableType(cassandraTableSchema);
484+
}
485+
}
486+
481487

482488
public static void markLastExecutedStatement(String lastLine, String lastMethod) {
483489
getCurrentAdditionalInfo().pushLastExecutedStatement(lastLine, lastMethod);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.foo.somedifferentpackage.examples.methodreplacement;
2+
3+
import org.evomaster.client.java.instrumentation.example.cassandra.MappingCassandraEntityOperations;
4+
import org.springframework.data.cassandra.core.convert.CassandraConverter;
5+
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
6+
import org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation;
7+
8+
public class MappingCassandraEntityOperationsImpl implements MappingCassandraEntityOperations {
9+
@Override
10+
public <T, ID> MappingCassandraEntityInformation<?, ?> callMappingCassandraEntityInformation(CassandraPersistentEntity<T> entity, CassandraConverter converter) {
11+
return new MappingCassandraEntityInformation<>(entity, converter);
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.evomaster.client.java.instrumentation.example.cassandra;
2+
3+
import com.datastax.oss.driver.api.core.CqlIdentifier;
4+
import com.foo.somedifferentpackage.examples.methodreplacement.MappingCassandraEntityOperationsImpl;
5+
import org.evomaster.client.java.instrumentation.InstrumentingClassLoader;
6+
import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer;
7+
import org.junit.jupiter.api.AfterEach;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
import org.springframework.data.cassandra.core.convert.CassandraConverter;
11+
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
12+
import org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation;
13+
14+
import static org.junit.jupiter.api.Assertions.*;
15+
import static org.mockito.Mockito.mock;
16+
import static org.mockito.Mockito.when;
17+
18+
19+
public class MappingCassandraEntityInstrumentedTest {
20+
21+
public static class SomeType {
22+
public String someField;
23+
}
24+
25+
protected MappingCassandraEntityOperations getInstance() throws Exception {
26+
27+
InstrumentingClassLoader cl = new InstrumentingClassLoader("com.foo");
28+
29+
return (MappingCassandraEntityOperations)
30+
cl.loadClass(MappingCassandraEntityOperationsImpl.class.getName())
31+
.newInstance();
32+
}
33+
34+
@BeforeEach
35+
public void init(){
36+
ExecutionTracer.reset();
37+
assertEquals(0 , ExecutionTracer.getNumberOfObjectives());
38+
}
39+
40+
@AfterEach
41+
public void checkInstrumentation(){
42+
assertTrue(ExecutionTracer.getNumberOfObjectives() > 0);
43+
}
44+
45+
@SuppressWarnings("unchecked")
46+
@Test
47+
public void testConstructor() throws Exception {
48+
MappingCassandraEntityOperations cassandraInstrumented = getInstance();
49+
50+
CassandraPersistentEntity<SomeType> entity = mock(CassandraPersistentEntity.class);
51+
when(entity.getType()).thenReturn(SomeType.class);
52+
when(entity.getTableName()).thenReturn(CqlIdentifier.fromCql("some_table"));
53+
54+
CassandraConverter converter = mock(CassandraConverter.class);
55+
56+
MappingCassandraEntityInformation<?, ?> mappingCassandraEntityInformation =
57+
cassandraInstrumented.callMappingCassandraEntityInformation(entity, converter);
58+
assertNotNull(mappingCassandraEntityInformation);
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.evomaster.client.java.instrumentation.example.cassandra;
2+
3+
import org.springframework.data.cassandra.core.convert.CassandraConverter;
4+
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
5+
import org.springframework.data.cassandra.repository.support.MappingCassandraEntityInformation;
6+
7+
public interface MappingCassandraEntityOperations {
8+
<T, ID> MappingCassandraEntityInformation<?, ?> callMappingCassandraEntityInformation(CassandraPersistentEntity<T> entity, CassandraConverter converter);
9+
}

0 commit comments

Comments
 (0)