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

IDE fatal error during tests generation #68 #379

Merged
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
package org.utbot.intellij.plugin.generator

import org.utbot.common.HTML_LINE_SEPARATOR
import org.utbot.common.PathUtil.classFqnToPath
import org.utbot.common.PathUtil.toHtmlLinkTag
import org.utbot.common.appendHtmlLine
import org.utbot.framework.codegen.Import
import org.utbot.framework.codegen.ParametrizedTestSource
import org.utbot.framework.codegen.StaticImport
import org.utbot.framework.codegen.TestsCodeWithTestReport
import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator
import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.UtTestCase
import org.utbot.intellij.plugin.sarif.SarifReportIdea
import org.utbot.intellij.plugin.sarif.SourceFindingStrategyIdea
import org.utbot.intellij.plugin.settings.Settings
import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath
import org.utbot.sarif.SarifReport
import com.intellij.codeInsight.CodeInsightUtil
import com.intellij.codeInsight.FileModificationService
import com.intellij.ide.fileTemplates.FileTemplateManager
import com.intellij.ide.fileTemplates.FileTemplateUtil
import com.intellij.ide.fileTemplates.JavaTemplateUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
import com.intellij.openapi.command.executeCommand
import com.intellij.openapi.components.service
Expand All @@ -21,51 +38,54 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.search.GlobalSearchScopesCore
import com.intellij.testIntegration.TestIntegrationUtils
import com.intellij.util.IncorrectOperationException
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.io.exists
import com.siyeh.ig.psiutils.ImportUtils
import java.nio.file.Paths
import java.util.concurrent.CountDownLatch
import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
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.application.invokeLater
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.startOffset
import org.jetbrains.kotlin.scripting.resolve.classId
import org.utbot.common.HTML_LINE_SEPARATOR
import org.utbot.common.PathUtil.classFqnToPath
import org.utbot.common.PathUtil.toHtmlLinkTag
import org.utbot.common.appendHtmlLine
import org.utbot.framework.codegen.Import
import org.utbot.framework.codegen.ParametrizedTestSource
import org.utbot.framework.codegen.StaticImport
import org.utbot.framework.codegen.TestsCodeWithTestReport
import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator
import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.UtTestCase
import org.utbot.intellij.plugin.error.showErrorDialogLater
import org.utbot.intellij.plugin.sarif.SarifReportIdea
import org.utbot.intellij.plugin.sarif.SourceFindingStrategyIdea
import org.utbot.intellij.plugin.settings.Settings
import org.utbot.intellij.plugin.ui.*
import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath
import org.utbot.sarif.SarifReport
import java.nio.file.Paths
import org.utbot.intellij.plugin.ui.GenerateTestsModel
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.packageName

object TestGenerator {
fun generateTests(model: GenerateTestsModel, testCases: Map<PsiClass, List<UtTestCase>>) {
generateTestsInternal(model, testCases)
private enum class Target {THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER}

private fun run(target: Target, runnable: Runnable) {
UtContext.currentContext()?.let {
when (target) {
THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit {
withUtContext(it) {
runnable.run()
}
}
READ_ACTION -> runReadAction { withUtContext(it) { runnable.run() } }
WRITE_ACTION -> runWriteAction { withUtContext(it) { runnable.run() } }
EDT_LATER -> invokeLater { withUtContext(it) { runnable.run() } }
}
} ?: error("No context in thread ${Thread.currentThread()}")
}

private fun generateTestsInternal(model: GenerateTestsModel, testCasesByClass: Map<PsiClass, List<UtTestCase>>) {
fun generateTests(model: GenerateTestsModel, testCasesByClass: Map<PsiClass, List<UtTestCase>>) {
val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project)
?: return
val allTestPackages = getPackageDirectories(baseTestDirectory)
val latch = CountDownLatch(testCasesByClass.size)

for (srcClass in testCasesByClass.keys) {
val testCases = testCasesByClass[srcClass] ?: continue
Expand All @@ -75,20 +95,37 @@ object TestGenerator {
val testDirectory = allTestPackages[classPackageName] ?: baseTestDirectory
val testClass = createTestClass(srcClass, testDirectory, model) ?: continue
val file = testClass.containingFile

runWriteCommandAction(model.project, "Generate tests with UtBot", null, {
addTestMethodsAndSaveReports(testClass, file, testCases, model)
try {
addTestMethodsAndSaveReports(testClass, file, testCases, model, latch)
} catch (e: IncorrectOperationException) {
showCreatingClassError(model.project, createTestClassName(srcClass))
}
})
} catch (e: IncorrectOperationException) {
showCreatingClassError(model.project, createTestClassName(srcClass))
}
}
run(Target.READ_ACTION) {
val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
run(Target.THREAD_POOL) {
waitForCountDown(latch, model, sarifReportsPath)
}
}
}

mergeSarifReports(model)
private fun waitForCountDown(latch: CountDownLatch, model: GenerateTestsModel, sarifReportsPath : Path) {
try {
if (!latch.await(5, TimeUnit.SECONDS)) {
run(THREAD_POOL) { waitForCountDown(latch, model, sarifReportsPath) }
} else {
mergeSarifReports(model, sarifReportsPath)
}
} catch (ignored: InterruptedException) {
}
}

private fun mergeSarifReports(model: GenerateTestsModel) {
val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
private fun mergeSarifReports(model: GenerateTestsModel, sarifReportsPath : Path) {
val sarifReports = sarifReportsPath.toFile()
.walkTopDown()
.filter { it.extension == "sarif" }
Expand Down Expand Up @@ -175,6 +212,7 @@ object TestGenerator {
file: PsiFile,
testCases: List<UtTestCase>,
model: GenerateTestsModel,
reportsCountDown: CountDownLatch,
) {
val selectedMethods = TestIntegrationUtils.extractClassMethods(testClass, false)
val testFramework = model.testFramework
Expand Down Expand Up @@ -205,33 +243,50 @@ object TestGenerator {
when (generator) {
is ModelBasedTestCodeGenerator -> {
val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, file, testClass)
val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases)
val generatedTestsCode = testsCodeWithTestReport.generatedCode
//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) {
val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases)
val generatedTestsCode = testsCodeWithTestReport.generatedCode
run(EDT_LATER) {
run(WRITE_ACTION) {
unblockDocument(testClass.project, editor.document)
// TODO: JIRA:1246 - display warnings if we rewrite the file
executeCommand(testClass.project, "Insert Generated Tests") {
editor.document.setText(generatedTestsCode)
}
unblockDocument(testClass.project, editor.document)

// after committing the document the `testClass` is invalid in PsiTree,
// so we have to reload it from the corresponding `file`
val testClassUpdated = (file as PsiClassOwner).classes.first() // only one class in the file

// reformatting before creating reports due to
// SarifReport requires the final version of the generated tests code
runWriteCommandAction(testClassUpdated.project, "UtBot tests reformatting", null, {
reformat(model, file, testClassUpdated)
})
unblockDocument(testClassUpdated.project, editor.document)

// uploading formatted code
val testsCodeWithTestReportFormatted =
testsCodeWithTestReport.copy(generatedCode = file.text)

// creating and saving reports
saveSarifAndTestReports(
testClassUpdated,
testCases,
model,
testsCodeWithTestReportFormatted,
reportsCountDown
)

unblockDocument(testClass.project, editor.document)
// TODO: JIRA:1246 - display warnings if we rewrite the file
executeCommand(testClass.project, "Insert Generated Tests") {
editor.document.setText(generatedTestsCode)
unblockDocument(testClassUpdated.project, editor.document)
}
}
}
unblockDocument(testClass.project, editor.document)

// after committing the document the `testClass` is invalid in PsiTree,
// so we have to reload it from the corresponding `file`
val testClassUpdated = (file as PsiClassOwner).classes.first() // only one class in the file

// reformatting before creating reports due to
// SarifReport requires the final version of the generated tests code
reformat(model, file, testClassUpdated)
unblockDocument(testClassUpdated.project, editor.document)

// uploading formatted code
val testsCodeWithTestReportFormatted = testsCodeWithTestReport.copy(generatedCode = file.text)

// creating and saving reports
saveSarifAndTestReports(testClassUpdated, testCases, model, testsCodeWithTestReportFormatted)

unblockDocument(testClassUpdated.project, editor.document)
}
//Note that reportsCountDown.countDown() has to be called in every generator implementation to complete whole process
else -> TODO("Only model based code generator supported, but got: ${generator::class}")
}
}
Expand All @@ -257,7 +312,8 @@ object TestGenerator {
testClass: PsiClass,
testCases: List<UtTestCase>,
model: GenerateTestsModel,
testsCodeWithTestReport: TestsCodeWithTestReport
testsCodeWithTestReport: TestsCodeWithTestReport,
reportsCountDown: CountDownLatch
) {
val project = model.project
val generatedTestsCode = testsCodeWithTestReport.generatedCode
Expand All @@ -274,6 +330,8 @@ object TestGenerator {
message = "Cannot save Sarif report via generated tests: error occurred '${e.message}'",
title = "Failed to save Sarif report"
)
} finally {
reportsCountDown.countDown()
Vassiliy-Kudryashov marked this conversation as resolved.
Show resolved Hide resolved
}

try {
Expand Down