Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

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 @@ -173,6 +173,12 @@ object SpringModelUtils {
private val objectMapperClassId = ClassId("com.fasterxml.jackson.databind.ObjectMapper")
private val cookieClassId = ClassId("javax.servlet.http.Cookie")

// as of Spring 6.0 `NestedServletException` is deprecated in favor of standard `ServletException` nesting
val nestedServletExceptionClassIds = listOf(
ClassId("org.springframework.web.util.NestedServletException"),
ClassId("jakarta.servlet.ServletException")
)

private val requestAttributesMethodId = MethodId(
classId = mockHttpServletRequestBuilderClassId,
name = "requestAttr",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,26 +296,8 @@ interface CgContextOwner {
return block()
}

fun addExceptionIfNeeded(exception: ClassId) {
if (exception !is BuiltinClassId) {
require(exception isSubtypeOf Throwable::class.id) {
"Class $exception which is not a Throwable was passed"
}

val isUnchecked = !exception.jClass.isCheckedException
val alreadyAdded =
collectedExceptions.any { existingException -> exception isSubtypeOf existingException }

if (isUnchecked || alreadyAdded) return

collectedExceptions
.removeIf { existingException -> existingException isSubtypeOf exception }
}

if (collectedExceptions.add(exception)) {
importIfNeeded(exception)
}
}
fun addExceptionIfNeeded(exception: ClassId)
fun <T> runWithoutCollectingExceptions(block: () -> T): T

fun createGetClassExpression(id: ClassId, codegenLanguage: CodegenLanguage): CgGetClass =
when (codegenLanguage) {
Expand Down Expand Up @@ -624,6 +606,40 @@ class CgContext(
mockFrameworkUsed = false
}

// number of times collection of exceptions was suspended
private var exceptionCollectionSuspensionDepth = 0

override fun <T> runWithoutCollectingExceptions(block: () -> T): T {
exceptionCollectionSuspensionDepth++
return try {
block()
} finally {
exceptionCollectionSuspensionDepth--
}
}

override fun addExceptionIfNeeded(exception: ClassId) {
if (exceptionCollectionSuspensionDepth > 0) return
if (exception !is BuiltinClassId) {
require(exception isSubtypeOf Throwable::class.id) {
"Class $exception which is not a Throwable was passed"
}

val isUnchecked = !exception.jClass.isCheckedException
val alreadyAdded =
collectedExceptions.any { existingException -> exception isSubtypeOf existingException }

if (isUnchecked || alreadyAdded) return

collectedExceptions
.removeIf { existingException -> existingException isSubtypeOf exception }
}

if (collectedExceptions.add(exception)) {
importIfNeeded(exception)
}
}

override var currentTestSetId: Int = -1

override var currentExecutionId: Int = -1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.utbot.framework.codegen.services.access

import kotlinx.collections.immutable.PersistentList
import org.utbot.framework.codegen.domain.Junit5
import org.utbot.framework.codegen.domain.TestNg
import org.utbot.framework.codegen.domain.builtin.any
import org.utbot.framework.codegen.domain.builtin.anyOfClass
import org.utbot.framework.codegen.domain.builtin.getMethodId
Expand Down Expand Up @@ -47,7 +45,6 @@ import org.utbot.framework.plugin.api.ConstructorId
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.UtExplicitlyThrownException
import org.utbot.framework.plugin.api.util.exceptions
import org.utbot.framework.plugin.api.util.extensionReceiverParameterIndex
import org.utbot.framework.plugin.api.util.humanReadableName
Expand Down Expand Up @@ -160,16 +157,6 @@ class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableAccessMana
addExceptionIfNeeded(Throwable::class.id)
}

val methodIsToCallAndThrowsExplicitly = methodId == currentExecutableToCall
&& currentExecution?.result is UtExplicitlyThrownException
val frameworkSupportsAssertThrows = testFramework == Junit5 || testFramework == TestNg

//If explicit exception is wrapped with assertThrows,
// no "throws" in test method signature is required.
if (methodIsToCallAndThrowsExplicitly && frameworkSupportsAssertThrows) {
return
}

methodId.method.exceptionTypes.forEach { addExceptionIfNeeded(it.id) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,9 @@ internal class TestNgManager(context: CgContext) : TestFrameworkManager(context)

override fun expectException(exception: ClassId, block: () -> Unit) {
require(testFramework is TestNg) { "According to settings, TestNg was expected, but got: $testFramework" }
val lambda = statementConstructor.lambda(testFramework.throwingRunnableClassId) { block() }
val lambda = statementConstructor.lambda(testFramework.throwingRunnableClassId) {
runWithoutCollectingExceptions(block)
}
+assertions[assertThrows](exception.toExceptionClass(), lambda)
}

Expand Down Expand Up @@ -474,7 +476,9 @@ internal class Junit5Manager(context: CgContext) : TestFrameworkManager(context)

override fun expectException(exception: ClassId, block: () -> Unit) {
require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" }
val lambda = statementConstructor.lambda(testFramework.executableClassId) { block() }
val lambda = statementConstructor.lambda(testFramework.executableClassId) {
runWithoutCollectingExceptions(block)
}
+assertions[assertThrows](exception.toExceptionClass(), lambda)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,18 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte
.map { it.escapeControlChars() }
.toMutableList()

+CgMultilineComment(warningLine + collectNeededStackTraceLines(
exception,
executableToStartCollectingFrom = currentExecutableToCall!!
))
}

protected open fun collectNeededStackTraceLines(
exception: Throwable,
executableToStartCollectingFrom: ExecutableId
): List<String> {
val executableName = "${executableToStartCollectingFrom.classId.name}.${executableToStartCollectingFrom.name}"

val neededStackTraceLines = mutableListOf<String>()
var executableCallFound = false
exception.stackTrace.reversed().forEach { stackTraceElement ->
Expand All @@ -485,7 +497,7 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte
if (!executableCallFound)
logger.warn(exception) { "Failed to find executable call in stack trace" }

+CgMultilineComment(warningLine + neededStackTraceLines.reversed())
return neededStackTraceLines.reversed()
}

protected fun writeWarningAboutCrash() {
Expand Down Expand Up @@ -1819,6 +1831,9 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte
currentExecution = execution
determineExecutionType()
statesCache = EnvironmentFieldStateCache.emptyCacheFor(execution)
// modelToUsageCountInMethod = countUsages(ignoreAssembleOrigin = true) { counter ->
// execution.mapAllModels(counter)
// }
return try {
block()
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import org.utbot.framework.codegen.domain.models.CgMethodTestSet
import org.utbot.framework.codegen.domain.models.builders.SimpleTestClassModelBuilder
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
import org.utbot.framework.codegen.tree.CgCustomAssertConstructor
import org.utbot.framework.codegen.tree.CgMethodConstructor
import org.utbot.framework.codegen.tree.CgSpringIntegrationTestClassConstructor
import org.utbot.framework.codegen.tree.CgSpringMethodConstructor
import org.utbot.framework.codegen.tree.CgSpringUnitTestClassConstructor
import org.utbot.framework.codegen.tree.CgSpringVariableConstructor
import org.utbot.framework.codegen.tree.CgVariableConstructor
Expand All @@ -30,6 +32,9 @@ class SpringCodeGenerator(
// TODO decorate original `params.cgLanguageAssistant.getVariableConstructorBy(context)`
CgSpringVariableConstructor(context)

override fun getMethodConstructorBy(context: CgContext): CgMethodConstructor =
CgSpringMethodConstructor(context)

override fun getCustomAssertConstructorBy(context: CgContext): CgCustomAssertConstructor =
params.cgLanguageAssistant.getCustomAssertConstructorBy(context)
.withCustomAssertForMockMvcResultActions()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.utbot.framework.codegen.tree

import org.utbot.framework.codegen.domain.context.CgContext
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.UtExecution
import org.utbot.framework.plugin.api.util.SpringModelUtils.mockMvcPerformMethodId
import org.utbot.framework.plugin.api.util.SpringModelUtils.nestedServletExceptionClassIds
import org.utbot.framework.plugin.api.util.id

class CgSpringMethodConstructor(context: CgContext) : CgMethodConstructor(context) {
override fun shouldTestPassWithException(execution: UtExecution, exception: Throwable): Boolean =
!isNestedServletException(exception) && super.shouldTestPassWithException(execution, exception)

override fun collectNeededStackTraceLines(
exception: Throwable,
executableToStartCollectingFrom: ExecutableId
): List<String> =
// `mockMvc.perform` wraps exceptions from user code into NestedServletException, so we unwrap them back
exception.takeIf {
executableToStartCollectingFrom == mockMvcPerformMethodId && isNestedServletException(it)
}?.cause?.let { cause ->
super.collectNeededStackTraceLines(cause, currentExecutableUnderTest!!)
} ?: super.collectNeededStackTraceLines(exception, executableToStartCollectingFrom)

private fun isNestedServletException(exception: Throwable): Boolean =
exception::class.java.id in nestedServletExceptionClassIds
}