Skip to content

Commit a2883ee

Browse files
authored
Generate parametrized tests when there is no force mocking (#573)
1 parent 086f225 commit a2883ee

File tree

12 files changed

+99
-31
lines changed

12 files changed

+99
-31
lines changed

utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ class UtBotSymbolicEngine(
180180

181181
fun attachMockListener(mockListener: MockListener) = mocker.mockListenerController?.attach(mockListener)
182182

183+
fun detachMockListener(mockListener: MockListener) = mocker.mockListenerController?.detach(mockListener)
184+
183185
private val statesForConcreteExecution: MutableList<ExecutionState> = mutableListOf()
184186

185187
private val traverser = Traverser(

utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/ForceMockListener.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.utbot.engine.util.mockListeners
22
import org.utbot.engine.EngineController
33
import org.utbot.engine.MockStrategy
44
import org.utbot.engine.UtMockInfo
5+
import org.utbot.framework.plugin.api.TestCaseGenerator
56
import org.utbot.framework.util.Conflict
67
import org.utbot.framework.util.ConflictTriggers
78

@@ -18,4 +19,13 @@ class ForceMockListener(triggers: ConflictTriggers): MockListener(triggers) {
1819

1920
triggers[Conflict.ForceMockHappened] = true
2021
}
22+
23+
companion object {
24+
fun create(testCaseGenerator: TestCaseGenerator, conflictTriggers: ConflictTriggers) : ForceMockListener {
25+
val listener = ForceMockListener(conflictTriggers)
26+
testCaseGenerator.engineActions.add { engine -> engine.attachMockListener(listener) }
27+
28+
return listener
29+
}
30+
}
2131
}

utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/ForceStaticMockListener.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.utbot.engine.UtMockInfo
66
import org.utbot.engine.UtNewInstanceMockInfo
77
import org.utbot.engine.UtStaticMethodMockInfo
88
import org.utbot.engine.UtStaticObjectMockInfo
9+
import org.utbot.framework.plugin.api.TestCaseGenerator
910
import org.utbot.framework.util.Conflict
1011
import org.utbot.framework.util.ConflictTriggers
1112

@@ -26,4 +27,13 @@ class ForceStaticMockListener(triggers: ConflictTriggers): MockListener(triggers
2627
triggers[Conflict.ForceStaticMockHappened] = true
2728
}
2829
}
30+
31+
companion object {
32+
fun create(testCaseGenerator: TestCaseGenerator, conflictTriggers: ConflictTriggers) : ForceStaticMockListener {
33+
val listener = ForceStaticMockListener(conflictTriggers)
34+
testCaseGenerator.engineActions.add { engine -> engine.attachMockListener(listener) }
35+
36+
return listener
37+
}
38+
}
2939
}

utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListener.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.utbot.engine.util.mockListeners
33
import org.utbot.engine.EngineController
44
import org.utbot.engine.MockStrategy
55
import org.utbot.engine.UtMockInfo
6+
import org.utbot.framework.plugin.api.TestCaseGenerator
67
import org.utbot.framework.util.ConflictTriggers
78

89
/**
@@ -12,4 +13,8 @@ abstract class MockListener(
1213
val triggers: ConflictTriggers
1314
) {
1415
abstract fun onShouldMock(controller: EngineController, strategy: MockStrategy, mockInfo: UtMockInfo)
16+
17+
fun detach(testCaseGenerator: TestCaseGenerator, listener: MockListener) {
18+
testCaseGenerator.engineActions.add { engine -> engine.detachMockListener(listener) }
19+
}
1520
}

utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListenerController.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ class MockListenerController(private val controller: EngineController) {
1414
listeners += listener
1515
}
1616

17+
fun detach(listener: MockListener) {
18+
listeners -= listener
19+
}
20+
1721
fun onShouldMock(strategy: MockStrategy, mockInfo: UtMockInfo) {
1822
listeners.map { it.onShouldMock(controller, strategy, mockInfo) }
1923
}

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,11 +1215,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12151215
private val expectedResultVarName = "expectedResult"
12161216
private val expectedErrorVarName = "expectedError"
12171217

1218-
fun createParameterizedTestMethod(testSet: UtMethodTestSet, dataProviderMethodName: String): CgTestMethod? {
1219-
if (testSet.executions.isEmpty()) {
1220-
return null
1221-
}
1222-
1218+
fun createParameterizedTestMethod(testSet: UtMethodTestSet, dataProviderMethodName: String): CgTestMethod {
12231219
//TODO: orientation on generic execution may be misleading, but what is the alternative?
12241220
//may be a heuristic to select a model with minimal number of internal nulls should be used
12251221
val genericExecution = testSet.executions

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ internal class CgTestClassConstructor(val context: CgContext) :
5252
cgDataProviderMethods.clear()
5353
for (testSet in testSets) {
5454
updateCurrentExecutable(testSet.method)
55-
val currentMethodUnderTestRegions = construct(testSet)
55+
val currentMethodUnderTestRegions = construct(testSet) ?: continue
5656
val executableUnderTestCluster = CgExecutableUnderTestCluster(
5757
"Test suites for executable $currentExecutable",
5858
currentMethodUnderTestRegions
@@ -76,7 +76,11 @@ internal class CgTestClassConstructor(val context: CgContext) :
7676
}
7777
}
7878

79-
private fun construct(testSet: UtMethodTestSet): List<CgRegion<CgMethod>> {
79+
private fun construct(testSet: UtMethodTestSet): List<CgRegion<CgMethod>>? {
80+
if (testSet.executions.isEmpty()) {
81+
return null
82+
}
83+
8084
val (methodUnderTest, executions, _, _, clustersInfo) = testSet
8185
val regions = mutableListOf<CgRegion<CgMethod>>()
8286
val requiredFields = mutableListOf<CgParameterDeclaration>()
@@ -107,17 +111,15 @@ internal class CgTestClassConstructor(val context: CgContext) :
107111
val parameterizedTestMethod =
108112
methodConstructor.createParameterizedTestMethod(testSet, dataProviderMethodName)
109113

110-
if (parameterizedTestMethod != null) {
111-
requiredFields += parameterizedTestMethod.requiredFields
114+
requiredFields += parameterizedTestMethod.requiredFields
112115

113-
cgDataProviderMethods +=
114-
methodConstructor.createParameterizedTestDataProvider(testSet, dataProviderMethodName)
116+
cgDataProviderMethods +=
117+
methodConstructor.createParameterizedTestDataProvider(testSet, dataProviderMethodName)
115118

116-
regions += CgSimpleRegion(
117-
"Parameterized test for method ${methodUnderTest.displayName}",
118-
listOf(parameterizedTestMethod),
119-
)
120-
}
119+
regions += CgSimpleRegion(
120+
"Parameterized test for method ${methodUnderTest.displayName}",
121+
listOf(parameterizedTestMethod),
122+
)
121123
}.onFailure { error -> processFailure(testSet, error) }
122124
}
123125
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ open class TestCaseGenerator(
117117
executionTimeEstimator: ExecutionTimeEstimator = ExecutionTimeEstimator(utBotGenerationTimeoutInMillis, 1)
118118
): Flow<UtResult> {
119119
val engine = createSymbolicEngine(controller, method, mockStrategy, chosenClassesToMockAlways, executionTimeEstimator)
120+
engineActions.map { engine.apply(it) }
121+
engineActions.clear()
120122
return defaultTestFlow(engine, executionTimeEstimator.userTimeout)
121123
}
122124

utbot-framework/src/test/kotlin/org/utbot/examples/TestSpecificTestCaseGenerator.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import org.utbot.common.runIgnoringCancellationException
77
import org.utbot.engine.EngineController
88
import org.utbot.engine.Mocker
99
import org.utbot.engine.UtBotSymbolicEngine
10+
import org.utbot.engine.util.mockListeners.ForceMockListener
11+
import org.utbot.engine.util.mockListeners.ForceStaticMockListener
1012
import org.utbot.framework.UtSettings
1113
import org.utbot.framework.plugin.api.MockStrategyApi
1214
import org.utbot.framework.plugin.api.TestCaseGenerator
@@ -45,6 +47,9 @@ class TestSpecificTestCaseGenerator(
4547
val mockAlwaysDefaults = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }
4648
val defaultTimeEstimator = ExecutionTimeEstimator(UtSettings.utBotGenerationTimeoutInMillis, 1)
4749

50+
val forceMockListener = ForceMockListener.create(this, conflictTriggers)
51+
val forceStaticMockListener = ForceStaticMockListener.create(this, conflictTriggers)
52+
4853
runIgnoringCancellationException {
4954
runBlockingWithCancellationPredicate(isCanceled) {
5055
super
@@ -58,6 +63,9 @@ class TestSpecificTestCaseGenerator(
5863
}
5964
}
6065

66+
forceMockListener.detach(this, forceMockListener)
67+
forceStaticMockListener.detach(this, forceStaticMockListener)
68+
6169
val minimizedExecutions = super.minimizeExecutions(executions)
6270
return UtMethodTestSet(method, minimizedExecutions, jimpleBody(method), errors)
6371
}

utbot-framework/src/test/kotlin/org/utbot/examples/TestUtil.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import org.utbot.framework.plugin.api.MockFramework
1414
import org.utbot.framework.plugin.api.MockStrategyApi
1515
import org.utbot.framework.plugin.api.MockStrategyApi.NO_MOCKS
1616
import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_CLASSES
17+
import org.utbot.framework.util.Conflict
18+
import org.utbot.framework.util.ConflictTriggers
1719

1820

1921
data class TestFrameworkConfiguration(
@@ -28,6 +30,11 @@ data class TestFrameworkConfiguration(
2830
val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.PASS,
2931
val enableTestsTimeout: Boolean = false // our tests should not fail due to timeout
3032
) {
33+
val isParametrizedAndMocked: Boolean
34+
get() = parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE &&
35+
(mockStrategy != NO_MOCKS ||
36+
conflictTriggers[Conflict.ForceMockHappened] ?: false || conflictTriggers[Conflict.ForceStaticMockHappened] ?: false)
37+
3138
val isDisabled: Boolean
3239
get() = run {
3340
// TODO Any? JIRA:1366
@@ -42,9 +49,6 @@ data class TestFrameworkConfiguration(
4249
// because otherwise the code generator will not create mocks even for mandatory to mock classes
4350
if (forceStaticMocking == ForceStaticMocking.FORCE && staticsMocking == NoStaticMocking) return true
4451

45-
// TODO find if mocks are used during the analysis JIRA:1418
46-
if (parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE) return true
47-
4852
// junit4 doesn't support parametrized tests
4953
if (testFramework == Junit4 && parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE) return true
5054

@@ -55,6 +59,8 @@ data class TestFrameworkConfiguration(
5559
}
5660
}
5761

62+
val conflictTriggers: ConflictTriggers = ConflictTriggers()
63+
5864
val allTestFrameworkConfigurations: List<TestFrameworkConfiguration> = run {
5965
val possibleConfiguration = mutableListOf<TestFrameworkConfiguration>()
6066

utbot-framework/src/test/kotlin/org/utbot/framework/codegen/TestCodeGeneratorPipeline.kt

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.utbot.common.bracket
77
import org.utbot.common.info
88
import org.utbot.common.packageName
99
import org.utbot.examples.TestFrameworkConfiguration
10+
import org.utbot.examples.conflictTriggers
1011
import org.utbot.framework.codegen.ExecutionStatus.SUCCESS
1112
import org.utbot.framework.codegen.model.CodeGenerator
1213
import org.utbot.framework.plugin.api.CodegenLanguage
@@ -71,22 +72,43 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram
7172
val testSets = data as List<UtMethodTestSet>
7273

7374
val codegenLanguage = testFrameworkConfiguration.codegenLanguage
75+
val parametrizedTestSource = testFrameworkConfiguration.parametrizedTestSource
76+
val isParametrizedAndMocked = testFrameworkConfiguration.isParametrizedAndMocked
7477

7578
val testClass = callToCodeGenerator(testSets, classUnderTest)
7679

80+
// clear triggered flags from the current launch in order to get ready for the next possible run
81+
conflictTriggers.clear()
82+
7783
// actual number of the tests in the generated testClass
7884
val generatedMethodsCount = testClass
7985
.lines()
8086
.count {
8187
val trimmedLine = it.trimStart()
82-
if (codegenLanguage == CodegenLanguage.JAVA) {
83-
trimmedLine.startsWith("public void")
84-
} else {
85-
trimmedLine.startsWith("fun ")
88+
val prefix = when (codegenLanguage) {
89+
CodegenLanguage.JAVA ->
90+
when (parametrizedTestSource) {
91+
ParametrizedTestSource.DO_NOT_PARAMETRIZE -> "public void "
92+
ParametrizedTestSource.PARAMETRIZE -> "public void parameterizedTestsFor"
93+
}
94+
95+
CodegenLanguage.KOTLIN ->
96+
when (parametrizedTestSource) {
97+
ParametrizedTestSource.DO_NOT_PARAMETRIZE -> "fun "
98+
ParametrizedTestSource.PARAMETRIZE -> "fun parameterizedTestsFor"
99+
}
86100
}
101+
trimmedLine.startsWith(prefix)
87102
}
88103
// expected number of the tests in the generated testClass
89-
val expectedNumberOfGeneratedMethods = testSets.sumOf { it.executions.size }
104+
// if force mocking took place in parametrized test generation,
105+
// we don't generate tests at all
106+
val expectedNumberOfGeneratedMethods =
107+
if (isParametrizedAndMocked) 0
108+
else when (parametrizedTestSource) {
109+
ParametrizedTestSource.DO_NOT_PARAMETRIZE -> testSets.sumOf { it.executions.size }
110+
ParametrizedTestSource.PARAMETRIZE -> testSets.size
111+
}
90112

91113
// check for error in the generated file
92114
runCatching {
@@ -220,7 +242,12 @@ class TestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFram
220242
}
221243
val testClassCustomName = "${classUnderTest.java.simpleName}GeneratedTest"
222244

223-
return codeGenerator.generateAsString(testSets, testClassCustomName)
245+
// if force mocking took place in parametrized test generation,
246+
// we don't generate tests at all by passing empty list instead of test sets
247+
return codeGenerator.generateAsString(
248+
if (testFrameworkConfiguration.isParametrizedAndMocked) listOf() else testSets,
249+
testClassCustomName
250+
)
224251
}
225252

226253
private fun checkPipelinesResults(classesPipelines: List<ClassPipeline>) {

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,11 @@ object UtTestsDialogProcessor {
178178
val mockFrameworkInstalled = model.mockFramework?.isInstalled ?: true
179179

180180
if (!mockFrameworkInstalled) {
181-
ForceMockListener(model.conflictTriggers).apply {
182-
testCaseGenerator.engineActions.add { engine -> engine.attachMockListener(this) }
183-
}
181+
ForceMockListener.create(testCaseGenerator, model.conflictTriggers)
184182
}
185183

186184
if (!model.staticsMocking.isConfigured) {
187-
ForceStaticMockListener(model.conflictTriggers).apply {
188-
testCaseGenerator.engineActions.add { engine -> engine.attachMockListener(this) }
189-
}
185+
ForceStaticMockListener.create(testCaseGenerator, model.conflictTriggers)
190186
}
191187

192188
val notEmptyCases = withUtContext(context) {

0 commit comments

Comments
 (0)