From e694ba6851a5ee4796f0865d0b22ae1502d5244f Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Mon, 26 Jun 2023 16:54:16 +0300 Subject: [PATCH 01/44] Remove unused code --- .../kotlin/org/utbot/python/code/CodeGen.kt | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/code/CodeGen.kt b/utbot-python/src/main/kotlin/org/utbot/python/code/CodeGen.kt index cd2450c602..ccecb68e13 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/code/CodeGen.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/code/CodeGen.kt @@ -11,34 +11,6 @@ import org.utbot.python.newtyping.general.Type object PythonCodeGenerator { - fun generateRunFunctionCode( - method: PythonMethod, - methodArguments: List, - directoriesForSysPath: Set, - moduleToImport: String, - additionalModules: Set = emptySet(), - fileForOutputName: String, - coverageDatabasePath: String, - ): String { - val context = UtContext(this::class.java.classLoader) - withUtContext(context) { - val codegen = org.utbot.python.framework.codegen.model.PythonCodeGenerator( - PythonClassId("TopLevelFunction"), - paramNames = emptyMap>().toMutableMap(), - testFramework = PythonCgLanguageAssistant.getLanguageTestFrameworkManager().testFrameworks[0], - testClassPackageName = "", - ) - return codegen.generateFunctionCall( - method, - methodArguments, - directoriesForSysPath, - moduleToImport, - additionalModules, - fileForOutputName, - coverageDatabasePath, - ) - } - } fun generateMypyCheckCode( method: PythonMethod, From fade414338f82e9509b6f790e3c93ffdd5e688f7 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Mon, 26 Jun 2023 17:00:11 +0300 Subject: [PATCH 02/44] Add new requirements installer --- utbot-intellij-python/build.gradle.kts | 1 + .../python/IntellijRequirementsInstaller.kt | 87 +++++++++++++++++++ .../language/python/PythonDialogProcessor.kt | 58 ++++++++++--- .../org/utbot/python/RequirementsInstaller.kt | 6 ++ 4 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt create mode 100644 utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt diff --git a/utbot-intellij-python/build.gradle.kts b/utbot-intellij-python/build.gradle.kts index 5c737516ef..bf7d404342 100644 --- a/utbot-intellij-python/build.gradle.kts +++ b/utbot-intellij-python/build.gradle.kts @@ -33,6 +33,7 @@ tasks { } dependencies { + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") implementation(project(":utbot-ui-commons")) diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt new file mode 100644 index 0000000000..3d4e0b0f27 --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt @@ -0,0 +1,87 @@ +package org.utbot.intellij.plugin.language.python + +import com.intellij.notification.NotificationType +import com.intellij.openapi.module.Module +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogPanel +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.dsl.builder.panel +import org.utbot.intellij.plugin.ui.Notifier +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.intellij.plugin.util.IntelliJApiHelper +import org.utbot.python.RequirementsInstaller +import org.utbot.python.utils.RequirementsUtils +import javax.swing.JComponent + + +class IntellijRequirementsInstaller( + val project: Project, +): RequirementsInstaller { + override fun checkRequirements(pythonPath: String, requirements: List): Boolean { + return RequirementsUtils.requirementsAreInstalled(pythonPath, requirements) + } + + override fun installRequirements(pythonPath: String, requirements: List) { + ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Installing requirements") { + override fun run(indicator: ProgressIndicator) { + IntelliJApiHelper.run( + IntelliJApiHelper.Target.EDT_LATER, + indicator, + "Installing requirements" + ) { + if (InstallRequirementsDialog(requirements).showAndGet()) { + PythonNotifier.notify("Start requirements installation") + val installResult = RequirementsUtils.installRequirements(pythonPath, requirements) + if (installResult.exitValue != 0) { + showErrorDialogLater( + project, + "Requirements installing failed.
" + + "${installResult.stderr}

" + + "Try to install with pip:
" + + " ${requirements.joinToString("
")}", + "Requirements error" + ) + } else { + PythonNotifier.notify("Requirements installation is complete") + } + } + } + } + }) + } +} + + +class InstallRequirementsDialog(private val requirements: List) : DialogWrapper(true) { + init { + title = "Python Requirements Installation" + init() + } + + private lateinit var panel: DialogPanel + + override fun createCenterPanel(): JComponent { + panel = panel { + row("Some requirements are not installed.") { } + row("Requirements:") { } + indent { + requirements.map { row {text(it)} } + } + row("Install them?") { } + } + return panel + } +} + +object PythonNotifier : Notifier() { + override val notificationType: NotificationType = NotificationType.INFORMATION + override val displayId: String = "Python notification" + override fun notify(info: String, project: Project?, module: Module?) { + val contentText = content(project, module, info) + notificationGroup.createNotification(contentText, notificationType) + logger.info(contentText) + } +} diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt index 05b0853bfa..685e1728af 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt @@ -1,6 +1,7 @@ package org.utbot.intellij.plugin.language.python import com.intellij.codeInsight.CodeInsightUtil +import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.invokeLater import com.intellij.openapi.application.readAction import com.intellij.openapi.components.service @@ -25,6 +26,7 @@ import com.jetbrains.python.psi.PyElement import com.jetbrains.python.psi.PyFile import com.jetbrains.python.psi.PyFunction import com.jetbrains.python.psi.resolve.QualifiedNameFinder +import mu.KotlinLogging import kotlinx.coroutines.runBlocking import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.module @@ -53,6 +55,26 @@ import kotlin.io.path.Path const val DEFAULT_TIMEOUT_FOR_RUN_IN_MILLIS = 2000L object PythonDialogProcessor { + private val logger = KotlinLogging.logger {} + + enum class ProgressRange(val from : Double, val to: Double) { + ANALYZE(from = 0.0, to = 0.1), + SOLVING(from = 0.1, to = 0.95), + CODEGEN(from = 0.95, to = 1.0), + } + + fun updateIndicator(indicator: ProgressIndicator, range : ProgressRange, text: String? = null, fraction: Double? = null) { + invokeLater { + if (indicator.isCanceled) return@invokeLater + text?.let { indicator.text = it } + fraction?.let { + indicator.fraction = + indicator.fraction.coerceAtLeast(range.from + (range.to - range.from) * fraction.coerceIn(0.0, 1.0)) + } + logger.debug("Phase ${indicator.text} with progress ${String.format("%.2f",indicator.fraction)}") + } + } + fun createDialogAndGenerateTests( project: Project, elementsToShow: Set, @@ -120,8 +142,7 @@ object PythonDialogProcessor { } private fun findSelectedPythonMethods(model: PythonTestLocalModel): List { - return runBlocking { - readAction { + return ReadAction.nonBlocking> { model.selectedElements .filter { model.selectedElements.contains(it) } .flatMap { @@ -144,8 +165,7 @@ object PythonDialogProcessor { } .toSet() .toList() - } - } + }.executeSynchronously() ?: emptyList() } private fun groupPyElementsByModule(model: PythonTestsModel): Set { @@ -195,6 +215,14 @@ object PythonDialogProcessor { } } + private fun getFileContent(file: PyFile): String { + return runBlocking { + readAction { + getContentFromPyFile(file) + } + } + } + private fun createTests(project: Project, baseModel: PythonTestsModel) { ProgressManager.getInstance().run(object : Backgroundable(project, "Generate python tests") { override fun run(indicator: ProgressIndicator) { @@ -202,18 +230,27 @@ object PythonDialogProcessor { return } try { + indicator.isIndeterminate = false + groupPyElementsByModule(baseModel).forEach { model -> + updateIndicator(indicator, ProgressRange.ANALYZE, "Analyze code: read files") val methods = findSelectedPythonMethods(model) + val content = getFileContent(model.file) + val requirementsList = requirements.toMutableList() if (!model.testFramework.isInstalled) { requirementsList += model.testFramework.mainPackage } - val content = runBlocking { - readAction { - getContentFromPyFile(model.file) - } - } + val installer = IntellijRequirementsInstaller(project) + + updateIndicator(indicator, ProgressRange.ANALYZE, "Analyze file ${model.file.name}") + + updateIndicator(indicator, ProgressRange.SOLVING, "Generate test cases for file ${model.file.name}") + + updateIndicator(indicator, ProgressRange.CODEGEN, "Generate code of tests for ${model.file.name}") + +// val processor = processTestGeneration( pythonPath = model.pythonPath, @@ -234,7 +271,8 @@ object PythonDialogProcessor { checkingRequirementsAction = { indicator.text = "Checking requirements" }, installingRequirementsAction = { indicator.text = "Installing requirements..." }, requirementsAreNotInstalledAction = { - askAndInstallRequirementsLater(model.project, model.pythonPath, requirementsList) + installer.installRequirements(model.pythonPath, requirementsList) +// askAndInstallRequirementsLater(model.project, model.pythonPath, requirementsList) PythonTestGenerationProcessor.MissingRequirementsActionResult.NOT_INSTALLED }, startedLoadingPythonTypesAction = { indicator.text = "Loading information about Python types" }, diff --git a/utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt b/utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt new file mode 100644 index 0000000000..09d03fff53 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt @@ -0,0 +1,6 @@ +package org.utbot.python + +interface RequirementsInstaller { + fun checkRequirements(pythonPath: String, requirements: List): Boolean + fun installRequirements(pythonPath: String, requirements: List) +} \ No newline at end of file From f0b72a8be8b44a84010a0d5f033a03c51d0f376c Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Mon, 26 Jun 2023 17:00:58 +0300 Subject: [PATCH 03/44] Previous version of base test generator --- .../python/PythonTestGenerationConfig.kt | 26 +++++ .../PythonTestGenerationProcessorNew.kt | 102 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt create mode 100644 utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt new file mode 100644 index 0000000000..81e9dbe1df --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt @@ -0,0 +1,26 @@ +package org.utbot.python + +import org.utbot.framework.codegen.domain.TestFramework +import java.nio.file.Path + +data class TestFileInformation( + val testedFilePath: String, + val testedFileContent: String, + val moduleName: String, +) + +interface PythonTestGenerationConfig { + val pythonPath: String + val testFileInformation: TestFileInformation + val sysPathDirectories: Set + val testedMethods: List + val timeout: Long + val timeoutForRun: Long + val testFramework: TestFramework + val executionPath: Path + val withMinimization: Boolean + val isCanceled: () -> Boolean + val requirementsInstaller: RequirementsInstaller + + fun saveCodeToFile(code: String): Unit +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt new file mode 100644 index 0000000000..3529b82b92 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt @@ -0,0 +1,102 @@ +package org.utbot.python + +import org.parsers.python.PythonParser +import org.utbot.python.code.PythonCode +import org.utbot.python.newtyping.PythonFunctionDefinition +import org.utbot.python.newtyping.general.CompositeType +import org.utbot.python.newtyping.getPythonAttributes +import org.utbot.python.newtyping.mypy.MypyAnnotationStorage +import org.utbot.python.newtyping.mypy.MypyReportLine +import org.utbot.python.newtyping.mypy.readMypyAnnotationStorageAndInitialErrors +import org.utbot.python.newtyping.mypy.setConfigFile +import org.utbot.python.utils.RequirementsUtils +import java.io.File + +abstract class PythonTestGenerationProcessorNew { + abstract val configuration: PythonTestGenerationConfig + + fun checkRequirements(): Boolean { + val requirements = RequirementsUtils.requirements + configuration.testFramework.mainPackage + if (!configuration.requirementsInstaller.checkRequirements(configuration.pythonPath, requirements)) { + configuration.requirementsInstaller.installRequirements(configuration.pythonPath, requirements) + return configuration.requirementsInstaller.checkRequirements(configuration.pythonPath, requirements) + } + return true + } + + fun sourceCodeAnalyze(mypyConfigFile: File): Pair> { +// val mypyConfigFile = setConfigFile(configuration.sysPathDirectories) + return readMypyAnnotationStorageAndInitialErrors( + configuration.pythonPath, + configuration.testFileInformation.testedFilePath, + configuration.testFileInformation.moduleName, + mypyConfigFile + ) + } + + fun testGenerate(mypyStorage: MypyAnnotationStorage, mypyConfigFile: File): List { + val startTime = System.currentTimeMillis() + + val testCaseGenerator = PythonTestCaseGenerator( + withMinimization = configuration.withMinimization, + directoriesForSysPath = configuration.sysPathDirectories, + curModule = configuration.testFileInformation.moduleName, + pythonPath = configuration.pythonPath, + fileOfMethod = configuration.testFileInformation.testedFilePath, + isCancelled = configuration.isCanceled, + timeoutForRun = configuration.timeoutForRun, + sourceFileContent = configuration.testFileInformation.testedFileContent, + mypyStorage = mypyStorage, + mypyReportLine = emptyList(), + mypyConfigFile = mypyConfigFile, + ) + + val until = startTime + configuration.timeout + return configuration.testedMethods.mapIndexed { index, methodHeader -> + val methodsLeft = configuration.testedMethods.size - index + val localUntil = (until - System.currentTimeMillis()) / methodsLeft + System.currentTimeMillis() + val method = findMethodByHeader( + mypyStorage, + methodHeader, + configuration.testFileInformation.moduleName, + configuration.testFileInformation.testedFileContent + ) + testCaseGenerator.generate(method, localUntil) + } + } + + fun testCodeGenerate() {} + + fun saveTests() {} + + private fun findMethodByHeader( + mypyStorage: MypyAnnotationStorage, + method: PythonMethodHeader, + curModule: String, + sourceFileContent: String + ): PythonMethod { + var containingClass: CompositeType? = null + val containingClassName = method.containingPythonClassId?.simpleName + val functionDef = if (containingClassName == null) { + mypyStorage.definitions[curModule]!![method.name]!!.getUtBotDefinition()!! + } else { + containingClass = + mypyStorage.definitions[curModule]!![containingClassName]!!.getUtBotType() as CompositeType + mypyStorage.definitions[curModule]!![containingClassName]!!.type.asUtBotType.getPythonAttributes().first { + it.meta.name == method.name + } + } as? PythonFunctionDefinition ?: error("Selected method is not a function definition") + + val parsedFile = PythonParser(sourceFileContent).Module() + val funcDef = PythonCode.findFunctionDefinition(parsedFile, method) + + return PythonMethod( + name = method.name, + moduleFilename = method.moduleFilename, + containingPythonClass = containingClass, + codeAsString = funcDef.body.source, + definition = functionDef, + ast = funcDef.body + ) + } +} \ No newline at end of file From a3afda0e6a18c19218024bf8d8c2cf6bffa417e7 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 27 Jun 2023 18:46:03 +0300 Subject: [PATCH 04/44] New test generation processor for intellij-python --- .../language/python/PythonDialogProcessor.kt | 177 ++++++++++++------ .../python/PythonIntellijProcessor.kt | 72 +++++++ .../python/PythonTestGenerationConfig.kt | 27 ++- .../PythonTestGenerationProcessorNew.kt | 158 ++++++++++++++-- .../org/utbot/python/RequirementsInstaller.kt | 14 ++ .../kotlin/org/utbot/python/UTPythonAPI.kt | 1 + .../codegen/model/PythonCodeGenerator.kt | 132 ------------- .../constructor/visitor/CgPythonRenderer.kt | 2 +- .../framework/codegen/utils/StringUtils.kt | 2 +- 9 files changed, 366 insertions(+), 219 deletions(-) create mode 100644 utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt index 685e1728af..db9747d715 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt @@ -21,6 +21,7 @@ import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiFileFactory +import com.intellij.util.concurrency.AppExecutorUtil import com.jetbrains.python.psi.PyClass import com.jetbrains.python.psi.PyElement import com.jetbrains.python.psi.PyFile @@ -33,23 +34,25 @@ import org.jetbrains.kotlin.idea.util.module import org.jetbrains.kotlin.idea.util.projectStructure.sdk import org.jetbrains.kotlin.j2k.getContainingClass import org.utbot.common.PathUtil.toPath -import org.utbot.common.appendHtmlLine import org.utbot.framework.plugin.api.util.LockFile import org.utbot.intellij.plugin.settings.Settings -import org.utbot.intellij.plugin.ui.WarningTestsReportNotifier import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.intellij.plugin.ui.utils.testModules import org.utbot.python.PythonMethodHeader -import org.utbot.python.PythonTestGenerationProcessor -import org.utbot.python.PythonTestGenerationProcessor.processTestGeneration +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.RequirementsInstaller +import org.utbot.python.TestFileInformation import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.codegen.PythonCgLanguageAssistant import org.utbot.python.newtyping.mypy.dropInitFile +import org.utbot.python.newtyping.mypy.setConfigFile import org.utbot.python.utils.RequirementsUtils.installRequirements import org.utbot.python.utils.RequirementsUtils.requirements import org.utbot.python.utils.camelToSnakeCase import java.nio.file.Path import java.nio.file.Paths +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit import kotlin.io.path.Path const val DEFAULT_TIMEOUT_FOR_RUN_IN_MILLIS = 2000L @@ -63,18 +66,60 @@ object PythonDialogProcessor { CODEGEN(from = 0.95, to = 1.0), } - fun updateIndicator(indicator: ProgressIndicator, range : ProgressRange, text: String? = null, fraction: Double? = null) { + fun updateIndicator( + indicator: ProgressIndicator, + range: ProgressRange, + text: String, + fraction: Double, + stepCount: Int, + stepNumber: Int = 0, + ) { + assert(stepCount > stepNumber) + val maxValue = 1.0 / stepCount + val shift = stepNumber.toDouble() invokeLater { if (indicator.isCanceled) return@invokeLater - text?.let { indicator.text = it } - fraction?.let { - indicator.fraction = - indicator.fraction.coerceAtLeast(range.from + (range.to - range.from) * fraction.coerceIn(0.0, 1.0)) - } + text.let { indicator.text = it } + indicator.fraction = indicator.fraction + .coerceAtLeast((shift + range.from + (range.to - range.from) * fraction.coerceIn(0.0, 1.0)) * maxValue) logger.debug("Phase ${indicator.text} with progress ${String.format("%.2f",indicator.fraction)}") } } + private fun runIndicatorWithTimeHandler(indicator: ProgressIndicator, range: ProgressRange, text: String, globalCount: Int, globalShift: Int, timeout: Long): ScheduledFuture<*> { + val startTime = System.currentTimeMillis() + return AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay({ + val innerTimeoutRatio = + ((System.currentTimeMillis() - startTime).toDouble() / timeout) + .coerceIn(0.0, 1.0) + updateIndicator( + indicator, + range, + text, + innerTimeoutRatio, + globalCount, + globalShift, + ) + }, 0, 100, TimeUnit.MILLISECONDS) + } + + private fun updateIndicatorTemplate( + indicator: ProgressIndicator, + stepCount: Int, + stepNumber: Int + ): (ProgressRange, String, Double) -> Unit { + return { range: ProgressRange, text: String, fraction: Double -> + updateIndicator( + indicator, + range, + text, + fraction, + stepCount, + stepNumber + ) + } + } + fun createDialogAndGenerateTests( project: Project, elementsToShow: Set, @@ -197,7 +242,13 @@ object PythonDialogProcessor { directoriesForSysPath, moduleToImport.dropInitFile(), file, - realElements.first().getContainingClass() as PyClass? + realElements.first().let { pyElement -> + if (pyElement is PyFunction) { + pyElement.containingClass + } else { + null + } + } ) } .toSet() @@ -232,8 +283,23 @@ object PythonDialogProcessor { try { indicator.isIndeterminate = false - groupPyElementsByModule(baseModel).forEach { model -> - updateIndicator(indicator, ProgressRange.ANALYZE, "Analyze code: read files") + val installer = IntellijRequirementsInstaller(project) + + indicator.text = "Checking requirements" + val requirementsAreInstalled = RequirementsInstaller.checkRequirements( + installer, + baseModel.pythonPath, + if (baseModel.testFramework.isInstalled) emptyList() else listOf(baseModel.testFramework.mainPackage) + ) + if (!requirementsAreInstalled) return // TODO: show message about stopping? + + val modelGroups = groupPyElementsByModule(baseModel) + val totalModules = modelGroups.size + + modelGroups.forEachIndexed { index, model -> + val localUpdateIndicator = updateIndicatorTemplate(indicator, totalModules, index) + localUpdateIndicator(ProgressRange.ANALYZE, "Analyze code: read files", 0.1) + val methods = findSelectedPythonMethods(model) val content = getFileContent(model.file) @@ -242,55 +308,52 @@ object PythonDialogProcessor { requirementsList += model.testFramework.mainPackage } - val installer = IntellijRequirementsInstaller(project) - - updateIndicator(indicator, ProgressRange.ANALYZE, "Analyze file ${model.file.name}") - - updateIndicator(indicator, ProgressRange.SOLVING, "Generate test cases for file ${model.file.name}") - - updateIndicator(indicator, ProgressRange.CODEGEN, "Generate code of tests for ${model.file.name}") - -// val processor = - - processTestGeneration( + val config = PythonTestGenerationConfig( pythonPath = model.pythonPath, - pythonFilePath = model.file.virtualFile.path, - pythonFileContent = content, - directoriesForSysPath = model.directoriesForSysPath, - currentPythonModule = model.currentPythonModule, - pythonMethods = methods, - containingClassName = model.containingClass?.name, + testFileInformation = TestFileInformation(model.file.virtualFile.path, content, model.currentPythonModule), + sysPathDirectories = model.directoriesForSysPath, + testedMethods = methods, timeout = model.timeout, - testFramework = model.testFramework, timeoutForRun = model.timeoutForRun, - writeTestTextToFile = { generatedCode -> - writeGeneratedCodeToPsiDocument(generatedCode, model) - }, - pythonRunRoot = Path(model.testSourceRootPath), + testFramework = model.testFramework, + executionPath = Path(model.testSourceRootPath), + withMinimization = true, isCanceled = { indicator.isCanceled }, - checkingRequirementsAction = { indicator.text = "Checking requirements" }, - installingRequirementsAction = { indicator.text = "Installing requirements..." }, - requirementsAreNotInstalledAction = { - installer.installRequirements(model.pythonPath, requirementsList) -// askAndInstallRequirementsLater(model.project, model.pythonPath, requirementsList) - PythonTestGenerationProcessor.MissingRequirementsActionResult.NOT_INSTALLED - }, - startedLoadingPythonTypesAction = { indicator.text = "Loading information about Python types" }, - startedTestGenerationAction = { indicator.text = "Generating tests" }, - notGeneratedTestsAction = { - showErrorDialogLater( - project, - message = "Cannot create tests for the following functions: " + it.joinToString(), - title = "Python test generation error" - ) - }, - processMypyWarnings = { - val message = it.fold(StringBuilder()) { acc, line -> acc.appendHtmlLine(line) } - WarningTestsReportNotifier.notify(message.toString()) - }, - runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour, - startedCleaningAction = { indicator.text = "Cleaning up..." } ) + val processor = PythonIntellijProcessor( + config, + project, + model + ) + + localUpdateIndicator(ProgressRange.ANALYZE, "Analyze module ${model.currentPythonModule}", 0.5) + + val mypyConfigFile = setConfigFile(config.sysPathDirectories) + val (mypyStorage, _) = processor.sourceCodeAnalyze(mypyConfigFile) + + localUpdateIndicator(ProgressRange.ANALYZE, "Analyze module ${model.currentPythonModule}", 1.0) + + val timerHandler = runIndicatorWithTimeHandler( + indicator, + ProgressRange.SOLVING, + "Generate test cases for module ${model.currentPythonModule}", + totalModules, + index, + model.timeout, + ) + try { + val testSets = processor.testGenerate(mypyStorage, mypyConfigFile) + timerHandler.cancel(true) + if (testSets.isEmpty()) return@forEachIndexed + + localUpdateIndicator(ProgressRange.CODEGEN, "Generate tests code for module ${model.currentPythonModule}", 0.0) + val testCode = processor.testCodeGenerate(testSets) + + localUpdateIndicator(ProgressRange.CODEGEN, "Saving tests module ${model.currentPythonModule}", 0.9) + processor.saveTests(testCode) + } finally { + timerHandler.cancel(true) + } } } finally { LockFile.unlock() diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt new file mode 100644 index 0000000000..420a9087a6 --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt @@ -0,0 +1,72 @@ +package org.utbot.intellij.plugin.language.python + +import com.intellij.codeInsight.CodeInsightUtil +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiFileFactory +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.PythonTestGenerationProcessorNew +import org.utbot.python.utils.camelToSnakeCase +import java.nio.file.Path +import java.nio.file.Paths + +class PythonIntellijProcessor( + override val configuration: PythonTestGenerationConfig, + val project: Project, + val model: PythonTestLocalModel, +) : PythonTestGenerationProcessorNew() { + + override fun saveTests(testsCode: String) { + invokeLater { + runWriteAction { + val testDir = createPsiDirectoryForTestSourceRoot(model) + val testFileName = getOutputFileName(model) + val testPsiFile = PsiFileFactory.getInstance(model.project) + .createFileFromText(testFileName, PythonLanguageAssistant.language, testsCode) + testDir.findFile(testPsiFile.name)?.delete() + testDir.add(testPsiFile) + val file = testDir.findFile(testPsiFile.name)!! + CodeInsightUtil.positionCursor(model.project, file, file) + } + } + } + + private fun getDirectoriesFromRoot(root: Path, path: Path): List { + if (path == root || path.parent == null) + return emptyList() + return getDirectoriesFromRoot(root, path.parent) + listOf(path.fileName.toString()) + } + + private fun createPsiDirectoryForTestSourceRoot(model: PythonTestLocalModel): PsiDirectory { + val root = getContentRoot(model.project, model.file.virtualFile) + val paths = getDirectoriesFromRoot( + Paths.get(root.path), + Paths.get(model.testSourceRootPath) + ) + val rootPSI = getContainingElement(model.file) { it.virtualFile == root }!! + return paths.fold(rootPSI) { acc, folderName -> + acc.findSubdirectory(folderName) ?: acc.createSubdirectory(folderName) + } + } + + private fun getOutputFileName(model: PythonTestLocalModel): String { + val moduleName = model.currentPythonModule.camelToSnakeCase().replace('.', '_') + return if (model.containingClass == null) { + "test_$moduleName.py" + } else { + val className = model.containingClass.name?.camelToSnakeCase()?.replace('.', '_') + "test_${moduleName}_$className.py" + } + } + + override fun notGeneratedTestsAction(testedFunctions: List) { + showErrorDialogLater( + project, + message = "Cannot create tests for the following functions: " + testedFunctions.joinToString(), + title = "Python test generation error" + ) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt index 81e9dbe1df..631b8b7379 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt @@ -9,18 +9,15 @@ data class TestFileInformation( val moduleName: String, ) -interface PythonTestGenerationConfig { - val pythonPath: String - val testFileInformation: TestFileInformation - val sysPathDirectories: Set - val testedMethods: List - val timeout: Long - val timeoutForRun: Long - val testFramework: TestFramework - val executionPath: Path - val withMinimization: Boolean - val isCanceled: () -> Boolean - val requirementsInstaller: RequirementsInstaller - - fun saveCodeToFile(code: String): Unit -} \ No newline at end of file +class PythonTestGenerationConfig( + val pythonPath: String, + val testFileInformation: TestFileInformation, + val sysPathDirectories: Set, + val testedMethods: List, + val timeout: Long, + val timeoutForRun: Long, + val testFramework: TestFramework, + val executionPath: Path, + val withMinimization: Boolean, + val isCanceled: () -> Boolean, +) \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt index 3529b82b92..325e433a78 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt @@ -1,29 +1,42 @@ package org.utbot.python import org.parsers.python.PythonParser +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtClusterInfo +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.python.code.PythonCode +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonMethodId +import org.utbot.python.framework.api.python.PythonModel +import org.utbot.python.framework.api.python.PythonUtExecution +import org.utbot.python.framework.api.python.RawPythonAnnotation +import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.framework.codegen.model.PythonCodeGenerator +import org.utbot.python.framework.codegen.model.PythonImport +import org.utbot.python.framework.codegen.model.PythonSysPathImport +import org.utbot.python.framework.codegen.model.PythonSystemImport +import org.utbot.python.framework.codegen.model.PythonUserImport import org.utbot.python.newtyping.PythonFunctionDefinition import org.utbot.python.newtyping.general.CompositeType import org.utbot.python.newtyping.getPythonAttributes import org.utbot.python.newtyping.mypy.MypyAnnotationStorage import org.utbot.python.newtyping.mypy.MypyReportLine import org.utbot.python.newtyping.mypy.readMypyAnnotationStorageAndInitialErrors -import org.utbot.python.newtyping.mypy.setConfigFile +import org.utbot.python.newtyping.pythonName +import org.utbot.python.newtyping.pythonTypeName import org.utbot.python.utils.RequirementsUtils import java.io.File +import java.nio.file.Path +import kotlin.io.path.Path +import kotlin.io.path.pathString +// TODO: add asserts that one or less of containing classes and only one file abstract class PythonTestGenerationProcessorNew { abstract val configuration: PythonTestGenerationConfig - fun checkRequirements(): Boolean { - val requirements = RequirementsUtils.requirements + configuration.testFramework.mainPackage - if (!configuration.requirementsInstaller.checkRequirements(configuration.pythonPath, requirements)) { - configuration.requirementsInstaller.installRequirements(configuration.pythonPath, requirements) - return configuration.requirementsInstaller.checkRequirements(configuration.pythonPath, requirements) - } - return true - } - fun sourceCodeAnalyze(mypyConfigFile: File): Pair> { // val mypyConfigFile = setConfigFile(configuration.sysPathDirectories) return readMypyAnnotationStorageAndInitialErrors( @@ -52,7 +65,7 @@ abstract class PythonTestGenerationProcessorNew { ) val until = startTime + configuration.timeout - return configuration.testedMethods.mapIndexed { index, methodHeader -> + val tests = configuration.testedMethods.mapIndexed { index, methodHeader -> val methodsLeft = configuration.testedMethods.size - index val localUntil = (until - System.currentTimeMillis()) / methodsLeft + System.currentTimeMillis() val method = findMethodByHeader( @@ -63,11 +76,121 @@ abstract class PythonTestGenerationProcessorNew { ) testCaseGenerator.generate(method, localUntil) } + val (notEmptyTests, emptyTestSets) = tests.partition { it.executions.isNotEmpty() } + + if (emptyTestSets.isNotEmpty()) { + notGeneratedTestsAction(emptyTestSets.map { it.method.name }) + } + + return notEmptyTests + } + + fun testCodeGenerate(testSets: List): String { + val containingClassName = getContainingClassName(testSets) + val classId = PythonClassId(configuration.testFileInformation.moduleName, containingClassName) + + val methodIds = testSets.associate { + it.method to PythonMethodId( + classId, + it.method.name, + RawPythonAnnotation(pythonAnyClassId.name), + it.method.arguments.map { argument -> + argument.annotation?.let { annotation -> + RawPythonAnnotation(annotation) + } ?: pythonAnyClassId + } + ) + } + + val paramNames = testSets.associate { testSet -> + var params = testSet.method.arguments.map { it.name } + if (testSet.method.hasThisArgument) { + params = params.drop(1) + } + methodIds[testSet.method] as ExecutableId to params + }.toMutableMap() + + val allImports = collectImports(testSets) + + val context = UtContext(this::class.java.classLoader) + withUtContext(context) { + val codegen = PythonCodeGenerator( + classId, + paramNames = paramNames, + testFramework = configuration.testFramework, + testClassPackageName = "", + ) + val testCode = codegen.pythonGenerateAsStringWithTestReport( + testSets.map { testSet -> + val intRange = testSet.executions.indices + val clusterInfo = listOf(Pair(UtClusterInfo("FUZZER"), intRange)) + CgMethodTestSet( + executableId = methodIds[testSet.method] as ExecutableId, + executions = testSet.executions, + clustersInfo = clusterInfo, + ) + }, + allImports + ).generatedCode + return testCode + } + } + + abstract fun saveTests(testsCode: String) + + abstract fun notGeneratedTestsAction(testedFunctions: List) + + private fun getContainingClassName(testSets: List): String { + val containingClasses = testSets.map { it.method.containingPythonClass?.pythonName() ?: "TopLevelFunctions" } + return containingClasses.toSet().first() } + + private fun collectImports(notEmptyTests: List): Set { + val importParamModules = notEmptyTests.flatMap { testSet -> + testSet.executions.flatMap { execution -> + val params = (execution.stateAfter.parameters + execution.stateBefore.parameters).toMutableSet() + val self = mutableListOf(execution.stateBefore.thisInstance, execution.stateAfter.thisInstance) + if (execution is PythonUtExecution) { + params.addAll(execution.stateInit.parameters) + self.add(execution.stateInit.thisInstance) + } + (params + self) + .filterNotNull() + .flatMap { utModel -> + (utModel as PythonModel).let { + it.allContainingClassIds.map { classId -> + PythonUserImport(importName_ = classId.moduleName) + } + } + } + } + } + val importResultModules = notEmptyTests.flatMap { testSet -> + testSet.executions.mapNotNull { execution -> + if (execution.result is UtExecutionSuccess) { + (execution.result as UtExecutionSuccess).let { result -> + (result.model as PythonModel).let { + it.allContainingClassIds.map { classId -> + PythonUserImport(importName_ = classId.moduleName) + } + } + } + } else null + }.flatten() + } + val rootModule = configuration.testFileInformation.moduleName.split(".").first() + val testRootModule = PythonUserImport(importName_ = rootModule) + val sysImport = PythonSystemImport("sys") + val sysPathImports = relativizePaths(configuration.executionPath, configuration.sysPathDirectories).map { PythonSysPathImport(it) } - fun testCodeGenerate() {} + val testFrameworkModule = + configuration.testFramework.testSuperClass?.let { PythonUserImport(importName_ = (it as PythonClassId).rootModuleName) } - fun saveTests() {} + val allImports = (importParamModules + importResultModules + testRootModule + sysPathImports + listOf( + testFrameworkModule, sysImport + )).filterNotNull().toSet() + return allImports + } private fun findMethodByHeader( mypyStorage: MypyAnnotationStorage, @@ -99,4 +222,13 @@ abstract class PythonTestGenerationProcessorNew { ast = funcDef.body ) } + + private fun relativizePaths(rootPath: Path?, paths: Set): Set = + if (rootPath != null) { + paths.map { path -> + rootPath.relativize(Path(path)).pathString + }.toSet() + } else { + paths + } } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt b/utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt index 09d03fff53..db9123008a 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt @@ -1,6 +1,20 @@ package org.utbot.python +import org.utbot.python.utils.RequirementsUtils + interface RequirementsInstaller { fun checkRequirements(pythonPath: String, requirements: List): Boolean fun installRequirements(pythonPath: String, requirements: List) + + companion object { + fun checkRequirements(requirementsInstaller: RequirementsInstaller, pythonPath: String, additionalRequirements: List): Boolean { + val requirements = RequirementsUtils.requirements + additionalRequirements + if (!requirementsInstaller.checkRequirements(pythonPath, requirements)) { + requirementsInstaller.installRequirements(pythonPath, requirements) + return requirementsInstaller.checkRequirements(pythonPath, requirements) + } + return true + } + + } } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt index 9914345deb..bad9410d71 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt @@ -26,6 +26,7 @@ class PythonMethod( var definition: PythonFunctionDefinition, val ast: Block ) { + fun methodSignature(): String = "$name(" + arguments.joinToString(", ") { "${it.name}: ${it.annotation ?: pythonAnyClassId.name}" } + ")" diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt index fc809207a0..e88d0ba665 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt @@ -8,42 +8,23 @@ import org.utbot.framework.codegen.domain.ProjectType import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.domain.StaticsMocking import org.utbot.framework.codegen.domain.TestFramework -import org.utbot.framework.codegen.domain.models.CgAssignment -import org.utbot.framework.codegen.domain.models.CgLiteral import org.utbot.framework.codegen.domain.models.CgMethodTestSet -import org.utbot.framework.codegen.domain.models.CgVariable import org.utbot.framework.codegen.domain.models.SimpleTestClassModel import org.utbot.framework.codegen.generator.CodeGenerator import org.utbot.framework.codegen.renderer.CgAbstractRenderer import org.utbot.framework.codegen.renderer.CgPrinterImpl import org.utbot.framework.codegen.renderer.CgRendererContext -import org.utbot.framework.codegen.tree.CgComponents.clearContextRelatedStorage import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.UtModel import org.utbot.python.PythonMethod -import org.utbot.python.framework.api.python.PythonClassId -import org.utbot.python.framework.api.python.PythonTreeModel -import org.utbot.python.framework.api.python.util.pythonAnyClassId -import org.utbot.python.framework.api.python.util.pythonNoneClassId -import org.utbot.python.framework.api.python.util.pythonStrClassId import org.utbot.python.framework.codegen.PythonCgLanguageAssistant -import org.utbot.python.framework.codegen.model.constructor.tree.PythonCgStatementConstructorImpl import org.utbot.python.framework.codegen.model.constructor.tree.PythonCgTestClassConstructor -import org.utbot.python.framework.codegen.model.constructor.tree.PythonCgVariableConstructor import org.utbot.python.framework.codegen.model.constructor.visitor.CgPythonRenderer -import org.utbot.python.framework.codegen.model.tree.CgPythonDict -import org.utbot.python.framework.codegen.model.tree.CgPythonFunctionCall -import org.utbot.python.framework.codegen.model.tree.CgPythonList -import org.utbot.python.framework.codegen.model.tree.CgPythonRepr -import org.utbot.python.framework.codegen.model.tree.CgPythonTree import org.utbot.python.newtyping.general.Type import org.utbot.python.newtyping.pythonAnyType import org.utbot.python.newtyping.pythonModules import org.utbot.python.newtyping.pythonTypeRepresentation -import org.utbot.python.framework.codegen.toPythonRawString -import org.utbot.python.newtyping.pythonName class PythonCodeGenerator( classUnderTest: ClassId, @@ -95,119 +76,6 @@ class PythonCodeGenerator( } } - fun generateFunctionCall( - method: PythonMethod, - methodArguments: List, - directoriesForSysPath: Set, - functionModule: String, - additionalModules: Set = emptySet(), - fileForOutputName: String, - coverageDatabasePath: String, - ): String = withCustomContext(testClassCustomName = null) { - context.withTestClassFileScope { - val cgStatementConstructor = - context.cgLanguageAssistant.getStatementConstructorBy(context) as PythonCgStatementConstructorImpl - with(cgStatementConstructor) { - clearContextRelatedStorage() - (context.cgLanguageAssistant as PythonCgLanguageAssistant).clear() - - val renderer = CgAbstractRenderer.makeRenderer(context) as CgPythonRenderer - - val executorModuleName = "utbot_executor.executor" - val executorModuleNameAlias = "__utbot_executor" - val executorFunctionName = "$executorModuleNameAlias.run_calculate_function_value" - val failArgumentsFunctionName = "$executorModuleNameAlias.fail_argument_initialization" - - val importExecutor = PythonUserImport(executorModuleName, alias_ = executorModuleNameAlias) - val importSys = PythonSystemImport("sys") - val importSysPaths = directoriesForSysPath.map { PythonSysPathImport(it) } - val importFunction = PythonUserImport(functionModule) - val imports = - listOf(importSys) + importSysPaths + listOf( - importExecutor, - importFunction - ) + additionalModules.map { PythonUserImport(it) } - imports.toSet().forEach { - context.cgLanguageAssistant.getNameGeneratorBy(context).variableName(it.moduleName ?: it.importName) - renderer.renderPythonImport(it) - } - - val fullpath = CgLiteral(pythonStrClassId, method.moduleFilename.toPythonRawString()) - val outputPath = CgLiteral(pythonStrClassId, fileForOutputName.toPythonRawString()) - val databasePath = CgLiteral(pythonStrClassId, coverageDatabasePath.toPythonRawString()) - - val containingClass = method.containingPythonClass - var functionTextName = - if (containingClass == null) - method.name - else - "${containingClass.pythonName()}.${method.name}" - if (functionModule.isNotEmpty()) { - functionTextName = "$functionModule.$functionTextName" - } - - val functionName = CgLiteral(pythonStrClassId, functionTextName) - - val arguments = method.arguments.map { argument -> - CgVariable(argument.name, argument.annotation?.let { PythonClassId(it) } ?: pythonAnyClassId) - } - - if (method.arguments.isNotEmpty()) { - var argumentsTryCatch = tryBlock { - methodArguments.zip(arguments).map { (model, argument) -> - if (model is PythonTreeModel) { - val obj = - (context.cgLanguageAssistant.getVariableConstructorBy(context) as PythonCgVariableConstructor) - .getOrCreateVariable(model) - +CgAssignment( - argument, - (obj as CgPythonTree).value - ) - } else { - +CgAssignment(argument, CgLiteral(model.classId, model.toString())) - } - } - } - argumentsTryCatch = argumentsTryCatch.catch(PythonClassId("builtins.Exception")) { exception -> - +CgPythonFunctionCall( - pythonNoneClassId, - failArgumentsFunctionName, - listOf( - outputPath, - exception, - ) - ) - emptyLine() - +CgPythonRepr(pythonAnyClassId, "sys.exit()") - } - argumentsTryCatch.accept(renderer) - } - - val args = CgPythonList(emptyList()) - val kwargs = CgPythonDict( - arguments.associateBy { argument -> CgLiteral(pythonStrClassId, "'${argument.name}'") } - ) - - val executorCall = CgPythonFunctionCall( - pythonNoneClassId, - executorFunctionName, - listOf( - databasePath, - functionName, - args, - kwargs, - fullpath, - outputPath, - ) - ) - - executorCall.accept(renderer) - - renderer.toString() - } - } - } - fun generateMypyCheckCode( method: PythonMethod, methodAnnotations: Map, diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt index b041c4c585..33923ec76a 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt @@ -68,7 +68,7 @@ import org.utbot.python.framework.api.python.pythonBuiltinsModuleName import org.utbot.python.framework.api.python.util.pythonAnyClassId import org.utbot.python.framework.codegen.model.tree.* import java.lang.StringBuilder -import org.utbot.python.framework.codegen.toPythonRawString +import org.utbot.python.framework.codegen.utils.toPythonRawString internal class CgPythonRenderer( context: CgRendererContext, diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt index 9a3653722f..44c00025fb 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt @@ -1,4 +1,4 @@ -package org.utbot.python.framework.codegen +package org.utbot.python.framework.codegen.utils fun String.toPythonRawString(): String { From c13163dbd0a0faabea3c0e07211b2c6f8b8300a7 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 28 Jun 2023 11:25:48 +0300 Subject: [PATCH 05/44] New test generation processor for cli-python and refactor --- .../python/CliRequirementsInstaller.kt | 27 ++ .../cli/language/python/PythonCliProcessor.kt | 30 ++ .../python/PythonGenerateTestsCommand.kt | 107 +++--- .../org/utbot/cli/language/python/Utils.kt | 9 +- .../language/python/PythonDialogProcessor.kt | 328 +++++++----------- .../python/PythonIntellijProcessor.kt | 6 +- .../PythonTestGenerationProcessorNew.kt | 64 +++- .../evaluation/PythonCoverageReceiver.kt | 3 +- 8 files changed, 290 insertions(+), 284 deletions(-) create mode 100644 utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt create mode 100644 utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt new file mode 100644 index 0000000000..ce8dcfe294 --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt @@ -0,0 +1,27 @@ +package org.utbot.cli.language.python + +import mu.KLogger +import org.utbot.python.RequirementsInstaller +import org.utbot.python.utils.RequirementsUtils + +class CliRequirementsInstaller( + private val installRequirementsIfMissing: Boolean, + private val logger: KLogger, +) : RequirementsInstaller { + override fun checkRequirements(pythonPath: String, requirements: List): Boolean { + return RequirementsUtils.requirementsAreInstalled(pythonPath, requirements) + } + + override fun installRequirements(pythonPath: String, requirements: List) { + if (installRequirementsIfMissing) { + val result = RequirementsUtils.installRequirements(pythonPath, requirements) + if (result.exitValue != 0) { + System.err.println(result.stderr) + logger.error("Failed to install requirements.") + } + } else { + logger.error("Missing some requirements. Please add --install-requirements flag or install them manually.") + } + logger.info("Requirements: ${requirements.joinToString()}") + } +} \ No newline at end of file diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt new file mode 100644 index 0000000000..1699b12ff5 --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt @@ -0,0 +1,30 @@ +package org.utbot.cli.language.python + +import mu.KLogger +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.PythonTestGenerationProcessorNew +import org.utbot.python.PythonTestSet + +class PythonCliProcessor( + override val configuration: PythonTestGenerationConfig, + private val output: String, + private val logger: KLogger, + private val coverageOutput: String?, +) : PythonTestGenerationProcessorNew() { + + override fun saveTests(testsCode: String) { + writeToFileAndSave(output, testsCode) + } + + override fun notGeneratedTestsAction(testedFunctions: List) { + logger.error( + "Couldn't generate tests for the following functions: ${testedFunctions.joinToString()}" + ) + } + + override fun processCoverageInfo(testSets: List) { + val coverageReport = getCoverageInfo(testSets) + val output = coverageOutput ?: return + writeToFileAndSave(output, coverageReport) + } +} \ No newline at end of file diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index 72169f00a1..1ae352b093 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -9,8 +9,11 @@ import mu.KotlinLogging import org.parsers.python.PythonParser import org.utbot.framework.codegen.domain.TestFramework import org.utbot.python.PythonMethodHeader +import org.utbot.python.PythonTestGenerationConfig import org.utbot.python.PythonTestGenerationProcessor import org.utbot.python.PythonTestGenerationProcessor.processTestGeneration +import org.utbot.python.RequirementsInstaller +import org.utbot.python.TestFileInformation import org.utbot.python.code.PythonCode import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.codegen.model.Pytest @@ -18,6 +21,7 @@ import org.utbot.python.framework.codegen.model.Unittest import org.utbot.python.newtyping.ast.parseClassDefinition import org.utbot.python.newtyping.ast.parseFunctionDefinition import org.utbot.python.newtyping.mypy.dropInitFile +import org.utbot.python.newtyping.mypy.setConfigFile import org.utbot.python.utils.* import org.utbot.python.utils.RequirementsUtils.installRequirements import org.utbot.python.utils.RequirementsUtils.requirements @@ -200,29 +204,6 @@ class PythonGenerateTestsCommand : CliktCommand( } } - private fun processMissingRequirements(): PythonTestGenerationProcessor.MissingRequirementsActionResult { - if (installRequirementsIfMissing) { - logger.info("Installing requirements...") - val result = installRequirements(pythonPath) - if (result.exitValue == 0) - return PythonTestGenerationProcessor.MissingRequirementsActionResult.INSTALLED - System.err.println(result.stderr) - logger.error("Failed to install requirements.") - } else { - logger.error("Missing some requirements. Please add --install-requirements flag or install them manually.") - } - logger.info("Requirements: ${requirements.joinToString()}") - return PythonTestGenerationProcessor.MissingRequirementsActionResult.NOT_INSTALLED - } - - private fun writeToFileAndSave(filename: String, fileContent: String) { - val file = File(filename) - file.parentFile?.mkdirs() - file.writeText(fileContent) - file.createNewFile() - } - - override fun run() { val status = calculateValues() if (status is Fail) { @@ -230,48 +211,50 @@ class PythonGenerateTestsCommand : CliktCommand( return } - processTestGeneration( + logger.info("Checking requirements...") + val installer = CliRequirementsInstaller(installRequirementsIfMissing, logger) + val requirementsAreInstalled = RequirementsInstaller.checkRequirements( + installer, + pythonPath, + if (testFramework.isInstalled) emptyList() else listOf(testFramework.mainPackage) + ) + if (!requirementsAreInstalled) { + return + } + + val config = PythonTestGenerationConfig( pythonPath = pythonPath, - pythonFilePath = sourceFile.toAbsolutePath(), - pythonFileContent = sourceFileContent, - directoriesForSysPath = directoriesForSysPath.map { it.toAbsolutePath() }.toSet(), - currentPythonModule = currentPythonModule.dropInitFile(), - pythonMethods = pythonMethods, - containingClassName = pythonClass, + testFileInformation = TestFileInformation(sourceFile.toAbsolutePath(), sourceFileContent, currentPythonModule.dropInitFile()), + sysPathDirectories = directoriesForSysPath.toSet(), + testedMethods = pythonMethods, timeout = timeout, - testFramework = testFramework, timeoutForRun = timeoutForRun, - writeTestTextToFile = { generatedCode -> - writeToFileAndSave(output, generatedCode) - }, - pythonRunRoot = Paths.get("").toAbsolutePath(), - doNotCheckRequirements = doNotCheckRequirements, + testFramework = testFramework, + executionPath = Paths.get("").toAbsolutePath(), withMinimization = !doNotMinimize, - checkingRequirementsAction = { - logger.info("Checking requirements...") - }, - installingRequirementsAction = { - logger.info("Installing requirements...") - }, - requirementsAreNotInstalledAction = ::processMissingRequirements, - startedLoadingPythonTypesAction = { - logger.info("Loading information about Python types...") - }, - startedTestGenerationAction = { - logger.info("Generating tests...") - }, - notGeneratedTestsAction = { - logger.error( - "Couldn't generate tests for the following functions: ${it.joinToString()}" - ) - }, - processMypyWarnings = { messages -> messages.forEach { println(it) } }, - processCoverageInfo = { coverageReport -> - val output = coverageOutput ?: return@processTestGeneration - writeToFileAndSave(output, coverageReport) - } - ) { - logger.info("Finished test generation for the following functions: ${it.joinToString()}") - } + isCanceled = { false }, + ) + + val processor = PythonCliProcessor( + config, + output, + logger, + coverageOutput, + ) + + logger.info("Loading information about Python types...") + val (mypyStorage, _) = processor.sourceCodeAnalyze() + + logger.info("Generating tests...") + val testSets = processor.testGenerate(mypyStorage) + + logger.info("Saving coverage report...") + processor.processCoverageInfo(testSets) + + logger.info( + "Finished test generation for the following functions: ${ + testSets.joinToString { it.method.name } + }" + ) } } diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Utils.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Utils.kt index 8f3b92f673..8ff7f71457 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Utils.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Utils.kt @@ -19,4 +19,11 @@ fun findCurrentPythonModule( } fun String.toAbsolutePath(): String = - File(this).canonicalPath \ No newline at end of file + File(this).canonicalPath + +fun writeToFileAndSave(filename: String, fileContent: String) { + val file = File(filename) + file.parentFile?.mkdirs() + file.writeText(fileContent) + file.createNewFile() +} diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt index db9747d715..a5c1e383af 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt @@ -4,6 +4,7 @@ import com.intellij.codeInsight.CodeInsightUtil import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.invokeLater import com.intellij.openapi.application.readAction +import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileDocumentManager @@ -29,7 +30,6 @@ import com.jetbrains.python.psi.PyFunction import com.jetbrains.python.psi.resolve.QualifiedNameFinder import mu.KotlinLogging import kotlinx.coroutines.runBlocking -import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.module import org.jetbrains.kotlin.idea.util.projectStructure.sdk import org.jetbrains.kotlin.j2k.getContainingClass @@ -46,17 +46,10 @@ import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.codegen.PythonCgLanguageAssistant import org.utbot.python.newtyping.mypy.dropInitFile import org.utbot.python.newtyping.mypy.setConfigFile -import org.utbot.python.utils.RequirementsUtils.installRequirements -import org.utbot.python.utils.RequirementsUtils.requirements -import org.utbot.python.utils.camelToSnakeCase -import java.nio.file.Path -import java.nio.file.Paths import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit import kotlin.io.path.Path -const val DEFAULT_TIMEOUT_FOR_RUN_IN_MILLIS = 2000L - object PythonDialogProcessor { private val logger = KotlinLogging.logger {} @@ -66,7 +59,7 @@ object PythonDialogProcessor { CODEGEN(from = 0.95, to = 1.0), } - fun updateIndicator( + private fun updateIndicator( indicator: ProgressIndicator, range: ProgressRange, text: String, @@ -154,10 +147,6 @@ object PythonDialogProcessor { } } - private fun getPythonPath(elementsToShow: Set): String? { - return findSrcModules(elementsToShow).first().sdk?.homePath - } - private fun createDialog( project: Project, elementsToShow: Set, @@ -178,7 +167,7 @@ object PythonDialogProcessor { elementsToShow, focusedElements, project.service().generationTimeoutInMillis, - DEFAULT_TIMEOUT_FOR_RUN_IN_MILLIS, + project.service().hangingTestsTimeout.timeoutMs, cgLanguageAssistant = PythonCgLanguageAssistant, pythonPath = pythonPath, names = elementsToShow.associateBy { Pair(it.fileName()!!, it.name!!) }, @@ -214,64 +203,44 @@ object PythonDialogProcessor { } private fun groupPyElementsByModule(model: PythonTestsModel): Set { - return runBlocking { - readAction { - model.selectedElements - .groupBy { it.containingFile } - .flatMap { fileGroup -> - fileGroup.value - .groupBy { it is PyClass }.values - } - .filter { it.isNotEmpty() } - .map { - val realElements = it.map { member -> model.names[Pair(member.fileName(), member.name)]!! } - val file = realElements.first().containingFile as PyFile - val srcModule = getSrcModule(realElements.first()) - - val (directoriesForSysPath, moduleToImport) = getDirectoriesForSysPath(srcModule, file) - PythonTestLocalModel( - model.project, - model.timeout, - model.timeoutForRun, - model.cgLanguageAssistant, - model.pythonPath, - model.testSourceRootPath, - model.testFramework, - realElements.toSet(), - model.runtimeExceptionTestsBehaviour, - directoriesForSysPath, - moduleToImport.dropInitFile(), - file, - realElements.first().let { pyElement -> - if (pyElement is PyFunction) { - pyElement.containingClass - } else { - null - } + return ReadAction.nonBlocking> { + model.selectedElements + .groupBy { it.containingFile } + .flatMap { fileGroup -> + fileGroup.value + .groupBy { it is PyClass }.values + } + .filter { it.isNotEmpty() } + .map { + val realElements = it.map { member -> model.names[Pair(member.fileName(), member.name)]!! } + val file = realElements.first().containingFile as PyFile + val srcModule = getSrcModule(realElements.first()) + + val (directoriesForSysPath, moduleToImport) = getDirectoriesForSysPath(srcModule, file) + PythonTestLocalModel( + model.project, + model.timeout, + model.timeoutForRun, + model.cgLanguageAssistant, + model.pythonPath, + model.testSourceRootPath, + model.testFramework, + realElements.toSet(), + model.runtimeExceptionTestsBehaviour, + directoriesForSysPath, + moduleToImport.dropInitFile(), + file, + realElements.first().let { pyElement -> + if (pyElement is PyFunction) { + pyElement.containingClass + } else { + null } - ) - } - .toSet() - } - } - } - - private fun getOutputFileName(model: PythonTestLocalModel): String { - val moduleName = model.currentPythonModule.camelToSnakeCase().replace('.', '_') - return if (model.containingClass == null) { - "test_$moduleName.py" - } else { - val className = model.containingClass.name?.camelToSnakeCase()?.replace('.', '_') - "test_${moduleName}_$className.py" - } - } - - private fun getFileContent(file: PyFile): String { - return runBlocking { - readAction { - getContentFromPyFile(file) - } - } + } + ) + } + .toSet() + }.executeSynchronously() ?: emptySet() } private fun createTests(project: Project, baseModel: PythonTestsModel) { @@ -291,7 +260,9 @@ object PythonDialogProcessor { baseModel.pythonPath, if (baseModel.testFramework.isInstalled) emptyList() else listOf(baseModel.testFramework.mainPackage) ) - if (!requirementsAreInstalled) return // TODO: show message about stopping? + if (!requirementsAreInstalled) { + return + } val modelGroups = groupPyElementsByModule(baseModel) val totalModules = modelGroups.size @@ -301,12 +272,7 @@ object PythonDialogProcessor { localUpdateIndicator(ProgressRange.ANALYZE, "Analyze code: read files", 0.1) val methods = findSelectedPythonMethods(model) - val content = getFileContent(model.file) - - val requirementsList = requirements.toMutableList() - if (!model.testFramework.isInstalled) { - requirementsList += model.testFramework.mainPackage - } + val content = getContentFromPyFile(model.file) val config = PythonTestGenerationConfig( pythonPath = model.pythonPath, @@ -328,8 +294,7 @@ object PythonDialogProcessor { localUpdateIndicator(ProgressRange.ANALYZE, "Analyze module ${model.currentPythonModule}", 0.5) - val mypyConfigFile = setConfigFile(config.sysPathDirectories) - val (mypyStorage, _) = processor.sourceCodeAnalyze(mypyConfigFile) + val (mypyStorage, _) = processor.sourceCodeAnalyze() localUpdateIndicator(ProgressRange.ANALYZE, "Analyze module ${model.currentPythonModule}", 1.0) @@ -342,7 +307,7 @@ object PythonDialogProcessor { model.timeout, ) try { - val testSets = processor.testGenerate(mypyStorage, mypyConfigFile) + val testSets = processor.testGenerate(mypyStorage) timerHandler.cancel(true) if (testSets.isEmpty()) return@forEachIndexed @@ -351,6 +316,12 @@ object PythonDialogProcessor { localUpdateIndicator(ProgressRange.CODEGEN, "Saving tests module ${model.currentPythonModule}", 0.9) processor.saveTests(testCode) + + logger.info( + "Finished test generation for the following functions: ${ + testSets.joinToString { it.method.name } + }" + ) } finally { timerHandler.cancel(true) } @@ -361,78 +332,10 @@ object PythonDialogProcessor { } }) } +} - private fun getDirectoriesFromRoot(root: Path, path: Path): List { - if (path == root || path.parent == null) - return emptyList() - return getDirectoriesFromRoot(root, path.parent) + listOf(path.fileName.toString()) - } - - private fun createPsiDirectoryForTestSourceRoot(model: PythonTestLocalModel): PsiDirectory { - val root = getContentRoot(model.project, model.file.virtualFile) - val paths = getDirectoriesFromRoot( - Paths.get(root.path), - Paths.get(model.testSourceRootPath) - ) - val rootPSI = getContainingElement(model.file) { it.virtualFile == root }!! - return paths.fold(rootPSI) { acc, folderName -> - acc.findSubdirectory(folderName) ?: acc.createSubdirectory(folderName) - } - } - - private fun writeGeneratedCodeToPsiDocument(generatedCode: String, model: PythonTestLocalModel) { - invokeLater { - runWriteAction { - val testDir = createPsiDirectoryForTestSourceRoot(model) - val testFileName = getOutputFileName(model) - val testPsiFile = PsiFileFactory.getInstance(model.project) - .createFileFromText(testFileName, PythonLanguageAssistant.language, generatedCode) - testDir.findFile(testPsiFile.name)?.delete() - testDir.add(testPsiFile) - val file = testDir.findFile(testPsiFile.name)!! - CodeInsightUtil.positionCursor(model.project, file, file) - } - } - } - - private fun askAndInstallRequirementsLater(project: Project, pythonPath: String, requirementsList: List) { - val message = """ - Some requirements are not installed. - Requirements:
- ${requirementsList.joinToString("
")} -
- Install them? - """.trimIndent() - invokeLater { - val result = Messages.showOkCancelDialog( - project, - message, - "Requirements Error", - "Install", - "Cancel", - null - ) - if (result == Messages.CANCEL) - return@invokeLater - - ProgressManager.getInstance().run(object : Backgroundable(project, "Installing requirements") { - override fun run(indicator: ProgressIndicator) { - val installResult = installRequirements(pythonPath, requirementsList) - - if (installResult.exitValue != 0) { - showErrorDialogLater( - project, - "Requirements installing failed.
" + - "${installResult.stderr}

" + - "Try to install with pip:
" + - " ${requirementsList.joinToString("
")}", - "Requirements error" - ) - } - } - }) - } - } +fun getPythonPath(elementsToShow: Set): String? { + return findSrcModules(elementsToShow).first().sdk?.homePath } fun findSrcModules(elements: Collection): List { @@ -447,7 +350,10 @@ fun getFullName(element: PyElement): String { return QualifiedNameFinder.getQualifiedName(element) ?: error("Name for source class or function not found") } -fun getContentFromPyFile(file: PyFile) = file.viewProvider.contents.toString() +fun getContentFromPyFile(file: PyFile) = + ReadAction.nonBlocking { + file.viewProvider.contents.toString() + }.executeSynchronously() ?: error("Cannot read file $file") /* * Returns set of sys paths and tested file import path @@ -456,76 +362,74 @@ fun getDirectoriesForSysPath( srcModule: Module, file: PyFile ): Pair, String> { - return runBlocking { - readAction { - val sources = ModuleRootManager.getInstance(srcModule).getSourceRoots(false).toMutableList() - val ancestor = ProjectFileIndex.getInstance(file.project).getContentRootForFile(file.virtualFile) - if (ancestor != null) - sources.add(ancestor) - - // Collect sys.path directories with imported modules - val importedPaths = emptyList().toMutableList() - - // 1. import - file.importTargets.forEach { importTarget -> - importTarget.multiResolve().forEach { - val element = it.element - if (element != null) { - val directory = element.parent - if (directory is PsiDirectory) { - // If we have `import a.b.c` we need to add syspath to module `a` only - val additionalLevel = importTarget.importedQName?.componentCount?.dec() ?: 0 - directory.topParent(additionalLevel)?.let { dir -> - importedPaths.add(dir.virtualFile) - } + return ReadAction.nonBlocking, String>> { + val sources = ModuleRootManager.getInstance(srcModule).getSourceRoots(false).toMutableList() + val ancestor = ProjectFileIndex.getInstance(file.project).getContentRootForFile(file.virtualFile) + if (ancestor != null) + sources.add(ancestor) + + // Collect sys.path directories with imported modules + val importedPaths = emptyList().toMutableList() + + // 1. import + file.importTargets.forEach { importTarget -> + importTarget.multiResolve().forEach { + val element = it.element + if (element != null) { + val directory = element.parent + if (directory is PsiDirectory) { + // If we have `import a.b.c` we need to add syspath to module `a` only + val additionalLevel = importTarget.importedQName?.componentCount?.dec() ?: 0 + directory.topParent(additionalLevel)?.let { dir -> + importedPaths.add(dir.virtualFile) } } } } + } - // 2. from import ... - file.fromImports.forEach { importTarget -> - importTarget.resolveImportSourceCandidates().forEach { - val directory = it.parent - val isRelativeImport = - importTarget.relativeLevel > 0 // If we have `from . import a` we don't need to add syspath - if (directory is PsiDirectory && !isRelativeImport) { - // If we have `from a.b.c import d` we need to add syspath to module `a` only - val additionalLevel = importTarget.importSourceQName?.componentCount?.dec() ?: 0 - directory.topParent(additionalLevel)?.let { dir -> - importedPaths.add(dir.virtualFile) - } + // 2. from import ... + file.fromImports.forEach { importTarget -> + importTarget.resolveImportSourceCandidates().forEach { + val directory = it.parent + val isRelativeImport = + importTarget.relativeLevel > 0 // If we have `from . import a` we don't need to add syspath + if (directory is PsiDirectory && !isRelativeImport) { + // If we have `from a.b.c import d` we need to add syspath to module `a` only + val additionalLevel = importTarget.importSourceQName?.componentCount?.dec() ?: 0 + directory.topParent(additionalLevel)?.let { dir -> + importedPaths.add(dir.virtualFile) } } } + } - // Select modules only from this project but not from installation directory - importedPaths.forEach { - val path = it.toNioPath() - val hasSitePackages = - (0 until (path.nameCount)).any { i -> path.subpath(i, i + 1).toString() == "site-packages" } - if (it.isProjectSubmodule(ancestor) && !hasSitePackages) { - sources.add(it) - } + // Select modules only from this project but not from installation directory + importedPaths.forEach { + val path = it.toNioPath() + val hasSitePackages = + (0 until (path.nameCount)).any { i -> path.subpath(i, i + 1).toString() == "site-packages" } + if (it.isProjectSubmodule(ancestor) && !hasSitePackages) { + sources.add(it) } + } - val fileName = file.name.removeSuffix(".py") - val importPath = ancestor?.let { - VfsUtil.getParentDir( - VfsUtilCore.getRelativeLocation(file.virtualFile, it) - ) - } ?: "" - val importStringPath = listOf( - importPath.toPath().joinToString("."), - fileName + val fileName = file.name.removeSuffix(".py") + val importPath = ancestor?.let { + VfsUtil.getParentDir( + VfsUtilCore.getRelativeLocation(file.virtualFile, it) ) - .filterNot { it.isEmpty() } - .joinToString(".") + } ?: "" + val importStringPath = listOf( + importPath.toPath().joinToString("."), + fileName + ) + .filterNot { it.isEmpty() } + .joinToString(".") - Pair( - sources.map { it.path }.toSet(), - importStringPath - ) - } - } + Pair( + sources.map { it.path }.toSet(), + importStringPath + ) + }.executeSynchronously() ?: error("Cannot collect sys path directories") } \ No newline at end of file diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt index 420a9087a6..90c66452dc 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt @@ -9,6 +9,7 @@ import com.intellij.psi.PsiFileFactory import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.python.PythonTestGenerationConfig import org.utbot.python.PythonTestGenerationProcessorNew +import org.utbot.python.PythonTestSet import org.utbot.python.utils.camelToSnakeCase import java.nio.file.Path import java.nio.file.Paths @@ -18,7 +19,6 @@ class PythonIntellijProcessor( val project: Project, val model: PythonTestLocalModel, ) : PythonTestGenerationProcessorNew() { - override fun saveTests(testsCode: String) { invokeLater { runWriteAction { @@ -69,4 +69,8 @@ class PythonIntellijProcessor( title = "Python test generation error" ) } + + override fun processCoverageInfo(testSets: List) { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt index 325e433a78..a5606bb467 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt @@ -1,5 +1,7 @@ package org.utbot.python +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import org.parsers.python.PythonParser import org.utbot.framework.codegen.domain.models.CgMethodTestSet import org.utbot.framework.plugin.api.ExecutableId @@ -25,10 +27,8 @@ import org.utbot.python.newtyping.getPythonAttributes import org.utbot.python.newtyping.mypy.MypyAnnotationStorage import org.utbot.python.newtyping.mypy.MypyReportLine import org.utbot.python.newtyping.mypy.readMypyAnnotationStorageAndInitialErrors +import org.utbot.python.newtyping.mypy.setConfigFile import org.utbot.python.newtyping.pythonName -import org.utbot.python.newtyping.pythonTypeName -import org.utbot.python.utils.RequirementsUtils -import java.io.File import java.nio.file.Path import kotlin.io.path.Path import kotlin.io.path.pathString @@ -37,8 +37,8 @@ import kotlin.io.path.pathString abstract class PythonTestGenerationProcessorNew { abstract val configuration: PythonTestGenerationConfig - fun sourceCodeAnalyze(mypyConfigFile: File): Pair> { -// val mypyConfigFile = setConfigFile(configuration.sysPathDirectories) + fun sourceCodeAnalyze(): Pair> { + val mypyConfigFile = setConfigFile(configuration.sysPathDirectories) return readMypyAnnotationStorageAndInitialErrors( configuration.pythonPath, configuration.testFileInformation.testedFilePath, @@ -47,7 +47,8 @@ abstract class PythonTestGenerationProcessorNew { ) } - fun testGenerate(mypyStorage: MypyAnnotationStorage, mypyConfigFile: File): List { + fun testGenerate(mypyStorage: MypyAnnotationStorage): List { + val mypyConfigFile = setConfigFile(configuration.sysPathDirectories) val startTime = System.currentTimeMillis() val testCaseGenerator = PythonTestCaseGenerator( @@ -140,6 +141,8 @@ abstract class PythonTestGenerationProcessorNew { abstract fun notGeneratedTestsAction(testedFunctions: List) + abstract fun processCoverageInfo(testSets: List) + private fun getContainingClassName(testSets: List): String { val containingClasses = testSets.map { it.method.containingPythonClass?.pythonName() ?: "TopLevelFunctions" } return containingClasses.toSet().first() @@ -231,4 +234,53 @@ abstract class PythonTestGenerationProcessorNew { } else { paths } + + data class InstructionSet( + val start: Int, + val end: Int + ) + + data class CoverageInfo( + val covered: List, + val notCovered: List + ) + + private val moshi: Moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() + private val jsonAdapter = moshi.adapter(CoverageInfo::class.java) + + private fun getInstructionSetList(instructions: Collection): List = + instructions.sorted().fold(emptyList()) { acc, lineNumber -> + if (acc.isEmpty()) + return@fold listOf(InstructionSet(lineNumber, lineNumber)) + val elem = acc.last() + if (elem.end + 1 == lineNumber) + acc.dropLast(1) + listOf(InstructionSet(elem.start, lineNumber)) + else + acc + listOf(InstructionSet(lineNumber, lineNumber)) + } + protected fun getCoverageInfo(testSets: List): String { + val covered = mutableSetOf() + val missed = mutableSetOf>() + testSets.forEach { testSet -> + testSet.executions.forEach inner@{ execution -> + val coverage = execution.coverage ?: return@inner + coverage.coveredInstructions.forEach { covered.add(it.lineNumber) } + missed.add(coverage.missedInstructions.map { it.lineNumber }.toSet()) + } + } + val coveredInstructionSets = getInstructionSetList(covered) + val missedInstructionSets = + if (missed.isEmpty()) + emptyList() + else + getInstructionSetList(missed.reduce { a, b -> a intersect b }) + + return jsonAdapter.toJson( + CoverageInfo( + coveredInstructionSets, + missedInstructionSets + ) + ) + } + } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt index 6c46215d13..0707d347af 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt @@ -29,12 +29,11 @@ class PythonCoverageReceiver( val request = DatagramPacket(buf, buf.size) socket.receive(request) val (id, line) = request.data.decodeToString().take(request.length).split(":") - logger.debug { "Got coverage: $id, $line" } val lineNumber = line.toInt() coverageStorage.getOrPut(id) { mutableSetOf() } .add(lineNumber) } } catch (ex: SocketException) { - logger.error("Socket error: " + ex.message) + logger.error(ex.message) } catch (ex: IOException) { logger.error("IO error: " + ex.message) } From a7ae3bb8eb49afed1259a8c9a256a9fabc8ad299 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 28 Jun 2023 11:27:56 +0300 Subject: [PATCH 06/44] Rename PythonTestGenerationProcessor --- .../cli/language/python/PythonCliProcessor.kt | 4 +- .../python/PythonGenerateTestsCommand.kt | 5 - .../python/PythonIntellijProcessor.kt | 4 +- .../python/PythonTestGenerationProcessor.kt | 378 ++++++++---------- .../PythonTestGenerationProcessorNew.kt | 286 ------------- 5 files changed, 161 insertions(+), 516 deletions(-) delete mode 100644 utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt index 1699b12ff5..801686e242 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt @@ -2,7 +2,7 @@ package org.utbot.cli.language.python import mu.KLogger import org.utbot.python.PythonTestGenerationConfig -import org.utbot.python.PythonTestGenerationProcessorNew +import org.utbot.python.PythonTestGenerationProcessor import org.utbot.python.PythonTestSet class PythonCliProcessor( @@ -10,7 +10,7 @@ class PythonCliProcessor( private val output: String, private val logger: KLogger, private val coverageOutput: String?, -) : PythonTestGenerationProcessorNew() { +) : PythonTestGenerationProcessor() { override fun saveTests(testsCode: String) { writeToFileAndSave(output, testsCode) diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index 1ae352b093..a822f1d69d 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -10,8 +10,6 @@ import org.parsers.python.PythonParser import org.utbot.framework.codegen.domain.TestFramework import org.utbot.python.PythonMethodHeader import org.utbot.python.PythonTestGenerationConfig -import org.utbot.python.PythonTestGenerationProcessor -import org.utbot.python.PythonTestGenerationProcessor.processTestGeneration import org.utbot.python.RequirementsInstaller import org.utbot.python.TestFileInformation import org.utbot.python.code.PythonCode @@ -21,10 +19,7 @@ import org.utbot.python.framework.codegen.model.Unittest import org.utbot.python.newtyping.ast.parseClassDefinition import org.utbot.python.newtyping.ast.parseFunctionDefinition import org.utbot.python.newtyping.mypy.dropInitFile -import org.utbot.python.newtyping.mypy.setConfigFile import org.utbot.python.utils.* -import org.utbot.python.utils.RequirementsUtils.installRequirements -import org.utbot.python.utils.RequirementsUtils.requirements import java.io.File import java.nio.file.Paths diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt index 90c66452dc..d0a514ac79 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt @@ -8,7 +8,7 @@ import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiFileFactory import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.python.PythonTestGenerationConfig -import org.utbot.python.PythonTestGenerationProcessorNew +import org.utbot.python.PythonTestGenerationProcessor import org.utbot.python.PythonTestSet import org.utbot.python.utils.camelToSnakeCase import java.nio.file.Path @@ -18,7 +18,7 @@ class PythonIntellijProcessor( override val configuration: PythonTestGenerationConfig, val project: Project, val model: PythonTestLocalModel, -) : PythonTestGenerationProcessorNew() { +) : PythonTestGenerationProcessor() { override fun saveTests(testsCode: String) { invokeLater { runWriteAction { diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt index 8206263588..383961fc3e 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt @@ -3,11 +3,6 @@ package org.utbot.python import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import org.parsers.python.PythonParser -import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour -import org.utbot.python.framework.codegen.model.PythonSysPathImport -import org.utbot.python.framework.codegen.model.PythonSystemImport -import org.utbot.python.framework.codegen.model.PythonUserImport -import org.utbot.framework.codegen.domain.TestFramework import org.utbot.framework.codegen.domain.models.CgMethodTestSet import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.UtClusterInfo @@ -22,224 +17,182 @@ import org.utbot.python.framework.api.python.PythonUtExecution import org.utbot.python.framework.api.python.RawPythonAnnotation import org.utbot.python.framework.api.python.util.pythonAnyClassId import org.utbot.python.framework.codegen.model.PythonCodeGenerator +import org.utbot.python.framework.codegen.model.PythonImport +import org.utbot.python.framework.codegen.model.PythonSysPathImport +import org.utbot.python.framework.codegen.model.PythonSystemImport +import org.utbot.python.framework.codegen.model.PythonUserImport import org.utbot.python.newtyping.PythonFunctionDefinition import org.utbot.python.newtyping.general.CompositeType import org.utbot.python.newtyping.getPythonAttributes import org.utbot.python.newtyping.mypy.MypyAnnotationStorage +import org.utbot.python.newtyping.mypy.MypyReportLine import org.utbot.python.newtyping.mypy.readMypyAnnotationStorageAndInitialErrors import org.utbot.python.newtyping.mypy.setConfigFile -import org.utbot.python.typing.MypyAnnotations -import org.utbot.python.utils.Cleaner -import org.utbot.python.utils.RequirementsUtils.requirementsAreInstalled -import org.utbot.python.utils.getLineOfFunction +import org.utbot.python.newtyping.pythonName import java.nio.file.Path import kotlin.io.path.Path import kotlin.io.path.pathString -object PythonTestGenerationProcessor { - fun processTestGeneration( - pythonPath: String, - pythonFilePath: String, - pythonFileContent: String, - directoriesForSysPath: Set, - currentPythonModule: String, - pythonMethods: List, - containingClassName: String?, - timeout: Long, - testFramework: TestFramework, - timeoutForRun: Long, - writeTestTextToFile: (String) -> Unit, - pythonRunRoot: Path, - doNotCheckRequirements: Boolean = false, - withMinimization: Boolean = true, - runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.FAIL, - isCanceled: () -> Boolean = { false }, - checkingRequirementsAction: () -> Unit = {}, - installingRequirementsAction: () -> Unit = {}, - requirementsAreNotInstalledAction: () -> MissingRequirementsActionResult = { - MissingRequirementsActionResult.NOT_INSTALLED - }, - startedLoadingPythonTypesAction: () -> Unit = {}, - startedTestGenerationAction: () -> Unit = {}, - notGeneratedTestsAction: (List) -> Unit = {}, // take names of functions without tests - processMypyWarnings: (List) -> Unit = {}, - processCoverageInfo: (String) -> Unit = {}, - startedCleaningAction: () -> Unit = {}, - finishedAction: (List) -> Unit = {}, // take names of functions with generated tests - ) { - Cleaner.restart() - - try { - if (!doNotCheckRequirements) { - checkingRequirementsAction() - if (!requirementsAreInstalled(pythonPath)) { - installingRequirementsAction() - val result = requirementsAreNotInstalledAction() - if (result == MissingRequirementsActionResult.NOT_INSTALLED) - return - } - } +// TODO: add asserts that one or less of containing classes and only one file +abstract class PythonTestGenerationProcessor { + abstract val configuration: PythonTestGenerationConfig + + fun sourceCodeAnalyze(): Pair> { + val mypyConfigFile = setConfigFile(configuration.sysPathDirectories) + return readMypyAnnotationStorageAndInitialErrors( + configuration.pythonPath, + configuration.testFileInformation.testedFilePath, + configuration.testFileInformation.moduleName, + mypyConfigFile + ) + } - startedLoadingPythonTypesAction() + fun testGenerate(mypyStorage: MypyAnnotationStorage): List { + val mypyConfigFile = setConfigFile(configuration.sysPathDirectories) + val startTime = System.currentTimeMillis() + + val testCaseGenerator = PythonTestCaseGenerator( + withMinimization = configuration.withMinimization, + directoriesForSysPath = configuration.sysPathDirectories, + curModule = configuration.testFileInformation.moduleName, + pythonPath = configuration.pythonPath, + fileOfMethod = configuration.testFileInformation.testedFilePath, + isCancelled = configuration.isCanceled, + timeoutForRun = configuration.timeoutForRun, + sourceFileContent = configuration.testFileInformation.testedFileContent, + mypyStorage = mypyStorage, + mypyReportLine = emptyList(), + mypyConfigFile = mypyConfigFile, + ) - val mypyConfigFile = setConfigFile(directoriesForSysPath) - val (mypyStorage, report) = readMypyAnnotationStorageAndInitialErrors( - pythonPath, - pythonFilePath, - currentPythonModule, - mypyConfigFile + val until = startTime + configuration.timeout + val tests = configuration.testedMethods.mapIndexed { index, methodHeader -> + val methodsLeft = configuration.testedMethods.size - index + val localUntil = (until - System.currentTimeMillis()) / methodsLeft + System.currentTimeMillis() + val method = findMethodByHeader( + mypyStorage, + methodHeader, + configuration.testFileInformation.moduleName, + configuration.testFileInformation.testedFileContent ) + testCaseGenerator.generate(method, localUntil) + } + val (notEmptyTests, emptyTestSets) = tests.partition { it.executions.isNotEmpty() } - startedTestGenerationAction() + if (emptyTestSets.isNotEmpty()) { + notGeneratedTestsAction(emptyTestSets.map { it.method.name }) + } - val startTime = System.currentTimeMillis() + return notEmptyTests + } - val testCaseGenerator = PythonTestCaseGenerator( - withMinimization = withMinimization, - directoriesForSysPath = directoriesForSysPath, - curModule = currentPythonModule, - pythonPath = pythonPath, - fileOfMethod = pythonFilePath, - isCancelled = isCanceled, - timeoutForRun = timeoutForRun, - sourceFileContent = pythonFileContent, - mypyStorage = mypyStorage, - mypyReportLine = report, - mypyConfigFile = mypyConfigFile, + fun testCodeGenerate(testSets: List): String { + val containingClassName = getContainingClassName(testSets) + val classId = PythonClassId(configuration.testFileInformation.moduleName, containingClassName) + + val methodIds = testSets.associate { + it.method to PythonMethodId( + classId, + it.method.name, + RawPythonAnnotation(pythonAnyClassId.name), + it.method.arguments.map { argument -> + argument.annotation?.let { annotation -> + RawPythonAnnotation(annotation) + } ?: pythonAnyClassId + } ) + } - val until = startTime + timeout - val tests = pythonMethods.mapIndexed { index, methodHeader -> - val methodsLeft = pythonMethods.size - index - val localUntil = (until - System.currentTimeMillis()) / methodsLeft + System.currentTimeMillis() - val method = findMethodByHeader(mypyStorage, methodHeader, currentPythonModule, pythonFileContent) - testCaseGenerator.generate(method, localUntil) - } - - val (notEmptyTests, emptyTestSets) = tests.partition { it.executions.isNotEmpty() } - - if (emptyTestSets.isNotEmpty()) { - notGeneratedTestsAction(emptyTestSets.map { it.method.name }) + val paramNames = testSets.associate { testSet -> + var params = testSet.method.arguments.map { it.name } + if (testSet.method.hasThisArgument) { + params = params.drop(1) } + methodIds[testSet.method] as ExecutableId to params + }.toMutableMap() + + val allImports = collectImports(testSets) + + val context = UtContext(this::class.java.classLoader) + withUtContext(context) { + val codegen = PythonCodeGenerator( + classId, + paramNames = paramNames, + testFramework = configuration.testFramework, + testClassPackageName = "", + ) + val testCode = codegen.pythonGenerateAsStringWithTestReport( + testSets.map { testSet -> + val intRange = testSet.executions.indices + val clusterInfo = listOf(Pair(UtClusterInfo("FUZZER"), intRange)) + CgMethodTestSet( + executableId = methodIds[testSet.method] as ExecutableId, + executions = testSet.executions, + clustersInfo = clusterInfo, + ) + }, + allImports + ).generatedCode + return testCode + } + } - if (notEmptyTests.isEmpty()) - return + abstract fun saveTests(testsCode: String) - val classId = - if (containingClassName == null) - PythonClassId(currentPythonModule, "TopLevelFunctions") - else - PythonClassId(currentPythonModule, containingClassName) + abstract fun notGeneratedTestsAction(testedFunctions: List) - val methodIds = notEmptyTests.associate { - it.method to PythonMethodId( - classId, - it.method.name, - RawPythonAnnotation(pythonAnyClassId.name), - it.method.arguments.map { argument -> - argument.annotation?.let { annotation -> - RawPythonAnnotation(annotation) - } ?: pythonAnyClassId - } - ) - } + abstract fun processCoverageInfo(testSets: List) - val paramNames = notEmptyTests.associate { testSet -> - var params = testSet.method.arguments.map { it.name } - if (testSet.method.hasThisArgument) { - params = params.drop(1) + private fun getContainingClassName(testSets: List): String { + val containingClasses = testSets.map { it.method.containingPythonClass?.pythonName() ?: "TopLevelFunctions" } + return containingClasses.toSet().first() + } + + private fun collectImports(notEmptyTests: List): Set { + val importParamModules = notEmptyTests.flatMap { testSet -> + testSet.executions.flatMap { execution -> + val params = (execution.stateAfter.parameters + execution.stateBefore.parameters).toMutableSet() + val self = mutableListOf(execution.stateBefore.thisInstance, execution.stateAfter.thisInstance) + if (execution is PythonUtExecution) { + params.addAll(execution.stateInit.parameters) + self.add(execution.stateInit.thisInstance) } - methodIds[testSet.method] as ExecutableId to params - }.toMutableMap() - - val importParamModules = notEmptyTests.flatMap { testSet -> - testSet.executions.flatMap { execution -> - val params = (execution.stateAfter.parameters + execution.stateBefore.parameters).toMutableSet() - val self = mutableListOf(execution.stateBefore.thisInstance, execution.stateAfter.thisInstance) - if (execution is PythonUtExecution) { - params.addAll(execution.stateInit.parameters) - self.add(execution.stateInit.thisInstance) - } - (params + self) - .filterNotNull() - .flatMap { utModel -> + (params + self) + .filterNotNull() + .flatMap { utModel -> (utModel as PythonModel).let { it.allContainingClassIds.map { classId -> PythonUserImport(importName_ = classId.moduleName) } } } - } } - val importResultModules = notEmptyTests.flatMap { testSet -> - testSet.executions.mapNotNull { execution -> - if (execution.result is UtExecutionSuccess) { - (execution.result as UtExecutionSuccess).let { result -> - (result.model as PythonModel).let { - it.allContainingClassIds.map { classId -> - PythonUserImport(importName_ = classId.moduleName) - } + } + val importResultModules = notEmptyTests.flatMap { testSet -> + testSet.executions.mapNotNull { execution -> + if (execution.result is UtExecutionSuccess) { + (execution.result as UtExecutionSuccess).let { result -> + (result.model as PythonModel).let { + it.allContainingClassIds.map { classId -> + PythonUserImport(importName_ = classId.moduleName) } } - } else null - }.flatten() - } - val testRootModules = notEmptyTests.mapNotNull { testSet -> - methodIds[testSet.method]?.rootModuleName?.let { PythonUserImport(importName_ = it) } - } - val sysImport = PythonSystemImport("sys") - val sysPathImports = relativizePaths(pythonRunRoot, directoriesForSysPath).map { PythonSysPathImport(it) } - - val testFrameworkModule = - testFramework.testSuperClass?.let { PythonUserImport(importName_ = (it as PythonClassId).rootModuleName) } - - val allImports = ( - importParamModules + importResultModules + testRootModules + sysPathImports + listOf( - testFrameworkModule, - sysImport - ) - ) - .filterNotNull() -// .filterNot { it.importName == pythonBuiltinsModuleName } - .toSet() - - val context = UtContext(this::class.java.classLoader) - withUtContext(context) { - val codegen = PythonCodeGenerator( - classId, - paramNames = paramNames, - testFramework = testFramework, - testClassPackageName = "", - runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, - ) - val testCode = codegen.pythonGenerateAsStringWithTestReport( - notEmptyTests.map { testSet -> - val intRange = testSet.executions.indices - val clusterInfo = listOf(Pair(UtClusterInfo("FUZZER"), intRange)) - CgMethodTestSet( - executableId = methodIds[testSet.method] as ExecutableId, - executions = testSet.executions, - clustersInfo = clusterInfo, - ) - }, - allImports - ).generatedCode - writeTestTextToFile(testCode) - } - - val coverageInfo = getCoverageInfo(notEmptyTests) - processCoverageInfo(coverageInfo) - - val mypyReport = getMypyReport(notEmptyTests, pythonFileContent) - if (mypyReport.isNotEmpty()) - processMypyWarnings(mypyReport) - - finishedAction(notEmptyTests.map { it.method.name }) - - } finally { - startedCleaningAction() - Cleaner.doCleaning() + } + } else null + }.flatten() } + val rootModule = configuration.testFileInformation.moduleName.split(".").first() + val testRootModule = PythonUserImport(importName_ = rootModule) + val sysImport = PythonSystemImport("sys") + val sysPathImports = relativizePaths(configuration.executionPath, configuration.sysPathDirectories).map { PythonSysPathImport(it) } + + val testFrameworkModule = + configuration.testFramework.testSuperClass?.let { PythonUserImport(importName_ = (it as PythonClassId).rootModuleName) } + + val allImports = (importParamModules + importResultModules + testRootModule + sysPathImports + listOf( + testFrameworkModule, sysImport + )).filterNotNull().toSet() + return allImports } private fun findMethodByHeader( @@ -273,26 +226,13 @@ object PythonTestGenerationProcessor { ) } - enum class MissingRequirementsActionResult { - INSTALLED, NOT_INSTALLED - } - - private fun getMypyReport(notEmptyTests: List, pythonFileContent: String): List = - notEmptyTests.flatMap { testSet -> - val lineOfFunction = getLineOfFunction(pythonFileContent, testSet.method.name) - val msgLines = testSet.mypyReport.mapNotNull { - if (it.file != MypyAnnotations.TEMPORARY_MYPY_FILE) - null - else if (lineOfFunction != null && it.line >= 0) - ":${it.line + lineOfFunction}: ${it.type}: ${it.message}" - else - "${it.type}: ${it.message}" - } - if (msgLines.isNotEmpty()) { - listOf("MYPY REPORT (function ${testSet.method.name})") + msgLines - } else { - emptyList() - } + private fun relativizePaths(rootPath: Path?, paths: Set): Set = + if (rootPath != null) { + paths.map { path -> + rootPath.relativize(Path(path)).pathString + }.toSet() + } else { + paths } data class InstructionSet( @@ -318,8 +258,7 @@ object PythonTestGenerationProcessor { else acc + listOf(InstructionSet(lineNumber, lineNumber)) } - - private fun getCoverageInfo(testSets: List): String { + protected fun getCoverageInfo(testSets: List): String { val covered = mutableSetOf() val missed = mutableSetOf>() testSets.forEach { testSet -> @@ -336,15 +275,12 @@ object PythonTestGenerationProcessor { else getInstructionSetList(missed.reduce { a, b -> a intersect b }) - return jsonAdapter.toJson(CoverageInfo(coveredInstructionSets, missedInstructionSets)) + return jsonAdapter.toJson( + CoverageInfo( + coveredInstructionSets, + missedInstructionSets + ) + ) } - private fun relativizePaths(rootPath: Path?, paths: Set): Set = - if (rootPath != null) { - paths.map { path -> - rootPath.relativize(Path(path)).pathString - }.toSet() - } else { - paths - } -} +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt deleted file mode 100644 index a5606bb467..0000000000 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessorNew.kt +++ /dev/null @@ -1,286 +0,0 @@ -package org.utbot.python - -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import org.parsers.python.PythonParser -import org.utbot.framework.codegen.domain.models.CgMethodTestSet -import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.UtClusterInfo -import org.utbot.framework.plugin.api.UtExecutionSuccess -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.python.code.PythonCode -import org.utbot.python.framework.api.python.PythonClassId -import org.utbot.python.framework.api.python.PythonMethodId -import org.utbot.python.framework.api.python.PythonModel -import org.utbot.python.framework.api.python.PythonUtExecution -import org.utbot.python.framework.api.python.RawPythonAnnotation -import org.utbot.python.framework.api.python.util.pythonAnyClassId -import org.utbot.python.framework.codegen.model.PythonCodeGenerator -import org.utbot.python.framework.codegen.model.PythonImport -import org.utbot.python.framework.codegen.model.PythonSysPathImport -import org.utbot.python.framework.codegen.model.PythonSystemImport -import org.utbot.python.framework.codegen.model.PythonUserImport -import org.utbot.python.newtyping.PythonFunctionDefinition -import org.utbot.python.newtyping.general.CompositeType -import org.utbot.python.newtyping.getPythonAttributes -import org.utbot.python.newtyping.mypy.MypyAnnotationStorage -import org.utbot.python.newtyping.mypy.MypyReportLine -import org.utbot.python.newtyping.mypy.readMypyAnnotationStorageAndInitialErrors -import org.utbot.python.newtyping.mypy.setConfigFile -import org.utbot.python.newtyping.pythonName -import java.nio.file.Path -import kotlin.io.path.Path -import kotlin.io.path.pathString - -// TODO: add asserts that one or less of containing classes and only one file -abstract class PythonTestGenerationProcessorNew { - abstract val configuration: PythonTestGenerationConfig - - fun sourceCodeAnalyze(): Pair> { - val mypyConfigFile = setConfigFile(configuration.sysPathDirectories) - return readMypyAnnotationStorageAndInitialErrors( - configuration.pythonPath, - configuration.testFileInformation.testedFilePath, - configuration.testFileInformation.moduleName, - mypyConfigFile - ) - } - - fun testGenerate(mypyStorage: MypyAnnotationStorage): List { - val mypyConfigFile = setConfigFile(configuration.sysPathDirectories) - val startTime = System.currentTimeMillis() - - val testCaseGenerator = PythonTestCaseGenerator( - withMinimization = configuration.withMinimization, - directoriesForSysPath = configuration.sysPathDirectories, - curModule = configuration.testFileInformation.moduleName, - pythonPath = configuration.pythonPath, - fileOfMethod = configuration.testFileInformation.testedFilePath, - isCancelled = configuration.isCanceled, - timeoutForRun = configuration.timeoutForRun, - sourceFileContent = configuration.testFileInformation.testedFileContent, - mypyStorage = mypyStorage, - mypyReportLine = emptyList(), - mypyConfigFile = mypyConfigFile, - ) - - val until = startTime + configuration.timeout - val tests = configuration.testedMethods.mapIndexed { index, methodHeader -> - val methodsLeft = configuration.testedMethods.size - index - val localUntil = (until - System.currentTimeMillis()) / methodsLeft + System.currentTimeMillis() - val method = findMethodByHeader( - mypyStorage, - methodHeader, - configuration.testFileInformation.moduleName, - configuration.testFileInformation.testedFileContent - ) - testCaseGenerator.generate(method, localUntil) - } - val (notEmptyTests, emptyTestSets) = tests.partition { it.executions.isNotEmpty() } - - if (emptyTestSets.isNotEmpty()) { - notGeneratedTestsAction(emptyTestSets.map { it.method.name }) - } - - return notEmptyTests - } - - fun testCodeGenerate(testSets: List): String { - val containingClassName = getContainingClassName(testSets) - val classId = PythonClassId(configuration.testFileInformation.moduleName, containingClassName) - - val methodIds = testSets.associate { - it.method to PythonMethodId( - classId, - it.method.name, - RawPythonAnnotation(pythonAnyClassId.name), - it.method.arguments.map { argument -> - argument.annotation?.let { annotation -> - RawPythonAnnotation(annotation) - } ?: pythonAnyClassId - } - ) - } - - val paramNames = testSets.associate { testSet -> - var params = testSet.method.arguments.map { it.name } - if (testSet.method.hasThisArgument) { - params = params.drop(1) - } - methodIds[testSet.method] as ExecutableId to params - }.toMutableMap() - - val allImports = collectImports(testSets) - - val context = UtContext(this::class.java.classLoader) - withUtContext(context) { - val codegen = PythonCodeGenerator( - classId, - paramNames = paramNames, - testFramework = configuration.testFramework, - testClassPackageName = "", - ) - val testCode = codegen.pythonGenerateAsStringWithTestReport( - testSets.map { testSet -> - val intRange = testSet.executions.indices - val clusterInfo = listOf(Pair(UtClusterInfo("FUZZER"), intRange)) - CgMethodTestSet( - executableId = methodIds[testSet.method] as ExecutableId, - executions = testSet.executions, - clustersInfo = clusterInfo, - ) - }, - allImports - ).generatedCode - return testCode - } - } - - abstract fun saveTests(testsCode: String) - - abstract fun notGeneratedTestsAction(testedFunctions: List) - - abstract fun processCoverageInfo(testSets: List) - - private fun getContainingClassName(testSets: List): String { - val containingClasses = testSets.map { it.method.containingPythonClass?.pythonName() ?: "TopLevelFunctions" } - return containingClasses.toSet().first() - } - - private fun collectImports(notEmptyTests: List): Set { - val importParamModules = notEmptyTests.flatMap { testSet -> - testSet.executions.flatMap { execution -> - val params = (execution.stateAfter.parameters + execution.stateBefore.parameters).toMutableSet() - val self = mutableListOf(execution.stateBefore.thisInstance, execution.stateAfter.thisInstance) - if (execution is PythonUtExecution) { - params.addAll(execution.stateInit.parameters) - self.add(execution.stateInit.thisInstance) - } - (params + self) - .filterNotNull() - .flatMap { utModel -> - (utModel as PythonModel).let { - it.allContainingClassIds.map { classId -> - PythonUserImport(importName_ = classId.moduleName) - } - } - } - } - } - val importResultModules = notEmptyTests.flatMap { testSet -> - testSet.executions.mapNotNull { execution -> - if (execution.result is UtExecutionSuccess) { - (execution.result as UtExecutionSuccess).let { result -> - (result.model as PythonModel).let { - it.allContainingClassIds.map { classId -> - PythonUserImport(importName_ = classId.moduleName) - } - } - } - } else null - }.flatten() - } - val rootModule = configuration.testFileInformation.moduleName.split(".").first() - val testRootModule = PythonUserImport(importName_ = rootModule) - val sysImport = PythonSystemImport("sys") - val sysPathImports = relativizePaths(configuration.executionPath, configuration.sysPathDirectories).map { PythonSysPathImport(it) } - - val testFrameworkModule = - configuration.testFramework.testSuperClass?.let { PythonUserImport(importName_ = (it as PythonClassId).rootModuleName) } - - val allImports = (importParamModules + importResultModules + testRootModule + sysPathImports + listOf( - testFrameworkModule, sysImport - )).filterNotNull().toSet() - return allImports - } - - private fun findMethodByHeader( - mypyStorage: MypyAnnotationStorage, - method: PythonMethodHeader, - curModule: String, - sourceFileContent: String - ): PythonMethod { - var containingClass: CompositeType? = null - val containingClassName = method.containingPythonClassId?.simpleName - val functionDef = if (containingClassName == null) { - mypyStorage.definitions[curModule]!![method.name]!!.getUtBotDefinition()!! - } else { - containingClass = - mypyStorage.definitions[curModule]!![containingClassName]!!.getUtBotType() as CompositeType - mypyStorage.definitions[curModule]!![containingClassName]!!.type.asUtBotType.getPythonAttributes().first { - it.meta.name == method.name - } - } as? PythonFunctionDefinition ?: error("Selected method is not a function definition") - - val parsedFile = PythonParser(sourceFileContent).Module() - val funcDef = PythonCode.findFunctionDefinition(parsedFile, method) - - return PythonMethod( - name = method.name, - moduleFilename = method.moduleFilename, - containingPythonClass = containingClass, - codeAsString = funcDef.body.source, - definition = functionDef, - ast = funcDef.body - ) - } - - private fun relativizePaths(rootPath: Path?, paths: Set): Set = - if (rootPath != null) { - paths.map { path -> - rootPath.relativize(Path(path)).pathString - }.toSet() - } else { - paths - } - - data class InstructionSet( - val start: Int, - val end: Int - ) - - data class CoverageInfo( - val covered: List, - val notCovered: List - ) - - private val moshi: Moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() - private val jsonAdapter = moshi.adapter(CoverageInfo::class.java) - - private fun getInstructionSetList(instructions: Collection): List = - instructions.sorted().fold(emptyList()) { acc, lineNumber -> - if (acc.isEmpty()) - return@fold listOf(InstructionSet(lineNumber, lineNumber)) - val elem = acc.last() - if (elem.end + 1 == lineNumber) - acc.dropLast(1) + listOf(InstructionSet(elem.start, lineNumber)) - else - acc + listOf(InstructionSet(lineNumber, lineNumber)) - } - protected fun getCoverageInfo(testSets: List): String { - val covered = mutableSetOf() - val missed = mutableSetOf>() - testSets.forEach { testSet -> - testSet.executions.forEach inner@{ execution -> - val coverage = execution.coverage ?: return@inner - coverage.coveredInstructions.forEach { covered.add(it.lineNumber) } - missed.add(coverage.missedInstructions.map { it.lineNumber }.toSet()) - } - } - val coveredInstructionSets = getInstructionSetList(covered) - val missedInstructionSets = - if (missed.isEmpty()) - emptyList() - else - getInstructionSetList(missed.reduce { a, b -> a intersect b }) - - return jsonAdapter.toJson( - CoverageInfo( - coveredInstructionSets, - missedInstructionSets - ) - ) - } - -} \ No newline at end of file From b5ce81674320ceebb779db85ed9185270603827f Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 28 Jun 2023 14:55:56 +0300 Subject: [PATCH 07/44] Refactor PythonEngine and PythonTestCaseGenerator --- .../kotlin/org/utbot/python/PythonEngine.kt | 177 +++++++++--------- .../utbot/python/PythonTestCaseGenerator.kt | 19 +- 2 files changed, 100 insertions(+), 96 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt index 17fda466fb..051d12ef48 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt @@ -186,9 +186,94 @@ class PythonEngine( ) } - fun fuzzing(parameters: List, isCancelled: () -> Boolean, until: Long): Flow = flow { + private fun fuzzingResultHandler( + description: PythonMethodDescription, + arguments: List, + parameters: List, + manager: PythonWorkerManager, + ): PythonExecutionResult? { val additionalModules = parameters.flatMap { it.pythonModules() } + val argumentValues = arguments.map { PythonTreeModel(it.tree, it.tree.type) } + logger.debug(argumentValues.map { it.tree } .toString()) + val argumentModules = argumentValues + .flatMap { it.allContainingClassIds } + .map { it.moduleName } + .filterNot { it.startsWith(moduleToImport) } + val localAdditionalModules = (additionalModules + argumentModules + moduleToImport).toSet() + + val (thisObject, modelList) = + if (methodUnderTest.hasThisArgument) + Pair(argumentValues[0], argumentValues.drop(1)) + else + Pair(null, argumentValues) + val functionArguments = FunctionArguments( + thisObject, + methodUnderTest.thisObjectName, + modelList, + methodUnderTest.argumentsNames + ) + try { + val coverageId = CoverageIdGenerator.createId() + return when (val evaluationResult = + manager.runWithCoverage(functionArguments, localAdditionalModules, coverageId)) { + is PythonEvaluationError -> { + val utError = UtError( + "Error evaluation: ${evaluationResult.status}, ${evaluationResult.message}", + Throwable(evaluationResult.stackTrace.joinToString("\n")) + ) + logger.debug(evaluationResult.stackTrace.joinToString("\n")) + PythonExecutionResult(InvalidExecution(utError), PythonFeedback(control = Control.PASS)) + } + + is PythonEvaluationTimeout -> { + val coveredLines = + manager.coverageReceiver.coverageStorage.getOrDefault(coverageId, mutableSetOf()) + val utTimeoutException = handleTimeoutResult(arguments, description, coveredLines) + val coveredInstructions = coveredLinesToInstructions(coveredLines, methodUnderTest) + val trieNode: Trie.Node = + if (coveredInstructions.isEmpty()) + Trie.emptyNode() + else + description.tracer.add(coveredInstructions) + PythonExecutionResult( + utTimeoutException, + PythonFeedback(control = Control.PASS, result = trieNode) + ) + } + + is PythonEvaluationSuccess -> { + val coveredInstructions = evaluationResult.coverage.coveredInstructions + + when (val result = handleSuccessResult( + arguments, + parameters, + evaluationResult, + description, + )) { + is ValidExecution -> { + val trieNode: Trie.Node = description.tracer.add(coveredInstructions) + PythonExecutionResult( + result, + PythonFeedback(control = Control.CONTINUE, result = trieNode) + ) + } + is InvalidExecution -> { + PythonExecutionResult(result, PythonFeedback(control = Control.CONTINUE)) + } + else -> { + PythonExecutionResult(result, PythonFeedback(control = Control.PASS)) + } + } + } + } + } catch (_: TimeoutException) { + logger.info { "Fuzzing process was interrupted by timeout" } + return null + } + } + + fun fuzzing(parameters: List, isCancelled: () -> Boolean, until: Long): Flow = flow { ServerSocket(0).use { serverSocket -> logger.info { "Server port: ${serverSocket.localPort}" } val manager = try { @@ -202,90 +287,6 @@ class PythonEngine( } logger.info { "Executor manager was created successfully" } - fun fuzzingResultHandler( - description: PythonMethodDescription, - arguments: List - ): PythonExecutionResult? { - val argumentValues = arguments.map { PythonTreeModel(it.tree, it.tree.type) } - logger.debug(argumentValues.map { it.tree } .toString()) - val argumentModules = argumentValues - .flatMap { it.allContainingClassIds } - .map { it.moduleName } - .filterNot { it.startsWith(moduleToImport) } - val localAdditionalModules = (additionalModules + argumentModules + moduleToImport).toSet() - - val (thisObject, modelList) = - if (methodUnderTest.hasThisArgument) - Pair(argumentValues[0], argumentValues.drop(1)) - else - Pair(null, argumentValues) - val functionArguments = FunctionArguments( - thisObject, - methodUnderTest.thisObjectName, - modelList, - methodUnderTest.argumentsNames - ) - try { - val coverageId = CoverageIdGenerator.createId() - return when (val evaluationResult = - manager.runWithCoverage(functionArguments, localAdditionalModules, coverageId)) { - is PythonEvaluationError -> { - val utError = UtError( - "Error evaluation: ${evaluationResult.status}, ${evaluationResult.message}", - Throwable(evaluationResult.stackTrace.joinToString("\n")) - ) - logger.debug(evaluationResult.stackTrace.joinToString("\n")) - PythonExecutionResult(InvalidExecution(utError), PythonFeedback(control = Control.PASS)) - } - - is PythonEvaluationTimeout -> { - val coveredLines = - manager.coverageReceiver.coverageStorage.getOrDefault(coverageId, mutableSetOf()) - val utTimeoutException = handleTimeoutResult(arguments, description, coveredLines) - val coveredInstructions = coveredLinesToInstructions(coveredLines, methodUnderTest) - val trieNode: Trie.Node = - if (coveredInstructions.isEmpty()) - Trie.emptyNode() - else description.tracer.add( - coveredInstructions - ) - PythonExecutionResult( - utTimeoutException, - PythonFeedback(control = Control.PASS, result = trieNode) - ) - } - - is PythonEvaluationSuccess -> { - val coveredInstructions = evaluationResult.coverage.coveredInstructions - - when (val result = handleSuccessResult( - arguments, - parameters, - evaluationResult, - description, - )) { - is ValidExecution -> { - val trieNode: Trie.Node = description.tracer.add(coveredInstructions) - PythonExecutionResult( - result, - PythonFeedback(control = Control.CONTINUE, result = trieNode) - ) - } - is InvalidExecution -> { - PythonExecutionResult(result, PythonFeedback(control = Control.CONTINUE)) - } - else -> { - PythonExecutionResult(result, PythonFeedback(control = Control.PASS)) - } - } - } - } - } catch (_: TimeoutException) { - logger.info { "Fuzzing process was interrupted by timeout" } - return null - } - } - val pmd = PythonMethodDescription( methodUnderTest.name, parameters, @@ -296,7 +297,7 @@ class PythonEngine( ) if (parameters.isEmpty()) { - val result = fuzzingResultHandler(pmd, emptyList()) + val result = fuzzingResultHandler(pmd, emptyList(), parameters, manager) result?.let { emit(it.fuzzingExecutionFeedback) } @@ -327,7 +328,7 @@ class PythonEngine( emit(CachedExecutionFeedback(mem.fuzzingExecutionFeedback)) return@PythonFuzzing mem.fuzzingPlatformFeedback } - val result = fuzzingResultHandler(description, arguments) + val result = fuzzingResultHandler(description, arguments, parameters, manager) if (result == null) { // timeout manager.disconnect() return@PythonFuzzing PythonFeedback(control = Control.STOP) @@ -337,7 +338,7 @@ class PythonEngine( emit(result.fuzzingExecutionFeedback) return@PythonFuzzing result.fuzzingPlatformFeedback }.fuzz(pmd) - } catch (_: NoSeedValueException) { // e.g. NoSeedValueException + } catch (_: NoSeedValueException) { logger.info { "Cannot fuzz values for types: ${parameters.map { it.pythonTypeRepresentation() }}" } } } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt index 6131dcc98c..cf71b2de55 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt @@ -138,7 +138,7 @@ class PythonTestCaseGenerator( val (hintCollector, constantCollector) = constructCollectors(mypyStorage, typeStorage, method) val constants = constantCollector.result.map { (type, value) -> - logger.debug("Collected constant: ${type.pythonTypeRepresentation()}: $value") + logger.debug { "Collected constant: ${type.pythonTypeRepresentation()}: $value" } PythonFuzzedConcreteValue(type, value) } @@ -175,7 +175,7 @@ class PythonTestCaseGenerator( when (it) { is ValidExecution -> { executions += it.utFuzzedExecution - missingLines = updateCoverage(it.utFuzzedExecution, coveredLines, missingLines) + missingLines = updateMissingLines(it.utFuzzedExecution, coveredLines, missingLines) feedback = SuccessFeedback limitManager.addSuccessExecution() } @@ -233,12 +233,13 @@ class PythonTestCaseGenerator( val firstUntil = (until - now) / 2 + now val originalDef = method.definition val shortType = meta.removeNonPositionalArgs(originalDef.type) + val posArgsCount = shortType.arguments.size val shortMeta = PythonFuncItemDescription( originalDef.meta.name, - originalDef.meta.args.take(shortType.arguments.size) + originalDef.meta.args.take(posArgsCount) ) val additionalVars = originalDef.meta.args - .drop(shortType.arguments.size) + .drop(posArgsCount) .joinToString(separator = "\n", prefix = "\n") { arg -> "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types } @@ -273,14 +274,16 @@ class PythonTestCaseGenerator( minimizeExecutions(failedExecutions) + emptyCoverageExecutions.take(MAX_EMPTY_COVERAGE_TESTS) else - executions, + coverageExecutions + emptyCoverageExecutions.take(MAX_EMPTY_COVERAGE_TESTS), errors, storageForMypyMessages ) } - // returns new missingLines - private fun updateCoverage( + /** + * Calculate a new set of missing lines in tested function + */ + private fun updateMissingLines( execution: UtExecution, coveredLines: MutableSet, missingLines: Set? @@ -338,7 +341,7 @@ class PythonTestCaseGenerator( val iterationNumber = algo.run(hintCollector.result, typeInferenceCancellation, annotationHandler) - if (iterationNumber == 1) { + if (iterationNumber == 1) { // Initial annotation can't be substituted limitManager.mode = TimeoutMode val existsAnnotation = method.definition.type annotationHandler(existsAnnotation) From c765749dcdae7b92883f09752f9cd9692a5634c4 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 28 Jun 2023 15:21:53 +0300 Subject: [PATCH 08/44] Change isEqual codegen for bool and None --- .../codegen/model/constructor/visitor/CgPythonRenderer.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt index 33923ec76a..55d94714f0 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt @@ -234,7 +234,12 @@ internal class CgPythonRenderer( override fun visit(element: CgEqualTo) { element.left.accept(this) - print(" == ") + val isCompareTypes = listOf("builtins.bool", "types.NoneType") + if (isCompareTypes.contains(element.right.type.canonicalName)) { + print(" is ") + } else { + print(" == ") + } element.right.accept(this) } From 91277382e1986666a77e03571a912243b9339bec Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Thu, 29 Jun 2023 10:47:43 +0300 Subject: [PATCH 09/44] Fix installation complete notification --- .../python/IntellijRequirementsInstaller.kt | 50 +++++++------------ .../language/python/PythonDialogProcessor.kt | 9 +--- 2 files changed, 20 insertions(+), 39 deletions(-) diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt index 3d4e0b0f27..a5eadee776 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt @@ -1,17 +1,14 @@ package org.utbot.intellij.plugin.language.python import com.intellij.notification.NotificationType -import com.intellij.openapi.module.Module -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.progress.Task +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogPanel import com.intellij.openapi.ui.DialogWrapper import com.intellij.ui.dsl.builder.panel import org.utbot.intellij.plugin.ui.Notifier import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater -import org.utbot.intellij.plugin.util.IntelliJApiHelper import org.utbot.python.RequirementsInstaller import org.utbot.python.utils.RequirementsUtils import javax.swing.JComponent @@ -25,32 +22,27 @@ class IntellijRequirementsInstaller( } override fun installRequirements(pythonPath: String, requirements: List) { - ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Installing requirements") { - override fun run(indicator: ProgressIndicator) { - IntelliJApiHelper.run( - IntelliJApiHelper.Target.EDT_LATER, - indicator, - "Installing requirements" - ) { - if (InstallRequirementsDialog(requirements).showAndGet()) { - PythonNotifier.notify("Start requirements installation") - val installResult = RequirementsUtils.installRequirements(pythonPath, requirements) - if (installResult.exitValue != 0) { - showErrorDialogLater( - project, - "Requirements installing failed.
" + - "${installResult.stderr}

" + - "Try to install with pip:
" + - " ${requirements.joinToString("
")}", - "Requirements error" - ) - } else { + invokeLater { + if (InstallRequirementsDialog(requirements).showAndGet()) { + val installResult = RequirementsUtils.installRequirements(pythonPath, requirements) + if (installResult.exitValue != 0) { + showErrorDialogLater( + project, + "Requirements installing failed.
" + + "${installResult.stderr}

" + + "Try to install with pip:
" + + " ${requirements.joinToString("
")}", + "Requirements error" + ) + } else { + invokeLater { + runReadAction { PythonNotifier.notify("Requirements installation is complete") } } } } - }) + } } } @@ -78,10 +70,6 @@ class InstallRequirementsDialog(private val requirements: List) : Dialog object PythonNotifier : Notifier() { override val notificationType: NotificationType = NotificationType.INFORMATION + override val displayId: String = "Python notification" - override fun notify(info: String, project: Project?, module: Module?) { - val contentText = content(project, module, info) - notificationGroup.createNotification(contentText, notificationType) - logger.info(contentText) - } } diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt index a5c1e383af..16ecb19d2d 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt @@ -1,9 +1,7 @@ package org.utbot.intellij.plugin.language.python -import com.intellij.codeInsight.CodeInsightUtil import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.invokeLater -import com.intellij.openapi.application.readAction import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor @@ -16,12 +14,10 @@ import com.intellij.openapi.progress.Task.Backgroundable import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ProjectFileIndex -import com.intellij.openapi.ui.Messages import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDirectory -import com.intellij.psi.PsiFileFactory import com.intellij.util.concurrency.AppExecutorUtil import com.jetbrains.python.psi.PyClass import com.jetbrains.python.psi.PyElement @@ -29,10 +25,8 @@ import com.jetbrains.python.psi.PyFile import com.jetbrains.python.psi.PyFunction import com.jetbrains.python.psi.resolve.QualifiedNameFinder import mu.KotlinLogging -import kotlinx.coroutines.runBlocking import org.jetbrains.kotlin.idea.util.module import org.jetbrains.kotlin.idea.util.projectStructure.sdk -import org.jetbrains.kotlin.j2k.getContainingClass import org.utbot.common.PathUtil.toPath import org.utbot.framework.plugin.api.util.LockFile import org.utbot.intellij.plugin.settings.Settings @@ -45,7 +39,6 @@ import org.utbot.python.TestFileInformation import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.codegen.PythonCgLanguageAssistant import org.utbot.python.newtyping.mypy.dropInitFile -import org.utbot.python.newtyping.mypy.setConfigFile import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit import kotlin.io.path.Path @@ -250,11 +243,11 @@ object PythonDialogProcessor { return } try { + indicator.text = "Checking requirements..." indicator.isIndeterminate = false val installer = IntellijRequirementsInstaller(project) - indicator.text = "Checking requirements" val requirementsAreInstalled = RequirementsInstaller.checkRequirements( installer, baseModel.pythonPath, From 09ce912ce81a241ee51e0c329b6cac1d0cd430b0 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Thu, 29 Jun 2023 13:37:02 +0300 Subject: [PATCH 10/44] Filter unused imports builtins and types --- utbot-python/samples/samples/dicts.py | 6 ++++ .../python/PythonTestGenerationProcessor.kt | 29 ++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/utbot-python/samples/samples/dicts.py b/utbot-python/samples/samples/dicts.py index 6c63404587..3b0e25a549 100644 --- a/utbot-python/samples/samples/dicts.py +++ b/utbot-python/samples/samples/dicts.py @@ -5,6 +5,9 @@ class Word: def __init__(self, translations: Dict[str, str]): self.translations = translations + def __eq__(self, other): + return self.translations == other.translations + def keys(self): return list(self.translations.keys()) @@ -18,6 +21,9 @@ def __init__( self.languages = languages self.words = [Word(translations) for translations in words] + def __eq__(self, other): + return self.languages == other.languages and self.words == other.words + def translate(self, word: str, language: Optional[str]): if language is not None: for word_ in self.words: diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt index 383961fc3e..fc69c386cd 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt @@ -15,7 +15,9 @@ import org.utbot.python.framework.api.python.PythonMethodId import org.utbot.python.framework.api.python.PythonModel import org.utbot.python.framework.api.python.PythonUtExecution import org.utbot.python.framework.api.python.RawPythonAnnotation +import org.utbot.python.framework.api.python.pythonBuiltinsModuleName import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.framework.api.python.util.pythonNoneClassId import org.utbot.python.framework.codegen.model.PythonCodeGenerator import org.utbot.python.framework.codegen.model.PythonImport import org.utbot.python.framework.codegen.model.PythonSysPathImport @@ -147,7 +149,7 @@ abstract class PythonTestGenerationProcessor { val containingClasses = testSets.map { it.method.containingPythonClass?.pythonName() ?: "TopLevelFunctions" } return containingClasses.toSet().first() } - + private fun collectImports(notEmptyTests: List): Set { val importParamModules = notEmptyTests.flatMap { testSet -> testSet.executions.flatMap { execution -> @@ -161,9 +163,9 @@ abstract class PythonTestGenerationProcessor { .filterNotNull() .flatMap { utModel -> (utModel as PythonModel).let { - it.allContainingClassIds.map { classId -> - PythonUserImport(importName_ = classId.moduleName) - } + it.allContainingClassIds + .filterNot { classId -> classId == pythonNoneClassId } + .map { classId -> PythonUserImport(importName_ = classId.moduleName) } } } } @@ -173,9 +175,9 @@ abstract class PythonTestGenerationProcessor { if (execution.result is UtExecutionSuccess) { (execution.result as UtExecutionSuccess).let { result -> (result.model as PythonModel).let { - it.allContainingClassIds.map { classId -> - PythonUserImport(importName_ = classId.moduleName) - } + it.allContainingClassIds + .filterNot { classId -> classId == pythonNoneClassId } + .map { classId -> PythonUserImport(importName_ = classId.moduleName) } } } } else null @@ -184,15 +186,20 @@ abstract class PythonTestGenerationProcessor { val rootModule = configuration.testFileInformation.moduleName.split(".").first() val testRootModule = PythonUserImport(importName_ = rootModule) val sysImport = PythonSystemImport("sys") - val sysPathImports = relativizePaths(configuration.executionPath, configuration.sysPathDirectories).map { PythonSysPathImport(it) } + val sysPathImports = relativizePaths( + configuration.executionPath, + configuration.sysPathDirectories + ).map { PythonSysPathImport(it) } val testFrameworkModule = configuration.testFramework.testSuperClass?.let { PythonUserImport(importName_ = (it as PythonClassId).rootModuleName) } - val allImports = (importParamModules + importResultModules + testRootModule + sysPathImports + listOf( + return (importParamModules + importResultModules + testRootModule + sysPathImports + listOf( testFrameworkModule, sysImport - )).filterNotNull().toSet() - return allImports + )) + .filterNotNull() + .filterNot { it.rootModuleName == pythonBuiltinsModuleName } + .toSet() } private fun findMethodByHeader( From e8610b81296381445dd91145d1980517b47f8a02 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Thu, 29 Jun 2023 14:37:58 +0300 Subject: [PATCH 11/44] Refactor structure --- .../language/python/CliRequirementsInstaller.kt | 2 +- .../language/python/PythonGenerateTestsCommand.kt | 2 +- .../python/IntellijRequirementsInstaller.kt | 2 +- .../language/python/PythonDialogProcessor.kt | 2 +- .../org/utbot/python/PythonTestCaseGenerator.kt | 9 ++++----- .../main/kotlin/org/utbot/python/UTPythonAPI.kt | 2 +- .../fuzzing/provider/ConstantValueProvider.kt | 2 +- .../value}/PreprocessedValueStorage.kt | 2 +- .../org/utbot/python/fuzzing/value/UndefValue.kt | 6 ------ .../utbot/python/newtyping/mypy/MypyAnnotations.kt | 13 ++++++++++++- .../org/utbot/python/typing/MypyAnnotations.kt | 14 -------------- .../python/{ => utils}/RequirementsInstaller.kt | 4 +--- 12 files changed, 24 insertions(+), 36 deletions(-) rename utbot-python/src/main/kotlin/org/utbot/python/{typing => fuzzing/value}/PreprocessedValueStorage.kt (97%) delete mode 100644 utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/UndefValue.kt delete mode 100644 utbot-python/src/main/kotlin/org/utbot/python/typing/MypyAnnotations.kt rename utbot-python/src/main/kotlin/org/utbot/python/{ => utils}/RequirementsInstaller.kt (91%) diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt index ce8dcfe294..8653520a30 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt @@ -1,7 +1,7 @@ package org.utbot.cli.language.python import mu.KLogger -import org.utbot.python.RequirementsInstaller +import org.utbot.python.utils.RequirementsInstaller import org.utbot.python.utils.RequirementsUtils class CliRequirementsInstaller( diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index a822f1d69d..6682609aa4 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -10,7 +10,7 @@ import org.parsers.python.PythonParser import org.utbot.framework.codegen.domain.TestFramework import org.utbot.python.PythonMethodHeader import org.utbot.python.PythonTestGenerationConfig -import org.utbot.python.RequirementsInstaller +import org.utbot.python.utils.RequirementsInstaller import org.utbot.python.TestFileInformation import org.utbot.python.code.PythonCode import org.utbot.python.framework.api.python.PythonClassId diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt index a5eadee776..e8701bdbe6 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/IntellijRequirementsInstaller.kt @@ -9,7 +9,7 @@ import com.intellij.openapi.ui.DialogWrapper import com.intellij.ui.dsl.builder.panel import org.utbot.intellij.plugin.ui.Notifier import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater -import org.utbot.python.RequirementsInstaller +import org.utbot.python.utils.RequirementsInstaller import org.utbot.python.utils.RequirementsUtils import javax.swing.JComponent diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt index 16ecb19d2d..d93bd3b1a5 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt @@ -34,7 +34,7 @@ import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.intellij.plugin.ui.utils.testModules import org.utbot.python.PythonMethodHeader import org.utbot.python.PythonTestGenerationConfig -import org.utbot.python.RequirementsInstaller +import org.utbot.python.utils.RequirementsInstaller import org.utbot.python.TestFileInformation import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.codegen.PythonCgLanguageAssistant diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt index cf71b2de55..bd59949006 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt @@ -22,7 +22,7 @@ import org.utbot.python.newtyping.mypy.MypyAnnotationStorage import org.utbot.python.newtyping.mypy.MypyReportLine import org.utbot.python.newtyping.mypy.getErrorNumber import org.utbot.python.newtyping.utils.getOffsetLine -import org.utbot.python.typing.MypyAnnotations +import org.utbot.python.newtyping.mypy.MypyAnnotations import org.utbot.python.utils.ExecutionWithTimeoutMode import org.utbot.python.utils.TestGenerationLimitManager import org.utbot.python.utils.PriorityCartesianProduct @@ -32,6 +32,7 @@ import java.io.File private val logger = KotlinLogging.logger {} private const val RANDOM_TYPE_FREQUENCY = 6 private const val MAX_EMPTY_COVERAGE_TESTS = 5 +private const val MAX_SUBSTITUTIONS = 10 class PythonTestCaseGenerator( private val withMinimization: Boolean = true, @@ -86,13 +87,11 @@ class PythonTestCaseGenerator( } } - private val maxSubstitutions = 10 - private fun generateTypesAfterSubstitution(type: Type, typeStorage: PythonTypeStorage): List { val params = type.getBoundedParameters() return PriorityCartesianProduct(params.map { getCandidates(it, typeStorage) }).getSequence().map { subst -> DefaultSubstitutionProvider.substitute(type, (params zip subst).associate { it }) - }.take(maxSubstitutions).toList() + }.take(MAX_SUBSTITUTIONS).toList() } private fun substituteTypeParameters(method: PythonMethod, typeStorage: PythonTypeStorage): List { @@ -117,7 +116,7 @@ class PythonTestCaseGenerator( method.ast ) } - }.take(maxSubstitutions) + }.take(MAX_SUBSTITUTIONS) } private fun methodHandler( diff --git a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt index bad9410d71..a11daba14e 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt @@ -8,7 +8,7 @@ import org.utbot.python.framework.api.python.PythonTreeModel import org.utbot.python.framework.api.python.util.pythonAnyClassId import org.utbot.python.newtyping.* import org.utbot.python.newtyping.general.CompositeType -import org.utbot.python.typing.MypyAnnotations +import org.utbot.python.newtyping.mypy.MypyAnnotations data class PythonArgument(val name: String, val annotation: String?) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ConstantValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ConstantValueProvider.kt index 1faf11d80f..a6b7af69ce 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ConstantValueProvider.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ConstantValueProvider.kt @@ -9,7 +9,7 @@ import org.utbot.python.fuzzing.PythonMethodDescription import org.utbot.python.fuzzing.provider.utils.isAny import org.utbot.python.newtyping.general.Type import org.utbot.python.newtyping.pythonTypeName -import org.utbot.python.typing.TypesFromJSONStorage +import org.utbot.python.fuzzing.value.TypesFromJSONStorage object ConstantValueProvider : ValueProvider { override fun accept(type: Type): Boolean { diff --git a/utbot-python/src/main/kotlin/org/utbot/python/typing/PreprocessedValueStorage.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/PreprocessedValueStorage.kt similarity index 97% rename from utbot-python/src/main/kotlin/org/utbot/python/typing/PreprocessedValueStorage.kt rename to utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/PreprocessedValueStorage.kt index e1188545a3..25f5a87e03 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/typing/PreprocessedValueStorage.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/PreprocessedValueStorage.kt @@ -1,4 +1,4 @@ -package org.utbot.python.typing +package org.utbot.python.fuzzing.value import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/UndefValue.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/UndefValue.kt deleted file mode 100644 index 73d61c594b..0000000000 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/UndefValue.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.utbot.python.fuzzing.value - -import org.utbot.fuzzing.Mutation -import org.utbot.fuzzing.seeds.KnownValue - -class ObjectValue : KnownValue diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyAnnotations.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyAnnotations.kt index 769a254bc1..350e5ab362 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyAnnotations.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyAnnotations.kt @@ -249,4 +249,15 @@ val annotationAdapter: PolymorphicJsonAdapterFactory = .withSubtype(PythonTuple::class.java, AnnotationType.Tuple.name) .withSubtype(PythonNoneType::class.java, AnnotationType.NoneType.name) .withSubtype(TypeAliasNode::class.java, AnnotationType.TypeAlias.name) - .withSubtype(UnknownAnnotationNode::class.java, AnnotationType.Unknown.name) \ No newline at end of file + .withSubtype(UnknownAnnotationNode::class.java, AnnotationType.Unknown.name) + +object MypyAnnotations { + + data class MypyReportLine( + val line: Int, + val type: String, + val message: String, + val file: String + ) + +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/typing/MypyAnnotations.kt b/utbot-python/src/main/kotlin/org/utbot/python/typing/MypyAnnotations.kt deleted file mode 100644 index 42cf1d7660..0000000000 --- a/utbot-python/src/main/kotlin/org/utbot/python/typing/MypyAnnotations.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.utbot.python.typing - -object MypyAnnotations { - const val TEMPORARY_MYPY_FILE = "" - - data class MypyReportLine( - val line: Int, - val type: String, - val message: String, - val file: String - ) - -} - diff --git a/utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsInstaller.kt similarity index 91% rename from utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt rename to utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsInstaller.kt index db9123008a..2aace97dec 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/RequirementsInstaller.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsInstaller.kt @@ -1,6 +1,4 @@ -package org.utbot.python - -import org.utbot.python.utils.RequirementsUtils +package org.utbot.python.utils interface RequirementsInstaller { fun checkRequirements(pythonPath: String, requirements: List): Boolean From 3af1401421c7a07805f67759ddea288fe79860c6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Thu, 29 Jun 2023 17:25:27 +0300 Subject: [PATCH 12/44] Working on optional arguments --- utbot-cli-python/src/README.md | 8 ++------ .../samples/easy_samples/with_kwargs.py | 16 +++++++++++++++ .../utbot/python/PythonTestCaseGenerator.kt | 10 +++++----- .../kotlin/org/utbot/python/UTPythonAPI.kt | 17 +++++++++++++++- .../org/utbot/python/newtyping/PythonType.kt | 20 +++++++++++++++++++ .../org/utbot/python/newtyping/utils/Utils.kt | 6 +++++- 6 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 utbot-python/samples/easy_samples/with_kwargs.py diff --git a/utbot-cli-python/src/README.md b/utbot-cli-python/src/README.md index bdc5c5e0b0..7a963bfcc2 100644 --- a/utbot-cli-python/src/README.md +++ b/utbot-cli-python/src/README.md @@ -6,13 +6,13 @@ - Required Java version: 11. - - Prefered Python version: 3.8 or 3.9. + - Prefered Python version: 3.8+. Make sure that your Python has `pip` installed (this is usually the case). [Read more about pip installation](https://pip.pypa.io/en/stable/installation/). Before running utbot install pip requirements (or use `--install-requirements` flag in `generate_python` command): - python -m pip install mypy==0.971 astor typeshed-client coverage + python -m pip install mypy==1.0 utbot_executor==0.4.31 utbot_mypy_runner==0.2.8 ## Basic usage @@ -66,10 +66,6 @@ Run generated tests: Turn off Python requirements check (to speed up). -- `--visit-only-specified-source` - - Do not search for classes and imported modules in other Python files from `--sys-path` option. - - `-t, --timeout INT` Specify the maximum time in milliseconds to spend on generating tests (60000 by default). diff --git a/utbot-python/samples/easy_samples/with_kwargs.py b/utbot-python/samples/easy_samples/with_kwargs.py new file mode 100644 index 0000000000..3e31ae879e --- /dev/null +++ b/utbot-python/samples/easy_samples/with_kwargs.py @@ -0,0 +1,16 @@ +def f(x, y=1, *, z): + if y == 0: + return 100 * x + else: + x *= y + if x % 2 == 0: + y = g(x) + z + return x + y + 100 / y + + +def g(x): + return x ** 2 + + +if __name__ == '__main__': + print(f(1, y=2, z=3)) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt index bd59949006..770aa4e015 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt @@ -23,6 +23,7 @@ import org.utbot.python.newtyping.mypy.MypyReportLine import org.utbot.python.newtyping.mypy.getErrorNumber import org.utbot.python.newtyping.utils.getOffsetLine import org.utbot.python.newtyping.mypy.MypyAnnotations +import org.utbot.python.newtyping.utils.isRequired import org.utbot.python.utils.ExecutionWithTimeoutMode import org.utbot.python.utils.TestGenerationLimitManager import org.utbot.python.utils.PriorityCartesianProduct @@ -227,18 +228,17 @@ class PythonTestCaseGenerator( try { val meta = method.definition.type.pythonDescription() as PythonCallableTypeDescription val argKinds = meta.argumentKinds - if (argKinds.any { it != PythonCallableTypeDescription.ArgKind.ARG_POS }) { + if (argKinds.any { !isRequired(it) }) { val now = System.currentTimeMillis() val firstUntil = (until - now) / 2 + now val originalDef = method.definition - val shortType = meta.removeNonPositionalArgs(originalDef.type) - val posArgsCount = shortType.arguments.size + val shortType = meta.removeNotRequiredArgs(originalDef.type) val shortMeta = PythonFuncItemDescription( originalDef.meta.name, - originalDef.meta.args.take(posArgsCount) + originalDef.meta.args.filterIndexed { index, _ -> isRequired(argKinds[index]) } ) val additionalVars = originalDef.meta.args - .drop(posArgsCount) + .filterIndexed { index, _ -> !isRequired(argKinds[index]) } .joinToString(separator = "\n", prefix = "\n") { arg -> "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt index a11daba14e..4b4869ddfd 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt @@ -31,6 +31,21 @@ class PythonMethod( "${it.name}: ${it.annotation ?: pythonAnyClassId.name}" } + ")" + fun methodSignarureWithKeywords(): String { + fun argWithAnnotation(arg: PythonVariableDescription): String = "${arg.name}" + val meta = definition.type.pythonDescription() as PythonCallableTypeDescription + val shortType = meta.removeNonPositionalArgs(definition.type) + val posArgsCount = shortType.arguments.size + val funcName = definition.meta.name + val baseArgs = definition.meta.args.take(posArgsCount) + val additionalVars = definition.meta.args.drop(posArgsCount) + + return "$funcName(${}" + .joinToString(separator = "\n", prefix = "\n") { arg -> + "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types + } + } + /* Check that the first argument is `self` of `cls`. TODO: Now we think that all class methods has `self` argument! We should support `@property` decorator @@ -42,7 +57,7 @@ class PythonMethod( get() { val paramNames = definition.meta.args.map { it.name } return (definition.type.arguments zip paramNames).map { - PythonArgument(it.second, it.first.pythonTypeRepresentation()) + PythonArgument(it.second, it.first.pythonTypeRepresentation()) // TODO: improve pythonTypeRepresentation } } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/PythonType.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/PythonType.kt index 8d7d936f4c..8d4ace3b93 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/PythonType.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/PythonType.kt @@ -2,6 +2,7 @@ package org.utbot.python.newtyping import org.utbot.python.newtyping.general.* import org.utbot.python.newtyping.general.Name +import org.utbot.python.newtyping.utils.isRequired sealed class PythonTypeDescription(name: Name) : TypeMetaDataWithName(name) { open fun castToCompatibleTypeApi(type: Type): Type = type @@ -226,6 +227,25 @@ class PythonCallableTypeDescription( ) } } + + fun removeNotRequiredArgs(type: Type): FunctionType { + val functionType = castToCompatibleTypeApi(type) + return createPythonCallableType( + functionType.parameters.size, + argumentKinds.filter { isRequired(it) }, + argumentNames.filterIndexed { index, _ -> isRequired(argumentKinds[index]) } + ) { self -> + val substitution = (functionType.parameters zip self.parameters).associate { + Pair(it.first as TypeParameter, it.second) + } + FunctionTypeCreator.InitializationData( + functionType.arguments + .filterIndexed { index, _ -> isRequired(argumentKinds[index]) } + .map { DefaultSubstitutionProvider.substitute(it, substitution) }, + DefaultSubstitutionProvider.substitute(functionType.returnValue, substitution) + ) + } + } } // Special Python annotations diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt index bf6c906b98..d865d317ad 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt @@ -1,6 +1,7 @@ package org.utbot.python.newtyping.utils import org.utbot.fuzzing.utils.chooseOne +import org.utbot.python.newtyping.PythonCallableTypeDescription import kotlin.random.Random fun getOffsetLine(sourceFileContent: String, offset: Int): Int { @@ -10,4 +11,7 @@ fun getOffsetLine(sourceFileContent: String, offset: Int): Int { fun weightedRandom(elems: List, weights: List, random: Random): T { val index = random.chooseOne(weights.toDoubleArray()) return elems[index] -} \ No newline at end of file +} + +fun isRequired(kind: PythonCallableTypeDescription.ArgKind) = + listOf(PythonCallableTypeDescription.ArgKind.ARG_POS, PythonCallableTypeDescription.ArgKind.ARG_NAMED).contains(kind) From 7af0581b405caefe878f4b11b006af1ca7357c5f Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Fri, 30 Jun 2023 15:20:44 +0300 Subject: [PATCH 13/44] Remove additionalVars --- .../main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt index 770aa4e015..bcee24b2a4 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt @@ -237,11 +237,6 @@ class PythonTestCaseGenerator( originalDef.meta.name, originalDef.meta.args.filterIndexed { index, _ -> isRequired(argKinds[index]) } ) - val additionalVars = originalDef.meta.args - .filterIndexed { index, _ -> !isRequired(argKinds[index]) } - .joinToString(separator = "\n", prefix = "\n") { arg -> - "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types - } method.definition = PythonFunctionDefinition(shortMeta, shortType) val missingLines = methodHandler( method, @@ -251,7 +246,6 @@ class PythonTestCaseGenerator( executions, null, firstUntil, - additionalVars ) method.definition = originalDef methodHandler(method, typeStorage, coveredLines, errors, executions, missingLines, until) From 74f8e33828af33761e2e58c911c14c3271e3c403 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Mon, 3 Jul 2023 13:46:41 +0300 Subject: [PATCH 14/44] Working on named arguments --- .../utbot/python/PythonTestCaseGenerator.kt | 6 ++++ .../kotlin/org/utbot/python/UTPythonAPI.kt | 28 ++++++++++--------- .../evaluation/PythonCodeSocketExecutor.kt | 19 +++++++++++-- .../codegen/model/PythonCodeGenerator.kt | 2 +- .../tree/PythonCgCallableAccessManager.kt | 15 +--------- .../codegen/model/tree/CgPythonElement.kt | 8 ++++++ .../org/utbot/python/newtyping/utils/Utils.kt | 3 ++ 7 files changed, 51 insertions(+), 30 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt index bcee24b2a4..770aa4e015 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt @@ -237,6 +237,11 @@ class PythonTestCaseGenerator( originalDef.meta.name, originalDef.meta.args.filterIndexed { index, _ -> isRequired(argKinds[index]) } ) + val additionalVars = originalDef.meta.args + .filterIndexed { index, _ -> !isRequired(argKinds[index]) } + .joinToString(separator = "\n", prefix = "\n") { arg -> + "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types + } method.definition = PythonFunctionDefinition(shortMeta, shortType) val missingLines = methodHandler( method, @@ -246,6 +251,7 @@ class PythonTestCaseGenerator( executions, null, firstUntil, + additionalVars ) method.definition = originalDef methodHandler(method, typeStorage, coveredLines, errors, executions, missingLines, until) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt index 4b4869ddfd..cf1e1f7489 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt @@ -31,20 +31,22 @@ class PythonMethod( "${it.name}: ${it.annotation ?: pythonAnyClassId.name}" } + ")" - fun methodSignarureWithKeywords(): String { - fun argWithAnnotation(arg: PythonVariableDescription): String = "${arg.name}" - val meta = definition.type.pythonDescription() as PythonCallableTypeDescription - val shortType = meta.removeNonPositionalArgs(definition.type) - val posArgsCount = shortType.arguments.size - val funcName = definition.meta.name - val baseArgs = definition.meta.args.take(posArgsCount) - val additionalVars = definition.meta.args.drop(posArgsCount) + fun arguments(): Map = definition.meta.args.associateBy { it.name } - return "$funcName(${}" - .joinToString(separator = "\n", prefix = "\n") { arg -> - "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types - } - } +// fun methodSignarureWithKeywords(): String { +// fun argWithAnnotation(arg: PythonVariableDescription): String = "${arg.name}" +// val meta = definition.type.pythonDescription() as PythonCallableTypeDescription +// val shortType = meta.removeNonPositionalArgs(definition.type) +// val posArgsCount = shortType.arguments.size +// val funcName = definition.meta.name +// val baseArgs = definition.meta.args.take(posArgsCount) +// val additionalVars = definition.meta.args.drop(posArgsCount) +// +// return "$funcName(${}" +// .joinToString(separator = "\n", prefix = "\n") { arg -> +// "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types +// } +// } /* Check that the first argument is `self` of `cls`. diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt index 7d8bb6a454..37e96cd479 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt @@ -14,8 +14,12 @@ import org.utbot.python.evaluation.serialiation.SuccessExecution import org.utbot.python.evaluation.serialiation.serializeObjects import org.utbot.python.evaluation.utils.CoverageIdGenerator import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.newtyping.PythonCallableTypeDescription +import org.utbot.python.newtyping.pythonDescription import org.utbot.python.newtyping.pythonTypeName import org.utbot.python.newtyping.pythonTypeRepresentation +import org.utbot.python.newtyping.utils.isNamed +import org.utbot.python.newtyping.utils.isRequired import java.net.SocketException private val logger = KotlinLogging.logger {} @@ -61,6 +65,17 @@ class PythonCodeSocketExecutor( ): PythonEvaluationResult { val (arguments, memory) = serializeObjects(fuzzedValues.allArguments.map { it.tree }) + val meta = method.definition.type.pythonDescription() as PythonCallableTypeDescription + val argKinds = meta.argumentKinds + val namedArgs = meta.argumentNames + .filterIndexed { index, _ -> !isNamed(argKinds[index]) } + + val (positionalArguments, namedArguments) = arguments + .zip(fuzzedValues.names) + .partition { (_, name) -> + namedArgs.contains(name) + } + val containingClass = method.containingPythonClass val functionTextName = if (containingClass == null) @@ -75,8 +90,8 @@ class PythonCodeSocketExecutor( moduleToImport, additionalModulesToImport.toList(), syspathDirectories.toList(), - arguments, - emptyMap(), // here can be only-kwargs arguments + positionalArguments.map { it.first }, + namedArguments.associate { it.second!! to it.first }, // here can be only-kwargs arguments memory, method.moduleFilename, coverageId, diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt index e88d0ba665..dcff4c4dc2 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt @@ -118,6 +118,6 @@ class PythonCodeGenerator( "", functionName, ) + method.codeAsString.split("\n").map { " $it" } - return mypyCheckCode.joinToString("\n") + return mypyCheckCode.joinToString("\n") } } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgCallableAccessManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgCallableAccessManager.kt index e4eae18db6..7830ad22a8 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgCallableAccessManager.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgCallableAccessManager.kt @@ -50,26 +50,13 @@ class PythonCgCallableAccessManagerImpl(val context: CgContext) : CgCallableAcce override fun CgIncompleteMethodCall.invoke(vararg args: Any?): CgMethodCall { val resolvedArgs = emptyList().toMutableList() args.forEach { arg -> + // if arg is named argument we must use this name if (arg is CgPythonTree) { resolvedArgs.add(arg.value) -// arg.children.forEach { +it } } else { resolvedArgs.add(arg as CgExpression) } } -// resolvedArgs.forEach { -// if (it is CgPythonTree) { -// it.children.forEach { child -> -// if (child is CgAssignment) { -// if (!existingVariableNames.contains(child.lValue.toString())) { -// +child -// } -// } else { -// +child -// } -// } -// } -// } val methodCall = CgMethodCall(caller, method, resolvedArgs) if (method is PythonMethodId) newMethodCall(method) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt index 8617f88f12..1d87ec834e 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt @@ -1,12 +1,20 @@ package org.utbot.python.framework.codegen.model.tree +import org.utbot.framework.codegen.domain.models.CgAnnotation +import org.utbot.framework.codegen.domain.models.CgDocumentationComment import org.utbot.framework.codegen.domain.models.CgElement import org.utbot.framework.codegen.domain.models.CgExpression import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTestMethodType import org.utbot.framework.codegen.domain.models.CgValue import org.utbot.framework.codegen.domain.models.CgVariable import org.utbot.framework.codegen.renderer.CgVisitor +import org.utbot.framework.codegen.tree.VisibilityModifier import org.utbot.framework.plugin.api.ClassId import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.api.python.PythonTree diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt index d865d317ad..d1e305d9d4 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt @@ -15,3 +15,6 @@ fun weightedRandom(elems: List, weights: List, random: Random): T fun isRequired(kind: PythonCallableTypeDescription.ArgKind) = listOf(PythonCallableTypeDescription.ArgKind.ARG_POS, PythonCallableTypeDescription.ArgKind.ARG_NAMED).contains(kind) + +fun isNamed(kind: PythonCallableTypeDescription.ArgKind) = + listOf(PythonCallableTypeDescription.ArgKind.ARG_NAMED_OPT, PythonCallableTypeDescription.ArgKind.ARG_NAMED).contains(kind) From ad6633e42083c36a9c724b17001f38ef3a8b290e Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Mon, 3 Jul 2023 18:07:36 +0300 Subject: [PATCH 15/44] Working on named arguments --- .../samples/easy_samples/with_kwargs.py | 10 ++++++ .../utbot/python/PythonTestCaseGenerator.kt | 6 ++-- .../python/PythonTestGenerationProcessor.kt | 11 +++--- .../kotlin/org/utbot/python/UTPythonAPI.kt | 34 ++++++++----------- .../python/framework/api/python/PythonApi.kt | 2 ++ .../tree/PythonCgMethodConstructor.kt | 8 +++++ .../constructor/visitor/CgPythonRenderer.kt | 5 +++ .../constructor/visitor/CgPythonVisitor.kt | 1 + .../codegen/model/tree/CgPythonElement.kt | 8 +++++ 9 files changed, 57 insertions(+), 28 deletions(-) diff --git a/utbot-python/samples/easy_samples/with_kwargs.py b/utbot-python/samples/easy_samples/with_kwargs.py index 3e31ae879e..db6d78da20 100644 --- a/utbot-python/samples/easy_samples/with_kwargs.py +++ b/utbot-python/samples/easy_samples/with_kwargs.py @@ -1,3 +1,12 @@ + +class A: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + def f(x, y=1, *, z): if y == 0: return 100 * x @@ -12,5 +21,6 @@ def g(x): return x ** 2 + if __name__ == '__main__': print(f(1, y=2, z=3)) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt index 770aa4e015..2d44daa95e 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt @@ -242,9 +242,10 @@ class PythonTestCaseGenerator( .joinToString(separator = "\n", prefix = "\n") { arg -> "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types } - method.definition = PythonFunctionDefinition(shortMeta, shortType) + val shortDef = PythonFunctionDefinition(shortMeta, shortType) + val shortMethod = PythonMethod(method.name, method.moduleFilename, method.containingPythonClass, method.codeAsString, shortDef, method.ast) val missingLines = methodHandler( - method, + shortMethod, typeStorage, coveredLines, errors, @@ -253,7 +254,6 @@ class PythonTestCaseGenerator( firstUntil, additionalVars ) - method.definition = originalDef methodHandler(method, typeStorage, coveredLines, errors, executions, missingLines, until) } else { methodHandler(method, typeStorage, coveredLines, errors, executions, null, until) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt index fc69c386cd..0013c7697d 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt @@ -92,16 +92,17 @@ abstract class PythonTestGenerationProcessor { val containingClassName = getContainingClassName(testSets) val classId = PythonClassId(configuration.testFileInformation.moduleName, containingClassName) - val methodIds = testSets.associate { - it.method to PythonMethodId( + val methodIds = testSets.associate { testSet -> + testSet.method to PythonMethodId( classId, - it.method.name, + testSet.method.name, RawPythonAnnotation(pythonAnyClassId.name), - it.method.arguments.map { argument -> + testSet.method.arguments.map { argument -> argument.annotation?.let { annotation -> RawPythonAnnotation(annotation) } ?: pythonAnyClassId - } + }, + arguments = testSet.method.arguments ) } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt index cf1e1f7489..49a5f0fc39 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt @@ -9,8 +9,13 @@ import org.utbot.python.framework.api.python.util.pythonAnyClassId import org.utbot.python.newtyping.* import org.utbot.python.newtyping.general.CompositeType import org.utbot.python.newtyping.mypy.MypyAnnotations +import org.utbot.python.newtyping.utils.isNamed -data class PythonArgument(val name: String, val annotation: String?) +data class PythonArgument( + val name: String, + val annotation: String?, + val isNamed: Boolean = false, +) class PythonMethodHeader( val name: String, @@ -31,22 +36,7 @@ class PythonMethod( "${it.name}: ${it.annotation ?: pythonAnyClassId.name}" } + ")" - fun arguments(): Map = definition.meta.args.associateBy { it.name } - -// fun methodSignarureWithKeywords(): String { -// fun argWithAnnotation(arg: PythonVariableDescription): String = "${arg.name}" -// val meta = definition.type.pythonDescription() as PythonCallableTypeDescription -// val shortType = meta.removeNonPositionalArgs(definition.type) -// val posArgsCount = shortType.arguments.size -// val funcName = definition.meta.name -// val baseArgs = definition.meta.args.take(posArgsCount) -// val additionalVars = definition.meta.args.drop(posArgsCount) -// -// return "$funcName(${}" -// .joinToString(separator = "\n", prefix = "\n") { arg -> -// "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types -// } -// } + val argumentNames: Map = definition.meta.args.associateBy { it.name } /* Check that the first argument is `self` of `cls`. @@ -57,9 +47,13 @@ class PythonMethod( val arguments: List get() { - val paramNames = definition.meta.args.map { it.name } - return (definition.type.arguments zip paramNames).map { - PythonArgument(it.second, it.first.pythonTypeRepresentation()) // TODO: improve pythonTypeRepresentation + val meta = definition.type.pythonDescription() as PythonCallableTypeDescription + return (definition.type.arguments).mapIndexed { index, type -> + PythonArgument( + meta.argumentNames[index]!!, + type.pythonTypeRepresentation(), // TODO: improve pythonTypeRepresentation + isNamed(meta.argumentKinds[index]) + ) } } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt index be50cf7f7c..7d2bd92302 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt @@ -1,6 +1,7 @@ package org.utbot.python.framework.api.python import org.utbot.framework.plugin.api.* +import org.utbot.python.PythonArgument import org.utbot.python.framework.api.python.util.comparePythonTree import org.utbot.python.framework.api.python.util.moduleOfType @@ -46,6 +47,7 @@ class PythonMethodId( override val name: String, override val returnType: RawPythonAnnotation, override val parameters: List, + val arguments: List? = null, ) : MethodId(classId, name, returnType, parameters) { val moduleName: String = classId.moduleName val rootModuleName: String = this.toString().split(".")[0] diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt index e2b94f4b08..1b39adc97e 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt @@ -23,6 +23,7 @@ import org.utbot.python.framework.api.python.util.pythonIntClassId import org.utbot.python.framework.api.python.util.pythonNoneClassId import org.utbot.python.framework.codegen.PythonCgLanguageAssistant import org.utbot.python.framework.codegen.model.tree.* +import org.utbot.python.newtyping.utils.isNamed class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(context) { private val maxDepth: Int = 5 @@ -33,6 +34,7 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex override fun createTestMethod(executableId: ExecutableId, execution: UtExecution): CgTestMethod = withTestMethodScope(execution) { + val arguments = (executableId as PythonMethodId).arguments val constructorState = (execution as PythonUtExecution).stateInit val diffIds = execution.diffIds (context.cgLanguageAssistant as PythonCgLanguageAssistant).clear() @@ -96,6 +98,9 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex stateAssertions[index] = Pair(argument, afterValue) } } + if (arguments?.get(index)?.isNamed == true) { + argument = CgPythonNamedArgument(name, argument) + } methodArguments += argument } @@ -103,6 +108,9 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex if (it is CgPythonTree) { context.currentBlock.addAll(it.arguments) } + if (it is CgPythonNamedArgument && it.value is CgPythonTree) { + context.currentBlock.addAll(it.value.arguments) + } } recordActualResult() diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt index 55d94714f0..ebcb1c43af 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt @@ -578,6 +578,11 @@ internal class CgPythonRenderer( withIndent { element.statements.forEach { it.accept(this) } } } + override fun visit(element: CgPythonNamedArgument) { + element.name?.let { print("$it=") } + element.value.accept(this) + } + override fun visit(element: CgPythonDict) { print("{") element.elements.map { (key, value) -> diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonVisitor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonVisitor.kt index 904012191a..de93c4de42 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonVisitor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonVisitor.kt @@ -16,4 +16,5 @@ interface CgPythonVisitor : CgVisitor { fun visit(element: CgPythonSet): R fun visit(element: CgPythonTree): R fun visit(element: CgPythonWith): R + fun visit(element: CgPythonNamedArgument): R } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt index 1d87ec834e..140eed8183 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt @@ -36,6 +36,7 @@ interface CgPythonElement : CgElement { is CgPythonTuple -> visitor.visit(element) is CgPythonTree -> visitor.visit(element) is CgPythonWith -> visitor.visit(element) + is CgPythonNamedArgument -> visitor.visit(element) else -> throw IllegalArgumentException("Can not visit element of type ${element::class}") } } else { @@ -123,3 +124,10 @@ data class CgPythonWith( val target: CgExpression?, val statements: List, ) : CgStatement, CgPythonElement + +class CgPythonNamedArgument( + val name: String?, + val value: CgExpression, +) : CgValue, CgPythonElement { + override val type: ClassId = value.type +} From 5a122dcb560852c64cb0e40e53d7e6fab6e75407 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 4 Jul 2023 11:54:36 +0300 Subject: [PATCH 16/44] Supported named arguments --- .../samples/easy_samples/with_kwargs.py | 14 +- .../kotlin/org/utbot/python/PythonEngine.kt | 7 +- .../utbot/python/PythonTestCaseGenerator.kt | 210 ++++++++++-------- .../python/PythonTestGenerationProcessor.kt | 1 - .../kotlin/org/utbot/python/UTPythonAPI.kt | 5 +- .../evaluation/PythonCodeSocketExecutor.kt | 2 +- .../python/framework/api/python/PythonApi.kt | 7 +- .../tree/PythonCgMethodConstructor.kt | 6 +- 8 files changed, 140 insertions(+), 112 deletions(-) diff --git a/utbot-python/samples/easy_samples/with_kwargs.py b/utbot-python/samples/easy_samples/with_kwargs.py index db6d78da20..40f592c78d 100644 --- a/utbot-python/samples/easy_samples/with_kwargs.py +++ b/utbot-python/samples/easy_samples/with_kwargs.py @@ -4,10 +4,19 @@ def __init__(self, x: int): self.x = x def __eq__(self, other): - return self.x == other.x + return self.x == other + def f1(self, x, y=1, *, z, t=2): + if y == 0: + return 100 * x + t + else: + x *= y + if x % 2 == 0: + y = g(x) + z + return x + y + 100 / y -def f(x, y=1, *, z): + +def f(x, y=1, *, z, t=2): if y == 0: return 100 * x else: @@ -21,6 +30,5 @@ def g(x): return x ** 2 - if __name__ == '__main__': print(f(1, y=2, z=3)) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt index 051d12ef48..23e22c8026 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt @@ -106,7 +106,6 @@ class PythonEngine( val coveredInstructions = coveredLinesToInstructions(coveredLines, methodUnderTest) val coverage = Coverage(coveredInstructions) - val utFuzzedExecution = PythonUtExecution( stateInit = EnvironmentModels(beforeThisObject, beforeModelList, emptyMap()), stateBefore = EnvironmentModels(beforeThisObject, beforeModelList, emptyMap()), @@ -116,7 +115,8 @@ class PythonEngine( coverage = coverage, testMethodName = testMethodName.testName?.camelToSnakeCase(), displayName = testMethodName.displayName, - summary = summary.map { DocRegularStmt(it) } + summary = summary.map { DocRegularStmt(it) }, + arguments = methodUnderTest.argumentsWithoutSelf ) return ValidExecution(utFuzzedExecution) } @@ -170,7 +170,8 @@ class PythonEngine( coverage = evaluationResult.coverage, testMethodName = testMethodName.testName?.camelToSnakeCase(), displayName = testMethodName.displayName, - summary = summary.map { DocRegularStmt(it) } + summary = summary.map { DocRegularStmt(it) }, + arguments = methodUnderTest.argumentsWithoutSelf, ) return ValidExecution(utFuzzedExecution) } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt index 2d44daa95e..e19454598c 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt @@ -95,13 +95,13 @@ class PythonTestCaseGenerator( }.take(MAX_SUBSTITUTIONS).toList() } - private fun substituteTypeParameters(method: PythonMethod, typeStorage: PythonTypeStorage): List { - val newClasses = - if (method.containingPythonClass != null) { - generateTypesAfterSubstitution(method.containingPythonClass, typeStorage) - } else { - listOf(null) - } + private fun substituteTypeParameters( + method: PythonMethod, + typeStorage: PythonTypeStorage, + ): List { + val newClasses = method.containingPythonClass?.let { + generateTypesAfterSubstitution(it, typeStorage) + } ?: listOf(null) return newClasses.flatMap { newClass -> val funcType = newClass?.getPythonAttributeByName(typeStorage, method.name)?.type as? FunctionType ?: method.definition.type @@ -128,7 +128,7 @@ class PythonTestCaseGenerator( executions: MutableList, initMissingLines: Set?, until: Long, - additionalVars: String = "" + additionalVars: String = "", ): Set? { // returns missing lines val limitManager = TestGenerationLimitManager( ExecutionWithTimeoutMode, @@ -142,75 +142,73 @@ class PythonTestCaseGenerator( PythonFuzzedConcreteValue(type, value) } - substituteTypeParameters(method, typeStorage).forEach { newMethod -> - inferAnnotations( - newMethod, - mypyStorage, - typeStorage, - hintCollector, - mypyReportLine, - mypyConfigFile, - limitManager, - additionalVars - ) { functionType -> - val args = (functionType as FunctionType).arguments - - logger.info { "Inferred annotations: ${args.joinToString { it.pythonTypeRepresentation() }}" } - - val engine = PythonEngine( - newMethod, - directoriesForSysPath, - curModule, - pythonPath, - constants, - timeoutForRun, - PythonTypeStorage.get(mypyStorage) - ) + inferAnnotations( + method, + mypyStorage, + typeStorage, + hintCollector, + mypyReportLine, + mypyConfigFile, + limitManager, + additionalVars + ) { functionType -> + val args = (functionType as FunctionType).arguments - var feedback: InferredTypeFeedback = SuccessFeedback + logger.info { "Inferred annotations: ${args.joinToString { it.pythonTypeRepresentation() }}" } - val fuzzerCancellation = { isCancelled() || limitManager.isCancelled() } + val engine = PythonEngine( + method, + directoriesForSysPath, + curModule, + pythonPath, + constants, + timeoutForRun, + PythonTypeStorage.get(mypyStorage) + ) - engine.fuzzing(args, fuzzerCancellation, until).collect { - when (it) { - is ValidExecution -> { - executions += it.utFuzzedExecution - missingLines = updateMissingLines(it.utFuzzedExecution, coveredLines, missingLines) - feedback = SuccessFeedback - limitManager.addSuccessExecution() - } - is InvalidExecution -> { - errors += it.utError - feedback = InvalidTypeFeedback - limitManager.addInvalidExecution() - } - is ArgumentsTypeErrorFeedback -> { - feedback = InvalidTypeFeedback - limitManager.addInvalidExecution() - } - is TypeErrorFeedback -> { - feedback = InvalidTypeFeedback - limitManager.addInvalidExecution() - } - is CachedExecutionFeedback -> { - when (it.cachedFeedback) { - is ValidExecution -> { - limitManager.addSuccessExecution() - } - else -> { - limitManager.addInvalidExecution() - } + var feedback: InferredTypeFeedback = SuccessFeedback + + val fuzzerCancellation = { isCancelled() || limitManager.isCancelled() } + + engine.fuzzing(args, fuzzerCancellation, until).collect { + when (it) { + is ValidExecution -> { + executions += it.utFuzzedExecution + missingLines = updateMissingLines(it.utFuzzedExecution, coveredLines, missingLines) + feedback = SuccessFeedback + limitManager.addSuccessExecution() + } + is InvalidExecution -> { + errors += it.utError + feedback = InvalidTypeFeedback + limitManager.addInvalidExecution() + } + is ArgumentsTypeErrorFeedback -> { + feedback = InvalidTypeFeedback + limitManager.addInvalidExecution() + } + is TypeErrorFeedback -> { + feedback = InvalidTypeFeedback + limitManager.addInvalidExecution() + } + is CachedExecutionFeedback -> { + when (it.cachedFeedback) { + is ValidExecution -> { + limitManager.addSuccessExecution() + } + else -> { + limitManager.addInvalidExecution() } - } - is FakeNodeFeedback -> { - limitManager.addFakeNodeExecutions() } } - limitManager.missedLines = missingLines?.size + is FakeNodeFeedback -> { + limitManager.addFakeNodeExecutions() + } } - limitManager.restart() - feedback + limitManager.missedLines = missingLines?.size } + limitManager.restart() + feedback } return missingLines } @@ -226,37 +224,27 @@ class PythonTestCaseGenerator( logger.info("Start test generation for ${method.name}") try { - val meta = method.definition.type.pythonDescription() as PythonCallableTypeDescription - val argKinds = meta.argumentKinds - if (argKinds.any { !isRequired(it) }) { - val now = System.currentTimeMillis() - val firstUntil = (until - now) / 2 + now - val originalDef = method.definition - val shortType = meta.removeNotRequiredArgs(originalDef.type) - val shortMeta = PythonFuncItemDescription( - originalDef.meta.name, - originalDef.meta.args.filterIndexed { index, _ -> isRequired(argKinds[index]) } - ) - val additionalVars = originalDef.meta.args - .filterIndexed { index, _ -> !isRequired(argKinds[index]) } - .joinToString(separator = "\n", prefix = "\n") { arg -> - "${arg.name}: ${pythonAnyType.pythonTypeRepresentation()}" // TODO: better types - } - val shortDef = PythonFunctionDefinition(shortMeta, shortType) - val shortMethod = PythonMethod(method.name, method.moduleFilename, method.containingPythonClass, method.codeAsString, shortDef, method.ast) - val missingLines = methodHandler( - shortMethod, + val methodModifications = mutableSetOf>() // Set of pairs + + substituteTypeParameters(method, typeStorage).forEach { newMethod -> + createShortForm(newMethod)?.let { methodModifications.add(it) } + methodModifications.add(newMethod to "") + } + + val now = System.currentTimeMillis() + val timeout = (until - now) / methodModifications.size + var missingLines: Set? = null + methodModifications.forEach { (method, additionalVars) -> + missingLines = methodHandler( + method, typeStorage, coveredLines, errors, executions, - null, - firstUntil, - additionalVars + missingLines, + minOf(until, System.currentTimeMillis() + timeout), + additionalVars, ) - methodHandler(method, typeStorage, coveredLines, errors, executions, missingLines, until) - } else { - methodHandler(method, typeStorage, coveredLines, errors, executions, null, until) } } catch (_: OutOfMemoryError) { logger.info { "Out of memory error. Stop test generation process" } @@ -347,4 +335,36 @@ class PythonTestCaseGenerator( } } } + + companion object { + fun createShortForm(method: PythonMethod): Pair? { + val meta = method.definition.type.pythonDescription() as PythonCallableTypeDescription + val argKinds = meta.argumentKinds + if (argKinds.any { !isRequired(it) }) { + val originalDef = method.definition + val shortType = meta.removeNotRequiredArgs(originalDef.type) + val shortMeta = PythonFuncItemDescription( + originalDef.meta.name, + originalDef.meta.args.filterIndexed { index, _ -> isRequired(argKinds[index]) } + ) + val additionalVars = originalDef.meta.args + .filterIndexed { index, _ -> !isRequired(argKinds[index]) } + .mapIndexed { index, arg -> + "${arg.name}: ${method.argumentsWithoutSelf[index].annotation ?: pythonAnyType.pythonTypeRepresentation()}" + } + .joinToString(separator = "\n", prefix = "\n") + val shortDef = PythonFunctionDefinition(shortMeta, shortType) + val shortMethod = PythonMethod( + method.name, + method.moduleFilename, + method.containingPythonClass, + method.codeAsString, + shortDef, + method.ast + ) + return Pair(shortMethod, additionalVars) + } + return null + } + } } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt index 0013c7697d..19c7b361bf 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt @@ -102,7 +102,6 @@ abstract class PythonTestGenerationProcessor { RawPythonAnnotation(annotation) } ?: pythonAnyClassId }, - arguments = testSet.method.arguments ) } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt index 49a5f0fc39..71a46b2905 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt @@ -36,8 +36,6 @@ class PythonMethod( "${it.name}: ${it.annotation ?: pythonAnyClassId.name}" } + ")" - val argumentNames: Map = definition.meta.args.associateBy { it.name } - /* Check that the first argument is `self` of `cls`. TODO: Now we think that all class methods has `self` argument! We should support `@property` decorator @@ -57,6 +55,9 @@ class PythonMethod( } } + val argumentsWithoutSelf: List + get() = if (hasThisArgument) arguments.drop(1) else arguments + val thisObjectName: String? get() = if (hasThisArgument) arguments[0].name else null diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt index 37e96cd479..be259a046b 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt @@ -71,7 +71,7 @@ class PythonCodeSocketExecutor( .filterIndexed { index, _ -> !isNamed(argKinds[index]) } val (positionalArguments, namedArguments) = arguments - .zip(fuzzedValues.names) + .zip(meta.argumentNames) .partition { (_, name) -> namedArgs.contains(name) } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt index 7d2bd92302..928378f5ec 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt @@ -47,7 +47,6 @@ class PythonMethodId( override val name: String, override val returnType: RawPythonAnnotation, override val parameters: List, - val arguments: List? = null, ) : MethodId(classId, name, returnType, parameters) { val moduleName: String = classId.moduleName val rootModuleName: String = this.toString().split(".")[0] @@ -95,10 +94,11 @@ class PythonUtExecution( stateAfter: EnvironmentModels, val diffIds: List, result: UtExecutionResult, + val arguments: List, coverage: Coverage? = null, summary: List? = null, testMethodName: String? = null, - displayName: String? = null + displayName: String? = null, ) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName) { override fun copy( stateBefore: EnvironmentModels, @@ -118,7 +118,8 @@ class PythonUtExecution( coverage = coverage, summary = summary, testMethodName = testMethodName, - displayName = displayName + displayName = displayName, + arguments = arguments ) } } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt index 1b39adc97e..6c38e8aacb 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt @@ -23,7 +23,6 @@ import org.utbot.python.framework.api.python.util.pythonIntClassId import org.utbot.python.framework.api.python.util.pythonNoneClassId import org.utbot.python.framework.codegen.PythonCgLanguageAssistant import org.utbot.python.framework.codegen.model.tree.* -import org.utbot.python.newtyping.utils.isNamed class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(context) { private val maxDepth: Int = 5 @@ -34,7 +33,6 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex override fun createTestMethod(executableId: ExecutableId, execution: UtExecution): CgTestMethod = withTestMethodScope(execution) { - val arguments = (executableId as PythonMethodId).arguments val constructorState = (execution as PythonUtExecution).stateInit val diffIds = execution.diffIds (context.cgLanguageAssistant as PythonCgLanguageAssistant).clear() @@ -85,7 +83,7 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex // build arguments val stateAssertions = emptyMap>().toMutableMap() for ((index, param) in constructorState.parameters.withIndex()) { - val name = paramNames[executableId]?.get(index) + val name = execution.arguments[index].name var argument = variableConstructor.getOrCreateVariable(param, name) val beforeValue = execution.stateBefore.parameters[index] @@ -98,7 +96,7 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex stateAssertions[index] = Pair(argument, afterValue) } } - if (arguments?.get(index)?.isNamed == true) { + if (execution.arguments[index].isNamed) { argument = CgPythonNamedArgument(name, argument) } From a02ca1d007c41eb4980476a9f22cad929c0ef834 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 4 Jul 2023 18:28:44 +0300 Subject: [PATCH 17/44] Supported self checking --- utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt index 71a46b2905..e2a7e10037 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt @@ -38,10 +38,10 @@ class PythonMethod( /* Check that the first argument is `self` of `cls`. - TODO: Now we think that all class methods has `self` argument! We should support `@property` decorator + TODO: We should support `@property` decorator */ val hasThisArgument: Boolean - get() = containingPythonClass != null + get() = containingPythonClass != null && definition.meta.args.any { it.isSelf } val arguments: List get() { From a26d4cf980152e6a70386cc55414da47562a797d Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 11 Jul 2023 16:57:17 +0300 Subject: [PATCH 18/44] Update sys path rendering --- .../org/utbot/python/PythonTestGenerationProcessor.kt | 3 ++- .../codegen/model/constructor/visitor/CgPythonRenderer.kt | 4 ++-- .../org/utbot/python/framework/codegen/utils/StringUtils.kt | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt index 19c7b361bf..62af82a542 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt @@ -186,6 +186,7 @@ abstract class PythonTestGenerationProcessor { val rootModule = configuration.testFileInformation.moduleName.split(".").first() val testRootModule = PythonUserImport(importName_ = rootModule) val sysImport = PythonSystemImport("sys") + val osImport = PythonSystemImport("os") val sysPathImports = relativizePaths( configuration.executionPath, configuration.sysPathDirectories @@ -195,7 +196,7 @@ abstract class PythonTestGenerationProcessor { configuration.testFramework.testSuperClass?.let { PythonUserImport(importName_ = (it as PythonClassId).rootModuleName) } return (importParamModules + importResultModules + testRootModule + sysPathImports + listOf( - testFrameworkModule, sysImport + testFrameworkModule, osImport, sysImport )) .filterNotNull() .filterNot { it.rootModuleName == pythonBuiltinsModuleName } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt index ebcb1c43af..4661943b34 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt @@ -68,7 +68,7 @@ import org.utbot.python.framework.api.python.pythonBuiltinsModuleName import org.utbot.python.framework.api.python.util.pythonAnyClassId import org.utbot.python.framework.codegen.model.tree.* import java.lang.StringBuilder -import org.utbot.python.framework.codegen.utils.toPythonRawString +import org.utbot.python.framework.codegen.utils.toRelativeRawPath internal class CgPythonRenderer( context: CgRendererContext, @@ -323,7 +323,7 @@ internal class CgPythonRenderer( fun renderPythonImport(pythonImport: PythonImport) { val importBuilder = StringBuilder() if (pythonImport is PythonSysPathImport) { - importBuilder.append("sys.path.append(${pythonImport.sysPath.toPythonRawString()})") + importBuilder.append("sys.path.append(${pythonImport.sysPath.toRelativeRawPath()})") } else if (pythonImport.moduleName == null) { importBuilder.append("import ${pythonImport.importName}") } else { diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt index 44c00025fb..409b7acc85 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt @@ -1,6 +1,8 @@ package org.utbot.python.framework.codegen.utils +import java.nio.file.FileSystems -fun String.toPythonRawString(): String { - return "r'${this}'" + +fun String.toRelativeRawPath(): String { + return "os.path.dirname(__file__) + r'${FileSystems.getDefault().separator}${this}'" } \ No newline at end of file From bec549a2dde3f9667a23ab3a54c70903c3539c41 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 12 Jul 2023 16:32:28 +0300 Subject: [PATCH 19/44] Fix named arguments in magic methods --- .../{with_kwargs.py => named_arguments.py} | 10 ++++++++++ .../utbot/python/newtyping/mypy/MypyAnnotations.kt | 2 +- .../org/utbot/python/newtyping/mypy/MypyStorage.kt | 14 ++++++++++++++ .../org/utbot/python/utils/RequirementsUtils.kt | 2 +- 4 files changed, 26 insertions(+), 2 deletions(-) rename utbot-python/samples/easy_samples/{with_kwargs.py => named_arguments.py} (68%) diff --git a/utbot-python/samples/easy_samples/with_kwargs.py b/utbot-python/samples/easy_samples/named_arguments.py similarity index 68% rename from utbot-python/samples/easy_samples/with_kwargs.py rename to utbot-python/samples/easy_samples/named_arguments.py index 40f592c78d..9818916856 100644 --- a/utbot-python/samples/easy_samples/with_kwargs.py +++ b/utbot-python/samples/easy_samples/named_arguments.py @@ -6,6 +6,16 @@ def __init__(self, x: int): def __eq__(self, other): return self.x == other + def __round__(self, n=None): + if n is not None: + return round(self.x, n) + return round(self.x) + + def __pow__(self, power, modulo=None): + if modulo is None: + return self.x ** power + return pow(self.x, power, modulo) + def f1(self, x, y=1, *, z, t=2): if y == 0: return 100 * x + t diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyAnnotations.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyAnnotations.kt index 350e5ab362..358c8bcadb 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyAnnotations.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyAnnotations.kt @@ -105,7 +105,7 @@ class FunctionNode( val returnType: MypyAnnotation, val typeVars: List, val argKinds: List, - val argNames: List, + var argNames: List, ): MypyAnnotationNode() { override val children: List get() = super.children + argTypes + listOf(returnType) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyStorage.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyStorage.kt index 8ad7cb48fb..c8641f99b9 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyStorage.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/MypyStorage.kt @@ -40,6 +40,19 @@ class MypyAnnotationStorage( annotation.initialized = true annotation.args?.forEach { initAnnotation(it) } } + private fun fillArgNames(definition: MypyDefinition) { + val node = definition.type.node + if (node is ConcreteAnnotation) { + node.members.filterIsInstance().forEach { funcDef -> + val nodeInfo = nodeStorage[funcDef.type.nodeId] + if (nodeInfo is FunctionNode && nodeInfo.argNames.contains(null)) { + nodeInfo.argNames = nodeInfo.argNames.zip(funcDef.args).map { + it.first ?: (it.second as Variable).name + } + } + } + } + } val nodeToUtBotType: MutableMap = mutableMapOf() fun getUtBotTypeOfNode(node: MypyAnnotationNode): Type { //println("entering $node") @@ -57,6 +70,7 @@ class MypyAnnotationStorage( definitions.values.forEach { defsInModule -> defsInModule.forEach { initAnnotation(it.value.type) + fillArgNames(it.value) } } types.values.flatten().forEach { diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt index 25489323bc..082317ea84 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt @@ -4,7 +4,7 @@ object RequirementsUtils { val requirements: List = listOf( "mypy==1.0.0", "utbot-executor==1.4.31", - "utbot-mypy-runner==0.2.8", + "utbot-mypy-runner==0.2.11", ) private val requirementsScriptContent: String = From d0c1b1fc9edda3b163d3b01422a50785df73dcda Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 12 Jul 2023 19:05:45 +0300 Subject: [PATCH 20/44] Refactor example testset --- utbot-python/samples/easy_samples/.gitignore | 3 -- .../samples/easy_samples/import_test.py | 10 ---- utbot-python/samples/easy_samples/regex.py | 18 ------- .../samples/easy_samples/sample_classes.py | 36 ------------- .../easy_samples/test_package/__init__.py | 2 - .../empty_file.py => samples/__init__.py} | 0 .../samples/samples/algorithms/__init__.py | 0 .../samples/{graph.py => algorithms/bfs.py} | 2 +- .../{ => algorithms}/longest_subsequence.py | 0 .../samples/{ => algorithms}/quick_sort.py | 0 .../samples/samples/classes/__init__.py | 0 .../samples/samples/classes/dataclass.py | 9 ++++ .../samples/samples/{ => classes}/dicts.py | 0 .../samples/samples/classes/easy_class.py | 9 ++++ .../classes}/field.py | 0 .../samples/samples/classes/inner_class.py | 12 +++++ .../classes/rename_self.py} | 1 - .../classes}/setstate_test.py | 0 .../samples/samples/collections/__init__.py | 0 .../samples/{ => collections}/lists.py | 0 .../{ => collections}/using_collections.py | 0 .../samples/samples/controlflow/__init__.py | 0 .../samples/{ => controlflow}/arithmetic.py | 20 ------- .../samples/samples/controlflow/conditions.py | 18 +++++++ .../inner_conditions.py} | 0 .../samples/samples/easy_samples/__init__.py | 0 .../{ => samples}/easy_samples/deep_equals.py | 0 .../deep_equals_2.py} | 0 .../{ => easy_samples}/dummy_with_eq.py | 0 .../{ => easy_samples}/dummy_without_eq.py | 0 .../easy_samples/fully_annotated.py | 0 .../{ => samples}/easy_samples/general.py | 3 -- .../easy_samples/long_function_coverage.py | 0 .../samples/samples/exceptions/__init__.py | 0 .../samples/exceptions/exception_examples.py | 53 +++++++++++++++++++ .../exceptions/my_checked_exception.py | 6 +++ .../samples/samples/imports/__init__.py | 0 .../imports/builtins_module/__init__.py | 0 .../samples/imports/builtins_module/crypto.py | 15 ++++++ .../samples/imports/pack_1/__init__.py | 0 .../imports/pack_1/inner_pack_1/__init__.py | 0 .../pack_1/inner_pack_1/inner_mod_1.py | 7 +++ .../pack_1/inner_pack_2/inner_mod_2.py | 5 ++ .../pack_1/inner_pack_2/inner_mod_3.py | 2 + utbot-python/samples/samples/math/__init__.py | 0 .../samples/named_arguments/__init__.py | 0 .../method_named_arguments.py} | 22 ++------ .../named_arguments/named_arguments.py | 16 ++++++ .../samples/samples/primitives/__init__.py | 0 .../samples/primitives/bytes_example.py | 7 +++ .../samples/samples/primitives/numbers.py | 34 ++++++++++++ .../{ => primitives}/primitive_types.py | 0 .../samples/primitives/string/__init__.py | 0 .../samples/primitives/string/regex.py | 9 ++++ .../samples/primitives/string/str_example.py | 51 ++++++++++++++++++ .../samples/samples/recursion/__init__.py | 0 .../samples/samples/recursion/recursion.py | 41 ++++++++++++++ .../samples/samples/structures/__init__.py | 0 .../structures}/boruvka.py | 0 .../samples/samples/{ => structures}/deque.py | 0 .../samples/{ => structures}/matrix.py | 0 .../samples/type_inference/__init__.py | 0 .../type_inference/annotation2.py} | 9 +--- .../type_inference/annotations.py} | 0 .../type_inference}/generics.py | 0 .../{ => type_inference}/list_of_datetime.py | 0 .../type_inference}/subtypes.py | 3 -- .../{ => type_inference}/type_inference.py | 0 .../{ => type_inference}/type_inference_2.py | 0 69 files changed, 300 insertions(+), 123 deletions(-) delete mode 100644 utbot-python/samples/easy_samples/.gitignore delete mode 100644 utbot-python/samples/easy_samples/import_test.py delete mode 100644 utbot-python/samples/easy_samples/regex.py delete mode 100644 utbot-python/samples/easy_samples/sample_classes.py delete mode 100644 utbot-python/samples/easy_samples/test_package/__init__.py rename utbot-python/samples/{easy_samples/empty_file.py => samples/__init__.py} (100%) create mode 100644 utbot-python/samples/samples/algorithms/__init__.py rename utbot-python/samples/samples/{graph.py => algorithms/bfs.py} (96%) rename utbot-python/samples/samples/{ => algorithms}/longest_subsequence.py (100%) rename utbot-python/samples/samples/{ => algorithms}/quick_sort.py (100%) create mode 100644 utbot-python/samples/samples/classes/__init__.py create mode 100644 utbot-python/samples/samples/classes/dataclass.py rename utbot-python/samples/samples/{ => classes}/dicts.py (100%) create mode 100644 utbot-python/samples/samples/classes/easy_class.py rename utbot-python/samples/{easy_samples => samples/classes}/field.py (100%) create mode 100644 utbot-python/samples/samples/classes/inner_class.py rename utbot-python/samples/{easy_samples/corner_cases.py => samples/classes/rename_self.py} (89%) rename utbot-python/samples/{easy_samples => samples/classes}/setstate_test.py (100%) create mode 100644 utbot-python/samples/samples/collections/__init__.py rename utbot-python/samples/samples/{ => collections}/lists.py (100%) rename utbot-python/samples/samples/{ => collections}/using_collections.py (100%) create mode 100644 utbot-python/samples/samples/controlflow/__init__.py rename utbot-python/samples/samples/{ => controlflow}/arithmetic.py (59%) create mode 100644 utbot-python/samples/samples/controlflow/conditions.py rename utbot-python/samples/samples/{test_coverage.py => controlflow/inner_conditions.py} (100%) create mode 100644 utbot-python/samples/samples/easy_samples/__init__.py rename utbot-python/samples/{ => samples}/easy_samples/deep_equals.py (100%) rename utbot-python/samples/samples/{deep_equals.py => easy_samples/deep_equals_2.py} (100%) rename utbot-python/samples/samples/{ => easy_samples}/dummy_with_eq.py (100%) rename utbot-python/samples/samples/{ => easy_samples}/dummy_without_eq.py (100%) rename utbot-python/samples/{ => samples}/easy_samples/fully_annotated.py (100%) rename utbot-python/samples/{ => samples}/easy_samples/general.py (98%) rename utbot-python/samples/{ => samples}/easy_samples/long_function_coverage.py (100%) create mode 100644 utbot-python/samples/samples/exceptions/__init__.py create mode 100644 utbot-python/samples/samples/exceptions/exception_examples.py create mode 100644 utbot-python/samples/samples/exceptions/my_checked_exception.py create mode 100644 utbot-python/samples/samples/imports/__init__.py create mode 100644 utbot-python/samples/samples/imports/builtins_module/__init__.py create mode 100644 utbot-python/samples/samples/imports/builtins_module/crypto.py create mode 100644 utbot-python/samples/samples/imports/pack_1/__init__.py create mode 100644 utbot-python/samples/samples/imports/pack_1/inner_pack_1/__init__.py create mode 100644 utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py create mode 100644 utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_2.py create mode 100644 utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py create mode 100644 utbot-python/samples/samples/math/__init__.py create mode 100644 utbot-python/samples/samples/named_arguments/__init__.py rename utbot-python/samples/{easy_samples/named_arguments.py => samples/named_arguments/method_named_arguments.py} (66%) create mode 100644 utbot-python/samples/samples/named_arguments/named_arguments.py create mode 100644 utbot-python/samples/samples/primitives/__init__.py create mode 100644 utbot-python/samples/samples/primitives/bytes_example.py create mode 100644 utbot-python/samples/samples/primitives/numbers.py rename utbot-python/samples/samples/{ => primitives}/primitive_types.py (100%) create mode 100644 utbot-python/samples/samples/primitives/string/__init__.py create mode 100644 utbot-python/samples/samples/primitives/string/regex.py create mode 100644 utbot-python/samples/samples/primitives/string/str_example.py create mode 100644 utbot-python/samples/samples/recursion/__init__.py create mode 100644 utbot-python/samples/samples/recursion/recursion.py create mode 100644 utbot-python/samples/samples/structures/__init__.py rename utbot-python/samples/{easy_samples => samples/structures}/boruvka.py (100%) rename utbot-python/samples/samples/{ => structures}/deque.py (100%) rename utbot-python/samples/samples/{ => structures}/matrix.py (100%) create mode 100644 utbot-python/samples/samples/type_inference/__init__.py rename utbot-python/samples/{easy_samples/annotation_tests.py => samples/type_inference/annotation2.py} (87%) rename utbot-python/samples/{easy_samples/annotations2.py => samples/type_inference/annotations.py} (100%) rename utbot-python/samples/{easy_samples => samples/type_inference}/generics.py (100%) rename utbot-python/samples/samples/{ => type_inference}/list_of_datetime.py (100%) rename utbot-python/samples/{easy_samples => samples/type_inference}/subtypes.py (96%) rename utbot-python/samples/samples/{ => type_inference}/type_inference.py (100%) rename utbot-python/samples/samples/{ => type_inference}/type_inference_2.py (100%) diff --git a/utbot-python/samples/easy_samples/.gitignore b/utbot-python/samples/easy_samples/.gitignore deleted file mode 100644 index aef5dc69ef..0000000000 --- a/utbot-python/samples/easy_samples/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -utbot_tests -.tmp -.pytest_cache \ No newline at end of file diff --git a/utbot-python/samples/easy_samples/import_test.py b/utbot-python/samples/easy_samples/import_test.py deleted file mode 100644 index 3edca6b8da..0000000000 --- a/utbot-python/samples/easy_samples/import_test.py +++ /dev/null @@ -1,10 +0,0 @@ -import importlib.machinery as im -import collections as c -from collections import deque -import importlib - -importlib.machinery.PathFinder - - -class A: - pass diff --git a/utbot-python/samples/easy_samples/regex.py b/utbot-python/samples/easy_samples/regex.py deleted file mode 100644 index 96c8f092f3..0000000000 --- a/utbot-python/samples/easy_samples/regex.py +++ /dev/null @@ -1,18 +0,0 @@ -import re -import time - - -def check_regex(string: str) -> bool: - pattern = r"'(''|\\\\|\\'|[^'])*'" - if re.match(pattern, string): - return True - return False - - -def timetest(): - t = time.time() - print(check_regex("\'\J/\\\\\\'\\N''''P'''\'x'L''\'';'\'N\$'\\\'\'\`''D\\''�='''m\\\\\'\'\\–H\\No'F'(''U]\'V")) - print((time.time() - t)) - -if __name__ == '__main__': - timetest() diff --git a/utbot-python/samples/easy_samples/sample_classes.py b/utbot-python/samples/easy_samples/sample_classes.py deleted file mode 100644 index 7d33528a08..0000000000 --- a/utbot-python/samples/easy_samples/sample_classes.py +++ /dev/null @@ -1,36 +0,0 @@ -from dataclasses import dataclass - - -class A: - def __init__(self, val: int): - self.description = val - - -class B: - def __init__(self, val: complex): - self.description = val - - def sqrt(self): - return self.description ** 0.5 - - -@dataclass -class C: - counter: int = 0 - - def inc(self): - self.counter += 1 - - -class Outer: - class Inner: - a = 1 - - def inc(self): - self.a += 1 - - def __init__(self): - self.inner = Outer.Inner() - - def inc1(self): - self.inner.inc() diff --git a/utbot-python/samples/easy_samples/test_package/__init__.py b/utbot-python/samples/easy_samples/test_package/__init__.py deleted file mode 100644 index bf9748c947..0000000000 --- a/utbot-python/samples/easy_samples/test_package/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -def init_function(x: int): - return x ** 2 diff --git a/utbot-python/samples/easy_samples/empty_file.py b/utbot-python/samples/samples/__init__.py similarity index 100% rename from utbot-python/samples/easy_samples/empty_file.py rename to utbot-python/samples/samples/__init__.py diff --git a/utbot-python/samples/samples/algorithms/__init__.py b/utbot-python/samples/samples/algorithms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/graph.py b/utbot-python/samples/samples/algorithms/bfs.py similarity index 96% rename from utbot-python/samples/samples/graph.py rename to utbot-python/samples/samples/algorithms/bfs.py index 9ef845a919..727bcb7ba2 100644 --- a/utbot-python/samples/samples/graph.py +++ b/utbot-python/samples/samples/algorithms/bfs.py @@ -18,7 +18,7 @@ def __eq__(self, other): return False -def bfs(nodes): +def bfs(nodes: List[Node]): if len(nodes) == 0: return [] diff --git a/utbot-python/samples/samples/longest_subsequence.py b/utbot-python/samples/samples/algorithms/longest_subsequence.py similarity index 100% rename from utbot-python/samples/samples/longest_subsequence.py rename to utbot-python/samples/samples/algorithms/longest_subsequence.py diff --git a/utbot-python/samples/samples/quick_sort.py b/utbot-python/samples/samples/algorithms/quick_sort.py similarity index 100% rename from utbot-python/samples/samples/quick_sort.py rename to utbot-python/samples/samples/algorithms/quick_sort.py diff --git a/utbot-python/samples/samples/classes/__init__.py b/utbot-python/samples/samples/classes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/classes/dataclass.py b/utbot-python/samples/samples/classes/dataclass.py new file mode 100644 index 0000000000..0fd03ee182 --- /dev/null +++ b/utbot-python/samples/samples/classes/dataclass.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass + + +@dataclass +class C: + counter: int = 0 + + def inc(self): + self.counter += 1 diff --git a/utbot-python/samples/samples/dicts.py b/utbot-python/samples/samples/classes/dicts.py similarity index 100% rename from utbot-python/samples/samples/dicts.py rename to utbot-python/samples/samples/classes/dicts.py diff --git a/utbot-python/samples/samples/classes/easy_class.py b/utbot-python/samples/samples/classes/easy_class.py new file mode 100644 index 0000000000..2e3d679714 --- /dev/null +++ b/utbot-python/samples/samples/classes/easy_class.py @@ -0,0 +1,9 @@ +class B: + def __init__(self, val: complex): + self.description = val + + def __eq__(self, other): + return self.description == other.description + + def sqrt(self): + return self.description ** 0.5 \ No newline at end of file diff --git a/utbot-python/samples/easy_samples/field.py b/utbot-python/samples/samples/classes/field.py similarity index 100% rename from utbot-python/samples/easy_samples/field.py rename to utbot-python/samples/samples/classes/field.py diff --git a/utbot-python/samples/samples/classes/inner_class.py b/utbot-python/samples/samples/classes/inner_class.py new file mode 100644 index 0000000000..e77d866b1a --- /dev/null +++ b/utbot-python/samples/samples/classes/inner_class.py @@ -0,0 +1,12 @@ +class Outer: + class Inner: + a = 1 + + def inc(self): + self.a += 1 + + def __init__(self): + self.inner = Outer.Inner() + + def inc1(self): + self.inner.inc() \ No newline at end of file diff --git a/utbot-python/samples/easy_samples/corner_cases.py b/utbot-python/samples/samples/classes/rename_self.py similarity index 89% rename from utbot-python/samples/easy_samples/corner_cases.py rename to utbot-python/samples/samples/classes/rename_self.py index 2805e84108..b4081820fe 100644 --- a/utbot-python/samples/easy_samples/corner_cases.py +++ b/utbot-python/samples/samples/classes/rename_self.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -import sample_classes as s @dataclass diff --git a/utbot-python/samples/easy_samples/setstate_test.py b/utbot-python/samples/samples/classes/setstate_test.py similarity index 100% rename from utbot-python/samples/easy_samples/setstate_test.py rename to utbot-python/samples/samples/classes/setstate_test.py diff --git a/utbot-python/samples/samples/collections/__init__.py b/utbot-python/samples/samples/collections/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/lists.py b/utbot-python/samples/samples/collections/lists.py similarity index 100% rename from utbot-python/samples/samples/lists.py rename to utbot-python/samples/samples/collections/lists.py diff --git a/utbot-python/samples/samples/using_collections.py b/utbot-python/samples/samples/collections/using_collections.py similarity index 100% rename from utbot-python/samples/samples/using_collections.py rename to utbot-python/samples/samples/collections/using_collections.py diff --git a/utbot-python/samples/samples/controlflow/__init__.py b/utbot-python/samples/samples/controlflow/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/arithmetic.py b/utbot-python/samples/samples/controlflow/arithmetic.py similarity index 59% rename from utbot-python/samples/samples/arithmetic.py rename to utbot-python/samples/samples/controlflow/arithmetic.py index a43ca21442..6f26aa94f4 100644 --- a/utbot-python/samples/samples/arithmetic.py +++ b/utbot-python/samples/samples/controlflow/arithmetic.py @@ -15,23 +15,3 @@ def calculate_function_value(x, y): return (3*x**2 - 2*x*y + y**2) / math.sin(x) else: return (0.01 * x) ** math.log2(y) - - -def f(x): - if x == 10: - return 100 - if x == 20: - return 200 - if x == 30: - return 300 - if x == 40: - return 400 - if x == 50: - return 500 - if x == 60: - return 600 - if x < 0: - return x ** 2 - if x > 0: - return 239 - return 100500 \ No newline at end of file diff --git a/utbot-python/samples/samples/controlflow/conditions.py b/utbot-python/samples/samples/controlflow/conditions.py new file mode 100644 index 0000000000..3897f578b1 --- /dev/null +++ b/utbot-python/samples/samples/controlflow/conditions.py @@ -0,0 +1,18 @@ +def f(x): + if x == 10: + return 100 + if x == 20: + return 200 + if x == 30: + return 300 + if x == 40: + return 400 + if x == 50: + return 500 + if x == 60: + return 600 + if x < 0: + return x ** 2 + if x > 0: + return 239 + return 100500 diff --git a/utbot-python/samples/samples/test_coverage.py b/utbot-python/samples/samples/controlflow/inner_conditions.py similarity index 100% rename from utbot-python/samples/samples/test_coverage.py rename to utbot-python/samples/samples/controlflow/inner_conditions.py diff --git a/utbot-python/samples/samples/easy_samples/__init__.py b/utbot-python/samples/samples/easy_samples/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/easy_samples/deep_equals.py b/utbot-python/samples/samples/easy_samples/deep_equals.py similarity index 100% rename from utbot-python/samples/easy_samples/deep_equals.py rename to utbot-python/samples/samples/easy_samples/deep_equals.py diff --git a/utbot-python/samples/samples/deep_equals.py b/utbot-python/samples/samples/easy_samples/deep_equals_2.py similarity index 100% rename from utbot-python/samples/samples/deep_equals.py rename to utbot-python/samples/samples/easy_samples/deep_equals_2.py diff --git a/utbot-python/samples/samples/dummy_with_eq.py b/utbot-python/samples/samples/easy_samples/dummy_with_eq.py similarity index 100% rename from utbot-python/samples/samples/dummy_with_eq.py rename to utbot-python/samples/samples/easy_samples/dummy_with_eq.py diff --git a/utbot-python/samples/samples/dummy_without_eq.py b/utbot-python/samples/samples/easy_samples/dummy_without_eq.py similarity index 100% rename from utbot-python/samples/samples/dummy_without_eq.py rename to utbot-python/samples/samples/easy_samples/dummy_without_eq.py diff --git a/utbot-python/samples/easy_samples/fully_annotated.py b/utbot-python/samples/samples/easy_samples/fully_annotated.py similarity index 100% rename from utbot-python/samples/easy_samples/fully_annotated.py rename to utbot-python/samples/samples/easy_samples/fully_annotated.py diff --git a/utbot-python/samples/easy_samples/general.py b/utbot-python/samples/samples/easy_samples/general.py similarity index 98% rename from utbot-python/samples/easy_samples/general.py rename to utbot-python/samples/samples/easy_samples/general.py index eae1ffaa6b..9aeb49c1fe 100644 --- a/utbot-python/samples/easy_samples/general.py +++ b/utbot-python/samples/samples/easy_samples/general.py @@ -4,9 +4,6 @@ from socket import socket from typing import List, Dict, Set, Optional, AbstractSet from dataclasses import dataclass -import logging -import datetime -import sample_classes class Dummy: diff --git a/utbot-python/samples/easy_samples/long_function_coverage.py b/utbot-python/samples/samples/easy_samples/long_function_coverage.py similarity index 100% rename from utbot-python/samples/easy_samples/long_function_coverage.py rename to utbot-python/samples/samples/easy_samples/long_function_coverage.py diff --git a/utbot-python/samples/samples/exceptions/__init__.py b/utbot-python/samples/samples/exceptions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/exceptions/exception_examples.py b/utbot-python/samples/samples/exceptions/exception_examples.py new file mode 100644 index 0000000000..c052935f66 --- /dev/null +++ b/utbot-python/samples/samples/exceptions/exception_examples.py @@ -0,0 +1,53 @@ +import typing + +from samples.exceptions.my_checked_exception import MyCheckedException + + +def init_array(n: int): + try: + a: typing.List[typing.Optional[int]] = [None] * n + a[n-1] = n + 1 + a[n-2] = n + 2 + return a[n-1] + a[n-2] + except ImportError: + return -1 + except IndexError: + return -2 + + +def nested_exceptions(i: int): + try: + return check_all(i) + except IndexError: + return 100 + except ValueError: + return -100 + + +def check_positive(i: int): + if i > 0: + raise IndexError("Positive") + return 0 + + +def check_all(i: int): + if i < 0: + raise ValueError("Negative") + return check_positive(i) + + +def throw_exception(i: int): + r = 1 + if i > 0: + r += 10 + r -= (i + r) / 0 + else: + r += 100 + return r + + +def throw_my_exception(i: int): + if i > 0: + raise MyCheckedException("i > 0") + return i ** 2 + diff --git a/utbot-python/samples/samples/exceptions/my_checked_exception.py b/utbot-python/samples/samples/exceptions/my_checked_exception.py new file mode 100644 index 0000000000..3be517e3a4 --- /dev/null +++ b/utbot-python/samples/samples/exceptions/my_checked_exception.py @@ -0,0 +1,6 @@ +class MyCheckedException(Exception): + def __init__(self, x: str): + self.x = x + + def method(self, y: str): + return self.x == y diff --git a/utbot-python/samples/samples/imports/__init__.py b/utbot-python/samples/samples/imports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/imports/builtins_module/__init__.py b/utbot-python/samples/samples/imports/builtins_module/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/imports/builtins_module/crypto.py b/utbot-python/samples/samples/imports/builtins_module/crypto.py new file mode 100644 index 0000000000..5315d61e37 --- /dev/null +++ b/utbot-python/samples/samples/imports/builtins_module/crypto.py @@ -0,0 +1,15 @@ +import hashlib +from collections import Counter + + +def f(word: str): + m = hashlib.sha256() + m.update(word.encode()) + code = m.hexdigest() + if len(code) > len(word): + return Counter(code) + return Counter(word) + + +if __name__ == '__main__': + print(f("fjasld")) diff --git a/utbot-python/samples/samples/imports/pack_1/__init__.py b/utbot-python/samples/samples/imports/pack_1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_1/__init__.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py new file mode 100644 index 0000000000..d0413e439f --- /dev/null +++ b/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py @@ -0,0 +1,7 @@ +from ..inner_pack_2 import inner_mod_2 + + +def inner_func_1(a: int): + if a > 1: + return inner_mod_2(a) + return a ** 2 \ No newline at end of file diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_2.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_2.py new file mode 100644 index 0000000000..8d349164a7 --- /dev/null +++ b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_2.py @@ -0,0 +1,5 @@ +from ..inner_pack_2 import inner_mod_3 + + +def inner_func_2(a: int): + return inner_mod_3.inner_func_3(a) diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py new file mode 100644 index 0000000000..c5b9093319 --- /dev/null +++ b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py @@ -0,0 +1,2 @@ +def inner_func_3(x: float): + return int(x) \ No newline at end of file diff --git a/utbot-python/samples/samples/math/__init__.py b/utbot-python/samples/samples/math/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/named_arguments/__init__.py b/utbot-python/samples/samples/named_arguments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/easy_samples/named_arguments.py b/utbot-python/samples/samples/named_arguments/method_named_arguments.py similarity index 66% rename from utbot-python/samples/easy_samples/named_arguments.py rename to utbot-python/samples/samples/named_arguments/method_named_arguments.py index 9818916856..10a61f8f63 100644 --- a/utbot-python/samples/easy_samples/named_arguments.py +++ b/utbot-python/samples/samples/named_arguments/method_named_arguments.py @@ -1,3 +1,5 @@ +from samples.named_arguments.named_arguments import g + class A: def __init__(self, x: int): @@ -23,22 +25,4 @@ def f1(self, x, y=1, *, z, t=2): x *= y if x % 2 == 0: y = g(x) + z - return x + y + 100 / y - - -def f(x, y=1, *, z, t=2): - if y == 0: - return 100 * x - else: - x *= y - if x % 2 == 0: - y = g(x) + z - return x + y + 100 / y - - -def g(x): - return x ** 2 - - -if __name__ == '__main__': - print(f(1, y=2, z=3)) + return x + y + 100 / y \ No newline at end of file diff --git a/utbot-python/samples/samples/named_arguments/named_arguments.py b/utbot-python/samples/samples/named_arguments/named_arguments.py new file mode 100644 index 0000000000..57f7c35b84 --- /dev/null +++ b/utbot-python/samples/samples/named_arguments/named_arguments.py @@ -0,0 +1,16 @@ +def f(x, y=1, *, z, t=2): + if y == 0: + return 100 * x + t + else: + x *= y + if x % 2 == 0: + y = g(x) + z + return x + y + 100 / y + + +def g(x): + return x ** 2 + + +if __name__ == '__main__': + print(f(1, y=2, z=3)) diff --git a/utbot-python/samples/samples/primitives/__init__.py b/utbot-python/samples/samples/primitives/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/primitives/bytes_example.py b/utbot-python/samples/samples/primitives/bytes_example.py new file mode 100644 index 0000000000..f23a7fa92b --- /dev/null +++ b/utbot-python/samples/samples/primitives/bytes_example.py @@ -0,0 +1,7 @@ +def neg_bytes(b: bytes): + return not b + + +def sum_bytes(a: bytes, b: bytes): + c = a + b + return c diff --git a/utbot-python/samples/samples/primitives/numbers.py b/utbot-python/samples/samples/primitives/numbers.py new file mode 100644 index 0000000000..1305a369ab --- /dev/null +++ b/utbot-python/samples/samples/primitives/numbers.py @@ -0,0 +1,34 @@ +import copy +import math +import typing + + +def summ(a: typing.SupportsInt, b: typing.SupportsInt): + return int(a) + int(b) + + +def create_table(a: int): + table = [] + for i in range(a): + row = [] + for j in range(a): + row.append(i * j) + table.append(copy.deepcopy(row)) + return table + + +def operations(a: int): + if a > 1024: + return math.log2(a) + if a > 512: + return math.exp(a) + if a > 256: + return math.isinf(a) + if a > 128: + return math.e * a + return math.sqrt(a) + + +def check_order(a: int, b: int, c: int): + return a < b < c + diff --git a/utbot-python/samples/samples/primitive_types.py b/utbot-python/samples/samples/primitives/primitive_types.py similarity index 100% rename from utbot-python/samples/samples/primitive_types.py rename to utbot-python/samples/samples/primitives/primitive_types.py diff --git a/utbot-python/samples/samples/primitives/string/__init__.py b/utbot-python/samples/samples/primitives/string/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/primitives/string/regex.py b/utbot-python/samples/samples/primitives/string/regex.py new file mode 100644 index 0000000000..a2e3b9fa35 --- /dev/null +++ b/utbot-python/samples/samples/primitives/string/regex.py @@ -0,0 +1,9 @@ +import re +import time + + +def check_regex(string: str) -> bool: + pattern = r"'(''|\\\\|\\'|[^'])*'" + if re.match(pattern, string): + return True + return False diff --git a/utbot-python/samples/samples/primitives/string/str_example.py b/utbot-python/samples/samples/primitives/string/str_example.py new file mode 100644 index 0000000000..834f5ca477 --- /dev/null +++ b/utbot-python/samples/samples/primitives/string/str_example.py @@ -0,0 +1,51 @@ +import dataclasses +import typing + + +@dataclasses.dataclass +class IntPair: + fst: int + snd: int + + +def concat(a: str, b: str): + return a + b + + +def concat_pair(pair: IntPair): + return pair.fst + pair.snd + + +def string_constants(s: str): + return "String('" + s + "')" + + +def contains(s: str, t: str): + return t in s + + +def const_contains(s: str): + return "ab" in s + + +def to_str(a: int, b: int): + if a > b: + return str(a) + else: + return str(b) + + +def starts_with(s: str): + if s.startswith("1234567890"): + s = s.replace("3", "A") + else: + s = s.strip() + + if s[0] == "x": + return s + else: + return s.upper() + + +def join_str(strings: typing.List[str]): + return "--".join(strings) \ No newline at end of file diff --git a/utbot-python/samples/samples/recursion/__init__.py b/utbot-python/samples/samples/recursion/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/recursion/recursion.py b/utbot-python/samples/samples/recursion/recursion.py new file mode 100644 index 0000000000..09c25521c6 --- /dev/null +++ b/utbot-python/samples/samples/recursion/recursion.py @@ -0,0 +1,41 @@ +def factorial(n: int): + if n < 0: + raise ValueError() + if n == 0: + return 1 + return n * factorial(n-1) + + +def fib(n: int): + if n < 0: + raise ValueError + if n == 0: + return 0 + if n == 1: + return 1 + return fib(n-1) + fib(n-2) + + +def summ(fst: int, snd: int): + def signum(x: int): + return 0 if x == 0 else 1 if x > 0 else -1 + if snd == 0: + return fst + return summ(fst + signum(snd), snd - signum(snd)) + + +def recursion_with_exception(n: int): + if n < 42: + recursion_with_exception(n+1) + if n > 42: + recursion_with_exception(n-1) + raise ValueError + + +def first(n: int): + def second(n: int): + first(n) + if n < 4: + return + second(n) + diff --git a/utbot-python/samples/samples/structures/__init__.py b/utbot-python/samples/samples/structures/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/easy_samples/boruvka.py b/utbot-python/samples/samples/structures/boruvka.py similarity index 100% rename from utbot-python/samples/easy_samples/boruvka.py rename to utbot-python/samples/samples/structures/boruvka.py diff --git a/utbot-python/samples/samples/deque.py b/utbot-python/samples/samples/structures/deque.py similarity index 100% rename from utbot-python/samples/samples/deque.py rename to utbot-python/samples/samples/structures/deque.py diff --git a/utbot-python/samples/samples/matrix.py b/utbot-python/samples/samples/structures/matrix.py similarity index 100% rename from utbot-python/samples/samples/matrix.py rename to utbot-python/samples/samples/structures/matrix.py diff --git a/utbot-python/samples/samples/type_inference/__init__.py b/utbot-python/samples/samples/type_inference/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/easy_samples/annotation_tests.py b/utbot-python/samples/samples/type_inference/annotation2.py similarity index 87% rename from utbot-python/samples/easy_samples/annotation_tests.py rename to utbot-python/samples/samples/type_inference/annotation2.py index d9412617e6..567cf818c8 100644 --- a/utbot-python/samples/easy_samples/annotation_tests.py +++ b/utbot-python/samples/samples/type_inference/annotation2.py @@ -1,3 +1,4 @@ +from __future__ import annotations from typing import * import collections from enum import Enum @@ -10,7 +11,7 @@ class A(Generic[XXX]): self_: XXX - def f(self, a, b: 'A'[int]): + def f(self, a, b: A[int]): self.y = b self.self_.x = b pass @@ -70,9 +71,3 @@ def supports_abs(x: SupportsAbs): def tuple_(x: Tuple[int, str]): return x[1] + str(x[0]) - - -if __name__ == "__main__": - # print(square(collections.defaultdict(int))) - # enum_literal(Color.BLUE) - pass diff --git a/utbot-python/samples/easy_samples/annotations2.py b/utbot-python/samples/samples/type_inference/annotations.py similarity index 100% rename from utbot-python/samples/easy_samples/annotations2.py rename to utbot-python/samples/samples/type_inference/annotations.py diff --git a/utbot-python/samples/easy_samples/generics.py b/utbot-python/samples/samples/type_inference/generics.py similarity index 100% rename from utbot-python/samples/easy_samples/generics.py rename to utbot-python/samples/samples/type_inference/generics.py diff --git a/utbot-python/samples/samples/list_of_datetime.py b/utbot-python/samples/samples/type_inference/list_of_datetime.py similarity index 100% rename from utbot-python/samples/samples/list_of_datetime.py rename to utbot-python/samples/samples/type_inference/list_of_datetime.py diff --git a/utbot-python/samples/easy_samples/subtypes.py b/utbot-python/samples/samples/type_inference/subtypes.py similarity index 96% rename from utbot-python/samples/easy_samples/subtypes.py rename to utbot-python/samples/samples/type_inference/subtypes.py index 8c3a480de2..13e70f7595 100644 --- a/utbot-python/samples/easy_samples/subtypes.py +++ b/utbot-python/samples/samples/type_inference/subtypes.py @@ -1,5 +1,4 @@ import collections -import numpy from typing import * @@ -51,5 +50,3 @@ def func_for_R(x: R) -> None: def func_abs(x: SupportsAbs[T]): return abs(x) - -b: int = 10 diff --git a/utbot-python/samples/samples/type_inference.py b/utbot-python/samples/samples/type_inference/type_inference.py similarity index 100% rename from utbot-python/samples/samples/type_inference.py rename to utbot-python/samples/samples/type_inference/type_inference.py diff --git a/utbot-python/samples/samples/type_inference_2.py b/utbot-python/samples/samples/type_inference/type_inference_2.py similarity index 100% rename from utbot-python/samples/samples/type_inference_2.py rename to utbot-python/samples/samples/type_inference/type_inference_2.py From 1e49085315ad1701df68b29582512e755a2ab2d2 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 12 Jul 2023 19:26:39 +0300 Subject: [PATCH 21/44] Fix comparePythonTree --- utbot-python/samples/samples/structures/boruvka.py | 3 --- .../python/framework/api/python/util/PythonTreeComparator.kt | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/utbot-python/samples/samples/structures/boruvka.py b/utbot-python/samples/samples/structures/boruvka.py index 8f406b6012..1e757abcf8 100644 --- a/utbot-python/samples/samples/structures/boruvka.py +++ b/utbot-python/samples/samples/structures/boruvka.py @@ -1,6 +1,3 @@ -from typing import Any - - class Graph: def __init__(self, num_of_nodes: int) -> None: """ diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonTreeComparator.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonTreeComparator.kt index 7d5ac0637f..e6a03ee171 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonTreeComparator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonTreeComparator.kt @@ -26,7 +26,7 @@ fun comparePythonTree( return false } if (visitedLeft[left.id] == VisitStatus.CLOSED) { - return equals[left.id to right.id]!! + return equals[left.id to right.id] ?: false } if (visitedLeft[left.id] == VisitStatus.OPENED) { return true From 5e67969aed970ecd6c8f4fba043e43fbdd6f8db0 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Thu, 13 Jul 2023 13:19:19 +0300 Subject: [PATCH 22/44] Fix timeout group separation --- .../plugin/language/python/PythonDialogProcessor.kt | 8 +++++++- .../intellij/plugin/language/python/PythonDialogWindow.kt | 4 ++-- .../plugin/language/python/PythonIntellijProcessor.kt | 6 +++++- .../type_inference/{annotation2.py => annotations2.py} | 0 .../main/kotlin/org/utbot/python/newtyping/PythonType.kt | 6 +++--- 5 files changed, 17 insertions(+), 7 deletions(-) rename utbot-python/samples/samples/type_inference/{annotation2.py => annotations2.py} (100%) diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt index d93bd3b1a5..9b296a3b68 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt @@ -184,7 +184,7 @@ object PythonDialogProcessor { val functionName = it.name ?: return@mapNotNull null val moduleFilename = it.containingFile.virtualFile?.canonicalPath ?: "" val containingClassId = it.containingClass?.name?.let{ cls -> PythonClassId(cls) } - return@mapNotNull PythonMethodHeader( + PythonMethodHeader( functionName, moduleFilename, containingClassId, @@ -203,6 +203,12 @@ object PythonDialogProcessor { fileGroup.value .groupBy { it is PyClass }.values } + .flatMap { fileGroup -> + val classes = fileGroup.filterIsInstance() + val functions = fileGroup.filterIsInstance() + val groups: List> = classes.map { listOf(it) } + listOf(functions) + groups + } .filter { it.isNotEmpty() } .map { val realElements = it.map { member -> model.names[Pair(member.fileName(), member.name)]!! } diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogWindow.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogWindow.kt index 7f349c0cfd..32b83945a9 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogWindow.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogWindow.kt @@ -76,9 +76,9 @@ class PythonDialogWindow(val model: PythonTestsModel) : DialogWrapper(model.proj row("Test generation timeout:") { cell(BorderLayoutPanel().apply { addToLeft(timeoutSpinnerForTotalTimeout) - addToRight(JBLabel("seconds per module")) + addToRight(JBLabel("seconds per group")) }) - contextHelp("Set the timeout for all test generation processes per module to complete.") + contextHelp("Set the timeout for all test generation processes per class or top level functions in one module to complete.") } row("Generate tests for:") {} row { diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt index d0a514ac79..e190ffb0bb 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt @@ -6,6 +6,7 @@ import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiFileFactory +import com.jetbrains.python.psi.PyClass import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.python.PythonTestGenerationConfig import org.utbot.python.PythonTestGenerationProcessor @@ -54,7 +55,10 @@ class PythonIntellijProcessor( private fun getOutputFileName(model: PythonTestLocalModel): String { val moduleName = model.currentPythonModule.camelToSnakeCase().replace('.', '_') - return if (model.containingClass == null) { + return if (model.selectedElements.size == 1 && model.selectedElements.first() is PyClass) { + val className = model.selectedElements.first().name?.camelToSnakeCase()?.replace('.', '_') + "test_${moduleName}_$className.py" + } else if (model.containingClass == null) { "test_$moduleName.py" } else { val className = model.containingClass.name?.camelToSnakeCase()?.replace('.', '_') diff --git a/utbot-python/samples/samples/type_inference/annotation2.py b/utbot-python/samples/samples/type_inference/annotations2.py similarity index 100% rename from utbot-python/samples/samples/type_inference/annotation2.py rename to utbot-python/samples/samples/type_inference/annotations2.py diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/PythonType.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/PythonType.kt index 8d4ace3b93..c9f94260e8 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/PythonType.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/PythonType.kt @@ -186,9 +186,9 @@ class PythonCallableTypeDescription( argumentKinds, argumentNames ) { self -> - val oldToNewParameters = (like.parameters zip self.parameters).associate { - (it.first as TypeParameter) to it.second - } + val oldToNewParameters = (self.parameters zip like.parameters).mapNotNull { + if (it.second is TypeParameter) it else null + }.toMap() val newArgs = args.map { DefaultSubstitutionProvider.substitute(it, oldToNewParameters) } From 549870cd0d806d26a9d0a3aa96ed3aecb58140ef Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Thu, 13 Jul 2023 18:59:11 +0300 Subject: [PATCH 23/44] Fix constructor building --- .../utbot/python/fuzzing/provider/ReduceValueProvider.kt | 7 ++----- .../kotlin/org/utbot/python/utils/RequirementsUtils.kt | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ReduceValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ReduceValueProvider.kt index 41d5e738aa..4e0c8831af 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ReduceValueProvider.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ReduceValueProvider.kt @@ -31,7 +31,7 @@ object ReduceValueProvider : ValueProvider !(attr.meta.name.startsWith("__") && attr.meta.name.endsWith("__") && attr.meta.name.length >= 4) && diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt index 082317ea84..9cd85f2c88 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt @@ -3,7 +3,7 @@ package org.utbot.python.utils object RequirementsUtils { val requirements: List = listOf( "mypy==1.0.0", - "utbot-executor==1.4.31", + "utbot-executor==1.4.31.1.6", "utbot-mypy-runner==0.2.11", ) From 6b14e876b50e47c498a8433824d0a2ad165a6d3c Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Mon, 17 Jul 2023 15:59:12 +0300 Subject: [PATCH 24/44] Fix constructor building --- .../org/utbot/python/evaluation/PythonWorkerManager.kt | 2 +- .../org/utbot/python/framework/api/python/PythonApi.kt | 7 +++++++ .../model/constructor/tree/PythonCgMethodConstructor.kt | 3 ++- .../kotlin/org/utbot/python/utils/RequirementsUtils.kt | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt index 958d2ed07d..3941b56d62 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt @@ -43,7 +43,7 @@ class PythonWorkerManager( coverageReceiver.address().first, coverageReceiver.address().second, "--logfile", logfile.absolutePath, - "--loglevel", "INFO", // "DEBUG", "INFO", "WARNING", "ERROR" + "--loglevel", "DEBUG", // "DEBUG", "INFO", "WARNING", "ERROR" )) timeout = max(until - processStartTime, 0) workerSocket = try { diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt index 928378f5ec..ffa160c939 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt @@ -100,6 +100,13 @@ class PythonUtExecution( testMethodName: String? = null, displayName: String? = null, ) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName) { + init { + stateInit.parameters.zip(stateBefore.parameters).map { (init, before) -> + if (init is PythonTreeModel && before is PythonTreeModel) { + init.tree.comparable = before.tree.comparable + } + } + } override fun copy( stateBefore: EnvironmentModels, stateAfter: EnvironmentModels, diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt index 6c38e8aacb..6a4396fff6 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt @@ -333,8 +333,9 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex ) { if (expectedNode.comparable || depth == 0) { emptyLineIfNeeded() + val expectedValue = variableConstructor.getOrCreateVariable(PythonTreeModel(expectedNode)) testFrameworkManager.assertEquals( - expected, + expectedValue, actual, ) return diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt index 9cd85f2c88..65e6cd452b 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt @@ -3,7 +3,7 @@ package org.utbot.python.utils object RequirementsUtils { val requirements: List = listOf( "mypy==1.0.0", - "utbot-executor==1.4.31.1.6", + "utbot-executor==1.4.31.1.12", "utbot-mypy-runner==0.2.11", ) From ba4076705f600dc6e94c27417613407ff6839493 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 18 Jul 2023 09:29:14 +0300 Subject: [PATCH 25/44] Add more test examples --- .../samples/samples/classes/constructors.py | 36 +++++++++++++++++++ .../samples/samples/classes/equals.py | 23 ++++++++++++ .../samples/samples/collections/dicts.py | 24 +++++++++++++ .../samples/samples/collections/lists.py | 6 ++++ .../samples/samples/collections/sets.py | 24 +++++++++++++ .../samples/samples/collections/tuples.py | 36 +++++++++++++++++++ 6 files changed, 149 insertions(+) create mode 100644 utbot-python/samples/samples/classes/constructors.py create mode 100644 utbot-python/samples/samples/classes/equals.py create mode 100644 utbot-python/samples/samples/collections/dicts.py create mode 100644 utbot-python/samples/samples/collections/sets.py create mode 100644 utbot-python/samples/samples/collections/tuples.py diff --git a/utbot-python/samples/samples/classes/constructors.py b/utbot-python/samples/samples/classes/constructors.py new file mode 100644 index 0000000000..aac8cd71c1 --- /dev/null +++ b/utbot-python/samples/samples/classes/constructors.py @@ -0,0 +1,36 @@ +class EmptyClass: + pass + + +class WithInitClass: + def __init__(self, x: int): + self.x = x + + +class EmptyInitClass: + def __init__(self): + pass + + +class BasicNewCass: + def __new__(cls, *args, **kwargs): + super().__new__(cls, *args, **kwargs) + + +class ParentEmptyInitClass(EmptyClass): + pass + + +class ParentWithInitClass(WithInitClass): + pass + + +class ParentEmptyClass(EmptyClass): + pass + + +def func(a: EmptyClass, b: WithInitClass, c: EmptyInitClass, d: BasicNewCass, e: ParentEmptyClass, + f: ParentWithInitClass, g: ParentEmptyInitClass): + return a.__class__.__name__ + str( + b.x) + c.__class__.__name__ + d.__class__.__name__ + e.__class__.__name__ + str( + f.x) + g.__class__.__name__ diff --git a/utbot-python/samples/samples/classes/equals.py b/utbot-python/samples/samples/classes/equals.py new file mode 100644 index 0000000000..6ed34ec5fa --- /dev/null +++ b/utbot-python/samples/samples/classes/equals.py @@ -0,0 +1,23 @@ +class WithEqual: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + +class WithoutEqual: + def __init__(self, x: int): + self.x = x + + +class WithoutEqualChild(WithoutEqual): + def __init__(self, x: int): + super().__init__(x) + + def __eq__(self, other): + return self.x == other.x + + +def f1(a: WithEqual, b: WithoutEqual, c: WithoutEqualChild): + return a.x + b.x + c.x diff --git a/utbot-python/samples/samples/collections/dicts.py b/utbot-python/samples/samples/collections/dicts.py new file mode 100644 index 0000000000..52651e8893 --- /dev/null +++ b/utbot-python/samples/samples/collections/dicts.py @@ -0,0 +1,24 @@ +import typing + + +class MyClass: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + def __hash__(self): + return hash(self.x) + + +def f(x: typing.Dict[int, int]): + if len(x) == 0: + return "Empty!" + return len(x) + + +def g(x: typing.Dict[MyClass, int]): + if len(x) == 0: + return "Empty!" + return len(x) diff --git a/utbot-python/samples/samples/collections/lists.py b/utbot-python/samples/samples/collections/lists.py index daa1472ac5..005f16401b 100644 --- a/utbot-python/samples/samples/collections/lists.py +++ b/utbot-python/samples/samples/collections/lists.py @@ -18,6 +18,12 @@ def find_articles_with_author(articles: List[Article], author: str) -> List[Arti ] +def f(x: List[int]): + if len(x) == 0: + return "Empty!" + return sum(x) + + if __name__ == '__main__': print(find_articles_with_author([ Article('a', 'a1', 'jfls', datetime.datetime.today()), diff --git a/utbot-python/samples/samples/collections/sets.py b/utbot-python/samples/samples/collections/sets.py new file mode 100644 index 0000000000..2913fbcc58 --- /dev/null +++ b/utbot-python/samples/samples/collections/sets.py @@ -0,0 +1,24 @@ +import typing + + +class MyClass: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + def __hash__(self): + return hash(self.x) + + +def f(x: typing.Set[int]): + if len(x) == 0: + return "Empty!" + return len(x) + + +def g(x: typing.Set[MyClass]): + if len(x) == 0: + return "Empty!" + return len(x) diff --git a/utbot-python/samples/samples/collections/tuples.py b/utbot-python/samples/samples/collections/tuples.py new file mode 100644 index 0000000000..2ea141806e --- /dev/null +++ b/utbot-python/samples/samples/collections/tuples.py @@ -0,0 +1,36 @@ +import typing + + +class MyClass: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + def __hash__(self): + return hash(self.x) + + +def f(x: typing.Tuple[int, ...]): + if len(x) == 0: + return "Empty!" + return len(x) + + +def f1(x: typing.Tuple[int, int, int]): + if len(x) != 3: + return "Very bad input!!!" + return x[0] + x[1] + x[2] + + +def g(x: typing.Tuple[MyClass, ...]): + if len(x) == 0: + return "Empty!" + return len(x) + + +def g1(x: typing.Tuple[MyClass, MyClass]): + if len(x) != 2: + return "Very bad input!!!" + return x[0].x + x[1].x From ae455a7d8f7407d677fec71d9969b73664f83b68 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 18 Jul 2023 10:43:04 +0300 Subject: [PATCH 26/44] Add draft of run test script --- utbot-python/samples/run_tests.py | 13 ++++++ utbot-python/samples/samples.md | 27 ------------- utbot-python/samples/test_configuration.json | 42 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 27 deletions(-) create mode 100644 utbot-python/samples/run_tests.py delete mode 100644 utbot-python/samples/samples.md create mode 100644 utbot-python/samples/test_configuration.json diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py new file mode 100644 index 0000000000..3357b914e5 --- /dev/null +++ b/utbot-python/samples/run_tests.py @@ -0,0 +1,13 @@ +import json +import os + + +def parse_config(config_path: str): + with open(config_path, "r") as fin: + return json.loads(fin.read()) + + +def run_tests(jar_path: str, sys_paths: [str], python_path: str, files: list[str], timeout: int = 20, output: str): + command = f"java -jar {jar_path} generate_python {' '.join(files)} -p {python_path} -o {output} -s {' '.join(sys_path)} --timeout {timeout * 1000}" + try: + os.system(command) diff --git a/utbot-python/samples/samples.md b/utbot-python/samples/samples.md deleted file mode 100644 index b3cf2794e8..0000000000 --- a/utbot-python/samples/samples.md +++ /dev/null @@ -1,27 +0,0 @@ -## Соответствие файлов и сгенерированных тестов - -Примеры в `/samples`, сгенерированный код в `/cli_utbot_tests`. - -Команда по умолчанию -```bash -java -jar utbot-cli.jar generate_python samples/.py -p -o cli_utbot_tests/.py -s samples/ ----timeout-for-run 500 --timeout 10000 --visit-only-specified-source -``` - -| Пример | Тесты | Дополнительные аргументы | -|--------------------------|-------------------------------------------|-------------------------------------------| -| `arithmetic.py` | `generated_tests__arithmetic.py` | | -| `deep_equals.py` | `generated_tests__deep_equals.py` | | -| `dicts.py` | `generated_tests__dicts.py` | `-c Dictionary -m translate` | -| `deque.py` | `generated_tests__deque.py` | | -| `dummy_with_eq.py` | `generated_tests__dummy_with_eq.py` | `-c Dummy -m propogate` | -| `dummy_without_eq.py` | `generated_tests__dummy_without_eq.py` | `-c Dummy -m propogate` | -| `graph.py` | `generated_tests__graph.py` | | -| `lists.py` | `generated_tests__lists.py` | | -| `list_of_datetime.py` | `generated_tests__list_of_datetime.py` | | -| `longest_subsequence.py` | `generated_tests__longest_subsequence.py` | | -| `matrix.py` | `generated_tests__matrix.py` | `-c Matrix -m __add__,__mul__,__matmul__` | -| `primitive_types.py` | `generated_tests__primitive_types.py` | | -| `quick_sort.py` | `generated_tests__quick_sort.py` | | -| `test_coverage.py` | `generated_tests__test_coverage.py` | | -| `type_inhibition.py` | `generated_tests__type_inhibition.py` | | -| `using_collections.py` | `generated_tests__using_collections.py` | | diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json new file mode 100644 index 0000000000..b260697f1d --- /dev/null +++ b/utbot-python/samples/test_configuration.json @@ -0,0 +1,42 @@ +{ + "parts": [ + { + "path": "samples.algorithms", + "files": [ + { + "name": "bfs", + "groups": [ + { + "function_names": [ + "bfs" + ], + "timeout": 10 + } + ] + }, + { + "name": "longest_subsequence", + "groups": [ + { + "function_names": [ + "longest_subsequence" + ], + "timeout": 10 + } + ] + }, + { + "name": "quick_sort", + "groups": [ + { + "function_names": [ + "quick_sort" + ], + "timeout": 10 + } + ] + } + ] + } + ] +} \ No newline at end of file From 8ae89904ed040cf54d3ad4bd8dbc5defbb538124 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 18 Jul 2023 12:02:13 +0300 Subject: [PATCH 27/44] Update run_tests.py --- utbot-python/samples/run_tests.py | 55 ++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py index 3357b914e5..d99ee441ec 100644 --- a/utbot-python/samples/run_tests.py +++ b/utbot-python/samples/run_tests.py @@ -1,13 +1,60 @@ +import argparse import json import os +def parse_arguments(): + parser = argparse.ArgumentParser( + prog='UtBot Python test', + description='Generage tests for example files' + ) + parser.add_argument('jar') + parser.add_argument('path_to_test_dir') + parser.add_argument('-c', '--config_file') + parser.add_argument('-p', '--python_path') + parser.add_argument('-o', '--output_dir') + return parser.parse_args() + + def parse_config(config_path: str): with open(config_path, "r") as fin: return json.loads(fin.read()) -def run_tests(jar_path: str, sys_paths: [str], python_path: str, files: list[str], timeout: int = 20, output: str): - command = f"java -jar {jar_path} generate_python {' '.join(files)} -p {python_path} -o {output} -s {' '.join(sys_path)} --timeout {timeout * 1000}" - try: - os.system(command) +def run_tests( + jar_path: str, + sys_paths: list[str], + python_path: str, + file: str, + timeout: int, + output: str, + class_name: typing.Optional[str] = None, + method_names: typing.Optional[str] = None + ): + command = f"java -jar {jar_path} generate_python {file} -p {python_path} -o {output} -s {' '.join(sys_path)} --timeout {timeout * 1000}" + if class_name is not None: + command += f" -c {class_name}" + if method_names is not None: + command += f" -m {','.join(method_names)}" + os.system(command) + + +def main(): + args = parse_arguments() + config = parse_config(args.config_file) + for part in config['parts']: + for file in part['files']: + for group in file['groups']: + full_name = '/'.join([args.path_to_test_dir, part['path'], file['name']]) + output_file = '/'.join([args.output]) + run_tests( + args.jar, + args.path_to_test_dir, + args.python_path, + full_name, + group.timeout, + output_file, + group.classes, + group.methods + ) + From 7c34a13fbaeed915309773826a56870ed6972a87 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 18 Jul 2023 15:51:43 +0300 Subject: [PATCH 28/44] Update tests and finalize run test script --- .../python/PythonGenerateTestsCommand.kt | 7 +- .../language/python/PythonDialogProcessor.kt | 2 +- utbot-python/samples/.gitignore | 1 + utbot-python/samples/run_tests.py | 30 +- .../samples/samples/easy_samples/general.py | 11 - .../pack_1/inner_pack_1/inner_mod_1.py | 4 +- .../pack_1/inner_pack_2/inner_mod_3.py | 2 +- .../samples/primitives/{string => }/regex.py | 1 - .../primitives/{string => }/str_example.py | 0 .../samples/primitives/string/__init__.py | 0 .../samples/type_inference/annotations2.py | 2 - .../samples/type_inference/subtypes.py | 8 +- utbot-python/samples/test_configuration.json | 532 +++++++++++++++++- .../python/PythonTestGenerationConfig.kt | 2 +- .../python/PythonTestGenerationProcessor.kt | 2 +- .../tree/PythonCgMethodConstructor.kt | 4 +- .../tree/PythonTestFrameworkManager.kt | 12 +- 17 files changed, 567 insertions(+), 53 deletions(-) rename utbot-python/samples/samples/primitives/{string => }/regex.py (93%) rename utbot-python/samples/samples/primitives/{string => }/str_example.py (100%) delete mode 100644 utbot-python/samples/samples/primitives/string/__init__.py diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index 6682609aa4..f4899ad5ae 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -225,7 +225,7 @@ class PythonGenerateTestsCommand : CliktCommand( timeout = timeout, timeoutForRun = timeoutForRun, testFramework = testFramework, - executionPath = Paths.get("").toAbsolutePath(), + testSourceRootPath = Paths.get(output).parent.toAbsolutePath(), withMinimization = !doNotMinimize, isCanceled = { false }, ) @@ -243,6 +243,11 @@ class PythonGenerateTestsCommand : CliktCommand( logger.info("Generating tests...") val testSets = processor.testGenerate(mypyStorage) + logger.info("Saving tests...") + val testCode = processor.testCodeGenerate(testSets) + processor.saveTests(testCode) + + logger.info("Saving coverage report...") processor.processCoverageInfo(testSets) diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt index 9b296a3b68..1c4675340e 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt @@ -281,7 +281,7 @@ object PythonDialogProcessor { timeout = model.timeout, timeoutForRun = model.timeoutForRun, testFramework = model.testFramework, - executionPath = Path(model.testSourceRootPath), + testSourceRootPath = Path(model.testSourceRootPath), withMinimization = true, isCanceled = { indicator.isCanceled }, ) diff --git a/utbot-python/samples/.gitignore b/utbot-python/samples/.gitignore index 5f40fc7fee..42d795037d 100644 --- a/utbot-python/samples/.gitignore +++ b/utbot-python/samples/.gitignore @@ -1,5 +1,6 @@ .tmp/ utbot_tests/ +cli_test_dir/ utbot-cli.jar __pycache__/ .venv/ diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py index d99ee441ec..e7dd0d5141 100644 --- a/utbot-python/samples/run_tests.py +++ b/utbot-python/samples/run_tests.py @@ -1,6 +1,8 @@ import argparse import json import os +import typing +import pathlib def parse_arguments(): @@ -8,6 +10,7 @@ def parse_arguments(): prog='UtBot Python test', description='Generage tests for example files' ) + parser.add_argument('java') parser.add_argument('jar') parser.add_argument('path_to_test_dir') parser.add_argument('-c', '--config_file') @@ -22,6 +25,7 @@ def parse_config(config_path: str): def run_tests( + java: str, jar_path: str, sys_paths: list[str], python_path: str, @@ -31,12 +35,14 @@ def run_tests( class_name: typing.Optional[str] = None, method_names: typing.Optional[str] = None ): - command = f"java -jar {jar_path} generate_python {file} -p {python_path} -o {output} -s {' '.join(sys_path)} --timeout {timeout * 1000}" + command = f"{java} -jar {jar_path} generate_python {file}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements" if class_name is not None: command += f" -c {class_name}" if method_names is not None: command += f" -m {','.join(method_names)}" - os.system(command) + print(command) + code = os.system(command) + return code def main(): @@ -45,16 +51,20 @@ def main(): for part in config['parts']: for file in part['files']: for group in file['groups']: - full_name = '/'.join([args.path_to_test_dir, part['path'], file['name']]) - output_file = '/'.join([args.output]) + full_name = pathlib.PurePath(args.path_to_test_dir, part['path'], file['name']) + output_file = pathlib.PurePath(args.output_dir, f"utbot_tests_{part['path'].replace('/', '_')}_{file['name']}.py") run_tests( + args.java, args.jar, - args.path_to_test_dir, + [args.path_to_test_dir], args.python_path, - full_name, - group.timeout, - output_file, - group.classes, - group.methods + str(full_name), + group['timeout'], + str(output_file), + group['classes'], + group['methods'] ) + +if __name__ == '__main__': + main() diff --git a/utbot-python/samples/samples/easy_samples/general.py b/utbot-python/samples/samples/easy_samples/general.py index 9aeb49c1fe..40630c2550 100644 --- a/utbot-python/samples/samples/easy_samples/general.py +++ b/utbot-python/samples/samples/easy_samples/general.py @@ -25,17 +25,6 @@ def dict_f(x, a, b, c): return 2 -class A: - x = 4 - y = 5 - - def func(self): - n = 0 - for i in range(self.x): - n += self.y - return n - - def fact(n): ans = 1 for i in range(1, n + 1): diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py index d0413e439f..a8112a5831 100644 --- a/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py +++ b/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py @@ -3,5 +3,5 @@ def inner_func_1(a: int): if a > 1: - return inner_mod_2(a) - return a ** 2 \ No newline at end of file + return inner_mod_2.inner_func_2(a) + return a ** 2 diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py index c5b9093319..60d7ab5ea2 100644 --- a/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py +++ b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py @@ -1,2 +1,2 @@ def inner_func_3(x: float): - return int(x) \ No newline at end of file + return int(x) diff --git a/utbot-python/samples/samples/primitives/string/regex.py b/utbot-python/samples/samples/primitives/regex.py similarity index 93% rename from utbot-python/samples/samples/primitives/string/regex.py rename to utbot-python/samples/samples/primitives/regex.py index a2e3b9fa35..acee35cc7b 100644 --- a/utbot-python/samples/samples/primitives/string/regex.py +++ b/utbot-python/samples/samples/primitives/regex.py @@ -1,5 +1,4 @@ import re -import time def check_regex(string: str) -> bool: diff --git a/utbot-python/samples/samples/primitives/string/str_example.py b/utbot-python/samples/samples/primitives/str_example.py similarity index 100% rename from utbot-python/samples/samples/primitives/string/str_example.py rename to utbot-python/samples/samples/primitives/str_example.py diff --git a/utbot-python/samples/samples/primitives/string/__init__.py b/utbot-python/samples/samples/primitives/string/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/utbot-python/samples/samples/type_inference/annotations2.py b/utbot-python/samples/samples/type_inference/annotations2.py index 567cf818c8..5dfa3d36ee 100644 --- a/utbot-python/samples/samples/type_inference/annotations2.py +++ b/utbot-python/samples/samples/type_inference/annotations2.py @@ -1,8 +1,6 @@ from __future__ import annotations from typing import * -import collections from enum import Enum -import datetime XXX = TypeVar("XXX", "A", int) diff --git a/utbot-python/samples/samples/type_inference/subtypes.py b/utbot-python/samples/samples/type_inference/subtypes.py index 13e70f7595..24eb1c18e6 100644 --- a/utbot-python/samples/samples/type_inference/subtypes.py +++ b/utbot-python/samples/samples/type_inference/subtypes.py @@ -17,11 +17,11 @@ def f(self, x: Union[int, str]) -> object: return collections.Counter([x]) -def func_for_P(x: P) -> None: +def func_for_p(x: P) -> None: return None -func_for_P(S()) +# func_for_p(S()) class R(Protocol): @@ -34,11 +34,11 @@ def f(self) -> 'RImpl': return self -def func_for_R(x: R) -> None: +def func_for_r(x: R) -> None: return None -func_for_R(RImpl()) +# func_for_r(RImpl()) a: List[int] = [] diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json index b260697f1d..ad8a32cca7 100644 --- a/utbot-python/samples/test_configuration.json +++ b/utbot-python/samples/test_configuration.json @@ -1,15 +1,14 @@ { "parts": [ { - "path": "samples.algorithms", + "path": "samples/algorithms", "files": [ { "name": "bfs", "groups": [ { - "function_names": [ - "bfs" - ], + "classes": null, + "methods": null, "timeout": 10 } ] @@ -18,9 +17,8 @@ "name": "longest_subsequence", "groups": [ { - "function_names": [ - "longest_subsequence" - ], + "classes": null, + "methods": null, "timeout": 10 } ] @@ -29,14 +27,528 @@ "name": "quick_sort", "groups": [ { - "function_names": [ - "quick_sort" - ], + "classes": null, + "methods": null, "timeout": 10 } ] } ] + }, + { + "path": "samples/classes", + "files": [ + { + "name": "constructors", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "dataclass", + "groups": [ + { + "classes": "C", + "methods": "inc", + "timeout": 10 + } + ] + }, + { + "name": "dicts", + "groups": [ + { + "classes": "Dictionary", + "methods": "translate", + "timeout": 10 + } + ] + }, + { + "name": "easy_class", + "groups": [ + { + "classes": "B", + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "equals", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "fields", + "groups": [ + { + "classes": "NoTestsProblem", + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "inner_class", + "groups": [ + { + "classes": "Outer", + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "rename_self", + "groups": [ + { + "classes": "A", + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "setstate_test", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + } + ] + }, + { + "path": "samples/collections", + "files": [ + { + "name": "dicts", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "lists", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "sets", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "tuple", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "using_collections", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + } + ] + }, + { + "path": "samples/controlflow", + "files": [ + { + "name": "arithmetic", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "conditions", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + }, + { + "name": "inner_conditions", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10 + } + ] + } + ] + }, + { + "path": "samples/easy_samples", + "files": [ + { + "name": "deep_equals", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 90 + } + ] + }, + { + "name": "deep_equals_2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20 + } + ] + }, + { + "name": "full_annotated", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 150 + } + ] + }, + { + "name": "general", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 300 + } + ] + }, + { + "name": "long_function_coverage", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20 + } + ] + } + ] + }, + { + "path": "samples/exceptions", + "files": [ + { + "name": "exception_example", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 180 + } + ] + } + ] + }, + { + "path": "samples/imports/builtins_module", + "files": [ + { + "name": "crypto", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 180 + } + ] + } + ] + }, + { + "path": "samples/imports/inner_pack_1", + "files": [ + { + "name": "inner_mod_1", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 180 + } + ] + } + ] + }, + { + "path": "samples/imports/inner_pack_2", + "files": [ + { + "name": "inner_mod_2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20 + } + ] + } + ] + }, + { + "path": "samples/imports/inner_pack_2", + "files": [ + { + "name": "inner_mod_2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20 + } + ] + } + ] + }, + { + "path": "samples/primitives", + "files": [ + { + "name": "bytes_example", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 30 + } + ] + }, + { + "name": "numbers", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 80 + } + ] + }, + { + "name": "primitive_types", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 40 + } + ] + }, + { + "name": "regex", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 40 + } + ] + }, + { + "name": "str_example", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 160 + } + ] + } + ] + }, + { + "path": "samples/recursion", + "files": [ + { + "name": "recursion", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 150 + } + ] + } + ] + }, + { + "path": "samples/structures", + "files": [ + { + "name": "boruvka", + "groups": [ + { + "classes": "Graph", + "methods": null, + "timeout": 150 + } + ] + }, + { + "name": "deque", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 150 + } + ] + }, + { + "name": "matrix", + "groups": [ + { + "classes": "Matrix", + "methods": null, + "timeout": 240 + } + ] + } + ] + }, + { + "path": "samples/type_inference", + "files": [ + { + "name": "annotations", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 140 + } + ] + }, + { + "name": "annotations2", + "groups": [ + { + "classes": "A", + "methods": null, + "timeout": 40 + } + ] + }, + { + "name": "annotations2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 120 + } + ] + }, + { + "name": "generics", + "groups": [ + { + "classes": "LoggedVar", + "methods": null, + "timeout": 30 + } + ] + }, + { + "name": "generics", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 30 + } + ] + }, + { + "name": "list_of_datetime", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 30 + } + ] + }, + { + "name": "subtypes", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 90 + } + ] + }, + { + "name": "type_inference", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 40 + } + ] + }, + { + "name": "type_inference_2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 40 + } + ] + } + ] } ] } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt index 631b8b7379..254d94778f 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt @@ -17,7 +17,7 @@ class PythonTestGenerationConfig( val timeout: Long, val timeoutForRun: Long, val testFramework: TestFramework, - val executionPath: Path, + val testSourceRootPath: Path, val withMinimization: Boolean, val isCanceled: () -> Boolean, ) \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt index 62af82a542..c9d9d7e835 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt @@ -188,7 +188,7 @@ abstract class PythonTestGenerationProcessor { val sysImport = PythonSystemImport("sys") val osImport = PythonSystemImport("os") val sysPathImports = relativizePaths( - configuration.executionPath, + configuration.testSourceRootPath, configuration.sysPathDirectories ).map { PythonSysPathImport(it) } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt index 6a4396fff6..73d78c9668 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt @@ -251,9 +251,9 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex private fun assertIsInstance(expected: CgValue, actual: CgVariable) { when (testFrameworkManager) { is PytestManager -> - (testFrameworkManager as PytestManager).assertIsinstance(listOf(expected.type), actual) + (testFrameworkManager as PytestManager).assertIsinstance(listOf(expected.type as PythonClassId), actual) is UnittestManager -> - (testFrameworkManager as UnittestManager).assertIsinstance(listOf(expected.type), actual) + (testFrameworkManager as UnittestManager).assertIsinstance(listOf(expected.type as PythonClassId), actual) else -> testFrameworkManager.assertEquals(expected, actual) } } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonTestFrameworkManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonTestFrameworkManager.kt index 49f7b96e29..16734893c8 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonTestFrameworkManager.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonTestFrameworkManager.kt @@ -83,7 +83,7 @@ internal class PytestManager(context: CgContext) : TestFrameworkManager(context) ) } - fun assertIsinstance(types: List, actual: CgVariable) { + fun assertIsinstance(types: List, actual: CgVariable) { +CgPythonAssertEquals( CgPythonFunctionCall( pythonBoolClassId, @@ -91,9 +91,9 @@ internal class PytestManager(context: CgContext) : TestFrameworkManager(context) listOf( actual, if (types.size == 1) - CgLiteral(pythonAnyClassId, types[0].name) + CgLiteral(pythonAnyClassId, types[0].prettyName) else - CgPythonTuple(types.map { CgLiteral(pythonAnyClassId, it.name) }) + CgPythonTuple(types.map { CgLiteral(pythonAnyClassId, it.prettyName) }) ), ), ) @@ -151,7 +151,7 @@ internal class UnittestManager(context: CgContext) : TestFrameworkManager(contex ) } - fun assertIsinstance(types: List, actual: CgVariable) { + fun assertIsinstance(types: List, actual: CgVariable) { +assertions[assertTrue]( CgPythonFunctionCall( pythonBoolClassId, @@ -159,9 +159,9 @@ internal class UnittestManager(context: CgContext) : TestFrameworkManager(contex listOf( actual, if (types.size == 1) - CgLiteral(pythonAnyClassId, types[0].name) + CgLiteral(pythonAnyClassId, types[0].prettyName) else - CgPythonTuple(types.map { CgLiteral(pythonAnyClassId, it.name) }) + CgPythonTuple(types.map { CgLiteral(pythonAnyClassId, it.prettyName) }) ), ), ) From 24f725c70951e302e2d089d6e4b1341e5f1fd3fc Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 18 Jul 2023 15:57:42 +0300 Subject: [PATCH 29/44] Add example command --- utbot-python/samples/run_tests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py index e7dd0d5141..1b54756cab 100644 --- a/utbot-python/samples/run_tests.py +++ b/utbot-python/samples/run_tests.py @@ -1,3 +1,8 @@ +""" +Example command + run_tests.py -c test_configuration.json + -p -o +""" import argparse import json import os From 92a8d955684296b6977bd64fc3e5cc9798f3327a Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 19 Jul 2023 08:02:50 +0300 Subject: [PATCH 30/44] Update version of utbot-executor --- .../kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt | 2 +- .../src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt index 3941b56d62..958d2ed07d 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt @@ -43,7 +43,7 @@ class PythonWorkerManager( coverageReceiver.address().first, coverageReceiver.address().second, "--logfile", logfile.absolutePath, - "--loglevel", "DEBUG", // "DEBUG", "INFO", "WARNING", "ERROR" + "--loglevel", "INFO", // "DEBUG", "INFO", "WARNING", "ERROR" )) timeout = max(until - processStartTime, 0) workerSocket = try { diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt index 65e6cd452b..008439aa7f 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt @@ -3,7 +3,7 @@ package org.utbot.python.utils object RequirementsUtils { val requirements: List = listOf( "mypy==1.0.0", - "utbot-executor==1.4.31.1.12", + "utbot-executor==1.4.32", "utbot-mypy-runner==0.2.11", ) From 33f4bba7ed1dbf8ef2fc23d5faf71285c915902c Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Thu, 20 Jul 2023 15:06:13 +0300 Subject: [PATCH 31/44] Fix annotation import for python --- .../model/constructor/tree/PythonCgStatementConstructor.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgStatementConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgStatementConstructor.kt index c0e87a5b87..64de3da058 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgStatementConstructor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgStatementConstructor.kt @@ -34,8 +34,10 @@ import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.codegen.PythonCgLanguageAssistant.getCallableAccessManagerBy import org.utbot.python.framework.codegen.PythonCgLanguageAssistant.getNameGeneratorBy +import org.utbot.python.framework.codegen.model.constructor.util.importIfNeeded import org.utbot.python.framework.codegen.model.constructor.util.plus import java.util.* @@ -253,7 +255,10 @@ class PythonCgStatementConstructorImpl(context: CgContext) : AnnotationTarget.Field -> error("Annotation ${annotation.target} is not supported in Python") } - importIfNeeded(annotation.classId) + val classId = annotation.classId + if (classId is PythonClassId) { + importIfNeeded(classId) + } } override fun returnStatement(expression: () -> CgExpression) { From efc8fe62107ff765844c7249fe64bae693906304 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Fri, 21 Jul 2023 13:05:06 +0300 Subject: [PATCH 32/44] Used setting runtimeExceptionTestsBehaviour --- .../python/PythonGenerateTestsCommand.kt | 6 +++ .../language/python/PythonDialogProcessor.kt | 1 + .../python/PythonIntellijProcessor.kt | 4 +- .../language/python/settings/Settings.kt | 3 ++ utbot-python/samples/run_tests.py | 46 +++++++++++++------ utbot-python/samples/test_configuration.json | 22 ++++++++- .../python/PythonTestGenerationConfig.kt | 2 + .../python/PythonTestGenerationProcessor.kt | 3 ++ 8 files changed, 70 insertions(+), 17 deletions(-) diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index f4899ad5ae..5efa1cbb14 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -7,6 +7,7 @@ import com.github.ajalt.clikt.parameters.types.choice import com.github.ajalt.clikt.parameters.types.long import mu.KotlinLogging import org.parsers.python.PythonParser +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.domain.TestFramework import org.utbot.python.PythonMethodHeader import org.utbot.python.PythonTestGenerationConfig @@ -97,6 +98,10 @@ class PythonGenerateTestsCommand : CliktCommand( .choice(Pytest.toString(), Unittest.toString()) .default(Unittest.toString()) + private val runtimeExceptionTestsBehaviour by option("--runtime-exception-behaviour", help = "Passing or Failing") + .choice("Passing", "Failing") + .default("Failing") + private val testFramework: TestFramework get() = when (testFrameworkAsString) { @@ -228,6 +233,7 @@ class PythonGenerateTestsCommand : CliktCommand( testSourceRootPath = Paths.get(output).parent.toAbsolutePath(), withMinimization = !doNotMinimize, isCanceled = { false }, + runtimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.valueOf(runtimeExceptionTestsBehaviour) ) val processor = PythonCliProcessor( diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt index 1c4675340e..b769ccceef 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonDialogProcessor.kt @@ -284,6 +284,7 @@ object PythonDialogProcessor { testSourceRootPath = Path(model.testSourceRootPath), withMinimization = true, isCanceled = { indicator.isCanceled }, + runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour ) val processor = PythonIntellijProcessor( config, diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt index e190ffb0bb..89f2c498ec 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/PythonIntellijProcessor.kt @@ -74,7 +74,5 @@ class PythonIntellijProcessor( ) } - override fun processCoverageInfo(testSets: List) { - TODO("Not yet implemented") - } + override fun processCoverageInfo(testSets: List) { } } \ No newline at end of file diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/settings/Settings.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/settings/Settings.kt index 931a462a6e..5c46f4d53b 100644 --- a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/settings/Settings.kt +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/language/python/settings/Settings.kt @@ -1,5 +1,6 @@ package org.utbot.intellij.plugin.language.python.settings +import org.utbot.framework.codegen.domain.HangingTestsTimeout import org.utbot.intellij.plugin.language.python.PythonTestsModel import org.utbot.intellij.plugin.settings.Settings @@ -13,5 +14,7 @@ private fun fromGenerateTestsModel(model: PythonTestsModel): Settings.State { testFramework = model.testFramework, generationTimeoutInMillis = model.timeout, enableExperimentalLanguagesSupport = true, + hangingTestsTimeout = HangingTestsTimeout(model.timeoutForRun), + runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour, ) } diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py index 1b54756cab..4dc01af781 100644 --- a/utbot-python/samples/run_tests.py +++ b/utbot-python/samples/run_tests.py @@ -1,7 +1,10 @@ """ Example command - run_tests.py -c test_configuration.json - -p -o + run_tests.py generate -c test_configuration.json + -p -o + + run_tests.py run -p -t + -c """ import argparse import json @@ -15,12 +18,18 @@ def parse_arguments(): prog='UtBot Python test', description='Generage tests for example files' ) - parser.add_argument('java') - parser.add_argument('jar') - parser.add_argument('path_to_test_dir') - parser.add_argument('-c', '--config_file') - parser.add_argument('-p', '--python_path') - parser.add_argument('-o', '--output_dir') + subparsers = parser.add_subparsers() + parser_generate = subparsers.add_parser('generate', help='Generate tests') + parser_generate.add_argument('java') + parser_generate.add_argument('jar') + parser_generate.add_argument('path_to_test_dir') + parser_generate.add_argument('-c', '--config_file') + parser_generate.add_argument('-p', '--python_path') + parser_generate.add_argument('-o', '--output_dir') + parser_run = subparsers.add_parser('run', help='Run tests') + parser_run.add_argument('-p', '--python_path') + parser_run.add_argument('-t', '--test_directory') + parser_run.add_argument('-c', '--code_directory') return parser.parse_args() @@ -29,7 +38,7 @@ def parse_config(config_path: str): return json.loads(fin.read()) -def run_tests( +def generate_tests( java: str, jar_path: str, sys_paths: list[str], @@ -40,7 +49,7 @@ def run_tests( class_name: typing.Optional[str] = None, method_names: typing.Optional[str] = None ): - command = f"{java} -jar {jar_path} generate_python {file}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements" + command = f"{java} -jar {jar_path} generate_python {file}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements --runtime-exception-behaviour Passing" if class_name is not None: command += f" -c {class_name}" if method_names is not None: @@ -50,15 +59,25 @@ def run_tests( return code +def run_tests( + python_path: str, + tests_dir: str, + samples_dir: str, +): + command = f'{python_path} -m coverage run --source={samples_dir} -m unittest {tests_dir} -p "utbot_*"' + print(command) + code = os.system(command) + return code + + def main(): - args = parse_arguments() config = parse_config(args.config_file) for part in config['parts']: for file in part['files']: for group in file['groups']: full_name = pathlib.PurePath(args.path_to_test_dir, part['path'], file['name']) output_file = pathlib.PurePath(args.output_dir, f"utbot_tests_{part['path'].replace('/', '_')}_{file['name']}.py") - run_tests( + generate_tests( args.java, args.jar, [args.path_to_test_dir], @@ -72,4 +91,5 @@ def main(): if __name__ == '__main__': - main() + args = parse_arguments() + print(args) diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json index ad8a32cca7..374f8f7659 100644 --- a/utbot-python/samples/test_configuration.json +++ b/utbot-python/samples/test_configuration.json @@ -244,7 +244,7 @@ ] }, { - "name": "full_annotated", + "name": "fully_annotated", "groups": [ { "classes": null, @@ -253,6 +253,26 @@ } ] }, + { + "name": "dummy_with_eq", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20 + } + ] + }, + { + "name": "dummy_without_eq", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20 + } + ] + }, { "name": "general", "groups": [ diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt index 254d94778f..405a1abf0d 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt @@ -1,5 +1,6 @@ package org.utbot.python +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.domain.TestFramework import java.nio.file.Path @@ -20,4 +21,5 @@ class PythonTestGenerationConfig( val testSourceRootPath: Path, val withMinimization: Boolean, val isCanceled: () -> Boolean, + val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour, ) \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt index c9d9d7e835..fd7df3a128 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt @@ -3,6 +3,7 @@ package org.utbot.python import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import org.parsers.python.PythonParser +import org.utbot.framework.codegen.domain.HangingTestsTimeout import org.utbot.framework.codegen.domain.models.CgMethodTestSet import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.UtClusterInfo @@ -122,6 +123,8 @@ abstract class PythonTestGenerationProcessor { paramNames = paramNames, testFramework = configuration.testFramework, testClassPackageName = "", + hangingTestsTimeout = HangingTestsTimeout(configuration.timeoutForRun), + runtimeExceptionTestsBehaviour = configuration.runtimeExceptionTestsBehaviour, ) val testCode = codegen.pythonGenerateAsStringWithTestReport( testSets.map { testSet -> From 091a719a27eea66846f10459298c60b06aed21e4 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Fri, 21 Jul 2023 16:13:45 +0300 Subject: [PATCH 33/44] update --- .../python/PythonGenerateTestsCommand.kt | 6 +-- utbot-python/samples/run_tests.py | 51 +++++++++++-------- utbot-python/samples/test_configuration.json | 12 ++--- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index 5efa1cbb14..096648f88e 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -98,9 +98,9 @@ class PythonGenerateTestsCommand : CliktCommand( .choice(Pytest.toString(), Unittest.toString()) .default(Unittest.toString()) - private val runtimeExceptionTestsBehaviour by option("--runtime-exception-behaviour", help = "Passing or Failing") - .choice("Passing", "Failing") - .default("Failing") + private val runtimeExceptionTestsBehaviour by option("--runtime-exception-behaviour", help = "PASS or FAIL") + .choice("PASS", "FAIL") + .default("FAIL") private val testFramework: TestFramework get() = diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py index 4dc01af781..306fb58b0b 100644 --- a/utbot-python/samples/run_tests.py +++ b/utbot-python/samples/run_tests.py @@ -7,8 +7,10 @@ -c """ import argparse +import asyncio import json import os +import sys import typing import pathlib @@ -38,25 +40,27 @@ def parse_config(config_path: str): return json.loads(fin.read()) -def generate_tests( - java: str, - jar_path: str, - sys_paths: list[str], - python_path: str, - file: str, - timeout: int, - output: str, - class_name: typing.Optional[str] = None, - method_names: typing.Optional[str] = None - ): - command = f"{java} -jar {jar_path} generate_python {file}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements --runtime-exception-behaviour Passing" +async def generate_tests( + java: str, + jar_path: str, + sys_paths: list[str], + python_path: str, + file: str, + timeout: int, + output: str, + class_name: typing.Optional[str] = None, + method_names: typing.Optional[str] = None +): + command = f"{java} -jar {jar_path} generate_python {file}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements --runtime-exception-behaviour PASS" if class_name is not None: command += f" -c {class_name}" if method_names is not None: command += f" -m {','.join(method_names)}" print(command) - code = os.system(command) - return code + # code = os.system(command) + proc = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE) + stdout, stderr = await proc.communicate() + return stdout def run_tests( @@ -70,13 +74,20 @@ def run_tests( return code -def main(): +async def main(args): config = parse_config(args.config_file) + params = [] for part in config['parts']: for file in part['files']: for group in file['groups']: - full_name = pathlib.PurePath(args.path_to_test_dir, part['path'], file['name']) - output_file = pathlib.PurePath(args.output_dir, f"utbot_tests_{part['path'].replace('/', '_')}_{file['name']}.py") + params.append((part, file, group)) + + async with asyncio.TaskGroup() as tg: + for (part, file, group) in params: + full_name = pathlib.PurePath(args.path_to_test_dir, part['path'], file['name']) + output_file = pathlib.PurePath(args.output_dir, + f"utbot_tests_{part['path'].replace('/', '_')}_{file['name']}.py") + task = tg.create_task( generate_tests( args.java, args.jar, @@ -88,8 +99,8 @@ def main(): group['classes'], group['methods'] ) - + ) + if __name__ == '__main__': - args = parse_arguments() - print(args) + asyncio.run(main(parse_arguments())) diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json index 374f8f7659..c66b042881 100644 --- a/utbot-python/samples/test_configuration.json +++ b/utbot-python/samples/test_configuration.json @@ -89,7 +89,7 @@ ] }, { - "name": "fields", + "name": "field", "groups": [ { "classes": "NoTestsProblem", @@ -164,7 +164,7 @@ ] }, { - "name": "tuple", + "name": "tuples", "groups": [ { "classes": null, @@ -299,7 +299,7 @@ "path": "samples/exceptions", "files": [ { - "name": "exception_example", + "name": "exception_examples", "groups": [ { "classes": null, @@ -326,7 +326,7 @@ ] }, { - "path": "samples/imports/inner_pack_1", + "path": "samples/imports/pack_1/inner_pack_1", "files": [ { "name": "inner_mod_1", @@ -341,7 +341,7 @@ ] }, { - "path": "samples/imports/inner_pack_2", + "path": "samples/imports/pack_1/inner_pack_2", "files": [ { "name": "inner_mod_2", @@ -356,7 +356,7 @@ ] }, { - "path": "samples/imports/inner_pack_2", + "path": "samples/imports/pack_1/inner_pack_2", "files": [ { "name": "inner_mod_2", From ad03cf677b70805f975af1bec2cef83bd860f2da Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Mon, 24 Jul 2023 11:46:18 +0300 Subject: [PATCH 34/44] Update run_tests --- utbot-python/samples/run_tests.py | 50 ++++++++++++------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py index 306fb58b0b..5dbf544365 100644 --- a/utbot-python/samples/run_tests.py +++ b/utbot-python/samples/run_tests.py @@ -7,10 +7,8 @@ -c """ import argparse -import asyncio import json import os -import sys import typing import pathlib @@ -40,27 +38,25 @@ def parse_config(config_path: str): return json.loads(fin.read()) -async def generate_tests( - java: str, - jar_path: str, - sys_paths: list[str], - python_path: str, - file: str, - timeout: int, - output: str, - class_name: typing.Optional[str] = None, - method_names: typing.Optional[str] = None -): - command = f"{java} -jar {jar_path} generate_python {file}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements --runtime-exception-behaviour PASS" +def generate_tests( + java: str, + jar_path: str, + sys_paths: list[str], + python_path: str, + file: str, + timeout: int, + output: str, + class_name: typing.Optional[str] = None, + method_names: typing.Optional[str] = None + ): + command = f"{java} -jar {jar_path} generate_python {file}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements --runtime-exception-behaviour Passing" if class_name is not None: command += f" -c {class_name}" if method_names is not None: command += f" -m {','.join(method_names)}" print(command) - # code = os.system(command) - proc = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE) - stdout, stderr = await proc.communicate() - return stdout + code = os.system(command) + return code def run_tests( @@ -74,20 +70,13 @@ def run_tests( return code -async def main(args): +def main(args): config = parse_config(args.config_file) - params = [] for part in config['parts']: for file in part['files']: for group in file['groups']: - params.append((part, file, group)) - - async with asyncio.TaskGroup() as tg: - for (part, file, group) in params: - full_name = pathlib.PurePath(args.path_to_test_dir, part['path'], file['name']) - output_file = pathlib.PurePath(args.output_dir, - f"utbot_tests_{part['path'].replace('/', '_')}_{file['name']}.py") - task = tg.create_task( + full_name = pathlib.PurePath(args.path_to_test_dir, part['path'], file['name']) + output_file = pathlib.PurePath(args.output_dir, f"utbot_tests_{part['path'].replace('/', '_')}_{file['name']}.py") generate_tests( args.java, args.jar, @@ -99,8 +88,7 @@ async def main(args): group['classes'], group['methods'] ) - ) - + if __name__ == '__main__': - asyncio.run(main(parse_arguments())) + main(parse_arguments()) From b03b695ddea257389822c94fe4c174981686a051 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Mon, 24 Jul 2023 16:58:23 +0300 Subject: [PATCH 35/44] Add coverage checker --- utbot-python/samples/run_tests.py | 80 +++++++++++++++----- utbot-python/samples/test_configuration.json | 27 +++---- 2 files changed, 76 insertions(+), 31 deletions(-) diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py index 5dbf544365..2be8fdc00f 100644 --- a/utbot-python/samples/run_tests.py +++ b/utbot-python/samples/run_tests.py @@ -18,7 +18,7 @@ def parse_arguments(): prog='UtBot Python test', description='Generage tests for example files' ) - subparsers = parser.add_subparsers() + subparsers = parser.add_subparsers(dest="command") parser_generate = subparsers.add_parser('generate', help='Generate tests') parser_generate.add_argument('java') parser_generate.add_argument('jar') @@ -26,10 +26,14 @@ def parse_arguments(): parser_generate.add_argument('-c', '--config_file') parser_generate.add_argument('-p', '--python_path') parser_generate.add_argument('-o', '--output_dir') + parser_generate.add_argument('-i', '--coverage_output_dir') parser_run = subparsers.add_parser('run', help='Run tests') parser_run.add_argument('-p', '--python_path') parser_run.add_argument('-t', '--test_directory') parser_run.add_argument('-c', '--code_directory') + parser_coverage = subparsers.add_parser('check_coverage', help='Check coverage') + parser_coverage.add_argument('-i', '--coverage_output_dir') + parser_coverage.add_argument('-c', '--config_file') return parser.parse_args() @@ -39,19 +43,20 @@ def parse_config(config_path: str): def generate_tests( - java: str, - jar_path: str, - sys_paths: list[str], - python_path: str, - file: str, - timeout: int, - output: str, - class_name: typing.Optional[str] = None, - method_names: typing.Optional[str] = None + java: str, + jar_path: str, + sys_paths: list[str], + python_path: str, + file_under_test: str, + timeout: int, + output: str, + coverage_output: str, + class_names: typing.Optional[list[str]] = None, + method_names: typing.Optional[list[str]] = None ): - command = f"{java} -jar {jar_path} generate_python {file}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements --runtime-exception-behaviour Passing" - if class_name is not None: - command += f" -c {class_name}" + command = f"{java} -jar {jar_path} generate_python {file_under_test}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements --runtime-exception-behaviour PASS --coverage={coverage_output}" + if class_names is not None: + command += f" -c {','.join(class_names)}" if method_names is not None: command += f" -m {','.join(method_names)}" print(command) @@ -70,13 +75,45 @@ def run_tests( return code -def main(args): +def check_coverage( + config_file: str, + coverage_output_dir: str, +): + config = parse_config(config_file) + report: typing.Dict[str, bool] = {} + for part in config['parts'][:2]: + for file in part['files'][:2]: + for group in file['groups'][:2]: + expected_coverage = group.get('coverage', 0) + + file_suffix = f"{part['path'].replace('/', '_')}_{file['name']}" + coverage_output_file = pathlib.PurePath(coverage_output_dir, f"coverage_{file_suffix}.json") + with open(coverage_output_file, "rt") as fin: + actual_coverage_json = json.loads(fin.readline()) + actual_covered = sum(lines['end'] - lines['start'] + 1 for lines in actual_coverage_json['covered']) + actual_not_covered = sum(lines['end'] - lines['start'] + 1 for lines in actual_coverage_json['notCovered']) + actual_coverage = round(actual_covered / (actual_not_covered + actual_covered)) * 100 + + report[file_suffix] = actual_coverage >= expected_coverage + if all(report.values()): + return True + print("-------------") + print("Bad coverage:") + print("-------------") + for file, good_coverage in report.items(): + if not good_coverage: + print(file) + return False + + +def main_test_generation(args): config = parse_config(args.config_file) - for part in config['parts']: - for file in part['files']: - for group in file['groups']: + for part in config['parts'][:2]: + for file in part['files'][:2]: + for group in file['groups'][:2]: full_name = pathlib.PurePath(args.path_to_test_dir, part['path'], file['name']) output_file = pathlib.PurePath(args.output_dir, f"utbot_tests_{part['path'].replace('/', '_')}_{file['name']}.py") + coverage_output_file = pathlib.PurePath(args.coverage_output_dir, f"coverage_{part['path'].replace('/', '_')}_{file['name']}.json") generate_tests( args.java, args.jar, @@ -85,10 +122,17 @@ def main(args): str(full_name), group['timeout'], str(output_file), + str(coverage_output_file), group['classes'], group['methods'] ) if __name__ == '__main__': - main(parse_arguments()) + arguments = parse_arguments() + if arguments.command == 'generate': + main_test_generation(arguments) + elif arguments.command == 'run': + run_tests(arguments.python_path, arguments.test_directory, arguments.code_directory) + elif arguments.command == 'check_coverage': + check_coverage(arguments.config_file, arguments.coverage_output_dir) diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json index c66b042881..34991b01c7 100644 --- a/utbot-python/samples/test_configuration.json +++ b/utbot-python/samples/test_configuration.json @@ -52,9 +52,10 @@ "name": "dataclass", "groups": [ { - "classes": "C", - "methods": "inc", - "timeout": 10 + "classes": ["C"], + "methods": ["inc"], + "timeout": 10, + "coverage": 100 } ] }, @@ -62,8 +63,8 @@ "name": "dicts", "groups": [ { - "classes": "Dictionary", - "methods": "translate", + "classes": ["Dictionary"], + "methods": ["translate"], "timeout": 10 } ] @@ -72,7 +73,7 @@ "name": "easy_class", "groups": [ { - "classes": "B", + "classes": ["B"], "methods": null, "timeout": 10 } @@ -92,7 +93,7 @@ "name": "field", "groups": [ { - "classes": "NoTestsProblem", + "classes": ["NoTestsProblem"], "methods": null, "timeout": 10 } @@ -102,7 +103,7 @@ "name": "inner_class", "groups": [ { - "classes": "Outer", + "classes": ["Outer"], "methods": null, "timeout": 10 } @@ -112,7 +113,7 @@ "name": "rename_self", "groups": [ { - "classes": "A", + "classes": ["A"], "methods": null, "timeout": 10 } @@ -447,7 +448,7 @@ "name": "boruvka", "groups": [ { - "classes": "Graph", + "classes": ["Graph"], "methods": null, "timeout": 150 } @@ -467,7 +468,7 @@ "name": "matrix", "groups": [ { - "classes": "Matrix", + "classes": ["Matrix"], "methods": null, "timeout": 240 } @@ -492,7 +493,7 @@ "name": "annotations2", "groups": [ { - "classes": "A", + "classes": ["A"], "methods": null, "timeout": 40 } @@ -512,7 +513,7 @@ "name": "generics", "groups": [ { - "classes": "LoggedVar", + "classes": ["LoggedVar"], "methods": null, "timeout": 30 } From 3cb62b7902c17c25b2a6026a7969ab4083106042 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Mon, 24 Jul 2023 17:05:28 +0300 Subject: [PATCH 36/44] Add coverage info --- utbot-python/samples/test_configuration.json | 147 ++++++++++++------- 1 file changed, 98 insertions(+), 49 deletions(-) diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json index 34991b01c7..b51ab7c6cb 100644 --- a/utbot-python/samples/test_configuration.json +++ b/utbot-python/samples/test_configuration.json @@ -9,7 +9,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -19,7 +20,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -29,7 +31,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] } @@ -44,7 +47,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -65,7 +69,8 @@ { "classes": ["Dictionary"], "methods": ["translate"], - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -75,7 +80,8 @@ { "classes": ["B"], "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -85,7 +91,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -95,7 +102,8 @@ { "classes": ["NoTestsProblem"], "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -105,7 +113,8 @@ { "classes": ["Outer"], "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -115,7 +124,8 @@ { "classes": ["A"], "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -125,7 +135,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] } @@ -140,7 +151,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -150,7 +162,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -160,7 +173,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -170,7 +184,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -180,7 +195,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] } @@ -195,7 +211,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -205,7 +222,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] }, @@ -215,7 +233,8 @@ { "classes": null, "methods": null, - "timeout": 10 + "timeout": 10, + "coverage": 100 } ] } @@ -230,7 +249,8 @@ { "classes": null, "methods": null, - "timeout": 90 + "timeout": 90, + "coverage": 100 } ] }, @@ -240,7 +260,8 @@ { "classes": null, "methods": null, - "timeout": 20 + "timeout": 20, + "coverage": 100 } ] }, @@ -250,7 +271,8 @@ { "classes": null, "methods": null, - "timeout": 150 + "timeout": 150, + "coverage": 100 } ] }, @@ -260,7 +282,8 @@ { "classes": null, "methods": null, - "timeout": 20 + "timeout": 20, + "coverage": 100 } ] }, @@ -270,7 +293,8 @@ { "classes": null, "methods": null, - "timeout": 20 + "timeout": 20, + "coverage": 100 } ] }, @@ -280,7 +304,8 @@ { "classes": null, "methods": null, - "timeout": 300 + "timeout": 300, + "coverage": 100 } ] }, @@ -290,7 +315,8 @@ { "classes": null, "methods": null, - "timeout": 20 + "timeout": 20, + "coverage": 100 } ] } @@ -305,7 +331,8 @@ { "classes": null, "methods": null, - "timeout": 180 + "timeout": 180, + "coverage": 100 } ] } @@ -320,7 +347,8 @@ { "classes": null, "methods": null, - "timeout": 180 + "timeout": 180, + "coverage": 100 } ] } @@ -335,7 +363,8 @@ { "classes": null, "methods": null, - "timeout": 180 + "timeout": 180, + "coverage": 100 } ] } @@ -350,7 +379,8 @@ { "classes": null, "methods": null, - "timeout": 20 + "timeout": 20, + "coverage": 100 } ] } @@ -365,7 +395,8 @@ { "classes": null, "methods": null, - "timeout": 20 + "timeout": 20, + "coverage": 100 } ] } @@ -380,7 +411,8 @@ { "classes": null, "methods": null, - "timeout": 30 + "timeout": 30, + "coverage": 100 } ] }, @@ -390,7 +422,8 @@ { "classes": null, "methods": null, - "timeout": 80 + "timeout": 80, + "coverage": 100 } ] }, @@ -400,7 +433,8 @@ { "classes": null, "methods": null, - "timeout": 40 + "timeout": 40, + "coverage": 100 } ] }, @@ -410,7 +444,8 @@ { "classes": null, "methods": null, - "timeout": 40 + "timeout": 40, + "coverage": 100 } ] }, @@ -420,7 +455,8 @@ { "classes": null, "methods": null, - "timeout": 160 + "timeout": 160, + "coverage": 100 } ] } @@ -435,7 +471,8 @@ { "classes": null, "methods": null, - "timeout": 150 + "timeout": 150, + "coverage": 100 } ] } @@ -450,7 +487,8 @@ { "classes": ["Graph"], "methods": null, - "timeout": 150 + "timeout": 150, + "coverage": 100 } ] }, @@ -460,7 +498,8 @@ { "classes": null, "methods": null, - "timeout": 150 + "timeout": 150, + "coverage": 100 } ] }, @@ -470,7 +509,8 @@ { "classes": ["Matrix"], "methods": null, - "timeout": 240 + "timeout": 240, + "coverage": 100 } ] } @@ -485,7 +525,8 @@ { "classes": null, "methods": null, - "timeout": 140 + "timeout": 140, + "coverage": 100 } ] }, @@ -495,7 +536,8 @@ { "classes": ["A"], "methods": null, - "timeout": 40 + "timeout": 40, + "coverage": 100 } ] }, @@ -505,7 +547,8 @@ { "classes": null, "methods": null, - "timeout": 120 + "timeout": 120, + "coverage": 100 } ] }, @@ -515,7 +558,8 @@ { "classes": ["LoggedVar"], "methods": null, - "timeout": 30 + "timeout": 30, + "coverage": 100 } ] }, @@ -525,7 +569,8 @@ { "classes": null, "methods": null, - "timeout": 30 + "timeout": 30, + "coverage": 100 } ] }, @@ -535,7 +580,8 @@ { "classes": null, "methods": null, - "timeout": 30 + "timeout": 30, + "coverage": 100 } ] }, @@ -545,7 +591,8 @@ { "classes": null, "methods": null, - "timeout": 90 + "timeout": 90, + "coverage": 100 } ] }, @@ -555,7 +602,8 @@ { "classes": null, "methods": null, - "timeout": 40 + "timeout": 40, + "coverage": 100 } ] }, @@ -565,7 +613,8 @@ { "classes": null, "methods": null, - "timeout": 40 + "timeout": 40, + "coverage": 100 } ] } From c4afc4f90d911b283c9342c93fe8569e40d7097a Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 25 Jul 2023 10:50:28 +0300 Subject: [PATCH 37/44] Fix builtins module rendering --- .../constructor/tree/PythonCgVariableConstructor.kt | 4 ++-- .../codegen/model/constructor/util/ConstructorUtils.kt | 7 +++++++ .../model/constructor/visitor/CgPythonRenderer.kt | 9 +++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgVariableConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgVariableConstructor.kt index e1de6bd0e4..7410ea4fab 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgVariableConstructor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgVariableConstructor.kt @@ -38,7 +38,7 @@ class PythonCgVariableConstructor(cgContext: CgContext) : CgVariableConstructor( private fun pythonBuildObject(objectNode: PythonTree.PythonTreeNode, baseName: String? = null): Pair> { return when (objectNode) { is PythonTree.PrimitiveNode -> { - Pair(CgLiteral(objectNode.type.dropBuiltins(), objectNode.repr), emptyList()) + Pair(CgLiteral(objectNode.type, objectNode.repr), emptyList()) } is PythonTree.ListNode -> { @@ -84,7 +84,7 @@ class PythonCgVariableConstructor(cgContext: CgContext) : CgVariableConstructor( getOrCreateVariable(PythonTreeModel(it, it.type)) } val constructor = ConstructorId( - objectNode.constructor.dropBuiltins(), + objectNode.constructor, initArgs.map { it.type } ) val constructorCall = CgConstructorCall(constructor, initArgs) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt index 06e0b3ba63..653c63478b 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -40,3 +40,10 @@ internal fun PythonClassId.dropBuiltins(): PythonClassId { this } +internal fun String.dropBuiltins(): String { + val builtinsPrefix = "$pythonBuiltinsModuleName." + return if (this.startsWith(builtinsPrefix)) + this.drop(builtinsPrefix.length) + else + this +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt index 4661943b34..5c91f5389d 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt @@ -66,6 +66,7 @@ import org.utbot.framework.plugin.api.WildcardTypeParameter import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.api.python.pythonBuiltinsModuleName import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.framework.codegen.model.constructor.util.dropBuiltins import org.utbot.python.framework.codegen.model.tree.* import java.lang.StringBuilder import org.utbot.python.framework.codegen.utils.toRelativeRawPath @@ -300,7 +301,7 @@ internal class CgPythonRenderer( } override fun visit(element: CgConstructorCall) { - print(element.executableId.classId.name) + print(element.executableId.classId.name.dropBuiltins()) renderExecutableCallArguments(element) } @@ -504,7 +505,7 @@ internal class CgPythonRenderer( } override fun visit(element: CgPythonRepr) { - print(element.content) + print(element.content.dropBuiltins()) } override fun visit(element: CgPythonIndex) { @@ -515,7 +516,7 @@ internal class CgPythonRenderer( } override fun visit(element: CgPythonFunctionCall) { - print(element.name) + print(element.name.dropBuiltins()) print("(") val newLinesNeeded = element.parameters.size > maxParametersAmountInOneLine element.parameters.renderSeparated(newLinesNeeded) @@ -604,7 +605,7 @@ internal class CgPythonRenderer( } override fun visit(element: CgLiteral) { - print(element.value.toString()) + print(element.value.toString().dropBuiltins()) } override fun visit(element: CgFormattedString) { From 3588baf75dd46cf88bdf7bb49a64641125b4e7e4 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 25 Jul 2023 15:02:10 +0300 Subject: [PATCH 38/44] Fix codegen --- .../python/framework/api/python/PythonApi.kt | 5 +++++ .../tree/PythonCgMethodConstructor.kt | 22 ++++++++++++++----- .../constructor/util/ConstructorUtils.kt | 3 +++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt index ffa160c939..d76d1a04cb 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt @@ -106,6 +106,11 @@ class PythonUtExecution( init.tree.comparable = before.tree.comparable } } + val init = stateInit.thisInstance + val before = stateBefore.thisInstance + if (init is PythonTreeModel && before is PythonTreeModel) { + init.tree.comparable = before.tree.comparable + } } override fun copy( stateBefore: EnvironmentModels, diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt index 73d78c9668..37efac1a0f 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt @@ -22,11 +22,18 @@ import org.utbot.python.framework.api.python.util.pythonExceptionClassId import org.utbot.python.framework.api.python.util.pythonIntClassId import org.utbot.python.framework.api.python.util.pythonNoneClassId import org.utbot.python.framework.codegen.PythonCgLanguageAssistant +import org.utbot.python.framework.codegen.model.constructor.util.importIfNeeded import org.utbot.python.framework.codegen.model.tree.* class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(context) { private val maxDepth: Int = 5 + private fun CgVariable.deepcopy(): CgVariable { + val classId = PythonClassId("copy.deepcopy") + importIfNeeded(classId) + return newVar(this.type) { CgPythonFunctionCall(classId, "copy.deepcopy", listOf(this)) } + } + override fun assertEquality(expected: CgValue, actual: CgVariable) { pythonDeepEquals(expected, actual) } @@ -276,7 +283,7 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex val firstChild = elements.first() // TODO: We can use only structure => we should use another element if the first is empty - emptyLine() + emptyLineIfNeeded() if (elementsHaveSameStructure) { val index = newVar(pythonNoneClassId, keyName) { CgLiteral(pythonNoneClassId, "None") @@ -299,7 +306,7 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex index ) } - pythonDeepTreeEquals(firstChild, indexExpected, indexActual) + pythonDeepTreeEquals(firstChild, indexExpected, indexActual, useExpectedAsValue = true) statements = currentBlock } } @@ -329,11 +336,16 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex expectedNode: PythonTree.PythonTreeNode, expected: CgValue, actual: CgVariable, - depth: Int = maxDepth + depth: Int = maxDepth, + useExpectedAsValue: Boolean = false ) { if (expectedNode.comparable || depth == 0) { - emptyLineIfNeeded() - val expectedValue = variableConstructor.getOrCreateVariable(PythonTreeModel(expectedNode)) + val expectedValue = + if (useExpectedAsValue) { + expected + } else { + variableConstructor.getOrCreateVariable(PythonTreeModel(expectedNode)) + } testFrameworkManager.assertEquals( expectedValue, actual, diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt index 653c63478b..0feb5e99d8 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -3,10 +3,12 @@ package org.utbot.python.framework.codegen.model.constructor.util import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentSet import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgVariable import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.api.python.PythonMethodId import org.utbot.python.framework.api.python.pythonBuiltinsModuleName import org.utbot.python.framework.codegen.model.PythonUserImport +import org.utbot.python.framework.codegen.model.tree.CgPythonFunctionCall internal fun CgContextOwner.importIfNeeded(method: PythonMethodId) { collectedImports += PythonUserImport(method.moduleName) @@ -47,3 +49,4 @@ internal fun String.dropBuiltins(): String { else this } + From b234affce960d19e7d9765fab7d4b17c7e947d64 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 25 Jul 2023 15:02:38 +0300 Subject: [PATCH 39/44] Fix empty test set rendering --- .../org/utbot/cli/language/python/PythonGenerateTestsCommand.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index 096648f88e..07af86255a 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -248,6 +248,7 @@ class PythonGenerateTestsCommand : CliktCommand( logger.info("Generating tests...") val testSets = processor.testGenerate(mypyStorage) + if (testSets.isEmpty()) return logger.info("Saving tests...") val testCode = processor.testCodeGenerate(testSets) From e088396b6bccc43736e68d738b4d05ab03edd496 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 25 Jul 2023 17:02:18 +0300 Subject: [PATCH 40/44] Add try before manager.shutdown() --- .../kotlin/org/utbot/python/PythonEngine.kt | 87 ++++++++++--------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt index 23e22c8026..5c00195421 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt @@ -297,53 +297,56 @@ class PythonEngine( Random(0), ) - if (parameters.isEmpty()) { - val result = fuzzingResultHandler(pmd, emptyList(), parameters, manager) - result?.let { - emit(it.fuzzingExecutionFeedback) - } - } else { - try { - PythonFuzzing(pmd.pythonTypeStorage) { description, arguments -> - if (isCancelled()) { - logger.info { "Fuzzing process was interrupted" } - manager.disconnect() - return@PythonFuzzing PythonFeedback(control = Control.STOP) - } - if (System.currentTimeMillis() >= until) { - logger.info { "Fuzzing process was interrupted by timeout" } - manager.disconnect() - return@PythonFuzzing PythonFeedback(control = Control.STOP) - } + try { + if (parameters.isEmpty()) { + val result = fuzzingResultHandler(pmd, emptyList(), parameters, manager) + result?.let { + emit(it.fuzzingExecutionFeedback) + } + } else { + try { + PythonFuzzing(pmd.pythonTypeStorage) { description, arguments -> + if (isCancelled()) { + logger.info { "Fuzzing process was interrupted" } + manager.disconnect() + return@PythonFuzzing PythonFeedback(control = Control.STOP) + } + if (System.currentTimeMillis() >= until) { + logger.info { "Fuzzing process was interrupted by timeout" } + manager.disconnect() + return@PythonFuzzing PythonFeedback(control = Control.STOP) + } - if (arguments.any { PythonTree.containsFakeNode(it.tree) }) { - logger.debug("FakeNode in Python model") - emit(FakeNodeFeedback) - return@PythonFuzzing PythonFeedback(control = Control.CONTINUE) - } + if (arguments.any { PythonTree.containsFakeNode(it.tree) }) { + logger.debug("FakeNode in Python model") + emit(FakeNodeFeedback) + return@PythonFuzzing PythonFeedback(control = Control.CONTINUE) + } - val pair = Pair(description, arguments.map { PythonTreeWrapper(it.tree) }) - val mem = cache.get(pair) - if (mem != null) { - logger.debug("Repeat in fuzzing") - emit(CachedExecutionFeedback(mem.fuzzingExecutionFeedback)) - return@PythonFuzzing mem.fuzzingPlatformFeedback - } - val result = fuzzingResultHandler(description, arguments, parameters, manager) - if (result == null) { // timeout - manager.disconnect() - return@PythonFuzzing PythonFeedback(control = Control.STOP) - } + val pair = Pair(description, arguments.map { PythonTreeWrapper(it.tree) }) + val mem = cache.get(pair) + if (mem != null) { + logger.debug("Repeat in fuzzing") + emit(CachedExecutionFeedback(mem.fuzzingExecutionFeedback)) + return@PythonFuzzing mem.fuzzingPlatformFeedback + } + val result = fuzzingResultHandler(description, arguments, parameters, manager) + if (result == null) { // timeout + manager.disconnect() + return@PythonFuzzing PythonFeedback(control = Control.STOP) + } - cache.add(pair, result) - emit(result.fuzzingExecutionFeedback) - return@PythonFuzzing result.fuzzingPlatformFeedback - }.fuzz(pmd) - } catch (_: NoSeedValueException) { - logger.info { "Cannot fuzz values for types: ${parameters.map { it.pythonTypeRepresentation() }}" } + cache.add(pair, result) + emit(result.fuzzingExecutionFeedback) + return@PythonFuzzing result.fuzzingPlatformFeedback + }.fuzz(pmd) + } catch (_: NoSeedValueException) { + logger.info { "Cannot fuzz values for types: ${parameters.map { it.pythonTypeRepresentation() }}" } + } } + } finally { + manager.shutdown() } - manager.shutdown() } } } From 289502977d7c9b43723e8f079568fd77df7b8a16 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 25 Jul 2023 17:02:58 +0300 Subject: [PATCH 41/44] Generate field state assertions only for success tests --- .../model/constructor/tree/PythonCgMethodConstructor.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt index 37efac1a0f..88ef4feb91 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt @@ -121,7 +121,9 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex recordActualResult() generateResultAssertions() - generateFieldStateAssertions(stateAssertions, assertThisObject, executableId) + if (methodType == CgTestMethodType.PASSED_EXCEPTION) { + generateFieldStateAssertions(stateAssertions, assertThisObject, executableId) + } } if (statics.isNotEmpty()) { @@ -340,8 +342,7 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex useExpectedAsValue: Boolean = false ) { if (expectedNode.comparable || depth == 0) { - val expectedValue = - if (useExpectedAsValue) { + val expectedValue = if (useExpectedAsValue) { expected } else { variableConstructor.getOrCreateVariable(PythonTreeModel(expectedNode)) From e3451d78de4427c78a574b40d876fd2ecc4398dd Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 25 Jul 2023 17:03:24 +0300 Subject: [PATCH 42/44] Update config and test set --- utbot-python/samples/run_tests.py | 19 +- .../samples/structures/graph_matrix.py | 40 +++ .../structures/multi_level_feedback_queue.py | 312 ++++++++++++++++++ utbot-python/samples/test_configuration.json | 65 ++-- 4 files changed, 401 insertions(+), 35 deletions(-) create mode 100644 utbot-python/samples/samples/structures/graph_matrix.py create mode 100644 utbot-python/samples/samples/structures/multi_level_feedback_queue.py diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py index 2be8fdc00f..7cb0bf4acd 100644 --- a/utbot-python/samples/run_tests.py +++ b/utbot-python/samples/run_tests.py @@ -61,6 +61,7 @@ def generate_tests( command += f" -m {','.join(method_names)}" print(command) code = os.system(command) + print(code) return code @@ -81,9 +82,10 @@ def check_coverage( ): config = parse_config(config_file) report: typing.Dict[str, bool] = {} - for part in config['parts'][:2]: - for file in part['files'][:2]: - for group in file['groups'][:2]: + coverage: typing.Dict[str, typing.Tuple[float, float]] = {} + for part in config['parts']: + for file in part['files']: + for group in file['groups']: expected_coverage = group.get('coverage', 0) file_suffix = f"{part['path'].replace('/', '_')}_{file['name']}" @@ -92,8 +94,9 @@ def check_coverage( actual_coverage_json = json.loads(fin.readline()) actual_covered = sum(lines['end'] - lines['start'] + 1 for lines in actual_coverage_json['covered']) actual_not_covered = sum(lines['end'] - lines['start'] + 1 for lines in actual_coverage_json['notCovered']) - actual_coverage = round(actual_covered / (actual_not_covered + actual_covered)) * 100 + actual_coverage = round(actual_covered / (actual_not_covered + actual_covered) * 100) + coverage[file_suffix] = (actual_coverage, expected_coverage) report[file_suffix] = actual_coverage >= expected_coverage if all(report.values()): return True @@ -102,15 +105,15 @@ def check_coverage( print("-------------") for file, good_coverage in report.items(): if not good_coverage: - print(file) + print(f"{file}: {coverage[file][0]}/{coverage[file][1]}") return False def main_test_generation(args): config = parse_config(args.config_file) - for part in config['parts'][:2]: - for file in part['files'][:2]: - for group in file['groups'][:2]: + for part in config['parts']: + for file in part['files']: + for group in file['groups']: full_name = pathlib.PurePath(args.path_to_test_dir, part['path'], file['name']) output_file = pathlib.PurePath(args.output_dir, f"utbot_tests_{part['path'].replace('/', '_')}_{file['name']}.py") coverage_output_file = pathlib.PurePath(args.coverage_output_dir, f"coverage_{part['path'].replace('/', '_')}_{file['name']}.json") diff --git a/utbot-python/samples/samples/structures/graph_matrix.py b/utbot-python/samples/samples/structures/graph_matrix.py new file mode 100644 index 0000000000..785c295724 --- /dev/null +++ b/utbot-python/samples/samples/structures/graph_matrix.py @@ -0,0 +1,40 @@ +# An island in matrix is a group of linked areas, all having the same value. +# This code counts number of islands in a given matrix, with including diagonal +# connections. + + +class Matrix: # Public class to implement a graph + def __init__(self, row: int, col: int, graph: list[list[bool]]) -> None: + self.ROW = row + self.COL = col + self.graph = graph + + def __eq__(self, other): + return self.graph == other.graph + + def is_safe(self, i: int, j: int, visited: list[list[bool]]) -> bool: + return ( + 0 <= i < self.ROW + and 0 <= j < self.COL + and not visited[i][j] + and self.graph[i][j] + ) + + def diffs(self, i: int, j: int, visited: list[list[bool]]) -> None: + # Checking all 8 elements surrounding nth element + row_nbr = [-1, -1, -1, 0, 0, 1, 1, 1] # Coordinate order + col_nbr = [-1, 0, 1, -1, 1, -1, 0, 1] + visited[i][j] = True # Make those cells visited + for k in range(8): + if self.is_safe(i + row_nbr[k], j + col_nbr[k], visited): + self.diffs(i + row_nbr[k], j + col_nbr[k], visited) + + def count_islands(self) -> int: # And finally, count all islands. + visited = [[False for j in range(self.COL)] for i in range(self.ROW)] + count = 0 + for i in range(self.ROW): + for j in range(self.COL): + if visited[i][j] is False and self.graph[i][j] == 1: + self.diffs(i, j, visited) + count += 1 + return count \ No newline at end of file diff --git a/utbot-python/samples/samples/structures/multi_level_feedback_queue.py b/utbot-python/samples/samples/structures/multi_level_feedback_queue.py new file mode 100644 index 0000000000..284c758b8f --- /dev/null +++ b/utbot-python/samples/samples/structures/multi_level_feedback_queue.py @@ -0,0 +1,312 @@ +from collections import deque + + +class Process: + def __init__(self, process_name: str, arrival_time: int, burst_time: int) -> None: + self.process_name = process_name # process name + self.arrival_time = arrival_time # arrival time of the process + # completion time of finished process or last interrupted time + self.stop_time = arrival_time + self.burst_time = burst_time # remaining burst time + self.waiting_time = 0 # total time of the process wait in ready queue + self.turnaround_time = 0 # time from arrival time to completion time + + +class MLFQ: + """ + MLFQ(Multi Level Feedback Queue) + https://en.wikipedia.org/wiki/Multilevel_feedback_queue + MLFQ has a lot of queues that have different priority + In this MLFQ, + The first Queue(0) to last second Queue(N-2) of MLFQ have Round Robin Algorithm + The last Queue(N-1) has First Come, First Served Algorithm + """ + + def __init__( + self, + number_of_queues: int, + time_slices: list[int], + queue: deque[Process], + current_time: int, + ) -> None: + # total number of mlfq's queues + self.number_of_queues = number_of_queues + # time slice of queues that round robin algorithm applied + self.time_slices = time_slices + # unfinished process is in this ready_queue + self.ready_queue = queue + # current time + self.current_time = current_time + # finished process is in this sequence queue + self.finish_queue: deque[Process] = deque() + + def calculate_sequence_of_finish_queue(self) -> list[str]: + """ + This method returns the sequence of finished processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_sequence_of_finish_queue() + ['P2', 'P4', 'P1', 'P3'] + """ + sequence = [] + for i in range(len(self.finish_queue)): + sequence.append(self.finish_queue[i].process_name) + return sequence + + def calculate_waiting_time(self, queue: list[Process]) -> list[int]: + """ + This method calculates waiting time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_waiting_time([P1, P2, P3, P4]) + [83, 17, 94, 101] + """ + waiting_times = [] + for i in range(len(queue)): + waiting_times.append(queue[i].waiting_time) + return waiting_times + + def calculate_turnaround_time(self, queue: list[Process]) -> list[int]: + """ + This method calculates turnaround time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_turnaround_time([P1, P2, P3, P4]) + [136, 34, 162, 125] + """ + turnaround_times = [] + for i in range(len(queue)): + turnaround_times.append(queue[i].turnaround_time) + return turnaround_times + + def calculate_completion_time(self, queue: list[Process]) -> list[int]: + """ + This method calculates completion time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_turnaround_time([P1, P2, P3, P4]) + [136, 34, 162, 125] + """ + completion_times = [] + for i in range(len(queue)): + completion_times.append(queue[i].stop_time) + return completion_times + + def calculate_remaining_burst_time_of_processes( + self, queue: deque[Process] + ) -> list[int]: + """ + This method calculate remaining burst time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> finish_queue, ready_queue = mlfq.round_robin(deque([P1, P2, P3, P4]), 17) + >>> mlfq.calculate_remaining_burst_time_of_processes(mlfq.finish_queue) + [0] + >>> mlfq.calculate_remaining_burst_time_of_processes(ready_queue) + [36, 51, 7] + >>> finish_queue, ready_queue = mlfq.round_robin(ready_queue, 25) + >>> mlfq.calculate_remaining_burst_time_of_processes(mlfq.finish_queue) + [0, 0] + >>> mlfq.calculate_remaining_burst_time_of_processes(ready_queue) + [11, 26] + """ + return [q.burst_time for q in queue] + + def update_waiting_time(self, process: Process) -> int: + """ + This method updates waiting times of unfinished processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> mlfq.current_time = 10 + >>> P1.stop_time = 5 + >>> mlfq.update_waiting_time(P1) + 5 + """ + process.waiting_time += self.current_time - process.stop_time + return process.waiting_time + + def first_come_first_served(self, ready_queue: deque[Process]) -> deque[Process]: + """ + FCFS(First Come, First Served) + FCFS will be applied to MLFQ's last queue + A first came process will be finished at first + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.first_come_first_served(mlfq.ready_queue) + >>> mlfq.calculate_sequence_of_finish_queue() + ['P1', 'P2', 'P3', 'P4'] + """ + finished: deque[Process] = deque() # sequence deque of finished process + while len(ready_queue) != 0: + cp = ready_queue.popleft() # current process + + # if process's arrival time is later than current time, update current time + if self.current_time < cp.arrival_time: + self.current_time += cp.arrival_time + + # update waiting time of current process + self.update_waiting_time(cp) + # update current time + self.current_time += cp.burst_time + # finish the process and set the process's burst-time 0 + cp.burst_time = 0 + # set the process's turnaround time because it is finished + cp.turnaround_time = self.current_time - cp.arrival_time + # set the completion time + cp.stop_time = self.current_time + # add the process to queue that has finished queue + finished.append(cp) + + self.finish_queue.extend(finished) # add finished process to finish queue + # FCFS will finish all remaining processes + return finished + + def round_robin( + self, ready_queue: deque[Process], time_slice: int + ) -> tuple[deque[Process], deque[Process]]: + """ + RR(Round Robin) + RR will be applied to MLFQ's all queues except last queue + All processes can't use CPU for time more than time_slice + If the process consume CPU up to time_slice, it will go back to ready queue + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> finish_queue, ready_queue = mlfq.round_robin(mlfq.ready_queue, 17) + >>> mlfq.calculate_sequence_of_finish_queue() + ['P2'] + """ + finished: deque[Process] = deque() # sequence deque of terminated process + # just for 1 cycle and unfinished processes will go back to queue + for _ in range(len(ready_queue)): + cp = ready_queue.popleft() # current process + + # if process's arrival time is later than current time, update current time + if self.current_time < cp.arrival_time: + self.current_time += cp.arrival_time + + # update waiting time of unfinished processes + self.update_waiting_time(cp) + # if the burst time of process is bigger than time-slice + if cp.burst_time > time_slice: + # use CPU for only time-slice + self.current_time += time_slice + # update remaining burst time + cp.burst_time -= time_slice + # update end point time + cp.stop_time = self.current_time + # locate the process behind the queue because it is not finished + ready_queue.append(cp) + else: + # use CPU for remaining burst time + self.current_time += cp.burst_time + # set burst time 0 because the process is finished + cp.burst_time = 0 + # set the finish time + cp.stop_time = self.current_time + # update the process' turnaround time because it is finished + cp.turnaround_time = self.current_time - cp.arrival_time + # add the process to queue that has finished queue + finished.append(cp) + + self.finish_queue.extend(finished) # add finished process to finish queue + # return finished processes queue and remaining processes queue + return finished, ready_queue + + def multi_level_feedback_queue(self) -> deque[Process]: + """ + MLFQ(Multi Level Feedback Queue) + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> finish_queue = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_sequence_of_finish_queue() + ['P2', 'P4', 'P1', 'P3'] + """ + + # all queues except last one have round_robin algorithm + for i in range(self.number_of_queues - 1): + finished, self.ready_queue = self.round_robin( + self.ready_queue, self.time_slices[i] + ) + # the last queue has first_come_first_served algorithm + self.first_come_first_served(self.ready_queue) + + return self.finish_queue + + +if __name__ == "__main__": + import doctest + + P1 = Process("P1", 0, 53) + P2 = Process("P2", 0, 17) + P3 = Process("P3", 0, 68) + P4 = Process("P4", 0, 24) + number_of_queues = 3 + time_slices = [17, 25] + queue = deque([P1, P2, P3, P4]) + + if len(time_slices) != number_of_queues - 1: + raise SystemExit(0) + + doctest.testmod(extraglobs={"queue": deque([P1, P2, P3, P4])}) + + P1 = Process("P1", 0, 53) + P2 = Process("P2", 0, 17) + P3 = Process("P3", 0, 68) + P4 = Process("P4", 0, 24) + number_of_queues = 3 + time_slices = [17, 25] + queue = deque([P1, P2, P3, P4]) + mlfq = MLFQ(number_of_queues, time_slices, queue, 0) + finish_queue = mlfq.multi_level_feedback_queue() + + # print total waiting times of processes(P1, P2, P3, P4) + print( + f"waiting time:\ + \t\t\t{MLFQ.calculate_waiting_time(mlfq, [P1, P2, P3, P4])}" + ) + # print completion times of processes(P1, P2, P3, P4) + print( + f"completion time:\ + \t\t{MLFQ.calculate_completion_time(mlfq, [P1, P2, P3, P4])}" + ) + # print total turnaround times of processes(P1, P2, P3, P4) + print( + f"turnaround time:\ + \t\t{MLFQ.calculate_turnaround_time(mlfq, [P1, P2, P3, P4])}" + ) + # print sequence of finished processes + print( + f"sequence of finished processes:\ + {mlfq.calculate_sequence_of_finish_queue()}" + ) \ No newline at end of file diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json index b51ab7c6cb..1746e07ba0 100644 --- a/utbot-python/samples/test_configuration.json +++ b/utbot-python/samples/test_configuration.json @@ -32,7 +32,7 @@ "classes": null, "methods": null, "timeout": 10, - "coverage": 100 + "coverage": 85 } ] } @@ -48,7 +48,7 @@ "classes": null, "methods": null, "timeout": 10, - "coverage": 100 + "coverage": 80 } ] }, @@ -70,7 +70,7 @@ "classes": ["Dictionary"], "methods": ["translate"], "timeout": 10, - "coverage": 100 + "coverage": 89 } ] }, @@ -195,8 +195,8 @@ { "classes": null, "methods": null, - "timeout": 10, - "coverage": 100 + "timeout": 60, + "coverage": 88 } ] } @@ -211,7 +211,7 @@ { "classes": null, "methods": null, - "timeout": 10, + "timeout": 100, "coverage": 100 } ] @@ -222,8 +222,8 @@ { "classes": null, "methods": null, - "timeout": 10, - "coverage": 100 + "timeout": 30, + "coverage": 72 } ] }, @@ -233,8 +233,8 @@ { "classes": null, "methods": null, - "timeout": 10, - "coverage": 100 + "timeout": 30, + "coverage": 75 } ] } @@ -271,7 +271,7 @@ { "classes": null, "methods": null, - "timeout": 150, + "timeout": 300, "coverage": 100 } ] @@ -298,17 +298,6 @@ } ] }, - { - "name": "general", - "groups": [ - { - "classes": null, - "methods": null, - "timeout": 300, - "coverage": 100 - } - ] - }, { "name": "long_function_coverage", "groups": [ @@ -348,7 +337,7 @@ "classes": null, "methods": null, "timeout": 180, - "coverage": 100 + "coverage": 86 } ] } @@ -433,7 +422,7 @@ { "classes": null, "methods": null, - "timeout": 40, + "timeout": 60, "coverage": 100 } ] @@ -503,6 +492,17 @@ } ] }, + { + "name": "graph_matrix", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 120, + "coverage": 100 + } + ] + }, { "name": "matrix", "groups": [ @@ -513,6 +513,17 @@ "coverage": 100 } ] + }, + { + "name": "multi_level_feedback_queue", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 180, + "coverage": 100 + } + ] } ] }, @@ -559,7 +570,7 @@ "classes": ["LoggedVar"], "methods": null, "timeout": 30, - "coverage": 100 + "coverage": 80 } ] }, @@ -581,7 +592,7 @@ "classes": null, "methods": null, "timeout": 30, - "coverage": 100 + "coverage": 50 } ] }, @@ -614,7 +625,7 @@ "classes": null, "methods": null, "timeout": 40, - "coverage": 100 + "coverage": 62 } ] } From 94d293b3b04531c17945bfc9018e1250af67a146 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 25 Jul 2023 17:04:09 +0300 Subject: [PATCH 43/44] Add generate-regression-suite param for cli --- utbot-cli-python/src/README.md | 8 ++++++++ .../python/PythonGenerateTestsCommand.kt | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/utbot-cli-python/src/README.md b/utbot-cli-python/src/README.md index 7a963bfcc2..e469b0c1e6 100644 --- a/utbot-cli-python/src/README.md +++ b/utbot-cli-python/src/README.md @@ -77,6 +77,14 @@ Run generated tests: - `--test-framework [pytest|Unittest]` Test framework to be used. + +- `--runtime-exception-behaviour [PASS|FAIL]` + + Expected behaviour for runtime exception. + +- `--generate-regression-suite` + + Generate regression test suite. Default error test suite generation only. ### `run_python` options diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index 07af86255a..dcd0f8163c 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -9,8 +9,10 @@ import mu.KotlinLogging import org.parsers.python.PythonParser import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.python.PythonMethodHeader import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.PythonTestSet import org.utbot.python.utils.RequirementsInstaller import org.utbot.python.TestFileInformation import org.utbot.python.code.PythonCode @@ -102,6 +104,9 @@ class PythonGenerateTestsCommand : CliktCommand( .choice("PASS", "FAIL") .default("FAIL") + private val generateRegressionSuite by option("--generate-regression-suite", help = "Generate regression test suite") + .flag(default = false) + private val testFramework: TestFramework get() = when (testFrameworkAsString) { @@ -247,8 +252,19 @@ class PythonGenerateTestsCommand : CliktCommand( val (mypyStorage, _) = processor.sourceCodeAnalyze() logger.info("Generating tests...") - val testSets = processor.testGenerate(mypyStorage) + var testSets = processor.testGenerate(mypyStorage) if (testSets.isEmpty()) return + if (!generateRegressionSuite) { + testSets = testSets.map { testSet -> + PythonTestSet( + testSet.method, + testSet.executions.filter { it.result is UtExecutionSuccess }, + testSet.errors, + testSet.mypyReport, + testSet.classId + ) + } + } logger.info("Saving tests...") val testCode = processor.testCodeGenerate(testSets) From 715c481b548159554b60e23ffa81816bf3e6bdca Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 26 Jul 2023 10:35:09 +0300 Subject: [PATCH 44/44] Invert regression suite param --- utbot-cli-python/src/README.md | 4 ++-- .../utbot/cli/language/python/PythonGenerateTestsCommand.kt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/utbot-cli-python/src/README.md b/utbot-cli-python/src/README.md index e469b0c1e6..67c689721d 100644 --- a/utbot-cli-python/src/README.md +++ b/utbot-cli-python/src/README.md @@ -82,9 +82,9 @@ Run generated tests: Expected behaviour for runtime exception. -- `--generate-regression-suite` +- `--do-not-generate-regression-suite` - Generate regression test suite. Default error test suite generation only. + Generate regression test suite or not. Regression suite and error suite generation is active by default. ### `run_python` options diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index dcd0f8163c..03361d44f7 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -104,7 +104,7 @@ class PythonGenerateTestsCommand : CliktCommand( .choice("PASS", "FAIL") .default("FAIL") - private val generateRegressionSuite by option("--generate-regression-suite", help = "Generate regression test suite") + private val doNotGenerateRegressionSuite by option("--do-not-generate-regression-suite", help = "Do not generate regression test suite") .flag(default = false) private val testFramework: TestFramework @@ -254,11 +254,11 @@ class PythonGenerateTestsCommand : CliktCommand( logger.info("Generating tests...") var testSets = processor.testGenerate(mypyStorage) if (testSets.isEmpty()) return - if (!generateRegressionSuite) { + if (doNotGenerateRegressionSuite) { testSets = testSets.map { testSet -> PythonTestSet( testSet.method, - testSet.executions.filter { it.result is UtExecutionSuccess }, + testSet.executions.filterNot { it.result is UtExecutionSuccess }, testSet.errors, testSet.mypyReport, testSet.classId