Skip to content

Commit 8852277

Browse files
authored
Extend Java API for Spring-aware generation (#2614)
Extend Java API for Spring-aware generation #2614
1 parent bfc2fde commit 8852277

File tree

10 files changed

+710
-686
lines changed

10 files changed

+710
-686
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: 75 additions & 52 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,53 @@ 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+
* Generates test code
45+
* @param methodsForGeneration specify methods that are supposed to be executed concretely.
46+
* In order to execute method you are supposed to provide some
47+
* values to pass in it this is why we use [TestMethodInfo] here.
48+
* @param generatedTestCases specify [UtMethodTestSet]s that are used for test code
49+
* generation. By comparison with the first parameter,
50+
* {@code UtMethodTestSet} contains more information about
51+
* test, including result of the executions. Note, that
52+
* you can get the object with any sort of analysis,
53+
* for instance, symbolic or fuzz execution.
54+
* @param destinationClassName the name of containing class for the generated tests
55+
* @param classpath classpath that are used to build the class under test
56+
* @param dependencyClassPath class path including dependencies required for the code generation
57+
* @param classUnderTest for this class test should be generated
58+
* @param projectType JVM, Spring, Python, or other type of project
59+
* @param testFramework test framework that is going to be used for running generated tests
60+
* @param mockFramework framework that will be used in the generated tests
61+
* @param codegenLanguage the target language of the test generation. It can be different from the source language.
62+
* @param staticsMocking the approach to the statics mocking
63+
* @param generateWarningsForStaticMocking enable generation of warning about forced static mocking in comments
64+
* of generated tests.
65+
* @param forceStaticMocking enables static mocking
66+
* @param testClassPackageName package name for the generated class with the tests
67+
* @param applicationContext specify application context here
68+
*/
5669
@JvmStatic
5770
@JvmOverloads
58-
fun generate(
71+
fun generateTestCode(
5972
methodsForGeneration: List<TestMethodInfo>,
6073
generatedTestCases: List<UtMethodTestSet> = mutableListOf(),
6174
destinationClassName: String,
@@ -69,16 +82,18 @@ object UtBotJavaApi {
6982
staticsMocking: StaticsMocking = NoStaticMocking,
7083
generateWarningsForStaticMocking: Boolean = false,
7184
forceStaticMocking: ForceStaticMocking = ForceStaticMocking.DO_NOT_FORCE,
72-
testClassPackageName: String = classUnderTest.nameOfPackage
85+
testClassPackageName: String = classUnderTest.nameOfPackage,
86+
applicationContext: ApplicationContext
7387
): String {
7488

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

7991
val concreteExecutor = ConcreteExecutor(
80-
SimpleUtExecutionInstrumentation.Factory(pathsToUserClasses = classpath.split(File.pathSeparator).toSet()),
81-
classpath,
92+
applicationContext.createConcreteExecutionContext(
93+
fullClasspath = dependencyClassPath,
94+
classpathWithoutDependencies = classpath
95+
).instrumentationFactory,
96+
classpath
8297
)
8398

8499
testSets.addAll(generateUnitTests(concreteExecutor, methodsForGeneration, classUnderTest))
@@ -87,8 +102,8 @@ object UtBotJavaApi {
87102
concreteExecutor.close()
88103
}
89104

90-
return withUtContext(utContext) {
91-
val codeGenerator = CodeGenerator(
105+
return withUtContext(UtContext(classUnderTest.classLoader)) {
106+
applicationContext.createCodeGenerator(
92107
CodeGeneratorParams(
93108
classUnderTest = classUnderTest.id,
94109
projectType = projectType,
@@ -99,11 +114,9 @@ object UtBotJavaApi {
99114
staticsMocking = staticsMocking,
100115
forceStaticMocking = forceStaticMocking,
101116
generateWarningsForStaticMocking = generateWarningsForStaticMocking,
102-
testClassPackageName = testClassPackageName
117+
testClassPackageName = testClassPackageName,
103118
)
104-
)
105-
106-
codeGenerator.generateAsString(testSets, destinationClassName)
119+
).generateAsString(testSets, destinationClassName)
107120
}
108121
}
109122

@@ -114,29 +127,36 @@ object UtBotJavaApi {
114127
*/
115128
@JvmStatic
116129
@JvmOverloads
117-
fun generateTestSets(
118-
methodsForAutomaticGeneration: List<TestMethodInfo>,
130+
fun generateTestSetsForMethods(
131+
methodsToAnalyze: List<Method>,
119132
classUnderTest: Class<*>,
120133
classpath: String,
121134
dependencyClassPath: String,
122135
mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES,
123-
generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis
136+
generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis,
137+
applicationContext: ApplicationContext
124138
): MutableList<UtMethodTestSet> {
125139

140+
assert(methodsToAnalyze.all {classUnderTest.declaredMethods.contains(it)})
141+
{ "Some methods are absent in the ${classUnderTest.name} class." }
142+
126143
val utContext = UtContext(classUnderTest.classLoader)
127144
val testSets: MutableList<UtMethodTestSet> = mutableListOf()
128145

129146
testSets.addAll(withUtContext(utContext) {
130147
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-
)
148+
TestCaseGenerator(
149+
listOf(buildPath),
150+
classpath,
151+
dependencyClassPath,
152+
jdkInfo = JdkInfoDefaultProvider().info,
153+
applicationContext = applicationContext
154+
).generate(
155+
methodsToAnalyze.map { it.executableId },
156+
mockStrategyApi,
157+
chosenClassesToMockAlways = emptySet(),
158+
generationTimeoutInMillis
159+
)
140160
})
141161

142162
return testSets
@@ -145,19 +165,24 @@ object UtBotJavaApi {
145165
/**
146166
* Generates test cases using only fuzzing workflow.
147167
*
148-
* @see [generateTestSets]
168+
* @see [generateTestSetsForMethods]
149169
*/
150170
@JvmStatic
151171
@JvmOverloads
152172
fun fuzzingTestSets(
153-
methodsForAutomaticGeneration: List<TestMethodInfo>,
173+
methodsToAnalyze: List<Method>,
154174
classUnderTest: Class<*>,
155175
classpath: String,
156176
dependencyClassPath: String,
157177
mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES,
158178
generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis,
159-
primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null }
179+
primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null },
180+
applicationContext: ApplicationContext
160181
): MutableList<UtMethodTestSet> {
182+
183+
assert(methodsToAnalyze.all {classUnderTest.declaredMethods.contains(it)})
184+
{ "Some methods are absent in the ${classUnderTest.name} class." }
185+
161186
fun createPrimitiveModels(supplier: CustomFuzzerValueSupplier, classId: ClassId): Sequence<UtPrimitiveModel> =
162187
supplier
163188
.takeIf { classId.isPrimitive || classId.isPrimitiveWrapper || classId == stringClassId }
@@ -189,14 +214,12 @@ object UtBotJavaApi {
189214
classpath,
190215
dependencyClassPath,
191216
jdkInfo = JdkInfoDefaultProvider().info,
192-
applicationContext = SimpleApplicationContext().transformValueProvider { defaultModelProvider ->
217+
applicationContext = applicationContext.transformValueProvider { defaultModelProvider ->
193218
customModelProvider.withFallback(defaultModelProvider)
194219
}
195220
)
196221
.generate(
197-
methodsForAutomaticGeneration.map {
198-
it.methodToBeTestedFromUserInput.executableId
199-
},
222+
methodsToAnalyze.map{ it.executableId },
200223
mockStrategyApi,
201224
chosenClassesToMockAlways = emptySet(),
202225
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: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package org.utbot.api.java;
2+
3+
import org.utbot.examples.spring.app.MyServiceImpl;
4+
import org.junit.jupiter.api.Test;
5+
import org.utbot.examples.spring.app.MyServiceUser;
6+
import org.utbot.examples.spring.app.SpringExampleApp;
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.context.spring.SpringApplicationContext;
16+
import org.utbot.framework.plugin.api.*;
17+
import org.utbot.framework.util.Snippet;
18+
19+
import java.io.File;
20+
import java.lang.reflect.Method;
21+
import java.util.*;
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 applicationContext returns Spring settings to run the generation with
31+
*/
32+
private void supplyConfigurationAndRunSpringTest(ApplicationContext applicationContext) {
33+
34+
UtBotJavaApi.setStopConcreteExecutorOnExit(false);
35+
36+
String classpath = getClassPath(MyServiceImpl.class);
37+
String dependencyClassPath = getDependencyClassPath();
38+
39+
Method getNameMethod = getMethodByName(
40+
MyServiceImpl.class,
41+
"getName"
42+
);
43+
44+
Method useMyServiceMethod = getMethodByName(
45+
MyServiceUser.class,
46+
"useMyService"
47+
);
48+
49+
List<UtMethodTestSet> myServiceUserTestSets = UtBotJavaApi.generateTestSetsForMethods(
50+
Collections.singletonList(useMyServiceMethod),
51+
MyServiceUser.class,
52+
classpath,
53+
dependencyClassPath,
54+
MockStrategyApi.OTHER_PACKAGES,
55+
60000L,
56+
applicationContext
57+
);
58+
59+
String generationResult = UtBotJavaApi.generateTestCode(
60+
Collections.emptyList(),
61+
myServiceUserTestSets,
62+
GENERATED_TEST_CLASS_NAME,
63+
classpath,
64+
dependencyClassPath,
65+
MyServiceUser.class,
66+
ProjectType.Spring,
67+
Junit4.INSTANCE,
68+
MOCKITO,
69+
CodegenLanguage.JAVA,
70+
MockitoStaticMocking.INSTANCE,
71+
false,
72+
ForceStaticMocking.DO_NOT_FORCE,
73+
MyServiceUser.class.getPackage().getName(),
74+
applicationContext
75+
);
76+
77+
Snippet snippet = new Snippet(CodegenLanguage.JAVA, generationResult);
78+
compileClassFile(GENERATED_TEST_CLASS_NAME, snippet);
79+
}
80+
81+
@Test
82+
public void testUnitTestWithoutSettings() {
83+
supplyConfigurationAndRunSpringTest(UtBotSpringApi.createSpringApplicationContext(
84+
SpringSettings.AbsentSpringSettings,
85+
SpringTestType.UNIT_TEST,
86+
Collections.emptyList()));
87+
}
88+
89+
@Test
90+
public void testUnitTestWithSettings() {
91+
SpringConfiguration configuration =
92+
UtBotSpringApi.createJavaSpringConfiguration(SpringExampleApp.class);
93+
94+
SpringSettings springSettings =
95+
new SpringSettings.PresentSpringSettings(configuration, Arrays.asList("test1", "test2"));
96+
97+
ApplicationContext applicationContext = UtBotSpringApi.createSpringApplicationContext(
98+
springSettings,
99+
SpringTestType.UNIT_TEST,
100+
Arrays.asList(getDependencyClassPath().split(File.pathSeparator))
101+
);
102+
103+
supplyConfigurationAndRunSpringTest(applicationContext);
104+
}
105+
}

0 commit comments

Comments
 (0)