Skip to content

Commit 651f7d4

Browse files
authored
Supported assemble models for streams in concrete execution (#1229)
1 parent 83750d3 commit 651f7d4

File tree

36 files changed

+781
-209
lines changed

36 files changed

+781
-209
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-core/src/main/kotlin/org/utbot/common/KClassUtil.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ import kotlin.reflect.KClass
66

77
val Class<*>.nameOfPackage: String get() = `package`?.name?:""
88

9+
/**
10+
* Invokes [this] method of passed [obj] instance (null for static methods) with the passed [args] arguments.
11+
* NOTE: vararg parameters must be passed as an array of the corresponding type.
12+
*/
913
fun Method.invokeCatching(obj: Any?, args: List<Any?>) = try {
10-
Result.success(invoke(obj, *args.toTypedArray()))
14+
val invocation = invoke(obj, *args.toTypedArray())
15+
16+
Result.success(invocation)
1117
} catch (e: InvocationTargetException) {
1218
Result.failure<Nothing>(e.targetException)
1319
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1271,7 +1271,8 @@ enum class CodegenLanguage(
12711271
"-d", buildDirectory,
12721272
"-cp", classPath,
12731273
"-XDignore.symbol.file", // to let javac use classes from rt.jar
1274-
"--add-exports", "java.base/sun.reflect.generics.repository=ALL-UNNAMED"
1274+
"--add-exports", "java.base/sun.reflect.generics.repository=ALL-UNNAMED",
1275+
"--add-exports", "java.base/sun.text=ALL-UNNAMED",
12751276
).plus(sourcesFiles)
12761277

12771278
KOTLIN -> listOf("-d", buildDirectory, "-jvm-target", jvmTarget, "-cp", classPath).plus(sourcesFiles)

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.utbot.framework.plugin.api
22

3+
import org.utbot.framework.plugin.api.visible.UtStreamConsumingException
34
import java.io.File
45
import java.util.LinkedList
56

@@ -11,6 +12,13 @@ data class UtExecutionSuccess(val model: UtModel) : UtExecutionResult() {
1112

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

1624
data class UtOverflowFailure(
@@ -21,6 +29,13 @@ data class UtSandboxFailure(
2129
override val exception: Throwable
2230
) : UtExecutionFailure()
2331

32+
data class UtStreamConsumingFailure(
33+
override val exception: UtStreamConsumingException,
34+
) : UtExecutionFailure() {
35+
override val rootCauseException: Throwable
36+
get() = exception.innerExceptionOrAny
37+
}
38+
2439
/**
2540
* unexpectedFail (when exceptions such as NPE, IOBE, etc. appear, but not thrown by a user, applies both for function under test and nested calls )
2641
* expectedCheckedThrow (when function under test or nested call explicitly says that checked exception could be thrown and throws it)
@@ -83,7 +98,7 @@ inline fun UtExecutionResult.onSuccess(action: (model: UtModel) -> Unit): UtExec
8398
}
8499

85100
inline fun UtExecutionResult.onFailure(action: (exception: Throwable) -> Unit): UtExecutionResult {
86-
if (this is UtExecutionFailure) action(exception)
101+
if (this is UtExecutionFailure) action(rootCauseException)
87102
return this
88103
}
89104

@@ -93,6 +108,6 @@ fun UtExecutionResult.getOrThrow(): UtModel = when (this) {
93108
}
94109

95110
fun UtExecutionResult.exceptionOrNull(): Throwable? = when (this) {
96-
is UtExecutionFailure -> exception
111+
is UtExecutionFailure -> rootCauseException
97112
is UtExecutionSuccess -> null
98113
}

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.utbot.framework.plugin.api.util
22

3+
import org.utbot.common.withAccessibility
34
import org.utbot.framework.plugin.api.BuiltinClassId
45
import org.utbot.framework.plugin.api.BuiltinConstructorId
56
import org.utbot.framework.plugin.api.BuiltinMethodId
@@ -114,22 +115,31 @@ infix fun ClassId.isSubtypeOf(type: ClassId): Boolean {
114115
// unwrap primitive wrappers
115116
val left = primitiveByWrapper[this] ?: this
116117
val right = primitiveByWrapper[type] ?: type
118+
117119
if (left == right) {
118120
return true
119121
}
122+
120123
val leftClass = this
124+
val superTypes = leftClass.allSuperTypes()
125+
126+
return right in superTypes
127+
}
128+
129+
fun ClassId.allSuperTypes(): Sequence<ClassId> {
121130
val interfaces = sequence {
122-
var types = listOf(leftClass)
131+
var types = listOf(this@allSuperTypes)
123132
while (types.isNotEmpty()) {
124133
yieldAll(types)
125134
types = types
126135
.flatMap { it.interfaces.toList() }
127136
.map { it.id }
128137
}
129138
}
130-
val superclasses = generateSequence(leftClass) { it.superclass?.id }
131-
val superTypes = interfaces + superclasses
132-
return right in superTypes
139+
140+
val superclasses = generateSequence(this) { it.superclass?.id }
141+
142+
return interfaces + superclasses
133143
}
134144

135145
infix fun ClassId.isNotSubtypeOf(type: ClassId): Boolean = !(this isSubtypeOf type)
@@ -267,6 +277,17 @@ val atomicIntegerGetAndIncrement = MethodId(atomicIntegerClassId, "getAndIncreme
267277
val iterableClassId = java.lang.Iterable::class.id
268278
val mapClassId = java.util.Map::class.id
269279

280+
val baseStreamClassId = java.util.stream.BaseStream::class.id
281+
val streamClassId = java.util.stream.Stream::class.id
282+
val intStreamClassId = java.util.stream.IntStream::class.id
283+
val longStreamClassId = java.util.stream.LongStream::class.id
284+
val doubleStreamClassId = java.util.stream.DoubleStream::class.id
285+
286+
val intStreamToArrayMethodId = methodId(intStreamClassId, "toArray", intArrayClassId)
287+
val longStreamToArrayMethodId = methodId(longStreamClassId, "toArray", longArrayClassId)
288+
val doubleStreamToArrayMethodId = methodId(doubleStreamClassId, "toArray", doubleArrayClassId)
289+
val streamToArrayMethodId = methodId(streamClassId, "toArray", objectArrayClassId)
290+
270291
val dateClassId = java.util.Date::class.id
271292

272293
@Suppress("RemoveRedundantQualifierName")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.utbot.framework.plugin.api.visible
2+
3+
/**
4+
* An artificial exception that stores an exception that would be thrown in case of consuming stream by invoking terminal operations.
5+
* [innerException] stores this possible exception (null if [UtStreamConsumingException] was constructed by the engine).
6+
*/
7+
data class UtStreamConsumingException(private val innerException: Exception?) : RuntimeException() {
8+
/**
9+
* Returns the original exception [innerException] if possible, and any [RuntimeException] otherwise.
10+
*/
11+
val innerExceptionOrAny: Throwable
12+
get() = innerException ?: RuntimeException("Unknown runtime exception during consuming stream")
13+
14+
override fun toString(): String = innerExceptionOrAny.toString()
15+
}

utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,14 @@ internal class CastExampleTest : UtValueTestCaseChecker(
8080

8181
@Test
8282
fun testComplicatedCast() {
83-
check(
84-
CastExample::complicatedCast,
85-
eq(2),
86-
{ i, a, _ -> i == 0 && a != null && a[i] != null && a[i] !is CastClassFirstSucc },
87-
{ i, a, r -> i == 0 && a != null && a[i] != null && a[i] is CastClassFirstSucc && r is CastClassFirstSucc },
88-
coverage = DoNotCalculate
89-
)
83+
withEnabledTestingCodeGeneration(testCodeGeneration = false) { // error: package sun.text is not visible
84+
check(
85+
CastExample::complicatedCast,
86+
eq(2),
87+
{ i, a, _ -> i == 0 && a != null && a[i] != null && a[i] !is CastClassFirstSucc },
88+
{ i, a, r -> i == 0 && a != null && a[i] != null && a[i] is CastClassFirstSucc && r is CastClassFirstSucc },
89+
coverage = DoNotCalculate
90+
)
91+
}
9092
}
9193
}

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,6 @@ class BaseStreamExampleTest : UtValueTestCaseChecker(
2929
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
3030
)
3131
) {
32-
@Test
33-
fun testReturningStreamExample() {
34-
withoutConcrete {
35-
check(
36-
BaseStreamExample::returningStreamExample,
37-
eq(2),
38-
// NOTE: the order of the matchers is important because Stream could be used only once
39-
{ c, r -> c.isNotEmpty() && c == r!!.toList() },
40-
{ c, r -> c.isEmpty() && c == r!!.toList() },
41-
coverage = FullWithAssumptions(assumeCallsNumber = 1)
42-
)
43-
}
44-
}
45-
4632
@Test
4733
fun testReturningStreamAsParameterExample() {
4834
withoutConcrete {

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,6 @@ class DoubleStreamExampleTest : UtValueTestCaseChecker(
2121
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
2222
)
2323
) {
24-
@Test
25-
fun testReturningStreamExample() {
26-
check(
27-
DoubleStreamExample::returningStreamExample,
28-
ignoreExecutionsNumber,
29-
// NOTE: the order of the matchers is important because Stream could be used only once
30-
{ c, r -> c.isNotEmpty() && c.doubles().contentEquals(r!!.toArray()) },
31-
{ c, r -> c.isEmpty() && r!!.count() == 0L },
32-
coverage = FullWithAssumptions(assumeCallsNumber = 1)
33-
)
34-
}
35-
3624
@Test
3725
fun testReturningStreamAsParameterExample() {
3826
withoutConcrete {

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,6 @@ class IntStreamExampleTest : UtValueTestCaseChecker(
2222
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
2323
)
2424
) {
25-
@Test
26-
fun testReturningStreamExample() {
27-
check(
28-
IntStreamExample::returningStreamExample,
29-
ignoreExecutionsNumber,
30-
// NOTE: the order of the matchers is important because Stream could be used only once
31-
{ c, r -> c.isNotEmpty() && c.ints().contentEquals(r!!.toArray()) },
32-
{ c, r -> c.isEmpty() && r!!.count() == 0L },
33-
coverage = FullWithAssumptions(assumeCallsNumber = 1)
34-
)
35-
}
36-
3725
@Test
3826
fun testReturningStreamAsParameterExample() {
3927
withoutConcrete {

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,6 @@ class LongStreamExampleTest : UtValueTestCaseChecker(
2222
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
2323
)
2424
) {
25-
@Test
26-
fun testReturningStreamExample() {
27-
check(
28-
LongStreamExample::returningStreamExample,
29-
ignoreExecutionsNumber,
30-
// NOTE: the order of the matchers is important because Stream could be used only once
31-
{ c, r -> c.isNotEmpty() && c.longs().contentEquals(r!!.toArray()) },
32-
{ c, r -> c.isEmpty() && r!!.count() == 0L },
33-
coverage = FullWithAssumptions(assumeCallsNumber = 1)
34-
)
35-
}
36-
3725
@Test
3826
fun testReturningStreamAsParameterExample() {
3927
withoutConcrete {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.utbot.examples.stream
2+
3+
import org.junit.jupiter.api.Test
4+
import org.utbot.framework.plugin.api.CodegenLanguage
5+
import org.utbot.framework.plugin.api.visible.UtStreamConsumingException
6+
import org.utbot.testcheckers.eq
7+
import org.utbot.tests.infrastructure.CodeGeneration
8+
import org.utbot.tests.infrastructure.FullWithAssumptions
9+
import org.utbot.tests.infrastructure.UtValueTestCaseChecker
10+
import org.utbot.tests.infrastructure.isException
11+
import kotlin.streams.toList
12+
13+
// TODO 1 instruction is always uncovered https://github.com/UnitTestBot/UTBotJava/issues/193
14+
// TODO failed Kotlin compilation (generics) JIRA:1332
15+
class StreamsAsMethodResultExampleTest : UtValueTestCaseChecker(
16+
testClass = StreamsAsMethodResultExample::class,
17+
testCodeGeneration = true,
18+
pipelines = listOf(
19+
TestLastStage(CodegenLanguage.JAVA),
20+
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
21+
),
22+
) {
23+
@Test
24+
fun testReturningStreamExample() {
25+
check(
26+
StreamsAsMethodResultExample::returningStreamExample,
27+
eq(2),
28+
{ c, r -> c.isEmpty() && c == r!!.toList() },
29+
{ c, r -> c.isNotEmpty() && c == r!!.toList() },
30+
coverage = FullWithAssumptions(assumeCallsNumber = 1)
31+
)
32+
}
33+
34+
@Test
35+
fun testReturningIntStreamExample() {
36+
checkWithException(
37+
StreamsAsMethodResultExample::returningIntStreamExample,
38+
eq(3),
39+
{ c, r -> c.isEmpty() && c == r.getOrThrow().toList() },
40+
{ c, r -> c.isNotEmpty() && c.none { it == null } && c.toIntArray().contentEquals(r.getOrThrow().toArray()) },
41+
{ c, r -> c.isNotEmpty() && c.any { it == null } && r.isException<UtStreamConsumingException>() },
42+
coverage = FullWithAssumptions(assumeCallsNumber = 2)
43+
)
44+
}
45+
46+
@Test
47+
fun testReturningLongStreamExample() {
48+
checkWithException(
49+
StreamsAsMethodResultExample::returningLongStreamExample,
50+
eq(3),
51+
{ c, r -> c.isEmpty() && c == r.getOrThrow().toList() },
52+
{ c, r -> c.isNotEmpty() && c.none { it == null } && c.map { it.toLong() }.toLongArray().contentEquals(r.getOrThrow().toArray()) },
53+
{ c, r -> c.isNotEmpty() && c.any { it == null } && r.isException<UtStreamConsumingException>() },
54+
coverage = FullWithAssumptions(assumeCallsNumber = 2)
55+
)
56+
}
57+
58+
@Test
59+
fun testReturningDoubleStreamExample() {
60+
checkWithException(
61+
StreamsAsMethodResultExample::returningDoubleStreamExample,
62+
eq(3),
63+
{ c, r -> c.isEmpty() && c == r.getOrThrow().toList() },
64+
{ c, r -> c.isNotEmpty() && c.none { it == null } && c.map { it.toDouble() }.toDoubleArray().contentEquals(r.getOrThrow().toArray()) },
65+
{ c, r -> c.isNotEmpty() && c.any { it == null } && r.isException<UtStreamConsumingException>() },
66+
coverage = FullWithAssumptions(assumeCallsNumber = 2)
67+
)
68+
}
69+
}

0 commit comments

Comments
 (0)