Skip to content

Commit 55c3a82

Browse files
Generated test output size should be customizable #1235 (#1285)
1 parent 0a77051 commit 55c3a82

File tree

11 files changed

+161
-38
lines changed

11 files changed

+161
-38
lines changed

utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ object FileUtil {
234234
bytesInDouble >= 1 shl 30 -> "%.1f GB".format(bytesInDouble / (1 shl 30))
235235
bytesInDouble >= 1 shl 20 -> "%.1f MB".format(bytesInDouble / (1 shl 20))
236236
bytesInDouble >= 1 shl 10 -> "%.0f kB".format(bytesInDouble / (1 shl 10))
237-
else -> "$bytesInDouble bytes"
237+
else -> "$bytes bytes"
238238
}
239239
}
240240
}

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,35 @@ object UtSettings : AbstractSettings(
384384
*/
385385
var useSandbox by getBooleanProperty(true)
386386

387+
/**
388+
* Limit for number of generated tests per method
389+
*/
390+
var maxTestsPerMethod by getIntProperty(50)
391+
392+
/**
393+
* Max file length for generated test file
394+
*/
395+
const val DEFAULT_MAX_FILE_SIZE = 1000000
396+
var maxTestFileSize by getProperty(DEFAULT_MAX_FILE_SIZE, ::parseFileSize)
397+
398+
399+
fun parseFileSize(s: String): Int {
400+
val suffix = StringBuilder()
401+
var value = 0
402+
for (ch in s) {
403+
(ch - '0').let {
404+
if (it in 0..9) {
405+
value = value * 10 + it
406+
} else suffix.append(ch)
407+
}
408+
}
409+
when (suffix.toString().trim().lowercase()) {
410+
"k", "kb" -> value *= 1000
411+
"m", "mb" -> value *= 1000000
412+
}
413+
return if (value > 0) value else DEFAULT_MAX_FILE_SIZE // fallback for incorrect value
414+
}
415+
387416
/**
388417
* If this options set in true, all soot classes will be removed from a Soot Scene,
389418
* therefore, you will be unable to test soot classes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.utbot.framework
2+
3+
import org.junit.jupiter.api.Test
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
6+
internal class UtSettingsTest {
7+
@Test
8+
fun testMaxTestFileSize() {
9+
assertEquals(1, UtSettings.parseFileSize("1"))
10+
assertEquals(12, UtSettings.parseFileSize("12"))
11+
assertEquals(123, UtSettings.parseFileSize("123"))
12+
assertEquals(30000, UtSettings.parseFileSize("30K"))
13+
assertEquals(30000, UtSettings.parseFileSize("30Kb"))
14+
assertEquals(30000, UtSettings.parseFileSize("30kB"))
15+
assertEquals(30000, UtSettings.parseFileSize("30kb"))
16+
assertEquals(7000000, UtSettings.parseFileSize("7MB"))
17+
assertEquals(7000000, UtSettings.parseFileSize("7Mb"))
18+
assertEquals(7000000, UtSettings.parseFileSize("7M"))
19+
assertEquals(7, UtSettings.parseFileSize("7abc"))
20+
assertEquals(UtSettings.DEFAULT_MAX_FILE_SIZE, UtSettings.parseFileSize("qwerty"))
21+
assertEquals(UtSettings.DEFAULT_MAX_FILE_SIZE, UtSettings.parseFileSize("MB"))
22+
assertEquals(UtSettings.DEFAULT_MAX_FILE_SIZE, UtSettings.parseFileSize("1000000"))
23+
}
24+
}

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.utbot.framework.codegen.model.constructor.tree
22

3+
import org.utbot.framework.UtSettings
34
import org.utbot.framework.codegen.Junit4
45
import org.utbot.framework.codegen.Junit5
56
import org.utbot.framework.codegen.ParametrizedTestSource
@@ -178,13 +179,26 @@ internal class CgTestClassConstructor(val context: CgContext) :
178179
for ((clusterSummary, executionIndices) in clustersInfo) {
179180
val currentTestCaseTestMethods = mutableListOf<CgTestMethod>()
180181
emptyLineIfNeeded()
181-
for (i in executionIndices) {
182+
val (checkedRange, needLimitExceedingComments) = if (executionIndices.last - executionIndices.first > UtSettings.maxTestsPerMethod) {
183+
IntRange(executionIndices.first, executionIndices.first + (UtSettings.maxTestsPerMethod - 1).coerceAtLeast(0)) to true
184+
} else {
185+
executionIndices to false
186+
}
187+
188+
for (i in checkedRange) {
182189
currentTestCaseTestMethods += methodConstructor.createTestMethod(methodUnderTest, testSet.executions[i])
183190
}
191+
192+
val comments = listOf("Actual number of generated tests (${executionIndices.last - executionIndices.first}) exceeds per-method limit (${UtSettings.maxTestsPerMethod})",
193+
"The limit can be configured in '{HOME_DIR}/.utbot/settings.properties' with 'maxTestsPerMethod' property")
194+
184195
val clusterHeader = clusterSummary?.header
185-
val clusterContent = clusterSummary?.content
196+
var clusterContent = clusterSummary?.content
186197
?.split('\n')
187-
?.let { CgTripleSlashMultilineComment(it) }
198+
?.let { CgTripleSlashMultilineComment(if (needLimitExceedingComments) {it.toMutableList() + comments} else {it}) }
199+
if (clusterContent == null && needLimitExceedingComments) {
200+
clusterContent = CgTripleSlashMultilineComment(comments)
201+
}
188202
regions += CgTestMethodCluster(clusterHeader, clusterContent, currentTestCaseTestMethods)
189203

190204
testsGenerationReport.addTestsByType(testSet, currentTestCaseTestMethods)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/CgPrinter.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface CgPrinter {
88
fun popIndent()
99

1010
override fun toString(): String
11+
var printedLength: Int
1112
}
1213

1314
class CgPrinterImpl(
@@ -30,6 +31,8 @@ class CgPrinterImpl(
3031

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

34+
override var printedLength: Int = builder.length
35+
3336
override fun print(text: String) {
3437
if (atLineStart) {
3538
printIndent()

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.utbot.framework.codegen.model.visitor
22

33
import org.apache.commons.text.StringEscapeUtils
4+
import org.utbot.common.FileUtil
45
import org.utbot.common.WorkaroundReason.LONG_CODE_FRAGMENTS
56
import org.utbot.common.workaround
7+
import org.utbot.framework.UtSettings
68
import org.utbot.framework.codegen.Import
79
import org.utbot.framework.codegen.RegularImport
810
import org.utbot.framework.codegen.StaticImport
@@ -109,6 +111,7 @@ internal abstract class CgAbstractRenderer(
109111

110112
protected val regionStart: String = "///region"
111113
protected val regionEnd: String = "///endregion"
114+
protected var isInterrupted = false
112115

113116
protected abstract val language: CodegenLanguage
114117

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

191194
print(regionStart)
192195
header?.let { print(" $it") }
193196
println()
194197

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

200+
var isLimitExceeded = false
197201
for (method in content) {
202+
if (printer.printedLength > UtSettings.maxTestFileSize) {
203+
isLimitExceeded = true
204+
break
205+
}
198206
println()
199207
method.accept(this@CgAbstractRenderer)
200208
}
201209

202210
if (printLineAfterContentEnd) println()
203211

204212
println(regionEnd)
213+
214+
if (isLimitExceeded && !isInterrupted) {
215+
visit(CgSingleLineComment("Abrupt generation termination: file size exceeds configured limit (${FileUtil.byteCountToDisplaySize(UtSettings.maxTestFileSize.toLong())})"))
216+
visit(CgSingleLineComment("The limit can be configured in '{HOME_DIR}/.utbot/settings.properties' with 'maxTestsFileSize' property"))
217+
isInterrupted = true
218+
}
205219
}
206220

207221
override fun visit(element: CgAuxiliaryClass) {

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import com.intellij.ide.fileTemplates.FileTemplateUtil
88
import com.intellij.ide.fileTemplates.JavaTemplateUtil
99
import com.intellij.ide.highlighter.JavaFileType
1010
import com.intellij.openapi.application.ApplicationManager
11-
import com.intellij.openapi.application.invokeLater
1211
import com.intellij.openapi.application.runReadAction
1312
import com.intellij.openapi.application.runWriteAction
1413
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
@@ -23,28 +22,47 @@ import com.intellij.openapi.project.DumbService
2322
import com.intellij.openapi.project.Project
2423
import com.intellij.openapi.util.Computable
2524
import com.intellij.openapi.wm.ToolWindowManager
26-
import com.intellij.psi.*
25+
import com.intellij.psi.JavaDirectoryService
26+
import com.intellij.psi.PsiClass
27+
import com.intellij.psi.PsiClassOwner
28+
import com.intellij.psi.PsiComment
29+
import com.intellij.psi.PsiDirectory
30+
import com.intellij.psi.PsiDocumentManager
31+
import com.intellij.psi.PsiElement
32+
import com.intellij.psi.PsiFile
33+
import com.intellij.psi.PsiFileFactory
34+
import com.intellij.psi.PsiManager
35+
import com.intellij.psi.PsiMethod
36+
import com.intellij.psi.SmartPointerManager
37+
import com.intellij.psi.SmartPsiElementPointer
2738
import com.intellij.psi.codeStyle.CodeStyleManager
2839
import com.intellij.psi.codeStyle.JavaCodeStyleManager
2940
import com.intellij.psi.search.GlobalSearchScopesCore
3041
import com.intellij.testIntegration.TestIntegrationUtils
3142
import com.siyeh.ig.psiutils.ImportUtils
43+
import java.nio.file.Path
44+
import java.util.concurrent.CancellationException
45+
import java.util.concurrent.CountDownLatch
46+
import java.util.concurrent.TimeUnit
3247
import mu.KotlinLogging
3348
import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
3449
import org.jetbrains.kotlin.idea.KotlinFileType
3550
import org.jetbrains.kotlin.idea.core.ShortenReferences
3651
import org.jetbrains.kotlin.idea.core.getPackage
3752
import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
3853
import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl
54+
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
3955
import org.jetbrains.kotlin.name.FqName
4056
import org.jetbrains.kotlin.psi.KtClass
4157
import org.jetbrains.kotlin.psi.KtNamedFunction
4258
import org.jetbrains.kotlin.psi.KtPsiFactory
4359
import org.jetbrains.kotlin.psi.psiUtil.endOffset
4460
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
4561
import org.jetbrains.kotlin.psi.psiUtil.startOffset
62+
import org.utbot.common.FileUtil
4663
import org.utbot.common.HTML_LINE_SEPARATOR
4764
import org.utbot.common.PathUtil.toHtmlLinkTag
65+
import org.utbot.framework.UtSettings
4866
import org.utbot.framework.codegen.Import
4967
import org.utbot.framework.codegen.ParametrizedTestSource
5068
import org.utbot.framework.codegen.RegularImport
@@ -59,23 +77,25 @@ import org.utbot.intellij.plugin.models.packageName
5977
import org.utbot.intellij.plugin.process.EngineProcess
6078
import org.utbot.intellij.plugin.process.RdTestGenerationResult
6179
import org.utbot.intellij.plugin.sarif.SarifReportIdea
62-
import org.utbot.intellij.plugin.ui.*
80+
import org.utbot.intellij.plugin.ui.CommonErrorNotifier
81+
import org.utbot.intellij.plugin.ui.DetailsTestsReportNotifier
82+
import org.utbot.intellij.plugin.ui.SarifReportNotifier
83+
import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener
84+
import org.utbot.intellij.plugin.ui.TestsReportNotifier
85+
import org.utbot.intellij.plugin.ui.WarningTestsReportNotifier
86+
import org.utbot.intellij.plugin.ui.utils.TestSourceRoot
6387
import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
88+
import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle
6489
import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater
6590
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots
66-
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.*
91+
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.EDT_LATER
92+
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.THREAD_POOL
93+
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.WRITE_ACTION
6794
import org.utbot.intellij.plugin.util.IntelliJApiHelper.run
6895
import org.utbot.intellij.plugin.util.RunConfigurationHelper
6996
import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested
7097
import org.utbot.sarif.Sarif
7198
import org.utbot.sarif.SarifReport
72-
import java.nio.file.Path
73-
import java.util.concurrent.CancellationException
74-
import java.util.concurrent.CountDownLatch
75-
import java.util.concurrent.TimeUnit
76-
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
77-
import org.utbot.intellij.plugin.ui.utils.TestSourceRoot
78-
import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle
7999

80100
object CodeGenerationController {
81101
private val logger = KotlinLogging.logger {}
@@ -143,14 +163,14 @@ object CodeGenerationController {
143163
}
144164
}
145165

146-
run(THREAD_POOL, indicator) {
166+
run(THREAD_POOL, indicator, "Waiting for per-class Sarif reports") {
147167
waitForCountDown(latch, indicator = indicator) {
148-
run(EDT_LATER, indicator) {
149-
run(WRITE_ACTION, indicator) {
168+
run(EDT_LATER, indicator,"Go to EDT for utility class creation") {
169+
run(WRITE_ACTION, indicator, "Need write action for utility class creation") {
150170
createUtilityClassIfNeed(utilClassListener, model, baseTestDirectory, indicator)
151-
run(EDT_LATER, indicator) {
171+
run(EDT_LATER, indicator, "Proceed test report") {
152172
proceedTestReport(proc, model)
153-
run(THREAD_POOL, indicator) {
173+
run(THREAD_POOL, indicator, "Generate summary Sarif report") {
154174
val sarifReportsPath =
155175
model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
156176
UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Merge Sarif reports", 0.75)
@@ -160,9 +180,9 @@ object CodeGenerationController {
160180
RunConfigurationHelper.runTestsWithCoverage(model, testFilesPointers)
161181
}
162182
proc.forceTermination()
163-
UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Start tests with coverage", 1.0)
183+
UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Generation finished", 1.0)
164184

165-
invokeLater {
185+
run(EDT_LATER, null, "Run sarif-based inspections") {
166186
runInspectionsIfNeeded(model, srcClassPathToSarifReport)
167187
}
168188
}
@@ -342,8 +362,8 @@ object CodeGenerationController {
342362

343363
val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage)
344364

345-
run(EDT_LATER, indicator) {
346-
run(WRITE_ACTION, indicator) {
365+
run(EDT_LATER, indicator, "Overwrite utility class") {
366+
run(WRITE_ACTION, indicator, "Overwrite utility class") {
347367
unblockDocument(model.project, utilsClassDocument)
348368
executeCommand {
349369
utilsClassDocument.setText(utUtilsText.replace("jdk.internal.misc", "sun.misc"))
@@ -520,7 +540,8 @@ object CodeGenerationController {
520540
private fun waitForCountDown(latch: CountDownLatch, timeout: Long = 5, timeUnit: TimeUnit = TimeUnit.SECONDS, indicator : ProgressIndicator, action: Runnable) {
521541
try {
522542
if (!latch.await(timeout, timeUnit)) {
523-
run(THREAD_POOL, indicator) { waitForCountDown(latch, timeout, timeUnit, indicator, action) }
543+
run(THREAD_POOL, indicator, "Waiting for ${latch.count} sarif report(s) in a loop") {
544+
waitForCountDown(latch, timeout, timeUnit, indicator, action) }
524545
} else {
525546
action.run()
526547
}
@@ -643,7 +664,7 @@ object CodeGenerationController {
643664
val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, filePointer.containingFile, testClass)
644665
//TODO: Use PsiDocumentManager.getInstance(model.project).getDocument(file)
645666
// if we don't want to open _all_ new files with tests in editor one-by-one
646-
run(THREAD_POOL, indicator) {
667+
run(THREAD_POOL, indicator, "Rendering test code") {
647668
val (generatedTestsCode, utilClassKind) = try {
648669
proc.render(
649670
testSetsId,
@@ -668,8 +689,8 @@ object CodeGenerationController {
668689
return@run
669690
}
670691
utilClassListener.onTestClassGenerated(utilClassKind)
671-
run(EDT_LATER, indicator) {
672-
run(WRITE_ACTION, indicator) {
692+
run(EDT_LATER, indicator, "Writing generation text to documents") {
693+
run(WRITE_ACTION, indicator, "Writing generation text to documents") {
673694
try {
674695
unblockDocument(testClass.project, editor.document)
675696
// TODO: JIRA:1246 - display warnings if we rewrite the file
@@ -689,9 +710,9 @@ object CodeGenerationController {
689710

690711
// reformatting before creating reports due to
691712
// SarifReport requires the final version of the generated tests code
692-
run(THREAD_POOL, indicator) {
713+
// run(THREAD_POOL, indicator) {
693714
// IntentionHelper(model.project, editor, filePointer).applyIntentions()
694-
run(EDT_LATER, indicator) {
715+
run(EDT_LATER, indicator, "Tests reformatting") {
695716
try {
696717
runWriteCommandAction(filePointer.project, "UtBot tests reformatting", null, {
697718
reformat(model, filePointer, testClassUpdated)
@@ -719,7 +740,7 @@ object CodeGenerationController {
719740

720741
unblockDocument(testClassUpdated.project, editor.document)
721742
}
722-
}
743+
// }
723744
}
724745
}
725746
}
@@ -729,6 +750,16 @@ object CodeGenerationController {
729750
val project = model.project
730751
val codeStyleManager = CodeStyleManager.getInstance(project)
731752
val file = smartPointer.containingFile?: return
753+
754+
if (file.virtualFile.length > UtSettings.maxTestFileSize) {
755+
CommonErrorNotifier.notify(
756+
"Size of ${file.virtualFile.presentableName} exceeds configured limit " +
757+
"(${FileUtil.byteCountToDisplaySize(UtSettings.maxTestFileSize.toLong())}), reformatting was skipped. " +
758+
"The limit can be configured in '{HOME_DIR}/.utbot/settings.properties' with 'maxTestFileSize' property",
759+
model.project)
760+
return
761+
}
762+
732763
DumbService.getInstance(model.project).runWhenSmart {
733764
codeStyleManager.reformat(file)
734765
when (model.codegenLanguage) {

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ object SarifReportIdea {
4141
}
4242
val reportFilePath = sarifReportsPath.resolve("${classFqnToPath(classFqn)}Report.sarif")
4343

44-
IntelliJApiHelper.run(IntelliJApiHelper.Target.THREAD_POOL, indicator) {
44+
IntelliJApiHelper.run(IntelliJApiHelper.Target.THREAD_POOL, indicator, "Save SARIF report for ${classId.name}") {
4545
try {
4646
val sarifReportAsJson = proc.writeSarif(reportFilePath, testSetsId, generatedTestsCode, sourceFinding)
4747
val sarifReport = Sarif.fromJson(sarifReportAsJson)

0 commit comments

Comments
 (0)