Skip to content

Commit ba9071e

Browse files
committed
Extend Java API for Spring-aware generation #2614
1 parent 4e5ba7f commit ba9071e

File tree

10 files changed

+712
-685
lines changed

10 files changed

+712
-685
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,7 @@ sealed class SpringConfiguration(val fullDisplayName: String) {
14391439
}
14401440

14411441
sealed interface SpringSettings {
1442-
object AbsentSpringSettings : SpringSettings {
1442+
companion object AbsentSpringSettings : SpringSettings {
14431443
// NOTE that overriding equals is required just because without it
14441444
// we will lose equality for objects after deserialization
14451445
override fun equals(other: Any?): Boolean = other is AbsentSpringSettings

utbot-framework-test/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java

Lines changed: 371 additions & 630 deletions
Large diffs are not rendered by default.

utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt

Lines changed: 50 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,14 @@ package org.utbot.external.api
33
import org.utbot.common.FileUtil
44
import org.utbot.common.nameOfPackage
55
import org.utbot.framework.UtSettings
6-
import org.utbot.framework.codegen.domain.ForceStaticMocking
7-
import org.utbot.framework.codegen.domain.Junit5
8-
import org.utbot.framework.codegen.domain.NoStaticMocking
9-
import org.utbot.framework.codegen.domain.ProjectType
10-
import org.utbot.framework.codegen.domain.StaticsMocking
11-
import org.utbot.framework.codegen.domain.TestFramework
12-
import org.utbot.framework.codegen.generator.CodeGenerator
6+
import org.utbot.framework.codegen.domain.*
137
import org.utbot.framework.codegen.generator.CodeGeneratorParams
148
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
15-
import org.utbot.framework.context.simple.SimpleApplicationContext
9+
import org.utbot.framework.context.ApplicationContext
1610
import org.utbot.framework.context.utils.transformValueProvider
1711
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
1812
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
1913
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
20-
import org.utbot.framework.plugin.api.ClassId
21-
import org.utbot.framework.plugin.api.CodegenLanguage
22-
import org.utbot.framework.plugin.api.MockFramework
23-
import org.utbot.framework.plugin.api.MockStrategyApi
24-
import org.utbot.framework.plugin.api.TestCaseGenerator
25-
import org.utbot.framework.plugin.api.UtMethodTestSet
26-
import org.utbot.framework.plugin.api.UtPrimitiveModel
27-
import org.utbot.framework.plugin.api.UtSymbolicExecution
2814
import org.utbot.framework.plugin.api.util.UtContext
2915
import org.utbot.framework.plugin.api.util.executableId
3016
import org.utbot.framework.plugin.api.util.id
@@ -36,26 +22,36 @@ import org.utbot.framework.plugin.api.util.stringClassId
3622
import org.utbot.framework.plugin.api.util.withUtContext
3723
import org.utbot.framework.plugin.api.util.wrapperByPrimitive
3824
import org.utbot.framework.plugin.services.JdkInfoDefaultProvider
39-
import org.utbot.fuzzer.FuzzedType
4025
import org.utbot.fuzzer.FuzzedValue
41-
import org.utbot.fuzzing.FuzzedDescription
4226
import org.utbot.fuzzing.JavaValueProvider
4327
import org.utbot.fuzzing.Seed
44-
import org.utbot.fuzzing.ValueProvider
4528
import org.utbot.instrumentation.ConcreteExecutor
4629
import org.utbot.instrumentation.execute
47-
import org.utbot.instrumentation.instrumentation.execution.SimpleUtExecutionInstrumentation
48-
import java.io.File
4930
import kotlin.reflect.jvm.kotlinFunction
31+
import org.utbot.framework.codegen.domain.StaticsMocking
32+
import org.utbot.framework.plugin.api.*
33+
import java.lang.reflect.Method
5034

5135
object UtBotJavaApi {
5236

37+
/**
38+
* For running tests it could be reasonable to reuse the same concrete executor
39+
*/
5340
@JvmStatic
5441
var stopConcreteExecutorOnExit: Boolean = true
5542

43+
44+
/**
45+
* For test generation, take a look at the first too arguments.
46+
* If you want to execute some methods concretely,
47+
* use the first argument. The first argument must contain
48+
* all information required for the execution, including arguments values.
49+
* The second one is ready to use {@code UtMethodTestSet}s.
50+
* Test for them will be generated without concrete execution.
51+
*/
5652
@JvmStatic
5753
@JvmOverloads
58-
fun generate(
54+
fun generateTestCode(
5955
methodsForGeneration: List<TestMethodInfo>,
6056
generatedTestCases: List<UtMethodTestSet> = mutableListOf(),
6157
destinationClassName: String,
@@ -69,16 +65,18 @@ object UtBotJavaApi {
6965
staticsMocking: StaticsMocking = NoStaticMocking,
7066
generateWarningsForStaticMocking: Boolean = false,
7167
forceStaticMocking: ForceStaticMocking = ForceStaticMocking.DO_NOT_FORCE,
72-
testClassPackageName: String = classUnderTest.nameOfPackage
68+
testClassPackageName: String = classUnderTest.nameOfPackage,
69+
applicationContext: ApplicationContext
7370
): String {
7471

75-
val utContext = UtContext(classUnderTest.classLoader)
76-
7772
val testSets: MutableList<UtMethodTestSet> = generatedTestCases.toMutableList()
7873

7974
val concreteExecutor = ConcreteExecutor(
80-
SimpleUtExecutionInstrumentation.Factory(pathsToUserClasses = classpath.split(File.pathSeparator).toSet()),
81-
classpath,
75+
applicationContext.createConcreteExecutionContext(
76+
fullClasspath = dependencyClassPath,
77+
classpathWithoutDependencies = classpath
78+
).instrumentationFactory,
79+
classpath
8280
)
8381

8482
testSets.addAll(generateUnitTests(concreteExecutor, methodsForGeneration, classUnderTest))
@@ -87,8 +85,8 @@ object UtBotJavaApi {
8785
concreteExecutor.close()
8886
}
8987

90-
return withUtContext(utContext) {
91-
val codeGenerator = CodeGenerator(
88+
return withUtContext(UtContext(classUnderTest.classLoader)) {
89+
applicationContext.createCodeGenerator(
9290
CodeGeneratorParams(
9391
classUnderTest = classUnderTest.id,
9492
projectType = projectType,
@@ -99,11 +97,9 @@ object UtBotJavaApi {
9997
staticsMocking = staticsMocking,
10098
forceStaticMocking = forceStaticMocking,
10199
generateWarningsForStaticMocking = generateWarningsForStaticMocking,
102-
testClassPackageName = testClassPackageName
100+
testClassPackageName = testClassPackageName,
103101
)
104-
)
105-
106-
codeGenerator.generateAsString(testSets, destinationClassName)
102+
).generateAsString(testSets, destinationClassName)
107103
}
108104
}
109105

@@ -114,29 +110,33 @@ object UtBotJavaApi {
114110
*/
115111
@JvmStatic
116112
@JvmOverloads
117-
fun generateTestSets(
118-
methodsForAutomaticGeneration: List<TestMethodInfo>,
113+
fun generateTestSetsForMethods(
114+
methodsToAnalyze: List<Method>,
119115
classUnderTest: Class<*>,
120116
classpath: String,
121117
dependencyClassPath: String,
122118
mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES,
123-
generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis
119+
generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis,
120+
applicationContext: ApplicationContext
124121
): MutableList<UtMethodTestSet> {
125122

126123
val utContext = UtContext(classUnderTest.classLoader)
127124
val testSets: MutableList<UtMethodTestSet> = mutableListOf()
128125

129126
testSets.addAll(withUtContext(utContext) {
130127
val buildPath = FileUtil.isolateClassFiles(classUnderTest).toPath()
131-
TestCaseGenerator(listOf(buildPath), classpath, dependencyClassPath, jdkInfo = JdkInfoDefaultProvider().info)
132-
.generate(
133-
methodsForAutomaticGeneration.map {
134-
it.methodToBeTestedFromUserInput.executableId
135-
},
136-
mockStrategyApi,
137-
chosenClassesToMockAlways = emptySet(),
138-
generationTimeoutInMillis
139-
)
128+
TestCaseGenerator(
129+
listOf(buildPath),
130+
classpath,
131+
dependencyClassPath,
132+
jdkInfo = JdkInfoDefaultProvider().info,
133+
applicationContext = applicationContext
134+
).generate(
135+
methodsToAnalyze.map { it.executableId },
136+
mockStrategyApi,
137+
chosenClassesToMockAlways = emptySet(),
138+
generationTimeoutInMillis
139+
)
140140
})
141141

142142
return testSets
@@ -150,13 +150,14 @@ object UtBotJavaApi {
150150
@JvmStatic
151151
@JvmOverloads
152152
fun fuzzingTestSets(
153-
methodsForAutomaticGeneration: List<TestMethodInfo>,
153+
methodsToAnalyze: List<Method>,
154154
classUnderTest: Class<*>,
155155
classpath: String,
156156
dependencyClassPath: String,
157157
mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES,
158158
generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis,
159-
primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null }
159+
primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null },
160+
applicationContext: ApplicationContext
160161
): MutableList<UtMethodTestSet> {
161162
fun createPrimitiveModels(supplier: CustomFuzzerValueSupplier, classId: ClassId): Sequence<UtPrimitiveModel> =
162163
supplier
@@ -189,14 +190,12 @@ object UtBotJavaApi {
189190
classpath,
190191
dependencyClassPath,
191192
jdkInfo = JdkInfoDefaultProvider().info,
192-
applicationContext = SimpleApplicationContext().transformValueProvider { defaultModelProvider ->
193+
applicationContext = applicationContext.transformValueProvider { defaultModelProvider ->
193194
customModelProvider.withFallback(defaultModelProvider)
194195
}
195196
)
196197
.generate(
197-
methodsForAutomaticGeneration.map {
198-
it.methodToBeTestedFromUserInput.executableId
199-
},
198+
methodsToAnalyze.map{ it.executableId },
200199
mockStrategyApi,
201200
chosenClassesToMockAlways = emptySet(),
202201
generationTimeoutInMillis,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.utbot.examples.spring.app;
2+
3+
public interface MyService {
4+
String getName();
5+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.utbot.examples.spring.app;
2+
3+
import org.springframework.stereotype.Service;
4+
5+
@Service
6+
public class MyServiceImpl implements MyService {
7+
@Override
8+
public String getName() {
9+
return "impl";
10+
}
11+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.utbot.examples.spring.app;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.stereotype.Service;
5+
6+
@Service
7+
public class MyServiceUser {
8+
private final MyService myService;
9+
10+
@Autowired
11+
public MyServiceUser(MyService myService) {
12+
this.myService = myService;
13+
}
14+
15+
public String useMyService() {
16+
return myService.getName();
17+
}
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.utbot.examples.spring.app;
2+
3+
import org.springframework.boot.autoconfigure.SpringBootApplication;
4+
5+
@SpringBootApplication
6+
public class SpringExampleApp {
7+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package org.utbot.api.java;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.utbot.examples.spring.app.SpringExampleApp;
5+
import org.utbot.examples.spring.autowiring.oneBeanForOneType.Order;
6+
import org.utbot.examples.spring.autowiring.oneBeanForOneType.ServiceWithInjectedField;
7+
import org.utbot.examples.spring.config.pure.ExamplePureSpringConfig;
8+
import org.utbot.external.api.UtBotJavaApi;
9+
import org.utbot.external.api.UtBotSpringApi;
10+
import org.utbot.framework.codegen.domain.ForceStaticMocking;
11+
import org.utbot.framework.codegen.domain.Junit4;
12+
import org.utbot.framework.codegen.domain.MockitoStaticMocking;
13+
import org.utbot.framework.codegen.domain.ProjectType;
14+
import org.utbot.framework.context.ApplicationContext;
15+
import org.utbot.framework.plugin.api.*;
16+
import org.utbot.framework.util.Snippet;
17+
18+
import java.io.File;
19+
import java.lang.reflect.Method;
20+
import java.util.*;
21+
import java.util.function.Supplier;
22+
23+
import static org.utbot.framework.plugin.api.MockFramework.MOCKITO;
24+
import static org.utbot.framework.util.TestUtilsKt.compileClassFile;
25+
26+
public class SpringUtBotJavaApiTest extends AbstractUtBotJavaApiTest {
27+
28+
/**
29+
* We are using the environment to check spring generation the only difference in the data supplied by the argument
30+
* @param configurationSupplier returns Spring settings to run the generation with
31+
*/
32+
private void supplyConfigurationAndRunSpringTest(Supplier<ApplicationContext> configurationSupplier) {
33+
34+
UtBotJavaApi.setStopConcreteExecutorOnExit(false);
35+
36+
String classpath = getClassPath(ServiceWithInjectedField.class);
37+
String dependencyClassPath = getDependencyClassPath();
38+
39+
Method createOrderMethod = getMethodByName(
40+
ServiceWithInjectedField.class,
41+
"createOrder",
42+
Order.class
43+
);
44+
45+
Method getOrderMethod = getMethodByName(
46+
ServiceWithInjectedField.class,
47+
"getOrders"
48+
);
49+
50+
ApplicationContext springApplicationContext = configurationSupplier.get();
51+
52+
List<UtMethodTestSet> testSets = UtBotJavaApi.generateTestSetsForMethods(
53+
Arrays.asList(createOrderMethod, getOrderMethod),
54+
ServiceWithInjectedField.class,
55+
classpath,
56+
dependencyClassPath,
57+
MockStrategyApi.OTHER_PACKAGES,
58+
60000L,
59+
springApplicationContext
60+
);
61+
62+
String generationResult = UtBotJavaApi.generateTestCode(
63+
Collections.emptyList(),
64+
testSets,
65+
GENERATED_TEST_CLASS_NAME,
66+
classpath,
67+
dependencyClassPath,
68+
ServiceWithInjectedField.class,
69+
ProjectType.PureJvm,
70+
Junit4.INSTANCE,
71+
MOCKITO,
72+
CodegenLanguage.JAVA,
73+
MockitoStaticMocking.INSTANCE,
74+
false,
75+
ForceStaticMocking.DO_NOT_FORCE,
76+
ServiceWithInjectedField.class.getPackage().getName(),
77+
springApplicationContext
78+
);
79+
80+
Snippet snippet = new Snippet(CodegenLanguage.JAVA, generationResult);
81+
compileClassFile(GENERATED_TEST_CLASS_NAME, snippet);
82+
}
83+
84+
@Test
85+
public void testUnitTestWithoutSettings() {
86+
supplyConfigurationAndRunSpringTest(() ->
87+
UtBotSpringApi.createSpringApplicationContext(
88+
SpringSettings.AbsentSpringSettings,
89+
SpringTestType.UNIT_TEST,
90+
Collections.emptyList()
91+
));
92+
}
93+
94+
@Test
95+
public void testUnitTestWithSettings() {
96+
supplyConfigurationAndRunSpringTest(() -> {
97+
98+
SpringConfiguration configuration =
99+
UtBotSpringApi.createJavaSpringConfiguration(ExamplePureSpringConfig.class);
100+
101+
SpringSettings springSettings =
102+
new SpringSettings.PresentSpringSettings(configuration, Arrays.asList("test1", "test2"));
103+
104+
return UtBotSpringApi.createSpringApplicationContext(
105+
springSettings,
106+
SpringTestType.UNIT_TEST,
107+
Collections.emptyList()
108+
);
109+
});
110+
}
111+
112+
@Test
113+
public void testIntegrationTestWithSettings() {
114+
supplyConfigurationAndRunSpringTest(() -> {
115+
116+
SpringConfiguration configuration =
117+
UtBotSpringApi.createJavaSpringConfiguration(SpringExampleApp.class);
118+
119+
SpringSettings springSettings =
120+
new SpringSettings.PresentSpringSettings(configuration, Collections.singletonList("default"));
121+
122+
// Here we use dependency classpath from the current thread. In real plugin consider
123+
// avoiding this, it can lead to dependency collisions. Put only important
124+
// classes on the classpath.
125+
return UtBotSpringApi.createSpringApplicationContext(
126+
springSettings,
127+
SpringTestType.INTEGRATION_TEST,
128+
Arrays.asList(getDependencyClassPath().split(File.pathSeparator))
129+
);
130+
});
131+
}
132+
}

0 commit comments

Comments
 (0)