Skip to content
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

Supported assemble models for streams in concrete execution #1229

Merged
merged 7 commits into from
Oct 28, 2022
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
9 changes: 9 additions & 0 deletions utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,13 @@ enum class WorkaroundReason {
* requires thorough [investigation](https://github.com/UnitTestBot/UTBotJava/issues/716).
*/
IGNORE_STATICS_FROM_TRUSTED_LIBRARIES,
/**
* Methods that return [java.util.stream.BaseStream] as a result, can return them ”dirty” - consuming of them lead to the exception.
* The symbolic engine and concrete execution create UtStreamConsumingFailure executions in such cases. To warn a
* user about unsafety of using such “dirty” streams, code generation consumes them (mostly with `toArray` methods)
* and asserts exception. Unfortunately, it doesn't work well for parametrized tests - they create assertions relying on
* such-called “generic execution”, so resulted tests always contain `deepEquals` for streams, and we cannot easily
* construct `toArray` invocation (because streams cannot be consumed twice).
*/
CONSUME_DIRTY_STREAMS,
}
8 changes: 7 additions & 1 deletion utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import kotlin.reflect.KClass

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

/**
* Invokes [this] method of passed [obj] instance (null for static methods) with the passed [args] arguments.
* NOTE: vararg parameters must be passed as an array of the corresponding type.
*/
fun Method.invokeCatching(obj: Any?, args: List<Any?>) = try {
Result.success(invoke(obj, *args.toTypedArray()))
val invocation = invoke(obj, *args.toTypedArray())

Result.success(invocation)
} catch (e: InvocationTargetException) {
Result.failure<Nothing>(e.targetException)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1271,7 +1271,8 @@ enum class CodegenLanguage(
"-d", buildDirectory,
"-cp", classPath,
"-XDignore.symbol.file", // to let javac use classes from rt.jar
"--add-exports", "java.base/sun.reflect.generics.repository=ALL-UNNAMED"
"--add-exports", "java.base/sun.reflect.generics.repository=ALL-UNNAMED",
"--add-exports", "java.base/sun.text=ALL-UNNAMED",
).plus(sourcesFiles)

KOTLIN -> listOf("-d", buildDirectory, "-jvm-target", jvmTarget, "-cp", classPath).plus(sourcesFiles)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.framework.plugin.api

import org.utbot.framework.plugin.api.visible.UtStreamConsumingException
import java.io.File
import java.util.LinkedList

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

sealed class UtExecutionFailure : UtExecutionResult() {
abstract val exception: Throwable

/**
* Represents the most inner exception in the failure.
* Often equals to [exception], but is wrapped exception in [UtStreamConsumingException].
*/
open val rootCauseException: Throwable
get() = exception
}

data class UtOverflowFailure(
Expand All @@ -21,6 +29,13 @@ data class UtSandboxFailure(
override val exception: Throwable
) : UtExecutionFailure()

data class UtStreamConsumingFailure(
override val exception: UtStreamConsumingException,
) : UtExecutionFailure() {
override val rootCauseException: Throwable
get() = exception.innerExceptionOrAny
}

/**
* unexpectedFail (when exceptions such as NPE, IOBE, etc. appear, but not thrown by a user, applies both for function under test and nested calls )
* expectedCheckedThrow (when function under test or nested call explicitly says that checked exception could be thrown and throws it)
Expand Down Expand Up @@ -83,7 +98,7 @@ inline fun UtExecutionResult.onSuccess(action: (model: UtModel) -> Unit): UtExec
}

inline fun UtExecutionResult.onFailure(action: (exception: Throwable) -> Unit): UtExecutionResult {
if (this is UtExecutionFailure) action(exception)
if (this is UtExecutionFailure) action(rootCauseException)
return this
}

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

fun UtExecutionResult.exceptionOrNull(): Throwable? = when (this) {
is UtExecutionFailure -> exception
is UtExecutionFailure -> rootCauseException
is UtExecutionSuccess -> null
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.framework.plugin.api.util

import org.utbot.common.withAccessibility
import org.utbot.framework.plugin.api.BuiltinClassId
import org.utbot.framework.plugin.api.BuiltinConstructorId
import org.utbot.framework.plugin.api.BuiltinMethodId
Expand Down Expand Up @@ -114,22 +115,31 @@ infix fun ClassId.isSubtypeOf(type: ClassId): Boolean {
// unwrap primitive wrappers
val left = primitiveByWrapper[this] ?: this
val right = primitiveByWrapper[type] ?: type

if (left == right) {
return true
}

val leftClass = this
val superTypes = leftClass.allSuperTypes()

return right in superTypes
}

fun ClassId.allSuperTypes(): Sequence<ClassId> {
val interfaces = sequence {
var types = listOf(leftClass)
var types = listOf(this@allSuperTypes)
while (types.isNotEmpty()) {
yieldAll(types)
types = types
.flatMap { it.interfaces.toList() }
.map { it.id }
}
}
val superclasses = generateSequence(leftClass) { it.superclass?.id }
val superTypes = interfaces + superclasses
return right in superTypes

val superclasses = generateSequence(this) { it.superclass?.id }

return interfaces + superclasses
}

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

val baseStreamClassId = java.util.stream.BaseStream::class.id
val streamClassId = java.util.stream.Stream::class.id
val intStreamClassId = java.util.stream.IntStream::class.id
val longStreamClassId = java.util.stream.LongStream::class.id
val doubleStreamClassId = java.util.stream.DoubleStream::class.id

val intStreamToArrayMethodId = methodId(intStreamClassId, "toArray", intArrayClassId)
val longStreamToArrayMethodId = methodId(longStreamClassId, "toArray", longArrayClassId)
val doubleStreamToArrayMethodId = methodId(doubleStreamClassId, "toArray", doubleArrayClassId)
val streamToArrayMethodId = methodId(streamClassId, "toArray", objectArrayClassId)

val dateClassId = java.util.Date::class.id

@Suppress("RemoveRedundantQualifierName")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.utbot.framework.plugin.api.visible

/**
* An artificial exception that stores an exception that would be thrown in case of consuming stream by invoking terminal operations.
* [innerException] stores this possible exception (null if [UtStreamConsumingException] was constructed by the engine).
*/
data class UtStreamConsumingException(private val innerException: Exception?) : RuntimeException() {
/**
* Returns the original exception [innerException] if possible, and any [RuntimeException] otherwise.
*/
val innerExceptionOrAny: Throwable
get() = innerException ?: RuntimeException("Unknown runtime exception during consuming stream")

override fun toString(): String = innerExceptionOrAny.toString()
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ internal class CastExampleTest : UtValueTestCaseChecker(

@Test
fun testComplicatedCast() {
check(
CastExample::complicatedCast,
eq(2),
{ i, a, _ -> i == 0 && a != null && a[i] != null && a[i] !is CastClassFirstSucc },
{ i, a, r -> i == 0 && a != null && a[i] != null && a[i] is CastClassFirstSucc && r is CastClassFirstSucc },
coverage = DoNotCalculate
)
withEnabledTestingCodeGeneration(testCodeGeneration = false) { // error: package sun.text is not visible
check(
CastExample::complicatedCast,
eq(2),
{ i, a, _ -> i == 0 && a != null && a[i] != null && a[i] !is CastClassFirstSucc },
{ i, a, r -> i == 0 && a != null && a[i] != null && a[i] is CastClassFirstSucc && r is CastClassFirstSucc },
coverage = DoNotCalculate
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,6 @@ class BaseStreamExampleTest : UtValueTestCaseChecker(
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
)
) {
@Test
fun testReturningStreamExample() {
withoutConcrete {
check(
BaseStreamExample::returningStreamExample,
eq(2),
// NOTE: the order of the matchers is important because Stream could be used only once
{ c, r -> c.isNotEmpty() && c == r!!.toList() },
{ c, r -> c.isEmpty() && c == r!!.toList() },
coverage = FullWithAssumptions(assumeCallsNumber = 1)
)
}
}

@Test
fun testReturningStreamAsParameterExample() {
withoutConcrete {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,6 @@ class DoubleStreamExampleTest : UtValueTestCaseChecker(
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
)
) {
@Test
fun testReturningStreamExample() {
check(
DoubleStreamExample::returningStreamExample,
ignoreExecutionsNumber,
// NOTE: the order of the matchers is important because Stream could be used only once
{ c, r -> c.isNotEmpty() && c.doubles().contentEquals(r!!.toArray()) },
{ c, r -> c.isEmpty() && r!!.count() == 0L },
coverage = FullWithAssumptions(assumeCallsNumber = 1)
)
}

@Test
fun testReturningStreamAsParameterExample() {
withoutConcrete {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,6 @@ class IntStreamExampleTest : UtValueTestCaseChecker(
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
)
) {
@Test
fun testReturningStreamExample() {
check(
IntStreamExample::returningStreamExample,
ignoreExecutionsNumber,
// NOTE: the order of the matchers is important because Stream could be used only once
{ c, r -> c.isNotEmpty() && c.ints().contentEquals(r!!.toArray()) },
{ c, r -> c.isEmpty() && r!!.count() == 0L },
coverage = FullWithAssumptions(assumeCallsNumber = 1)
)
}

@Test
fun testReturningStreamAsParameterExample() {
withoutConcrete {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,6 @@ class LongStreamExampleTest : UtValueTestCaseChecker(
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
)
) {
@Test
fun testReturningStreamExample() {
check(
LongStreamExample::returningStreamExample,
ignoreExecutionsNumber,
// NOTE: the order of the matchers is important because Stream could be used only once
{ c, r -> c.isNotEmpty() && c.longs().contentEquals(r!!.toArray()) },
{ c, r -> c.isEmpty() && r!!.count() == 0L },
coverage = FullWithAssumptions(assumeCallsNumber = 1)
)
}

@Test
fun testReturningStreamAsParameterExample() {
withoutConcrete {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.utbot.examples.stream

import org.junit.jupiter.api.Test
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.visible.UtStreamConsumingException
import org.utbot.testcheckers.eq
import org.utbot.tests.infrastructure.CodeGeneration
import org.utbot.tests.infrastructure.FullWithAssumptions
import org.utbot.tests.infrastructure.UtValueTestCaseChecker
import org.utbot.tests.infrastructure.isException
import kotlin.streams.toList

// TODO 1 instruction is always uncovered https://github.com/UnitTestBot/UTBotJava/issues/193
// TODO failed Kotlin compilation (generics) JIRA:1332
class StreamsAsMethodResultExampleTest : UtValueTestCaseChecker(
testClass = StreamsAsMethodResultExample::class,
testCodeGeneration = true,
pipelines = listOf(
TestLastStage(CodegenLanguage.JAVA),
TestLastStage(CodegenLanguage.KOTLIN, CodeGeneration)
),
) {
@Test
fun testReturningStreamExample() {
check(
StreamsAsMethodResultExample::returningStreamExample,
eq(2),
{ c, r -> c.isEmpty() && c == r!!.toList() },
{ c, r -> c.isNotEmpty() && c == r!!.toList() },
coverage = FullWithAssumptions(assumeCallsNumber = 1)
)
}

@Test
fun testReturningIntStreamExample() {
checkWithException(
StreamsAsMethodResultExample::returningIntStreamExample,
eq(3),
{ c, r -> c.isEmpty() && c == r.getOrThrow().toList() },
{ c, r -> c.isNotEmpty() && c.none { it == null } && c.toIntArray().contentEquals(r.getOrThrow().toArray()) },
{ c, r -> c.isNotEmpty() && c.any { it == null } && r.isException<UtStreamConsumingException>() },
coverage = FullWithAssumptions(assumeCallsNumber = 2)
)
}

@Test
fun testReturningLongStreamExample() {
checkWithException(
StreamsAsMethodResultExample::returningLongStreamExample,
eq(3),
{ c, r -> c.isEmpty() && c == r.getOrThrow().toList() },
{ c, r -> c.isNotEmpty() && c.none { it == null } && c.map { it.toLong() }.toLongArray().contentEquals(r.getOrThrow().toArray()) },
{ c, r -> c.isNotEmpty() && c.any { it == null } && r.isException<UtStreamConsumingException>() },
coverage = FullWithAssumptions(assumeCallsNumber = 2)
)
}

@Test
fun testReturningDoubleStreamExample() {
checkWithException(
StreamsAsMethodResultExample::returningDoubleStreamExample,
eq(3),
{ c, r -> c.isEmpty() && c == r.getOrThrow().toList() },
{ c, r -> c.isNotEmpty() && c.none { it == null } && c.map { it.toDouble() }.toDoubleArray().contentEquals(r.getOrThrow().toArray()) },
{ c, r -> c.isNotEmpty() && c.any { it == null } && r.isException<UtStreamConsumingException>() },
coverage = FullWithAssumptions(assumeCallsNumber = 2)
)
}
}
Loading