Skip to content

Generated test output size should be customizable #1235 #1285

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

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
2 changes: 1 addition & 1 deletion utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ object FileUtil {
bytesInDouble >= 1 shl 30 -> "%.1f GB".format(bytesInDouble / (1 shl 30))
bytesInDouble >= 1 shl 20 -> "%.1f MB".format(bytesInDouble / (1 shl 20))
bytesInDouble >= 1 shl 10 -> "%.0f kB".format(bytesInDouble / (1 shl 10))
else -> "$bytesInDouble bytes"
else -> "$bytes bytes"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,35 @@ object UtSettings : AbstractSettings(
*/
var useSandbox by getBooleanProperty(true)

/**
* Limit for number of generated tests per method
*/
var maxTestsPerMethod by getIntProperty(50)

/**
* Max file length for generated test file
*/
const val DEFAULT_MAX_FILE_SIZE = 1000000
var maxTestFileSize by getProperty(DEFAULT_MAX_FILE_SIZE, ::parseFileSize)


fun parseFileSize(s: String): Int {
val suffix = StringBuilder()
var value = 0
for (ch in s) {
(ch - '0').let {
if (it in 0..9) {
value = value * 10 + it
} else suffix.append(ch)
}
}
when (suffix.toString().trim().lowercase()) {
"k", "kb" -> value *= 1000
"m", "mb" -> value *= 1000000
}
return if (value > 0) value else DEFAULT_MAX_FILE_SIZE // fallback for incorrect value
}

/**
* If this options set in true, all soot classes will be removed from a Soot Scene,
* therefore, you will be unable to test soot classes.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.utbot.framework

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertEquals

internal class UtSettingsTest {
@Test
fun testMaxTestFileSize() {
assertEquals(1, UtSettings.parseFileSize("1"))
assertEquals(12, UtSettings.parseFileSize("12"))
assertEquals(123, UtSettings.parseFileSize("123"))
assertEquals(30000, UtSettings.parseFileSize("30K"))
assertEquals(30000, UtSettings.parseFileSize("30Kb"))
assertEquals(30000, UtSettings.parseFileSize("30kB"))
assertEquals(30000, UtSettings.parseFileSize("30kb"))
assertEquals(7000000, UtSettings.parseFileSize("7MB"))
assertEquals(7000000, UtSettings.parseFileSize("7Mb"))
assertEquals(7000000, UtSettings.parseFileSize("7M"))
assertEquals(7, UtSettings.parseFileSize("7abc"))
assertEquals(UtSettings.DEFAULT_MAX_FILE_SIZE, UtSettings.parseFileSize("qwerty"))
assertEquals(UtSettings.DEFAULT_MAX_FILE_SIZE, UtSettings.parseFileSize("MB"))
assertEquals(UtSettings.DEFAULT_MAX_FILE_SIZE, UtSettings.parseFileSize("1000000"))
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.framework.codegen.model.constructor.tree

import org.utbot.framework.UtSettings
import org.utbot.framework.codegen.Junit4
import org.utbot.framework.codegen.Junit5
import org.utbot.framework.codegen.ParametrizedTestSource
Expand Down Expand Up @@ -178,13 +179,26 @@ internal class CgTestClassConstructor(val context: CgContext) :
for ((clusterSummary, executionIndices) in clustersInfo) {
val currentTestCaseTestMethods = mutableListOf<CgTestMethod>()
emptyLineIfNeeded()
for (i in executionIndices) {
val (checkedRange, needLimitExceedingComments) = if (executionIndices.last - executionIndices.first > UtSettings.maxTestsPerMethod) {
IntRange(executionIndices.first, executionIndices.first + (UtSettings.maxTestsPerMethod - 1).coerceAtLeast(0)) to true
} else {
executionIndices to false
}

for (i in checkedRange) {
currentTestCaseTestMethods += methodConstructor.createTestMethod(methodUnderTest, testSet.executions[i])
}

val comments = listOf("Actual number of generated tests (${executionIndices.last - executionIndices.first}) exceeds per-method limit (${UtSettings.maxTestsPerMethod})",
"The limit can be configured in '{HOME_DIR}/.utbot/settings.properties' with 'maxTestsPerMethod' property")

val clusterHeader = clusterSummary?.header
val clusterContent = clusterSummary?.content
var clusterContent = clusterSummary?.content
?.split('\n')
?.let { CgTripleSlashMultilineComment(it) }
?.let { CgTripleSlashMultilineComment(if (needLimitExceedingComments) {it.toMutableList() + comments} else {it}) }
if (clusterContent == null && needLimitExceedingComments) {
clusterContent = CgTripleSlashMultilineComment(comments)
}
regions += CgTestMethodCluster(clusterHeader, clusterContent, currentTestCaseTestMethods)

testsGenerationReport.addTestsByType(testSet, currentTestCaseTestMethods)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface CgPrinter {
fun popIndent()

override fun toString(): String
var printedLength: Int
}

class CgPrinterImpl(
Expand All @@ -30,6 +31,8 @@ class CgPrinterImpl(

override fun toString(): String = builder.toString()

override var printedLength: Int = builder.length

override fun print(text: String) {
if (atLineStart) {
printIndent()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.utbot.framework.codegen.model.visitor

import org.apache.commons.text.StringEscapeUtils
import org.utbot.common.FileUtil
import org.utbot.common.WorkaroundReason.LONG_CODE_FRAGMENTS
import org.utbot.common.workaround
import org.utbot.framework.UtSettings
import org.utbot.framework.codegen.Import
import org.utbot.framework.codegen.RegularImport
import org.utbot.framework.codegen.StaticImport
Expand Down Expand Up @@ -109,6 +111,7 @@ internal abstract class CgAbstractRenderer(

protected val regionStart: String = "///region"
protected val regionEnd: String = "///endregion"
protected var isInterrupted = false

protected abstract val language: CodegenLanguage

Expand Down Expand Up @@ -186,22 +189,33 @@ internal abstract class CgAbstractRenderer(
* Renders the region with a specific rendering for [CgTestMethodCluster.description]
*/
private fun CgRegion<*>.render(printLineAfterContentEnd: Boolean = false) {
if (content.isEmpty()) return
if (content.isEmpty() || isInterrupted) return

print(regionStart)
header?.let { print(" $it") }
println()

if (this is CgTestMethodCluster) description?.accept(this@CgAbstractRenderer)

var isLimitExceeded = false
for (method in content) {
if (printer.printedLength > UtSettings.maxTestFileSize) {
isLimitExceeded = true
break
}
println()
method.accept(this@CgAbstractRenderer)
}

if (printLineAfterContentEnd) println()

println(regionEnd)

if (isLimitExceeded && !isInterrupted) {
visit(CgSingleLineComment("Abrupt generation termination: file size exceeds configured limit (${FileUtil.byteCountToDisplaySize(UtSettings.maxTestFileSize.toLong())})"))
visit(CgSingleLineComment("The limit can be configured in '{HOME_DIR}/.utbot/settings.properties' with 'maxTestsFileSize' property"))
isInterrupted = true
}
}

override fun visit(element: CgAuxiliaryClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.intellij.ide.fileTemplates.FileTemplateUtil
import com.intellij.ide.fileTemplates.JavaTemplateUtil
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
Expand All @@ -23,28 +22,47 @@ import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Computable
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.psi.*
import com.intellij.psi.JavaDirectoryService
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassOwner
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiMethod
import com.intellij.psi.SmartPointerManager
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.search.GlobalSearchScopesCore
import com.intellij.testIntegration.TestIntegrationUtils
import com.siyeh.ig.psiutils.ImportUtils
import java.nio.file.Path
import java.util.concurrent.CancellationException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import mu.KotlinLogging
import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.utbot.common.FileUtil
import org.utbot.common.HTML_LINE_SEPARATOR
import org.utbot.common.PathUtil.toHtmlLinkTag
import org.utbot.framework.UtSettings
import org.utbot.framework.codegen.Import
import org.utbot.framework.codegen.ParametrizedTestSource
import org.utbot.framework.codegen.RegularImport
Expand All @@ -59,23 +77,25 @@ import org.utbot.intellij.plugin.models.packageName
import org.utbot.intellij.plugin.process.EngineProcess
import org.utbot.intellij.plugin.process.RdTestGenerationResult
import org.utbot.intellij.plugin.sarif.SarifReportIdea
import org.utbot.intellij.plugin.ui.*
import org.utbot.intellij.plugin.ui.CommonErrorNotifier
import org.utbot.intellij.plugin.ui.DetailsTestsReportNotifier
import org.utbot.intellij.plugin.ui.SarifReportNotifier
import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener
import org.utbot.intellij.plugin.ui.TestsReportNotifier
import org.utbot.intellij.plugin.ui.WarningTestsReportNotifier
import org.utbot.intellij.plugin.ui.utils.TestSourceRoot
import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle
import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.*
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.EDT_LATER
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.THREAD_POOL
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.WRITE_ACTION
import org.utbot.intellij.plugin.util.IntelliJApiHelper.run
import org.utbot.intellij.plugin.util.RunConfigurationHelper
import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested
import org.utbot.sarif.Sarif
import org.utbot.sarif.SarifReport
import java.nio.file.Path
import java.util.concurrent.CancellationException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
import org.utbot.intellij.plugin.ui.utils.TestSourceRoot
import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle

object CodeGenerationController {
private val logger = KotlinLogging.logger {}
Expand Down Expand Up @@ -143,14 +163,14 @@ object CodeGenerationController {
}
}

run(THREAD_POOL, indicator) {
run(THREAD_POOL, indicator, "Waiting for per-class Sarif reports") {
waitForCountDown(latch, indicator = indicator) {
run(EDT_LATER, indicator) {
run(WRITE_ACTION, indicator) {
run(EDT_LATER, indicator,"Go to EDT for utility class creation") {
run(WRITE_ACTION, indicator, "Need write action for utility class creation") {
createUtilityClassIfNeed(utilClassListener, model, baseTestDirectory, indicator)
run(EDT_LATER, indicator) {
run(EDT_LATER, indicator, "Proceed test report") {
proceedTestReport(proc, model)
run(THREAD_POOL, indicator) {
run(THREAD_POOL, indicator, "Generate summary Sarif report") {
val sarifReportsPath =
model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Merge Sarif reports", 0.75)
Expand All @@ -160,9 +180,9 @@ object CodeGenerationController {
RunConfigurationHelper.runTestsWithCoverage(model, testFilesPointers)
}
proc.forceTermination()
UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Start tests with coverage", 1.0)
UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Generation finished", 1.0)

invokeLater {
run(EDT_LATER, null, "Run sarif-based inspections") {
runInspectionsIfNeeded(model, srcClassPathToSarifReport)
}
}
Expand Down Expand Up @@ -342,8 +362,8 @@ object CodeGenerationController {

val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage)

run(EDT_LATER, indicator) {
run(WRITE_ACTION, indicator) {
run(EDT_LATER, indicator, "Overwrite utility class") {
run(WRITE_ACTION, indicator, "Overwrite utility class") {
unblockDocument(model.project, utilsClassDocument)
executeCommand {
utilsClassDocument.setText(utUtilsText.replace("jdk.internal.misc", "sun.misc"))
Expand Down Expand Up @@ -520,7 +540,8 @@ object CodeGenerationController {
private fun waitForCountDown(latch: CountDownLatch, timeout: Long = 5, timeUnit: TimeUnit = TimeUnit.SECONDS, indicator : ProgressIndicator, action: Runnable) {
try {
if (!latch.await(timeout, timeUnit)) {
run(THREAD_POOL, indicator) { waitForCountDown(latch, timeout, timeUnit, indicator, action) }
run(THREAD_POOL, indicator, "Waiting for ${latch.count} sarif report(s) in a loop") {
waitForCountDown(latch, timeout, timeUnit, indicator, action) }
} else {
action.run()
}
Expand Down Expand Up @@ -643,7 +664,7 @@ object CodeGenerationController {
val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, filePointer.containingFile, testClass)
//TODO: Use PsiDocumentManager.getInstance(model.project).getDocument(file)
// if we don't want to open _all_ new files with tests in editor one-by-one
run(THREAD_POOL, indicator) {
run(THREAD_POOL, indicator, "Rendering test code") {
val (generatedTestsCode, utilClassKind) = try {
proc.render(
testSetsId,
Expand All @@ -668,8 +689,8 @@ object CodeGenerationController {
return@run
}
utilClassListener.onTestClassGenerated(utilClassKind)
run(EDT_LATER, indicator) {
run(WRITE_ACTION, indicator) {
run(EDT_LATER, indicator, "Writing generation text to documents") {
run(WRITE_ACTION, indicator, "Writing generation text to documents") {
try {
unblockDocument(testClass.project, editor.document)
// TODO: JIRA:1246 - display warnings if we rewrite the file
Expand All @@ -689,9 +710,9 @@ object CodeGenerationController {

// reformatting before creating reports due to
// SarifReport requires the final version of the generated tests code
run(THREAD_POOL, indicator) {
// run(THREAD_POOL, indicator) {
// IntentionHelper(model.project, editor, filePointer).applyIntentions()
run(EDT_LATER, indicator) {
run(EDT_LATER, indicator, "Tests reformatting") {
try {
runWriteCommandAction(filePointer.project, "UtBot tests reformatting", null, {
reformat(model, filePointer, testClassUpdated)
Expand Down Expand Up @@ -719,7 +740,7 @@ object CodeGenerationController {

unblockDocument(testClassUpdated.project, editor.document)
}
}
// }
}
}
}
Expand All @@ -729,6 +750,16 @@ object CodeGenerationController {
val project = model.project
val codeStyleManager = CodeStyleManager.getInstance(project)
val file = smartPointer.containingFile?: return

if (file.virtualFile.length > UtSettings.maxTestFileSize) {
CommonErrorNotifier.notify(
"Size of ${file.virtualFile.presentableName} exceeds configured limit " +
"(${FileUtil.byteCountToDisplaySize(UtSettings.maxTestFileSize.toLong())}), reformatting was skipped. " +
"The limit can be configured in '{HOME_DIR}/.utbot/settings.properties' with 'maxTestFileSize' property",
model.project)
return
}

DumbService.getInstance(model.project).runWhenSmart {
codeStyleManager.reformat(file)
when (model.codegenLanguage) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ object SarifReportIdea {
}
val reportFilePath = sarifReportsPath.resolve("${classFqnToPath(classFqn)}Report.sarif")

IntelliJApiHelper.run(IntelliJApiHelper.Target.THREAD_POOL, indicator) {
IntelliJApiHelper.run(IntelliJApiHelper.Target.THREAD_POOL, indicator, "Save SARIF report for ${classId.name}") {
try {
val sarifReportAsJson = proc.writeSarif(reportFilePath, testSetsId, generatedTestsCode, sourceFinding)
val sarifReport = Sarif.fromJson(sarifReportAsJson)
Expand Down
Loading