From b73fb01ebbe6de9dca32ee6d7b2e3350c94ca750 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 8 Sep 2023 19:33:04 +0300 Subject: [PATCH 1/2] Make tests throwing `NestedServletException` fail, print cause stacktrace --- .../plugin/api/util/SpringModelUtils.kt | 6 +++++ .../codegen/tree/CgMethodConstructor.kt | 17 +++++++++++- .../codegen/generator/SpringCodeGenerator.kt | 5 ++++ .../codegen/tree/CgSpringMethodConstructor.kt | 27 +++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringMethodConstructor.kt diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt index a328ee98de..b24827119c 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt @@ -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", diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt index 8839623ff3..775b3986b4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt @@ -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 { + val executableName = "${executableToStartCollectingFrom.classId.name}.${executableToStartCollectingFrom.name}" + val neededStackTraceLines = mutableListOf() var executableCallFound = false exception.stackTrace.reversed().forEach { stackTraceElement -> @@ -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() { @@ -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 { diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt index f33d63ec72..7aba0c939a 100644 --- a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt @@ -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 @@ -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() diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringMethodConstructor.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringMethodConstructor.kt new file mode 100644 index 0000000000..60aa678b41 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringMethodConstructor.kt @@ -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 = + // `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 +} From 2d2d41810f0b0c776e0981715bd83d170c8c9aa5 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 8 Sep 2023 19:41:09 +0300 Subject: [PATCH 2/2] Make exception collection only skip exceptions inside `assertThrows` --- .../codegen/domain/context/CgContext.kt | 56 ++++++++++++------- .../access/CgCallableAccessManager.kt | 13 ----- .../framework/TestFrameworkManager.kt | 8 ++- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/CgContext.kt index 219f846907..ada8bf2113 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/CgContext.kt @@ -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 runWithoutCollectingExceptions(block: () -> T): T fun createGetClassExpression(id: ClassId, codegenLanguage: CodegenLanguage): CgGetClass = when (codegenLanguage) { @@ -624,6 +606,40 @@ class CgContext( mockFrameworkUsed = false } + // number of times collection of exceptions was suspended + private var exceptionCollectionSuspensionDepth = 0 + + override fun 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 diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgCallableAccessManager.kt index 1c2b015c06..ef4d9c27cc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgCallableAccessManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgCallableAccessManager.kt @@ -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 @@ -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 @@ -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) } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/TestFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/TestFrameworkManager.kt index 871518e71e..18c1068bdb 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/TestFrameworkManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/TestFrameworkManager.kt @@ -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) } @@ -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) }