Skip to content

Extend Java API for Spring-aware generation #2614

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 17, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,7 @@ sealed class SpringConfiguration(val fullDisplayName: String) {
}

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

Large diffs are not rendered by default.

127 changes: 75 additions & 52 deletions utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,14 @@ package org.utbot.external.api
import org.utbot.common.FileUtil
import org.utbot.common.nameOfPackage
import org.utbot.framework.UtSettings
import org.utbot.framework.codegen.domain.ForceStaticMocking
import org.utbot.framework.codegen.domain.Junit5
import org.utbot.framework.codegen.domain.NoStaticMocking
import org.utbot.framework.codegen.domain.ProjectType
import org.utbot.framework.codegen.domain.StaticsMocking
import org.utbot.framework.codegen.domain.TestFramework
import org.utbot.framework.codegen.generator.CodeGenerator
import org.utbot.framework.codegen.domain.*
import org.utbot.framework.codegen.generator.CodeGeneratorParams
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
import org.utbot.framework.context.simple.SimpleApplicationContext
import org.utbot.framework.context.ApplicationContext
import org.utbot.framework.context.utils.transformValueProvider
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.MockFramework
import org.utbot.framework.plugin.api.MockStrategyApi
import org.utbot.framework.plugin.api.TestCaseGenerator
import org.utbot.framework.plugin.api.UtMethodTestSet
import org.utbot.framework.plugin.api.UtPrimitiveModel
import org.utbot.framework.plugin.api.UtSymbolicExecution
import org.utbot.framework.plugin.api.util.UtContext
import org.utbot.framework.plugin.api.util.executableId
import org.utbot.framework.plugin.api.util.id
Expand All @@ -36,26 +22,53 @@ import org.utbot.framework.plugin.api.util.stringClassId
import org.utbot.framework.plugin.api.util.withUtContext
import org.utbot.framework.plugin.api.util.wrapperByPrimitive
import org.utbot.framework.plugin.services.JdkInfoDefaultProvider
import org.utbot.fuzzer.FuzzedType
import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzing.FuzzedDescription
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.fuzzing.Seed
import org.utbot.fuzzing.ValueProvider
import org.utbot.instrumentation.ConcreteExecutor
import org.utbot.instrumentation.execute
import org.utbot.instrumentation.instrumentation.execution.SimpleUtExecutionInstrumentation
import java.io.File
import kotlin.reflect.jvm.kotlinFunction
import org.utbot.framework.codegen.domain.StaticsMocking
import org.utbot.framework.plugin.api.*
import java.lang.reflect.Method

object UtBotJavaApi {

/**
* For running tests it could be reasonable to reuse the same concrete executor
*/
@JvmStatic
var stopConcreteExecutorOnExit: Boolean = true

/**
* Generates test code
* @param methodsForGeneration specify methods that are supposed to be executed concretely.
* In order to execute method you are supposed to provide some
* values to pass in it this is why we use [TestMethodInfo] here.
* @param generatedTestCases specify [UtMethodTestSet]s that are used for test code
* generation. By comparison with the first parameter,
* {@code UtMethodTestSet} contains more information about
* test, including result of the executions. Note, that
* you can get the object with any sort of analysis,
* for instance, symbolic or fuzz execution.
* @param destinationClassName the name of containing class for the generated tests
* @param classpath classpath that are used to build the class under test
* @param dependencyClassPath class path including dependencies required for the code generation
* @param classUnderTest for this class test should be generated
* @param projectType JVM, Spring, Python, or other type of project
* @param testFramework test framework that is going to be used for running generated tests
* @param mockFramework framework that will be used in the generated tests
* @param codegenLanguage the target language of the test generation. It can be different from the source language.
* @param staticsMocking the approach to the statics mocking
* @param generateWarningsForStaticMocking enable generation of warning about forced static mocking in comments
* of generated tests.
* @param forceStaticMocking enables static mocking
* @param testClassPackageName package name for the generated class with the tests
* @param applicationContext specify application context here
*/
@JvmStatic
@JvmOverloads
fun generate(
fun generateTestCode(
methodsForGeneration: List<TestMethodInfo>,
generatedTestCases: List<UtMethodTestSet> = mutableListOf(),
destinationClassName: String,
Expand All @@ -69,16 +82,18 @@ object UtBotJavaApi {
staticsMocking: StaticsMocking = NoStaticMocking,
generateWarningsForStaticMocking: Boolean = false,
forceStaticMocking: ForceStaticMocking = ForceStaticMocking.DO_NOT_FORCE,
testClassPackageName: String = classUnderTest.nameOfPackage
testClassPackageName: String = classUnderTest.nameOfPackage,
applicationContext: ApplicationContext
): String {

val utContext = UtContext(classUnderTest.classLoader)

val testSets: MutableList<UtMethodTestSet> = generatedTestCases.toMutableList()

val concreteExecutor = ConcreteExecutor(
SimpleUtExecutionInstrumentation.Factory(pathsToUserClasses = classpath.split(File.pathSeparator).toSet()),
classpath,
applicationContext.createConcreteExecutionContext(
fullClasspath = dependencyClassPath,
classpathWithoutDependencies = classpath
).instrumentationFactory,
classpath
)

testSets.addAll(generateUnitTests(concreteExecutor, methodsForGeneration, classUnderTest))
Expand All @@ -87,8 +102,8 @@ object UtBotJavaApi {
concreteExecutor.close()
}

return withUtContext(utContext) {
val codeGenerator = CodeGenerator(
return withUtContext(UtContext(classUnderTest.classLoader)) {
applicationContext.createCodeGenerator(
CodeGeneratorParams(
classUnderTest = classUnderTest.id,
projectType = projectType,
Expand All @@ -99,11 +114,9 @@ object UtBotJavaApi {
staticsMocking = staticsMocking,
forceStaticMocking = forceStaticMocking,
generateWarningsForStaticMocking = generateWarningsForStaticMocking,
testClassPackageName = testClassPackageName
testClassPackageName = testClassPackageName,
)
)

codeGenerator.generateAsString(testSets, destinationClassName)
).generateAsString(testSets, destinationClassName)
}
}

Expand All @@ -114,29 +127,36 @@ object UtBotJavaApi {
*/
@JvmStatic
@JvmOverloads
fun generateTestSets(
methodsForAutomaticGeneration: List<TestMethodInfo>,
fun generateTestSetsForMethods(
methodsToAnalyze: List<Method>,
classUnderTest: Class<*>,
classpath: String,
dependencyClassPath: String,
mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES,
generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis
generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis,
applicationContext: ApplicationContext
): MutableList<UtMethodTestSet> {

assert(methodsToAnalyze.all {classUnderTest.declaredMethods.contains(it)})
{ "Some methods are absent in the ${classUnderTest.name} class." }

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

testSets.addAll(withUtContext(utContext) {
val buildPath = FileUtil.isolateClassFiles(classUnderTest).toPath()
TestCaseGenerator(listOf(buildPath), classpath, dependencyClassPath, jdkInfo = JdkInfoDefaultProvider().info)
.generate(
methodsForAutomaticGeneration.map {
it.methodToBeTestedFromUserInput.executableId
},
mockStrategyApi,
chosenClassesToMockAlways = emptySet(),
generationTimeoutInMillis
)
TestCaseGenerator(
listOf(buildPath),
classpath,
dependencyClassPath,
jdkInfo = JdkInfoDefaultProvider().info,
applicationContext = applicationContext
).generate(
methodsToAnalyze.map { it.executableId },
mockStrategyApi,
chosenClassesToMockAlways = emptySet(),
generationTimeoutInMillis
)
})

return testSets
Expand All @@ -145,19 +165,24 @@ object UtBotJavaApi {
/**
* Generates test cases using only fuzzing workflow.
*
* @see [generateTestSets]
* @see [generateTestSetsForMethods]
*/
@JvmStatic
@JvmOverloads
fun fuzzingTestSets(
methodsForAutomaticGeneration: List<TestMethodInfo>,
methodsToAnalyze: List<Method>,
classUnderTest: Class<*>,
classpath: String,
dependencyClassPath: String,
mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES,
generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis,
primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null }
primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null },
applicationContext: ApplicationContext
): MutableList<UtMethodTestSet> {

assert(methodsToAnalyze.all {classUnderTest.declaredMethods.contains(it)})
{ "Some methods are absent in the ${classUnderTest.name} class." }

fun createPrimitiveModels(supplier: CustomFuzzerValueSupplier, classId: ClassId): Sequence<UtPrimitiveModel> =
supplier
.takeIf { classId.isPrimitive || classId.isPrimitiveWrapper || classId == stringClassId }
Expand Down Expand Up @@ -189,14 +214,12 @@ object UtBotJavaApi {
classpath,
dependencyClassPath,
jdkInfo = JdkInfoDefaultProvider().info,
applicationContext = SimpleApplicationContext().transformValueProvider { defaultModelProvider ->
applicationContext = applicationContext.transformValueProvider { defaultModelProvider ->
customModelProvider.withFallback(defaultModelProvider)
}
)
.generate(
methodsForAutomaticGeneration.map {
it.methodToBeTestedFromUserInput.executableId
},
methodsToAnalyze.map{ it.executableId },
mockStrategyApi,
chosenClassesToMockAlways = emptySet(),
generationTimeoutInMillis,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.utbot.examples.spring.app;

public interface MyService {
String getName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.utbot.examples.spring.app;

import org.springframework.stereotype.Service;

@Service
public class MyServiceImpl implements MyService {
@Override
public String getName() {
return "impl";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.utbot.examples.spring.app;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MyServiceUser {
private final MyService myService;

@Autowired
public MyServiceUser(MyService myService) {
this.myService = myService;
}

public String useMyService() {
return myService.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.utbot.examples.spring.app;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringExampleApp {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.utbot.api.java;

import org.utbot.examples.spring.app.MyServiceImpl;
import org.junit.jupiter.api.Test;
import org.utbot.examples.spring.app.MyServiceUser;
import org.utbot.examples.spring.app.SpringExampleApp;
import org.utbot.examples.spring.config.pure.ExamplePureSpringConfig;
import org.utbot.external.api.UtBotJavaApi;
import org.utbot.external.api.UtBotSpringApi;
import org.utbot.framework.codegen.domain.ForceStaticMocking;
import org.utbot.framework.codegen.domain.Junit4;
import org.utbot.framework.codegen.domain.MockitoStaticMocking;
import org.utbot.framework.codegen.domain.ProjectType;
import org.utbot.framework.context.ApplicationContext;
import org.utbot.framework.context.spring.SpringApplicationContext;
import org.utbot.framework.plugin.api.*;
import org.utbot.framework.util.Snippet;

import java.io.File;
import java.lang.reflect.Method;
import java.util.*;

import static org.utbot.framework.plugin.api.MockFramework.MOCKITO;
import static org.utbot.framework.util.TestUtilsKt.compileClassFile;

public class SpringUtBotJavaApiTest extends AbstractUtBotJavaApiTest {

/**
* We are using the environment to check spring generation the only difference in the data supplied by the argument
* @param applicationContext returns Spring settings to run the generation with
*/
private void supplyConfigurationAndRunSpringTest(ApplicationContext applicationContext) {

UtBotJavaApi.setStopConcreteExecutorOnExit(false);

String classpath = getClassPath(MyServiceImpl.class);
String dependencyClassPath = getDependencyClassPath();

Method getNameMethod = getMethodByName(
MyServiceImpl.class,
"getName"
);

Method useMyServiceMethod = getMethodByName(
MyServiceUser.class,
"useMyService"
);

List<UtMethodTestSet> myServiceUserTestSets = UtBotJavaApi.generateTestSetsForMethods(
Collections.singletonList(useMyServiceMethod),
MyServiceUser.class,
classpath,
dependencyClassPath,
MockStrategyApi.OTHER_PACKAGES,
60000L,
applicationContext
);

String generationResult = UtBotJavaApi.generateTestCode(
Collections.emptyList(),
myServiceUserTestSets,
GENERATED_TEST_CLASS_NAME,
classpath,
dependencyClassPath,
MyServiceUser.class,
ProjectType.Spring,
Junit4.INSTANCE,
MOCKITO,
CodegenLanguage.JAVA,
MockitoStaticMocking.INSTANCE,
false,
ForceStaticMocking.DO_NOT_FORCE,
MyServiceUser.class.getPackage().getName(),
applicationContext
);

Snippet snippet = new Snippet(CodegenLanguage.JAVA, generationResult);
compileClassFile(GENERATED_TEST_CLASS_NAME, snippet);
}

@Test
public void testUnitTestWithoutSettings() {
supplyConfigurationAndRunSpringTest(UtBotSpringApi.createSpringApplicationContext(
SpringSettings.AbsentSpringSettings,
SpringTestType.UNIT_TEST,
Collections.emptyList()));
}

@Test
public void testUnitTestWithSettings() {
SpringConfiguration configuration =
UtBotSpringApi.createJavaSpringConfiguration(SpringExampleApp.class);

SpringSettings springSettings =
new SpringSettings.PresentSpringSettings(configuration, Arrays.asList("test1", "test2"));

ApplicationContext applicationContext = UtBotSpringApi.createSpringApplicationContext(
springSettings,
SpringTestType.UNIT_TEST,
Arrays.asList(getDependencyClassPath().split(File.pathSeparator))
);

supplyConfigurationAndRunSpringTest(applicationContext);
}
}
Loading