Skip to content

Commit 2f4e3c5

Browse files
committed
Fixed parametrized tests
1 parent ef79034 commit 2f4e3c5

File tree

7 files changed

+109
-44
lines changed

7 files changed

+109
-44
lines changed

utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,13 @@ enum class WorkaroundReason {
6767
* requires thorough [investigation](https://github.com/UnitTestBot/UTBotJava/issues/716).
6868
*/
6969
IGNORE_STATICS_FROM_TRUSTED_LIBRARIES,
70+
/**
71+
* Methods that return [java.util.stream.BaseStream] as a result, can return them ”dirty” - consuming of them lead to the exception.
72+
* The symbolic engine and concrete execution create UtStreamConsumingFailure executions in such cases. To warn a
73+
* user about unsafety of using such “dirty” streams, code generation consumes them (mostly with `toArray` methods)
74+
* and asserts exception. Unfortunately, it doesn't work well for parametrized tests - they create assertions relying on
75+
* such-called “generic execution”, so resulted tests always contain `deepEquals` for streams, and we cannot easily
76+
* construct `toArray` invocation (because streams cannot be consumed twice).
77+
*/
78+
CONSUME_DIRTY_STREAMS,
7079
}

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ data class UtExecutionSuccess(val model: UtModel) : UtExecutionResult() {
1111

1212
sealed class UtExecutionFailure : UtExecutionResult() {
1313
abstract val exception: Throwable
14+
15+
/**
16+
* Represents the most inner exception in the failure.
17+
* Often equals to [exception], but is wrapped exception in [UtStreamConsumingException].
18+
*/
19+
open val rootCauseException: Throwable
20+
get() = exception
1421
}
1522

1623
data class UtOverflowFailure(
@@ -22,8 +29,11 @@ data class UtSandboxFailure(
2229
) : UtExecutionFailure()
2330

2431
data class UtStreamConsumingFailure(
25-
override val exception: Throwable
26-
) : UtExecutionFailure()
32+
override val exception: UtStreamConsumingException,
33+
) : UtExecutionFailure() {
34+
override val rootCauseException: Throwable
35+
get() = exception.innerExceptionOrAny
36+
}
2737

2838
/**
2939
* unexpectedFail (when exceptions such as NPE, IOBE, etc. appear, but not thrown by a user, applies both for function under test and nested calls )
@@ -46,8 +56,8 @@ class TimeoutException(s: String) : Exception(s)
4656
data class UtTimeoutException(override val exception: TimeoutException) : UtExecutionFailure()
4757

4858
/**
49-
* Represents an exception that occurs during consuming a stream.
50-
* [innerException] stores original exception (if possible), null if [UtStreamConsumingException] was constructed by the engine.
59+
* An artificial exception that stores an exception that would be thrown in case of consuming stream by invoking terminal operations.
60+
* [innerException] stores this possible exception (null if [UtStreamConsumingException] was constructed by the engine).
5161
*/
5262
data class UtStreamConsumingException(private val innerException: Exception?) : RuntimeException() {
5363
/**
@@ -101,7 +111,7 @@ inline fun UtExecutionResult.onSuccess(action: (model: UtModel) -> Unit): UtExec
101111
}
102112

103113
inline fun UtExecutionResult.onFailure(action: (exception: Throwable) -> Unit): UtExecutionResult {
104-
if (this is UtExecutionFailure) action(exception)
114+
if (this is UtExecutionFailure) action(rootCauseException)
105115
return this
106116
}
107117

@@ -111,6 +121,6 @@ fun UtExecutionResult.getOrThrow(): UtModel = when (this) {
111121
}
112122

113123
fun UtExecutionResult.exceptionOrNull(): Throwable? = when (this) {
114-
is UtExecutionFailure -> exception
124+
is UtExecutionFailure -> rootCauseException
115125
is UtExecutionSuccess -> null
116126
}

utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/StreamsAsMethodResultExampleTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ class StreamsAsMethodResultExampleTest : UtValueTestCaseChecker(
2020
TestLastStage(CodegenLanguage.JAVA),
2121
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
2222
),
23-
codeGenerationModes = listOf(ParametrizedTestSource.DO_NOT_PARAMETRIZE) // TODO exception from concrete is passed to arguments list somehow
2423
) {
2524
@Test
2625
fun testReturningStreamExample() {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ import kotlin.math.max
9090
import kotlin.math.min
9191
import kotlinx.collections.immutable.persistentListOf
9292
import kotlinx.collections.immutable.persistentSetOf
93+
import org.utbot.framework.plugin.api.UtStreamConsumingException
94+
import org.utbot.framework.plugin.api.UtStreamConsumingFailure
9395

9496
// hack
9597
const val MAX_LIST_SIZE = 10
@@ -370,6 +372,12 @@ class Resolver(
370372
*/
371373
private fun SymbolicFailure.resolve(): UtExecutionFailure {
372374
val exception = concreteException()
375+
376+
if (exception is UtStreamConsumingException) {
377+
// This exception is artificial and is not really thrown
378+
return UtStreamConsumingFailure(exception)
379+
}
380+
373381
return if (explicit) {
374382
UtExplicitlyThrownException(exception, inNestedMethod)
375383
} else {

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

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.utbot.framework.codegen.model.constructor.tree
22

33
import org.utbot.common.PathUtil
4+
import org.utbot.common.WorkaroundReason
45
import org.utbot.common.isStatic
6+
import org.utbot.common.workaround
57
import org.utbot.framework.assemble.assemble
68
import org.utbot.framework.codegen.ForceStaticMocking
79
import org.utbot.framework.codegen.ParametrizedTestSource
@@ -146,7 +148,8 @@ import java.lang.reflect.InvocationTargetException
146148
import java.security.AccessControlException
147149
import java.lang.reflect.ParameterizedType
148150
import org.utbot.framework.UtSettings
149-
import org.utbot.framework.plugin.api.UtStreamConsumingException
151+
import org.utbot.framework.plugin.api.UtExecutionResult
152+
import org.utbot.framework.plugin.api.UtStreamConsumingFailure
150153
import org.utbot.framework.plugin.api.util.allSuperTypes
151154
import org.utbot.framework.plugin.api.util.baseStreamClassId
152155
import org.utbot.framework.plugin.api.util.doubleStreamClassId
@@ -182,6 +185,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
182185

183186
private val fieldsOfExecutionResults = mutableMapOf<Pair<FieldId, Int>, MutableList<UtModel>>()
184187

188+
/**
189+
* Contains whether [UtStreamConsumingFailure] is in [CgMethodTestSet] for parametrized tests.
190+
* See [WorkaroundReason.CONSUME_DIRTY_STREAMS].
191+
*/
192+
private var containsStreamConsumingFailureForParametrizedTests: Boolean = false
193+
185194
private fun setupInstrumentation() {
186195
if (currentExecution is UtSymbolicExecution) {
187196
val execution = currentExecution as UtSymbolicExecution
@@ -299,28 +308,30 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
299308
emptyLineIfNeeded()
300309
val method = currentExecutable as MethodId
301310
val currentExecution = currentExecution!!
311+
val executionResult = currentExecution.result
312+
302313
// build assertions
303-
currentExecution.result
304-
.onSuccess { result ->
314+
executionResult
315+
.onSuccess { resultModel ->
305316
methodType = SUCCESSFUL
306317

307-
// TODO possible engine bug - void method return type and result not UtVoidModel
308-
if (result.isUnit() || method.returnType == voidClassId) {
318+
// TODO possible engine bug - void method return type and result model not UtVoidModel
319+
if (resultModel.isUnit() || method.returnType == voidClassId) {
309320
+thisInstance[method](*methodArguments.toTypedArray())
310321
} else {
311-
resultModel = result
312-
val expected = variableConstructor.getOrCreateVariable(result, "expected")
322+
this.resultModel = resultModel
323+
val expected = variableConstructor.getOrCreateVariable(resultModel, "expected")
313324
assertEquality(expected, actual)
314325
}
315326
}
316-
.onFailure { exception -> processExecutionFailure(exception) }
327+
.onFailure { exception -> processExecutionFailure(exception, executionResult) }
317328
}
318329
else -> {} // TODO: check this specific case
319330
}
320331
}
321332

322-
private fun processExecutionFailure(exceptionFromAnalysis: Throwable) {
323-
val (methodInvocationBlock, expectedException) = constructExceptionProducingBlock(exceptionFromAnalysis)
333+
private fun processExecutionFailure(exceptionFromAnalysis: Throwable, executionResult: UtExecutionResult) {
334+
val (methodInvocationBlock, expectedException) = constructExceptionProducingBlock(exceptionFromAnalysis, executionResult)
324335

325336
when (methodType) {
326337
SUCCESSFUL -> error("Unexpected successful without exception method type for execution with exception $expectedException")
@@ -354,9 +365,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
354365
}
355366
}
356367

357-
private fun constructExceptionProducingBlock(exceptionFromAnalysis: Throwable): Pair<() -> Unit, Throwable> {
358-
if (exceptionFromAnalysis is UtStreamConsumingException) {
359-
return constructStreamConsumingBlock() to exceptionFromAnalysis.innerExceptionOrAny
368+
private fun constructExceptionProducingBlock(
369+
exceptionFromAnalysis: Throwable,
370+
executionResult: UtExecutionResult
371+
): Pair<() -> Unit, Throwable> {
372+
if (executionResult is UtStreamConsumingFailure) {
373+
return constructStreamConsumingBlock() to executionResult.rootCauseException
360374
}
361375

362376
return {
@@ -460,22 +474,32 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
460474
is ConstructorId -> generateConstructorCall(currentExecutable!!, currentExecution!!)
461475
is MethodId -> {
462476
val method = currentExecutable as MethodId
463-
currentExecution!!.result
464-
.onSuccess { result ->
465-
if (result.isUnit()) {
477+
val executionResult = currentExecution!!.result
478+
479+
executionResult
480+
.onSuccess { resultModel ->
481+
if (resultModel.isUnit()) {
466482
+thisInstance[method](*methodArguments.toTypedArray())
467483
} else {
468484
//"generic" expected variable is represented with a wrapper if
469485
//actual result is primitive to support cases with exceptions.
470-
resultModel = if (result is UtPrimitiveModel) assemble(result) else result
486+
this.resultModel = if (resultModel is UtPrimitiveModel) assemble(resultModel) else resultModel
471487

472488
val expectedVariable = currentMethodParameters[CgParameterKind.ExpectedResult]!!
473489
val expectedExpression = CgNotNullAssertion(expectedVariable)
474490

475491
assertEquality(expectedExpression, actual)
476492
}
477493
}
478-
.onFailure { thisInstance[method](*methodArguments.toTypedArray()).intercepted() }
494+
.onFailure {
495+
workaround(WorkaroundReason.CONSUME_DIRTY_STREAMS) {
496+
if (containsStreamConsumingFailureForParametrizedTests) {
497+
constructStreamConsumingBlock().invoke()
498+
} else {
499+
thisInstance[method](*methodArguments.toTypedArray()).intercepted()
500+
}
501+
}
502+
}
479503
}
480504
else -> {} // TODO: check this specific case
481505
}
@@ -1207,7 +1231,9 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12071231
// we cannot generate any assertions for constructor testing
12081232
// but we need to generate a constructor call
12091233
val constructorCall = currentExecutableId as ConstructorId
1210-
currentExecution.result
1234+
val executionResult = currentExecution.result
1235+
1236+
executionResult
12111237
.onSuccess {
12121238
methodType = SUCCESSFUL
12131239

@@ -1219,7 +1245,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12191245
constructorCall(*methodArguments.toTypedArray())
12201246
}
12211247
}
1222-
.onFailure { exception -> processExecutionFailure(exception) }
1248+
.onFailure { exception -> processExecutionFailure(exception, executionResult) }
12231249
}
12241250

12251251
/**
@@ -1240,7 +1266,15 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12401266
actual.type.isPrimitive -> generateDeepEqualsAssertion(expected, actual)
12411267
else -> ifStatement(
12421268
CgEqualTo(expected, nullLiteral()),
1243-
trueBranch = { +testFrameworkManager.assertions[testFramework.assertNull](actual).toStatement() },
1269+
trueBranch = {
1270+
workaround(WorkaroundReason.CONSUME_DIRTY_STREAMS) {
1271+
if (containsStreamConsumingFailureForParametrizedTests) {
1272+
constructStreamConsumingBlock().invoke()
1273+
} else {
1274+
+testFrameworkManager.assertions[testFramework.assertNull](actual).toStatement()
1275+
}
1276+
}
1277+
},
12441278
falseBranch = {
12451279
+testFrameworkManager.assertions[testFrameworkManager.assertNotNull](actual).toStatement()
12461280
generateDeepEqualsAssertion(expected, actual)
@@ -1269,21 +1303,23 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12691303
}
12701304

12711305
private fun recordActualResult() {
1272-
currentExecution!!.result.onSuccess { result ->
1306+
val executionResult = currentExecution!!.result
1307+
1308+
executionResult.onSuccess { resultModel ->
12731309
when (val executable = currentExecutable) {
12741310
is ConstructorId -> {
12751311
// there is nothing to generate for constructors
12761312
return
12771313
}
12781314
is BuiltinMethodId -> error("Unexpected BuiltinMethodId $currentExecutable while generating actual result")
12791315
is MethodId -> {
1280-
// TODO possible engine bug - void method return type and result not UtVoidModel
1281-
if (result.isUnit() || executable.returnType == voidClassId) return
1316+
// TODO possible engine bug - void method return type and result model not UtVoidModel
1317+
if (resultModel.isUnit() || executable.returnType == voidClassId) return
12821318

12831319
emptyLineIfNeeded()
12841320

12851321
actual = newVar(
1286-
CgClassId(result.classId, isNullable = result is UtNullModel),
1322+
CgClassId(resultModel.classId, isNullable = resultModel is UtNullModel),
12871323
"actual"
12881324
) {
12891325
thisInstance[executable](*methodArguments.toTypedArray())
@@ -1292,13 +1328,13 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12921328
else -> {} // TODO: check this specific case
12931329
}
12941330
}.onFailure {
1295-
processActualInvocationFailure(it)
1331+
processActualInvocationFailure(executionResult)
12961332
}
12971333
}
12981334

1299-
private fun processActualInvocationFailure(e: Throwable) {
1300-
when (e) {
1301-
is UtStreamConsumingException -> processStreamConsumingException(e.innerExceptionOrAny)
1335+
private fun processActualInvocationFailure(executionResult: UtExecutionResult) {
1336+
when (executionResult) {
1337+
is UtStreamConsumingFailure -> processStreamConsumingException(executionResult.rootCauseException)
13021338
else -> {} // Do nothing for now
13031339
}
13041340
}
@@ -1419,6 +1455,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
14191455
//may be a heuristic to select a model with minimal number of internal nulls should be used
14201456
val genericExecution = chooseGenericExecution(testSet.executions)
14211457

1458+
workaround(WorkaroundReason.CONSUME_DIRTY_STREAMS) {
1459+
containsStreamConsumingFailureForParametrizedTests = testSet.executions.any {
1460+
it.result is UtStreamConsumingFailure
1461+
}
1462+
}
1463+
14221464
val statics = genericExecution.stateBefore.statics
14231465

14241466
return withTestMethodScope(genericExecution) {

utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/CodeGenerationIntegrationTest.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ abstract class CodeGenerationIntegrationTest(
3333
private val languagesLastStages: List<TestLastStage> = listOf(
3434
TestLastStage(CodegenLanguage.JAVA),
3535
TestLastStage(CodegenLanguage.KOTLIN)
36-
),
37-
private val codeGenerationModes: List<ParametrizedTestSource> = parameterizationModes
36+
)
3837
) {
3938
private val testSets: MutableList<UtMethodTestSet> = arrayListOf()
4039

@@ -100,7 +99,7 @@ abstract class CodeGenerationIntegrationTest(
10099

101100
// TODO: leave kotlin & parameterized mode configuration alone for now
102101
val pipelineConfigurations = languages
103-
.flatMap { language -> codeGenerationModes.map { mode -> language to mode } }
102+
.flatMap { language -> parameterizationModes.map { mode -> language to mode } }
104103
.filterNot { it == CodegenLanguage.KOTLIN to ParametrizedTestSource.PARAMETRIZE }
105104

106105
for ((language, parameterizationMode) in pipelineConfigurations) {
@@ -163,7 +162,7 @@ abstract class CodeGenerationIntegrationTest(
163162

164163
private val languages = listOf(CodegenLanguage.JAVA, CodegenLanguage.KOTLIN)
165164

166-
internal val parameterizationModes = listOf(ParametrizedTestSource.DO_NOT_PARAMETRIZE, ParametrizedTestSource.PARAMETRIZE)
165+
private val parameterizationModes = listOf(ParametrizedTestSource.DO_NOT_PARAMETRIZE, ParametrizedTestSource.PARAMETRIZE)
167166

168167
data class CodeGenerationTestCases(
169168
val testClass: KClass<*>,

utbot-framework/src/main/kotlin/org/utbot/tests/infrastructure/UtValueTestCaseChecker.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import org.utbot.framework.UtSettings.daysLimitForTempFiles
1414
import org.utbot.framework.UtSettings.testDisplayName
1515
import org.utbot.framework.UtSettings.testName
1616
import org.utbot.framework.UtSettings.testSummary
17-
import org.utbot.framework.codegen.ParametrizedTestSource
1817
import org.utbot.framework.coverage.Coverage
1918
import org.utbot.framework.coverage.counters
2019
import org.utbot.framework.coverage.methodCoverage
@@ -73,9 +72,8 @@ abstract class UtValueTestCaseChecker(
7372
pipelines: List<TestLastStage> = listOf(
7473
TestLastStage(CodegenLanguage.JAVA),
7574
TestLastStage(CodegenLanguage.KOTLIN)
76-
),
77-
codeGenerationModes: List<ParametrizedTestSource> = parameterizationModes
78-
) : CodeGenerationIntegrationTest(testClass, testCodeGeneration, pipelines, codeGenerationModes) {
75+
)
76+
) : CodeGenerationIntegrationTest(testClass, testCodeGeneration, pipelines) {
7977
// contains already analyzed by the engine methods
8078
private val analyzedMethods: MutableMap<MethodWithMockStrategy, MethodResult> = mutableMapOf()
8179

0 commit comments

Comments
 (0)