diff --git a/utbot-python/build.gradle.kts b/utbot-python/build.gradle.kts index 00de176540..a297f3e9fb 100644 --- a/utbot-python/build.gradle.kts +++ b/utbot-python/build.gradle.kts @@ -21,7 +21,7 @@ tasks { } dependencies { - api(project(":utbot-fuzzers")) + api(project(":utbot-fuzzing")) api(project(":utbot-framework")) api(project(":utbot-python-parser")) testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") diff --git a/utbot-python/samples/easy_samples/annotation_tests.py b/utbot-python/samples/easy_samples/annotation_tests.py index 55492d5053..d9412617e6 100644 --- a/utbot-python/samples/easy_samples/annotation_tests.py +++ b/utbot-python/samples/easy_samples/annotation_tests.py @@ -10,7 +10,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 diff --git a/utbot-python/samples/easy_samples/boruvka.py b/utbot-python/samples/easy_samples/boruvka.py index cb6f34205b..8f406b6012 100644 --- a/utbot-python/samples/easy_samples/boruvka.py +++ b/utbot-python/samples/easy_samples/boruvka.py @@ -58,7 +58,7 @@ def boruvka(self) -> None: component_size = [] mst_weight = 0 - minimum_weight_edge: list[Any] = [-1] * self.m_num_of_nodes + minimum_weight_edge: list = [-1] * self.m_num_of_nodes # A list of components (initialized to all of the nodes) for node in range(self.m_num_of_nodes): 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 0b48a58411..211cda6387 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt @@ -3,47 +3,27 @@ package org.utbot.python import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.Instruction -import org.utbot.framework.plugin.api.UtError -import org.utbot.framework.plugin.api.UtExecutionResult -import org.utbot.framework.plugin.api.UtExecutionSuccess -import org.utbot.framework.plugin.api.UtExplicitlyThrownException -import org.utbot.framework.plugin.api.UtModel -import org.utbot.fuzzer.FuzzedValue +import org.utbot.framework.plugin.api.* import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.fuzzing.Control import org.utbot.fuzzing.fuzz import org.utbot.fuzzing.utils.Trie -import org.utbot.python.code.MemoryDump -import org.utbot.python.code.toPythonTree -import org.utbot.python.evaluation.PythonCodeExecutor -import org.utbot.python.evaluation.PythonCodeExecutorImpl -import org.utbot.python.evaluation.PythonEvaluationError -import org.utbot.python.evaluation.PythonEvaluationSuccess -import org.utbot.python.evaluation.PythonEvaluationTimeout +import org.utbot.python.evaluation.* +import org.utbot.python.evaluation.serialiation.MemoryDump +import org.utbot.python.evaluation.serialiation.toPythonTree import org.utbot.python.framework.api.python.PythonTreeModel -import org.utbot.python.fuzzing.PythonFeedback -import org.utbot.python.fuzzing.PythonFuzzedConcreteValue -import org.utbot.python.fuzzing.PythonFuzzedValue -import org.utbot.python.fuzzing.PythonFuzzing -import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.framework.api.python.PythonTreeWrapper +import org.utbot.python.fuzzing.* import org.utbot.python.newtyping.PythonTypeStorage import org.utbot.python.newtyping.general.Type import org.utbot.python.newtyping.pythonModules import org.utbot.python.newtyping.pythonTypeRepresentation import org.utbot.python.utils.camelToSnakeCase import org.utbot.summary.fuzzer.names.TestSuggestedInfo +import java.net.ServerSocket private val logger = KotlinLogging.logger {} -sealed interface FuzzingExecutionFeedback -class ValidExecution(val utFuzzedExecution: UtFuzzedExecution): FuzzingExecutionFeedback -class InvalidExecution(val utError: UtError): FuzzingExecutionFeedback -class TypeErrorFeedback(val message: String) : FuzzingExecutionFeedback -class ArgumentsTypeErrorFeedback(val message: String) : FuzzingExecutionFeedback - class PythonEngine( private val methodUnderTest: PythonMethod, private val directoriesForSysPath: Set, @@ -55,6 +35,8 @@ class PythonEngine( private val pythonTypeStorage: PythonTypeStorage, ) { + private val cache = EvaluationCache() + private fun suggestExecutionName( description: PythonMethodDescription, executionResult: UtExecutionResult @@ -146,30 +128,14 @@ class PythonEngine( return ValidExecution(utFuzzedExecution) } - private fun constructEvaluationInput(arguments: List, additionalModules: List): PythonCodeExecutor { - val argumentValues = arguments.map { PythonTreeModel(it.tree, it.tree.type) } - - val (thisObject, modelList) = - if (methodUnderTest.hasThisArgument) - Pair(argumentValues[0], argumentValues.drop(1)) - else - Pair(null, argumentValues) - - val argumentModules = argumentValues - .flatMap { it.allContainingClassIds } - .map { it.moduleName } - .filterNot { it.startsWith(moduleToImport) } - val localAdditionalModules = (additionalModules + argumentModules + moduleToImport).toSet() - - return PythonCodeExecutorImpl( + private fun constructEvaluationInput(pythonWorker: PythonWorker): PythonCodeExecutor { + return PythonCodeSocketExecutor( methodUnderTest, - FunctionArguments(thisObject, methodUnderTest.thisObjectName, modelList, methodUnderTest.argumentsNames), - argumentValues.map { FuzzedValue(it) }, moduleToImport, - localAdditionalModules, pythonPath, directoriesForSysPath, timeoutForRun, + pythonWorker, ) } @@ -177,78 +143,140 @@ class PythonEngine( val additionalModules = parameters.flatMap { it.pythonModules() } val coveredLines = initialCoveredLines.toMutableSet() - suspend fun fuzzingResultHandler(description: PythonMethodDescription, arguments: List): PythonFeedback { - val codeExecutor = constructEvaluationInput(arguments, additionalModules) - return when (val evaluationResult = codeExecutor.run()) { - is PythonEvaluationError -> { - val utError = UtError( - "Error evaluation: ${evaluationResult.status}, ${evaluationResult.message}", - Throwable(evaluationResult.stackTrace.joinToString("\n")) - ) - logger.debug(evaluationResult.stackTrace.joinToString("\n")) - emit(InvalidExecution(utError)) - PythonFeedback(control = Control.PASS) - } + ServerSocket(0).use { serverSocket -> + logger.info { "Server port: ${serverSocket.localPort}" } + val manager = PythonWorkerManager( + serverSocket, + pythonPath, + until, + { constructEvaluationInput(it) }, + timeoutForRun.toInt() + ) + logger.info { "Executor manager was created successfully" } - is PythonEvaluationTimeout -> { - val utError = UtError(evaluationResult.message, Throwable()) - emit(InvalidExecution(utError)) - PythonFeedback(control = Control.PASS) - } + 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() - is PythonEvaluationSuccess -> { - val coveredInstructions = evaluationResult.coverage.coveredInstructions - coveredInstructions.forEach { coveredLines.add(it.lineNumber) } + 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 { + return when (val evaluationResult = manager.run(functionArguments, localAdditionalModules)) { + 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)) + } - val summary = arguments - .zip(methodUnderTest.arguments) - .mapNotNull { it.first.summary?.replace("%var%", it.second.name) } + is PythonEvaluationTimeout -> { + val utError = UtError(evaluationResult.message, Throwable()) + PythonExecutionResult(InvalidExecution(utError), PythonFeedback(control = Control.PASS)) + } - val hasThisObject = codeExecutor.methodArguments.thisObject != null + is PythonEvaluationSuccess -> { + val coveredInstructions = evaluationResult.coverage.coveredInstructions + coveredInstructions.forEach { coveredLines.add(it.lineNumber) } - when (val result = handleSuccessResult(parameters, evaluationResult, description, hasThisObject, summary)) { - is ValidExecution -> { - logger.debug { arguments } - val trieNode: Trie.Node = description.tracer.add(coveredInstructions) - emit(result) - PythonFeedback(control = Control.CONTINUE, result = trieNode) - } - is ArgumentsTypeErrorFeedback, is TypeErrorFeedback -> { - emit(result) - PythonFeedback(control = Control.PASS) - } - is InvalidExecution -> { - emit(result) - PythonFeedback(control = Control.CONTINUE) + val summary = arguments + .zip(methodUnderTest.arguments) + .mapNotNull { it.first.summary?.replace("%var%", it.second.name) } + + val hasThisObject = methodUnderTest.hasThisArgument + + when (val result = handleSuccessResult( + parameters, + evaluationResult, + description, + hasThisObject, + summary + )) { + is ValidExecution -> { + val trieNode: Trie.Node = description.tracer.add(coveredInstructions) + PythonExecutionResult( + result, + PythonFeedback(control = Control.CONTINUE, result = trieNode) + ) + } + + is ArgumentsTypeErrorFeedback, is TypeErrorFeedback -> { + PythonExecutionResult(result, PythonFeedback(control = Control.PASS)) + } + + is InvalidExecution -> { + PythonExecutionResult(result, PythonFeedback(control = Control.CONTINUE)) + } + } } } + } catch (_: TimeoutException) { + return null } } - } - val pmd = PythonMethodDescription( - methodUnderTest.name, - parameters, - fuzzedConcreteValues, - pythonTypeStorage, - Trie(Instruction::id) - ) + val pmd = PythonMethodDescription( + methodUnderTest.name, + parameters, + fuzzedConcreteValues, + pythonTypeStorage, + Trie(Instruction::id) + ) - if (parameters.isEmpty()) { - fuzzingResultHandler(pmd, emptyList()) - } else { - PythonFuzzing(pmd.pythonTypeStorage) { description, arguments -> - if (isCancelled()) { - logger.info { "Fuzzing process was interrupted" } - return@PythonFuzzing PythonFeedback(control = Control.STOP) - } - if (System.currentTimeMillis() >= until) { - logger.info { "Fuzzing process was interrupted by timeout" } - return@PythonFuzzing PythonFeedback(control = Control.STOP) - } + if (parameters.isEmpty()) { + fuzzingResultHandler(pmd, emptyList()) + manager.disconnect() + } else { + 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) + } - return@PythonFuzzing fuzzingResultHandler(description, arguments) - }.fuzz(pmd) + val pair = Pair(description, arguments.map { PythonTreeWrapper(it.tree) }) + val mem = cache.get(pair) + if (mem != null) { + logger.debug("Repeat in fuzzing") + emit(mem.fuzzingExecutionFeedback) + return@PythonFuzzing mem.fuzzingPlatformFeedback + } + val result = fuzzingResultHandler(description, arguments) + if (result == null) { // timeout + logger.info { "Fuzzing process was interrupted by timeout" } + manager.disconnect() + return@PythonFuzzing PythonFeedback(control = Control.STOP) + } + + cache.add(pair, result) + emit(result.fuzzingExecutionFeedback) + return@PythonFuzzing result.fuzzingPlatformFeedback + + }.fuzz(pmd) + } } } } \ No newline at end of file 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 03675aebaa..9215bff3c2 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt @@ -2,13 +2,11 @@ package org.utbot.python import kotlinx.coroutines.runBlocking import mu.KotlinLogging -import org.parsers.python.PythonParser import org.utbot.framework.minimization.minimizeExecutions import org.utbot.framework.plugin.api.UtError import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionSuccess -import org.utbot.python.code.PythonCode -import org.utbot.python.fuzzing.PythonFuzzedConcreteValue +import org.utbot.python.fuzzing.* import org.utbot.python.newtyping.* import org.utbot.python.newtyping.ast.visitor.Visitor import org.utbot.python.newtyping.ast.visitor.constants.ConstantCollector @@ -29,7 +27,9 @@ import java.io.File private val logger = KotlinLogging.logger {} -private const val COVERAGE_LIMIT = 100 +private const val COVERAGE_LIMIT = 150 +private const val ADDITIONAL_LIMIT = 5 +private const val INVALID_EXECUTION_LIMIT = 10 class PythonTestCaseGenerator( private val withMinimization: Boolean = true, @@ -47,32 +47,6 @@ class PythonTestCaseGenerator( private val storageForMypyMessages: MutableList = mutableListOf() - private fun findMethodByDescription(mypyStorage: MypyAnnotationStorage, method: PythonMethodHeader): 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 constructCollectors( mypyStorage: MypyAnnotationStorage, typeStorage: PythonTypeStorage, @@ -144,11 +118,10 @@ class PythonTestCaseGenerator( }.take(maxSubstitutions) } - fun generate(methodDescription: PythonMethodHeader, until: Long): PythonTestSet { + fun generate(method: PythonMethod, until: Long): PythonTestSet { storageForMypyMessages.clear() val typeStorage = PythonTypeStorage.get(mypyStorage) - val method = findMethodByDescription(mypyStorage, methodDescription) val (hintCollector, constantCollector) = constructCollectors(mypyStorage, typeStorage, method) val constants = constantCollector.result.map { (type, value) -> @@ -161,8 +134,10 @@ class PythonTestCaseGenerator( var missingLines: Set? = null val coveredLines = mutableSetOf() var generated = 0 + + var additionalLimit = ADDITIONAL_LIMIT val typeInferenceCancellation = - { isCancelled() || System.currentTimeMillis() >= until || missingLines?.size == 0 } + { isCancelled() || System.currentTimeMillis() >= until || additionalLimit <= 0 } logger.info("Start test generation for ${method.name}") substituteTypeParameters(method, typeStorage).forEach { newMethod -> @@ -190,13 +165,19 @@ class PythonTestCaseGenerator( PythonTypeStorage.get(mypyStorage) ) + var invalidExecutionLimit = INVALID_EXECUTION_LIMIT var coverageLimit = COVERAGE_LIMIT var coveredBefore = coveredLines.size var feedback: InferredTypeFeedback = SuccessFeedback - val fuzzerCancellation = - { typeInferenceCancellation() || coverageLimit == 0 } // || feedback is InvalidTypeFeedback } + val fuzzerCancellation = { + typeInferenceCancellation() + || coverageLimit == 0 + || additionalLimit == 0 + || invalidExecutionLimit == 0 + } + val startTime = System.currentTimeMillis() engine.fuzzing(args, fuzzerCancellation, until).collect { generated += 1 @@ -211,16 +192,22 @@ class PythonTestCaseGenerator( feedback = SuccessFeedback } is ArgumentsTypeErrorFeedback -> { + invalidExecutionLimit-- feedback = InvalidTypeFeedback } is TypeErrorFeedback -> { + invalidExecutionLimit-- feedback = InvalidTypeFeedback } } + if (missingLines?.size == 0) { + additionalLimit-- + } val coveredAfter = coveredLines.size if (coveredAfter == coveredBefore) { - coverageLimit -= 1 + coverageLimit-- } + logger.info { "Time ${System.currentTimeMillis() - startTime}: $generated, $missingLines" } coveredBefore = coveredAfter } feedback @@ -294,12 +281,12 @@ class PythonTestCaseGenerator( return@breaking } + algo.run(hintCollector.result, isCancelled, annotationHandler) + val existsAnnotation = method.definition.type if (existsAnnotation.arguments.all { it.pythonTypeName() != "typing.Any" }) { annotationHandler(existsAnnotation) } - - algo.run(hintCollector.result, isCancelled, annotationHandler) } } } 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 46f4a7433c..b35d36a809 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt @@ -2,22 +2,30 @@ package org.utbot.python import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import mu.KotlinLogging +import org.parsers.python.PythonParser 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.TimeoutException 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.RawPythonAnnotation import org.utbot.python.framework.api.python.util.pythonAnyClassId import org.utbot.python.framework.codegen.model.PythonCodeGenerator +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.readMypyAnnotationStorageAndInitialErrors import org.utbot.python.newtyping.mypy.setConfigFile import org.utbot.python.typing.MypyAnnotations @@ -29,6 +37,8 @@ import java.nio.file.Path import kotlin.io.path.Path import kotlin.io.path.pathString +private val logger = KotlinLogging.logger {} + object PythonTestGenerationProcessor { fun processTestGeneration( pythonPath: String, @@ -106,9 +116,10 @@ object PythonTestGenerationProcessor { ) val until = startTime + timeout - val tests = pythonMethods.mapIndexed { index, method -> + 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) } @@ -229,6 +240,37 @@ object PythonTestGenerationProcessor { } } + 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 + ) + } + enum class MissingRequirementsActionResult { INSTALLED, NOT_INSTALLED } 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 e0d3bbda9c..9914345deb 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt @@ -4,7 +4,7 @@ import org.parsers.python.ast.Block import org.utbot.framework.plugin.api.UtError import org.utbot.framework.plugin.api.UtExecution import org.utbot.python.framework.api.python.PythonClassId -import org.utbot.python.framework.api.python.PythonModel +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 @@ -61,10 +61,10 @@ data class PythonTestSet( ) data class FunctionArguments( - val thisObject: PythonModel?, + val thisObject: PythonTreeModel?, val thisObjectName: String?, - val arguments: List, + val arguments: List, val names: List, ) { - val allArguments: List = (listOf(thisObject) + arguments).filterNotNull() + val allArguments: List = (listOf(thisObject) + arguments).filterNotNull() } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/code/PythonObjectDeserializer.kt b/utbot-python/src/main/kotlin/org/utbot/python/code/PythonObjectDeserializer.kt deleted file mode 100644 index d840892a9c..0000000000 --- a/utbot-python/src/main/kotlin/org/utbot/python/code/PythonObjectDeserializer.kt +++ /dev/null @@ -1,135 +0,0 @@ -package org.utbot.python.code - -import com.squareup.moshi.Moshi -import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import org.utbot.python.framework.api.python.PythonClassId -import org.utbot.python.framework.api.python.PythonTree - -object PythonObjectDeserializer { - private val moshi = Moshi.Builder() - .add( - PolymorphicJsonAdapterFactory.of(MemoryObject::class.java, "strategy") - .withSubtype(ReprMemoryObject::class.java, "repr") - .withSubtype(ListMemoryObject::class.java, "list") - .withSubtype(DictMemoryObject::class.java, "dict") - .withSubtype(ReduceMemoryObject::class.java, "reduce") - ) - .addLast(KotlinJsonAdapterFactory()) - .build() - - private val jsonAdapter = moshi.adapter(MemoryDump::class.java) - - fun parseDumpedObjects(jsonWithDump: String): MemoryDump { - return jsonAdapter.fromJson(jsonWithDump) ?: error("Couldn't parse json dump") - } -} - -class MemoryDump( - private val objects: Map -) { - fun getById(id: String): MemoryObject { - return objects[id]!! - } - - fun getByValue(value: MemoryObject): String { - return objects.filter { it.value == value }.keys.first() - } -} - -sealed class MemoryObject( - val kind: String, - val comparable: Boolean, -) - -class ReprMemoryObject( - kind: String, - comparable: Boolean, - val value: String, -): MemoryObject(kind, comparable) - -class ListMemoryObject( - kind: String, - comparable: Boolean, - val items: List, -): MemoryObject(kind, comparable) - -class DictMemoryObject( - kind: String, - comparable: Boolean, - val items: Map, -): MemoryObject(kind, comparable) - -class ReduceMemoryObject( - kind: String, - comparable: Boolean, - val constructor: String, - val args: String, - val state: String, - val listitems: String, - val dictitems: String -): MemoryObject(kind, comparable) - -fun MemoryObject.toPythonTree(memoryDump: MemoryDump): PythonTree.PythonTreeNode { - val obj = when(this) { - is ReprMemoryObject -> { - PythonTree.PrimitiveNode(PythonClassId(this.kind), this.value) - } - is DictMemoryObject -> { - PythonTree.DictNode( - items.entries.associate { - memoryDump.getById(it.key).toPythonTree(memoryDump) to memoryDump.getById(it.value).toPythonTree(memoryDump) - }.toMutableMap() - ) - } - is ListMemoryObject -> { - val elementsMap = items.withIndex().associate { - it.index to memoryDump.getById(it.value).toPythonTree(memoryDump) - }.toMutableMap() - when (this.kind) { - "builtins.tuple" -> { - PythonTree.TupleNode(elementsMap) - } - "builtins.set" -> { - PythonTree.SetNode(elementsMap.values.toMutableSet()) - } - else -> { - PythonTree.ListNode(elementsMap) - } - } - } - is ReduceMemoryObject -> { - val stateObjs = memoryDump.getById(state) as DictMemoryObject - val arguments = memoryDump.getById(args) as ListMemoryObject - val listitemsObjs = memoryDump.getById(listitems) as ListMemoryObject - val dictitemsObjs = memoryDump.getById(dictitems) as DictMemoryObject - PythonTree.ReduceNode( - memoryDump.getByValue(this).toLong(), - PythonClassId(this.kind), - PythonClassId(this.constructor), - arguments.items.map { memoryDump.getById(it).toPythonTree(memoryDump) }, - stateObjs.items.entries.associate { - (memoryDump.getById(it.key).toPythonTree(memoryDump) as PythonTree.PrimitiveNode).repr.drop(1).dropLast(1) to - memoryDump.getById(it.value).toPythonTree(memoryDump) - }.toMutableMap(), - listitemsObjs.items.map { memoryDump.getById(it).toPythonTree(memoryDump) }, - dictitemsObjs.items.entries.associate { - memoryDump.getById(it.key).toPythonTree(memoryDump) to - memoryDump.getById(it.value).toPythonTree(memoryDump) - }, - ) - } - } - obj.comparable = this.comparable - return obj -} - -fun main() { - val dump = """ - {"objects": {"140598295296000": {"strategy": "list", "kind": "builtins.list", "comparable": false, "items": ["140598416769264", "140598416769296", "140598416769328", "140598295298816", "140598427938368", "140598374175968"]}, "140598416769264": {"strategy": "repr", "kind": "builtins.int", "comparable": true, "value": "1"}, "140598416769296": {"strategy": "repr", "kind": "builtins.int", "comparable": true, "value": "2"}, "140598416769328": {"strategy": "repr", "kind": "builtins.int", "comparable": true, "value": "3"}, "140598295298816": {"strategy": "dict", "kind": "builtins.dict", "comparable": true, "items": {"140598416769264": "140598416769264"}}, "140598427938368": {"strategy": "repr", "kind": "types.NoneType", "comparable": true, "value": "None"}, "140598372733504": {"strategy": "list", "kind": "builtins.tuple", "comparable": true, "items": ["94206620040656", "140598427931232", "140598427938368"]}, "94206620040656": {"strategy": "repr", "kind": "builtins.type", "comparable": true, "value": "deep_serialization.example.B"}, "140598427931232": {"strategy": "repr", "kind": "builtins.type", "comparable": true, "value": "builtins.object"}, "140598374175968": {"strategy": "reduce", "kind": "deep_serialization.example.B", "comparable": false, "constructor": "copyreg._reconstructor", "args": "140598372733504", "state": "140598295238656", "listitems": "140598295487936", "dictitems": "140598295486336"}, "140598295238656": {"strategy": "dict", "kind": "builtins.dict", "comparable": true, "items": {"140598386103280": "140598416769264", "140598395465264": "140598416769296", "140598372712880": "140598416769328", "140598415768816": "140598374177584"}}, "140598386103280": {"strategy": "repr", "kind": "builtins.str", "comparable": true, "value": "\'b1\'"}, "140598395465264": {"strategy": "repr", "kind": "builtins.str", "comparable": true, "value": "\'b2\'"}, "140598372712880": {"strategy": "repr", "kind": "builtins.str", "comparable": true, "value": "\'b3\'"}, "140598415768816": {"strategy": "repr", "kind": "builtins.str", "comparable": true, "value": "\'time\'"}, "140598374184560": {"strategy": "list", "kind": "builtins.tuple", "comparable": true, "items": ["140598295162016"]}, "140598295162016": {"strategy": "repr", "kind": "builtins.bytes", "comparable": true, "value": "b\'\\\\x07\\\\xe7\\\\x01\\\\x13\\\\x11\\\\x01\\\\x1c\\\\x0e\\\\x921\'"}, "140598374177584": {"strategy": "reduce", "kind": "datetime.datetime", "comparable": true, "constructor": "datetime.datetime", "args": "140598374184560", "state": "140598295312768", "listitems": "140598295488000", "dictitems": "140598295485760"}, "140598295312768": {"strategy": "dict", "kind": "builtins.dict", "comparable": true, "items": {}}, "140598295488000": {"strategy": "list", "kind": "builtins.list", "comparable": true, "items": []}, "140598295485760": {"strategy": "dict", "kind": "builtins.dict", "comparable": true, "items": {}}, "140598295487936": {"strategy": "list", "kind": "builtins.list", "comparable": true, "items": []}, "140598295486336": {"strategy": "dict", "kind": "builtins.dict", "comparable": true, "items": {}}}} -""".trimIndent() - - val load = PythonObjectDeserializer.parseDumpedObjects(dump) - load.getById("140598295296000").toPythonTree(load) -} - diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt index ecb90c4096..faa5f516a9 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt @@ -1,22 +1,23 @@ package org.utbot.python.evaluation import org.utbot.framework.plugin.api.Coverage -import org.utbot.fuzzer.FuzzedValue import org.utbot.python.FunctionArguments import org.utbot.python.PythonMethod -import org.utbot.python.code.MemoryDump +import org.utbot.python.evaluation.serialiation.MemoryDump interface PythonCodeExecutor { val method: PythonMethod - val methodArguments: FunctionArguments - val fuzzedValues: List val moduleToImport: String - val additionalModulesToImport: Set val pythonPath: String val syspathDirectories: Set val executionTimeout: Long - fun run(): PythonEvaluationResult + fun run( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set + ): PythonEvaluationResult + + fun stop() } sealed class PythonEvaluationResult diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/EvaluationCache.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/EvaluationCache.kt new file mode 100644 index 0000000000..8890757b06 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/EvaluationCache.kt @@ -0,0 +1,26 @@ +package org.utbot.python.evaluation + +import mu.KotlinLogging +import org.utbot.python.framework.api.python.PythonTreeWrapper +import org.utbot.python.fuzzing.PythonExecutionResult +import org.utbot.python.fuzzing.PythonMethodDescription + +private const val MAX_CACHE_SIZE = 200 + +class EvaluationCache { + private val cache = mutableMapOf>, PythonExecutionResult>() + + fun add(key: Pair>, result: PythonExecutionResult) { + cache[key] = result + if (cache.size > MAX_CACHE_SIZE) { + val elemToDelete = cache.keys.maxBy { (_, args) -> + args.fold(0) { acc, arg -> arg.commonDiversity(acc) } + } + cache.remove(elemToDelete) + } + } + + fun get(key: Pair>): PythonExecutionResult? { + return cache[key] + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeExecutorImpl.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeExecutorImpl.kt index b56812fe90..1137bb1b15 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeExecutorImpl.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeExecutorImpl.kt @@ -2,10 +2,13 @@ package org.utbot.python.evaluation import org.utbot.framework.plugin.api.Coverage import org.utbot.framework.plugin.api.Instruction -import org.utbot.fuzzer.FuzzedValue import org.utbot.python.FunctionArguments import org.utbot.python.PythonMethod import org.utbot.python.code.PythonCodeGenerator +import org.utbot.python.evaluation.serialiation.ExecutionResultDeserializer +import org.utbot.python.evaluation.serialiation.FailExecution +import org.utbot.python.evaluation.serialiation.SuccessExecution +import org.utbot.python.framework.api.python.PythonTreeModel import org.utbot.python.framework.api.python.util.pythonAnyClassId import org.utbot.python.newtyping.pythonTypeRepresentation import org.utbot.python.utils.TemporaryFileManager @@ -20,20 +23,29 @@ data class EvaluationFiles( class PythonCodeExecutorImpl( override val method: PythonMethod, - override val methodArguments: FunctionArguments, - override val fuzzedValues: List, override val moduleToImport: String, - override val additionalModulesToImport: Set, override val pythonPath: String, override val syspathDirectories: Set, override val executionTimeout: Long, ) : PythonCodeExecutor { - override fun run(): PythonEvaluationResult { - val evaluationFiles = generateExecutionCode() + + override fun run( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set, + ): PythonEvaluationResult { + val evaluationFiles = generateExecutionCode( + additionalModulesToImport, + fuzzedValues.allArguments, + ) return getEvaluationResult(evaluationFiles) } - private fun generateExecutionCode(): EvaluationFiles { + override fun stop() {} + + private fun generateExecutionCode( + additionalModulesToImport: Set, + methodArguments: List, + ): EvaluationFiles { val fileForOutput = TemporaryFileManager.assignTemporaryFile( tag = "out_" + method.name + ".py", addToCleaner = false @@ -44,7 +56,7 @@ class PythonCodeExecutorImpl( ) val runCode = PythonCodeGenerator.generateRunFunctionCode( method, - methodArguments.allArguments, + methodArguments, syspathDirectories, moduleToImport, additionalModulesToImport, 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 new file mode 100644 index 0000000000..dc7bd8219f --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt @@ -0,0 +1,143 @@ +package org.utbot.python.evaluation + +import mu.KotlinLogging +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.Instruction +import org.utbot.python.FunctionArguments +import org.utbot.python.PythonMethod +import org.utbot.python.evaluation.serialiation.ExecutionRequest +import org.utbot.python.evaluation.serialiation.ExecutionRequestSerializer +import org.utbot.python.evaluation.serialiation.ExecutionResultDeserializer +import org.utbot.python.evaluation.serialiation.FailExecution +import org.utbot.python.evaluation.serialiation.PythonExecutionResult +import org.utbot.python.evaluation.serialiation.SuccessExecution +import org.utbot.python.evaluation.serialiation.serializeObjects +import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.newtyping.pythonTypeName +import org.utbot.python.newtyping.pythonTypeRepresentation +import java.net.SocketException + +private val logger = KotlinLogging.logger {} + +class PythonCodeSocketExecutor( + override val method: PythonMethod, + override val moduleToImport: String, + override val pythonPath: String, + override val syspathDirectories: Set, + override val executionTimeout: Long, +) : PythonCodeExecutor { + private lateinit var pythonWorker: PythonWorker + + constructor( + method: PythonMethod, + moduleToImport: String, + pythonPath: String, + syspathDirectories: Set, + executionTimeout: Long, + pythonWorker: PythonWorker + ) : this( + method, + moduleToImport, + pythonPath, + syspathDirectories, + executionTimeout + ) { + this.pythonWorker = pythonWorker + } + + override fun run( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set + ): PythonEvaluationResult { + val (arguments, memory) = serializeObjects(fuzzedValues.allArguments.map { it.tree }) + + val containingClass = method.containingPythonClass + val functionTextName = + if (containingClass == null) + method.name + else { + val fullname = "${containingClass.pythonTypeName()}.${method.name}" + fullname.drop(moduleToImport.length).removePrefix(".") + } + + val request = ExecutionRequest( + functionTextName, + moduleToImport, + additionalModulesToImport.toList(), + syspathDirectories.toList(), + arguments, + memory, + method.moduleFilename, + ) + val message = ExecutionRequestSerializer.serializeRequest(request) ?: error("Cannot serialize request to python executor") + try { + pythonWorker.sendData(message) + } catch (_: SocketException) { + logger.info { "Send data error" } + return parseExecutionResult(FailExecution("Send data error")) + } + val response = pythonWorker.receiveMessage() + val executionResult = if (response == null) { + logger.info { "Response error" } + FailExecution("Execution result error") + } else { + ExecutionResultDeserializer.parseExecutionResult(response) + ?: error("Cannot parse execution result: $response") + } + return parseExecutionResult(executionResult) + } + + private fun parseExecutionResult(executionResult: PythonExecutionResult): PythonEvaluationResult { + val parsingException = PythonEvaluationError( + -1, + "Incorrect format of output", + emptyList() + ) + return when (executionResult) { + is SuccessExecution -> { + val stateBefore = ExecutionResultDeserializer.parseMemoryDump(executionResult.stateBefore) ?: return parsingException + val stateAfter = ExecutionResultDeserializer.parseMemoryDump(executionResult.stateAfter) ?: return parsingException + PythonEvaluationSuccess( + executionResult.isException, + calculateCoverage(executionResult.statements, executionResult.missedStatements), + stateBefore, + stateAfter, + executionResult.argsIds + executionResult.kwargsIds, + executionResult.resultId, + ) + } + is FailExecution -> PythonEvaluationError( + -2, + "Fail Execution", + executionResult.exception.split(System.lineSeparator()), + ) + } + } + + private fun calculateCoverage(statements: List, missedStatements: List): Coverage { + val covered = statements.filter { it !in missedStatements } + return Coverage( + coveredInstructions=covered.map { + Instruction( + method.containingPythonClass?.pythonTypeRepresentation() ?: pythonAnyClassId.name, + method.methodSignature(), + it, + it.toLong() + ) + }, + instructionsCount = statements.size.toLong(), + missedInstructions = missedStatements.map { + Instruction( + method.containingPythonClass?.pythonTypeRepresentation() ?: pythonAnyClassId.name, + method.methodSignature(), + it, + it.toLong() + ) + } + ) + } + + override fun stop() { + pythonWorker.stopServer() + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorker.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorker.kt new file mode 100644 index 0000000000..a38d3f2acc --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorker.kt @@ -0,0 +1,52 @@ +package org.utbot.python.evaluation + +import java.io.BufferedReader +import java.io.BufferedWriter +import java.io.InputStreamReader +import java.io.OutputStreamWriter +import java.net.Socket + +class PythonWorker( + private val clientSocket: Socket +) { + private val outStream = BufferedWriter(OutputStreamWriter(clientSocket.getOutputStream(), "UTF8")) + private val inStream = BufferedReader(InputStreamReader(clientSocket.getInputStream(), "UTF8")) + + fun stop() { + outStream.write("STOP") + outStream.flush() + } + + fun sendData(msg: String) { + outStream.write("DATA") + + val size = msg.encodeToByteArray().size + outStream.write(size.toString().padStart(16)) + outStream.flush() + + outStream.write(msg) + outStream.flush() + } + + fun receiveMessage(): String? { + val lengthLine = inStream.readLine() ?: return null + val length = lengthLine.toInt() + val buffer = CharArray(length) + val bytesRead = inStream.read(buffer) + if (bytesRead < length) // TODO: maybe we should add more time for reading? + return null + return String(buffer) + } + + private fun stopConnection() { + inStream.close() + outStream.close() + clientSocket.close() + } + + fun stopServer() { + stop() + stopConnection() + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..fd98864bf8 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt @@ -0,0 +1,88 @@ +package org.utbot.python.evaluation + +import mu.KotlinLogging +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.python.FunctionArguments +import org.utbot.python.utils.TemporaryFileManager +import org.utbot.python.utils.getResult +import org.utbot.python.utils.startProcess +import java.lang.Long.max +import java.net.ServerSocket +import java.net.Socket +import java.net.SocketTimeoutException + +private val logger = KotlinLogging.logger {} + +class PythonWorkerManager( + private val serverSocket: ServerSocket, + val pythonPath: String, + val until: Long, + val pythonCodeExecutorConstructor: (PythonWorker) -> PythonCodeExecutor, + private val timeoutForRun: Int +) { + private val logfile = TemporaryFileManager.createTemporaryFile("","utbot_executor.log", "log", true) + + var timeout: Long = 0 + lateinit var process: Process + lateinit var workerSocket: Socket + lateinit var codeExecutor: PythonCodeExecutor + + init { + connect() + } + + fun connect() { + val processStartTime = System.currentTimeMillis() + process = startProcess(listOf( + pythonPath, + "-m", "utbot_executor", + "localhost", + serverSocket.localPort.toString(), + "--logfile", logfile.absolutePath, + //"--loglevel", "DEBUG", + )) + timeout = max(until - processStartTime, 0) + workerSocket = try { + serverSocket.soTimeout = timeout.toInt() + serverSocket.accept() + } catch (e: SocketTimeoutException) { + timeout = max(until - processStartTime, 0) + val result = getResult(process, timeout) + logger.info("utbot_executor exit value: ${result.exitValue}. stderr: ${result.stderr}.") + process.destroy() + throw TimeoutException("Worker not connected") + } + logger.debug { "Worker connected successfully" } + + workerSocket.soTimeout = timeoutForRun // TODO: maybe +eps for serialization/deserialization? + val pythonWorker = PythonWorker(workerSocket) + codeExecutor = pythonCodeExecutorConstructor(pythonWorker) + } + + fun disconnect() { + workerSocket.close() + process.destroy() + } + + fun reconnect() { + disconnect() + connect() + } + + fun run( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set + ): PythonEvaluationResult { + val evaluationResult = try { + codeExecutor.run(fuzzedValues, additionalModulesToImport) + } catch (_: SocketTimeoutException) { + logger.debug { "Socket timeout" } + reconnect() + PythonEvaluationTimeout() + } + if (evaluationResult is PythonEvaluationError) { + reconnect() + } + return evaluationResult + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionRequestSerializer.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionRequestSerializer.kt new file mode 100644 index 0000000000..33d2251107 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionRequestSerializer.kt @@ -0,0 +1,26 @@ +package org.utbot.python.evaluation.serialiation + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory + +object ExecutionRequestSerializer { + private val moshi = Moshi.Builder() + .addLast(KotlinJsonAdapterFactory()) + .build() + + private val jsonAdapter = moshi.adapter(ExecutionRequest::class.java) + + fun serializeRequest(request: ExecutionRequest): String? { + return jsonAdapter.toJson(request) + } +} + +data class ExecutionRequest( + val functionName: String, + val functionModule: String, + val imports: List, + val syspaths: List, + val argumentsIds: List, + val serializedMemory: String, + val filepath: String, +) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/ExecutionResultDeserializer.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionResultDeserializer.kt similarity index 83% rename from utbot-python/src/main/kotlin/org/utbot/python/evaluation/ExecutionResultDeserializer.kt rename to utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionResultDeserializer.kt index f230f7d314..3ffdbe7bce 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/ExecutionResultDeserializer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionResultDeserializer.kt @@ -1,14 +1,9 @@ -package org.utbot.python.evaluation +package org.utbot.python.evaluation.serialiation +import com.squareup.moshi.JsonEncodingException import com.squareup.moshi.Moshi import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import org.utbot.python.code.DictMemoryObject -import org.utbot.python.code.ListMemoryObject -import org.utbot.python.code.MemoryDump -import org.utbot.python.code.MemoryObject -import org.utbot.python.code.ReduceMemoryObject -import org.utbot.python.code.ReprMemoryObject object ExecutionResultDeserializer { private val moshi = Moshi.Builder() @@ -31,7 +26,12 @@ object ExecutionResultDeserializer { private val jsonAdapterMemoryDump = moshi.adapter(MemoryDump::class.java) fun parseExecutionResult(content: String): PythonExecutionResult? { - return jsonAdapter.fromJson(content) + try { + return jsonAdapter.fromJson(content) ?: error("Parsing error with: $content") + } catch (_: JsonEncodingException) { + println(content) + } + return null } fun parseMemoryDump(content: String): MemoryDump? { diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt new file mode 100644 index 0000000000..48ce151a80 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt @@ -0,0 +1,437 @@ +package org.utbot.python.evaluation.serialiation + +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonStrClassId + +object PythonObjectParser { + private val moshi = Moshi.Builder() + .add( + PolymorphicJsonAdapterFactory.of(MemoryObject::class.java, "strategy") + .withSubtype(ReprMemoryObject::class.java, "repr") + .withSubtype(ListMemoryObject::class.java, "list") + .withSubtype(DictMemoryObject::class.java, "dict") + .withSubtype(ReduceMemoryObject::class.java, "reduce") + ) + .addLast(KotlinJsonAdapterFactory()) + .build() + + private val jsonAdapter = moshi.adapter(MemoryDump::class.java) + + fun parseDumpedObjects(jsonWithDump: String): MemoryDump { + return jsonAdapter.fromJson(jsonWithDump) ?: error("Couldn't parse json dump") + } + + fun serializeMemory(memory: MemoryDump): String { + return jsonAdapter.toJson(memory) ?: error("Couldn't serialize dump to json") + } +} + +class MemoryDump( + private val objects: MutableMap +) { + fun getById(id: String): MemoryObject { + return objects[id]!! + } + + fun addObject(value: MemoryObject) { + objects[value.id] = value + } +} + +sealed class MemoryObject( + val id: String, + val kind: String, + val comparable: Boolean, +) + +class ReprMemoryObject( + id: String, + kind: String, + comparable: Boolean, + val value: String, +): MemoryObject(id, kind, comparable) + +class ListMemoryObject( + id: String, + kind: String, + comparable: Boolean, + val items: List, +): MemoryObject(id, kind, comparable) + +class DictMemoryObject( + id: String, + kind: String, + comparable: Boolean, + val items: Map, +): MemoryObject(id, kind, comparable) + +class ReduceMemoryObject( + id: String, + kind: String, + comparable: Boolean, + val constructor: String, + val args: String, + val state: String, + val listitems: String, + val dictitems: String +): MemoryObject(id, kind, comparable) + +fun PythonTree.PythonTreeNode.toMemoryObject(memoryDump: MemoryDump): String { + val obj = when(this) { + is PythonTree.PrimitiveNode -> { + ReprMemoryObject(this.id.toString(), this.type.name, this.comparable, this.repr) + } + is PythonTree.ListNode -> { + val items = this.items.entries + .sortedBy { it.key } + .map { it.value.toMemoryObject(memoryDump) } + ListMemoryObject(this.id.toString(), this.type.name, this.comparable, items) + } + is PythonTree.TupleNode -> { + val items = this.items.entries + .sortedBy { it.key } + .map { it.value.toMemoryObject(memoryDump) } + ListMemoryObject(this.id.toString(), this.type.name, this.comparable, items) + } + is PythonTree.SetNode -> { + val items = this.items.map { it.toMemoryObject(memoryDump) } + ListMemoryObject(this.id.toString(), this.type.name, this.comparable, items) + } + is PythonTree.DictNode -> { + val items = this.items.entries + .associate { + it.key.toMemoryObject(memoryDump) to it.value.toMemoryObject(memoryDump) + } + DictMemoryObject(this.id.toString(), this.type.name, this.comparable, items) + } + is PythonTree.ReduceNode -> { + val stateObjId = PythonTree.DictNode(this.state.entries.associate { PythonTree.PrimitiveNode(pythonStrClassId, it.key) to it.value }.toMutableMap()) + val argsIds = PythonTree.ListNode(this.args.withIndex().associate { it.index to it.value }.toMutableMap()) + val listItemsIds = PythonTree.ListNode(this.listitems.withIndex().associate { it.index to it.value }.toMutableMap()) + val dictItemsIds = PythonTree.DictNode(this.dictitems.toMutableMap()) + ReduceMemoryObject( + this.id.toString(), + this.type.name, + this.comparable, + this.constructor.name, + argsIds.toMemoryObject(memoryDump), + stateObjId.toMemoryObject(memoryDump), + listItemsIds.toMemoryObject(memoryDump), + dictItemsIds.toMemoryObject(memoryDump), + ) + } + else -> { + error("Invalid PythonTree.PythonTreeNode $this") + } + } + memoryDump.addObject(obj) + return obj.id +} + +fun MemoryObject.toPythonTree(memoryDump: MemoryDump): PythonTree.PythonTreeNode { + val obj = when(this) { + is ReprMemoryObject -> { + PythonTree.PrimitiveNode( + this.id.toLong(), + PythonClassId(this.kind), + this.value + ) + } + is DictMemoryObject -> { + PythonTree.DictNode( + this.id.toLong(), + items.entries.associate { + memoryDump.getById(it.key).toPythonTree(memoryDump) to memoryDump.getById(it.value).toPythonTree(memoryDump) + }.toMutableMap() + ) + } + is ListMemoryObject -> { + val elementsMap = items.withIndex().associate { + it.index to memoryDump.getById(it.value).toPythonTree(memoryDump) + }.toMutableMap() + when (this.kind) { + "builtins.tuple" -> { + PythonTree.TupleNode(this.id.toLong(), elementsMap) + } + "builtins.set" -> { + PythonTree.SetNode(this.id.toLong(), elementsMap.values.toMutableSet()) + } + else -> { + PythonTree.ListNode(this.id.toLong(), elementsMap) + } + } + } + is ReduceMemoryObject -> { + val stateObjs = memoryDump.getById(state) as DictMemoryObject + val arguments = memoryDump.getById(args) as ListMemoryObject + val listitemsObjs = memoryDump.getById(listitems) as ListMemoryObject + val dictitemsObjs = memoryDump.getById(dictitems) as DictMemoryObject + PythonTree.ReduceNode( + this.id.toLong(), + PythonClassId(this.kind), + PythonClassId(this.constructor), + arguments.items.map { memoryDump.getById(it).toPythonTree(memoryDump) }, + stateObjs.items.entries.associate { + (memoryDump.getById(it.key).toPythonTree(memoryDump) as PythonTree.PrimitiveNode).repr.drop(1).dropLast(1) to + memoryDump.getById(it.value).toPythonTree(memoryDump) + }.toMutableMap(), + listitemsObjs.items.map { memoryDump.getById(it).toPythonTree(memoryDump) }, + dictitemsObjs.items.entries.associate { + memoryDump.getById(it.key).toPythonTree(memoryDump) to + memoryDump.getById(it.value).toPythonTree(memoryDump) + }, + ) + } + } + obj.comparable = this.comparable + return obj +} + +fun serializeObjects(objs: List): Pair, String> { + val memoryDump = MemoryDump(emptyMap().toMutableMap()) + val ids = objs.map { it.toMemoryObject(memoryDump) } + return Pair(ids, PythonObjectParser.serializeMemory(memoryDump)) +} + +fun main() { + val dump = """ + { + "objects": { + "140239390887040": { + "strategy": "list", + "id": "140239390887040", + "kind": "builtins.list", + "comparable": false, + "items": [ + "140239394832624", + "140239394832656", + "140239392627184", + "140239394012784", + "140239392795520", + "140239406001728", + "140239392839840", + "140239390894848" + ] + }, + "140239394832624": { + "strategy": "repr", + "id": "140239394832624", + "kind": "builtins.int", + "comparable": true, + "value": "1" + }, + "140239394832656": { + "strategy": "repr", + "id": "140239394832656", + "kind": "builtins.int", + "comparable": true, + "value": "2" + }, + "140239392627184": { + "strategy": "repr", + "id": "140239392627184", + "kind": "builtins.float", + "comparable": true, + "value": "float('inf')" + }, + "140239394012784": { + "strategy": "repr", + "id": "140239394012784", + "kind": "builtins.str", + "comparable": true, + "value": "'abc'" + }, + "140239392795520": { + "strategy": "dict", + "id": "140239392795520", + "kind": "builtins.dict", + "comparable": true, + "items": { + "140239394832624": "140239394832624" + } + }, + "140239406001728": { + "strategy": "repr", + "id": "140239406001728", + "kind": "types.NoneType", + "comparable": true, + "value": "None" + }, + "140239391427840": { + "strategy": "list", + "id": "140239391427840", + "kind": "builtins.tuple", + "comparable": true, + "items": [ + "94246249326576", + "140239405994592", + "140239406001728" + ] + }, + "94246249326576": { + "strategy": "repr", + "id": "94246249326576", + "kind": "builtins.type", + "comparable": true, + "value": "deep_serialization.example.B" + }, + "140239405994592": { + "strategy": "repr", + "id": "140239405994592", + "kind": "builtins.type", + "comparable": true, + "value": "builtins.object" + }, + "140239392839840": { + "strategy": "reduce", + "id": "140239392839840", + "kind": "deep_serialization.example.B", + "comparable": false, + "constructor": "copyreg._reconstructor", + "args": "140239391427840", + "state": "140239392795712", + "listitems": "140239391672832", + "dictitems": "140239391673280" + }, + "140239392795712": { + "strategy": "dict", + "id": "140239392795712", + "kind": "builtins.dict", + "comparable": true, + "items": { + "140239392797168": "140239394832624", + "140239392797232": "140239394832656", + "140239392797296": "140239394832688", + "140239393831920": "140239392849760" + } + }, + "140239392797168": { + "strategy": "repr", + "id": "140239392797168", + "kind": "builtins.str", + "comparable": true, + "value": "'b1'" + }, + "140239392797232": { + "strategy": "repr", + "id": "140239392797232", + "kind": "builtins.str", + "comparable": true, + "value": "'b2'" + }, + "140239392797296": { + "strategy": "repr", + "id": "140239392797296", + "kind": "builtins.str", + "comparable": true, + "value": "'b3'" + }, + "140239394832688": { + "strategy": "repr", + "id": "140239394832688", + "kind": "builtins.int", + "comparable": true, + "value": "3" + }, + "140239393831920": { + "strategy": "repr", + "id": "140239393831920", + "kind": "builtins.str", + "comparable": true, + "value": "'time'" + }, + "140239394159488": { + "strategy": "list", + "id": "140239394159488", + "kind": "builtins.tuple", + "comparable": true, + "items": [ + "140239391514208" + ] + }, + "140239391514208": { + "strategy": "repr", + "id": "140239391514208", + "kind": "builtins.bytes", + "comparable": true, + "value": "b'\\x07\\xe7\\x02\\r\\x0c(3\\x06\\x1eA'" + }, + "140239392849760": { + "strategy": "reduce", + "id": "140239392849760", + "kind": "datetime.datetime", + "comparable": true, + "constructor": "datetime.datetime", + "args": "140239394159488", + "state": "140239391671232", + "listitems": "140239391671872", + "dictitems": "140239391672448" + }, + "140239391671232": { + "strategy": "dict", + "id": "140239391671232", + "kind": "builtins.dict", + "comparable": true, + "items": {} + }, + "140239391671872": { + "strategy": "list", + "id": "140239391671872", + "kind": "builtins.list", + "comparable": true, + "items": [] + }, + "140239391672448": { + "strategy": "dict", + "id": "140239391672448", + "kind": "builtins.dict", + "comparable": true, + "items": {} + }, + "140239391672832": { + "strategy": "list", + "id": "140239391672832", + "kind": "builtins.list", + "comparable": true, + "items": [] + }, + "140239391673280": { + "strategy": "dict", + "id": "140239391673280", + "kind": "builtins.dict", + "comparable": true, + "items": {} + }, + "140239390894848": { + "strategy": "list", + "id": "140239390894848", + "kind": "builtins.list", + "comparable": true, + "items": [ + "140239392797552" + ] + }, + "140239392797552": { + "strategy": "repr", + "id": "140239392797552", + "kind": "builtins.str", + "comparable": true, + "value": "'Alex'" + } + } + } +""".trimIndent() + + val load = PythonObjectParser.parseDumpedObjects(dump) + val obj = load.getById("140239390887040").toPythonTree(load) + val newMemory = MemoryDump(emptyMap().toMutableMap()) + val newId = obj.toMemoryObject(newMemory) + val newDump = PythonObjectParser.serializeMemory(newMemory) + val newLoad = PythonObjectParser.parseDumpedObjects(newDump) + val newObj = newLoad.getById(newId).toPythonTree(newLoad) +} + diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonTree.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonTree.kt index 3f793ae8f3..e07015daab 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonTree.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonTree.kt @@ -5,18 +5,40 @@ import org.utbot.python.framework.api.python.util.pythonFloatClassId import org.utbot.python.framework.api.python.util.pythonIntClassId import org.utbot.python.framework.api.python.util.pythonNoneClassId import org.utbot.python.framework.api.python.util.pythonStrClassId +import org.utbot.python.framework.api.python.util.toPythonRepr import org.utbot.python.newtyping.general.Type import org.utbot.python.newtyping.pythonTypeName import java.math.BigDecimal import java.math.BigInteger +import java.util.* +import java.util.concurrent.atomic.AtomicLong + object PythonTree { + fun isRecursiveObject(tree: PythonTreeNode): Boolean { + return isRecursiveObjectDFS(tree, mutableSetOf()) + } + + private fun isRecursiveObjectDFS(tree: PythonTreeNode, visited: MutableSet): Boolean { + if (visited.contains(tree)) + return true + visited.add(tree) + return tree.children.any { isRecursiveObjectDFS(it, visited) } + } + open class PythonTreeNode( + val id: Long, val type: PythonClassId, - var comparable: Boolean = true + var comparable: Boolean = true, ) { + constructor(type: PythonClassId, comparable: Boolean = true) : this(PythonIdGenerator.createId(), type, comparable) + open val children: List = emptyList() + override fun toString(): String { + return type.name + children.toString() + } + open fun typeEquals(other: Any?): Boolean { return if (other is PythonTreeNode) type == other.type && comparable && other.comparable @@ -28,38 +50,65 @@ object PythonTree { if (other !is PythonTreeNode) { return false } - return type == other.type + return id == other.id } override fun hashCode(): Int { - var result = type.hashCode() - result = 31 * result + comparable.hashCode() - result = 31 * result + children.hashCode() - return result + return id.hashCode() + } + + open fun softEquals(other: PythonTreeNode): Boolean { // must be called only from PythonTreeWrapper! + return type == other.type && children == other.children + } + + open fun softHashCode(): Int { // must be called only from PythonTreeWrapper! + return type.hashCode() * 31 + children.hashCode() } + + open fun diversity(): Int = // must be called only from PythonTreeWrapper! + 1 + children.fold(0) { acc, child -> acc + child.diversity() } } class PrimitiveNode( + id: Long, type: PythonClassId, val repr: String, - ) : PythonTreeNode(type) { + ) : PythonTreeNode(id, type) { + constructor(type: PythonClassId, repr: String) : this(PythonIdGenerator.getOrCreateIdForValue(repr), type, repr) + + override fun toString(): String { + return repr + } + override fun equals(other: Any?): Boolean { - if (other !is PrimitiveNode) { + if (other !is PrimitiveNode) return false - } - return repr == other.repr && type == other.type + return repr == other.repr } override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + repr.hashCode() - return result + return repr.hashCode() + } + + override fun softEquals(other: PythonTreeNode): Boolean { + if (other !is PrimitiveNode) + return false + return repr == other.repr + } + + override fun softHashCode(): Int { + return repr.hashCode() } + + override fun diversity(): Int = 2 } class ListNode( + id: Long, val items: MutableMap - ) : PythonTreeNode(PythonClassId("builtins.list")) { + ) : PythonTreeNode(id, PythonClassId("builtins.list")) { + constructor(items: MutableMap) : this(PythonIdGenerator.createId(), items) + override val children: List get() = items.values.toList() @@ -70,24 +119,14 @@ object PythonTree { } else false } - - override fun equals(other: Any?): Boolean { - if (other !is ListNode) { - return false - } - return children == other.children - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + items.hashCode() - return result - } } class DictNode( + id: Long, val items: MutableMap - ) : PythonTreeNode(PythonClassId("builtins.dict")) { + ) : PythonTreeNode(id, PythonClassId("builtins.dict")) { + constructor(items: MutableMap) : this(PythonIdGenerator.createId(), items) + override val children: List get() = items.values + items.keys @@ -100,24 +139,14 @@ object PythonTree { } else false } - override fun equals(other: Any?): Boolean { - if (other !is DictNode) { - return false - } - return children == other.children - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + items.hashCode() - return result - } - } class SetNode( + id: Long, val items: MutableSet - ) : PythonTreeNode(PythonClassId("builtins.set")) { + ) : PythonTreeNode(id, PythonClassId("builtins.set")) { + constructor(items: MutableSet) : this(PythonIdGenerator.createId(), items) + override val children: List get() = items.toList() @@ -133,25 +162,14 @@ object PythonTree { false } } - - override fun equals(other: Any?): Boolean { - if (other !is SetNode) { - return false - } - return items == other.items - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + items.hashCode() - return result - } - } class TupleNode( + id: Long, val items: MutableMap - ) : PythonTreeNode(PythonClassId("builtins.tuple")) { + ) : PythonTreeNode(id, PythonClassId("builtins.tuple")) { + constructor(items: MutableMap) : this(PythonIdGenerator.createId(), items) + override val children: List get() = items.values.toList() @@ -164,36 +182,22 @@ object PythonTree { false } } - - override fun equals(other: Any?): Boolean { - if (other !is TupleNode) { - return false - } - return items == other.items - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + items.hashCode() - return result - } } class ReduceNode( - val id: Long, + id: Long, type: PythonClassId, val constructor: PythonClassId, val args: List, var state: MutableMap, var listitems: List, var dictitems: Map, - ) : PythonTreeNode(type) { + ) : PythonTreeNode(id, type) { constructor( - id: Long, type: PythonClassId, constructor: PythonClassId, args: List, - ) : this(id, type, constructor, args, emptyMap().toMutableMap(), emptyList(), emptyMap()) + ) : this(PythonIdGenerator.createId(), type, constructor, args, emptyMap().toMutableMap(), emptyList(), emptyMap()) override val children: List get() = args + state.values + listitems + dictitems.values + dictitems.keys + PythonTreeNode(constructor) @@ -210,23 +214,15 @@ object PythonTree { } else false } - override fun equals(other: Any?): Boolean { - if (other !is ReduceNode) { + override fun softEquals(other: PythonTreeNode): Boolean { + if (other !is ReduceNode) return false - } - return type == other.type && - id == other.id && - constructor == other.constructor && - args == other.args && - state == other.state && - listitems == other.listitems && - dictitems == other.dictitems + return type == other.type && constructor == other.constructor && args == other.args && + state == other.state && listitems == other.listitems && dictitems == other.dictitems } - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + constructor.hashCode() + override fun softHashCode(): Int { + var result = constructor.hashCode() result = 31 * result + args.hashCode() result = 31 * result + state.hashCode() result = 31 * result + listitems.hashCode() @@ -268,12 +264,9 @@ object PythonTree { } fun fromString(value: String): PrimitiveNode { - val repr = value - .replace("\"", "\\\"") - .replace("\\\\\"", "\\\"") return PrimitiveNode( pythonStrClassId, - "\"$repr\"" + value.toPythonRepr() ) } @@ -310,4 +303,48 @@ object PythonTree { else -> null } } +} + +object PythonIdGenerator { + private const val lower_bound: Long = 1500_000_000 + + private val lastId: AtomicLong = AtomicLong(lower_bound) + private val cache: IdentityHashMap = IdentityHashMap() + + fun getOrCreateIdForValue(value: Any): Long { + return cache.getOrPut(value) { createId() } + } + + fun createId(): Long { + return lastId.incrementAndGet() + } + +} + +class PythonTreeWrapper(val tree: PythonTree.PythonTreeNode) { + override fun equals(other: Any?): Boolean { + if (other !is PythonTreeWrapper) + return false + if (PythonTree.isRecursiveObject(tree) || PythonTree.isRecursiveObject(other.tree)) + return tree == other.tree + return tree.softEquals(other.tree) + } + + override fun hashCode(): Int { + if (PythonTree.isRecursiveObject(tree)) + return tree.hashCode() + return tree.softHashCode() + } + + private val INF = 1000_000_000 + + private fun diversity(): Int { + if (PythonTree.isRecursiveObject(tree)) + return INF + return tree.diversity() + } + + fun commonDiversity(other: Int): Int { + return listOf(diversity() + other, INF).min() + } } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonIdUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonIdUtils.kt index 6ee9e07661..9a15f99dd4 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonIdUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonIdUtils.kt @@ -16,11 +16,11 @@ val pythonIntClassId = PythonClassId("builtins.int") val pythonFloatClassId = PythonClassId("builtins.float") val pythonComplexClassId = PythonClassId("builtins.complex") val pythonStrClassId = PythonClassId("builtins.str") -val pythonBoolClassId = PythonBoolModel.classId +val pythonBoolClassId = PythonClassId("builtins.bool") val pythonRangeClassId = PythonClassId("builtins.range") -val pythonListClassId = PythonListModel.classId -val pythonTupleClassId = PythonTupleModel.classId -val pythonDictClassId = PythonDictModel.classId -val pythonSetClassId = PythonSetModel.classId +val pythonListClassId = PythonClassId("builtins.list") +val pythonTupleClassId = PythonClassId("builtins.tuple") +val pythonDictClassId = PythonClassId("builtins.dict") +val pythonSetClassId = PythonClassId("builtins.set") val pythonBytearrayClassId = PythonClassId("builtins.bytearray") val pythonBytesClassId = PythonClassId("builtins.bytes") diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonUtils.kt index ea0ca0b6fe..f5aaefbd37 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonUtils.kt @@ -15,3 +15,10 @@ fun String.toSnakeCase(): String { } else c }.joinToString("") } + +fun String.toPythonRepr(): String { + val repr = this + .replace("\"", "\\\"") + .replace("\\\\\"", "\\\"") + return "\"$repr\"" +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/PythonApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/PythonApi.kt index 3cecc34fe7..6a7b27d176 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/PythonApi.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/PythonApi.kt @@ -2,8 +2,11 @@ package org.utbot.python.fuzzing import mu.KotlinLogging import org.utbot.framework.plugin.api.Instruction +import org.utbot.framework.plugin.api.UtError import org.utbot.fuzzer.FuzzedContext import org.utbot.fuzzer.IdGenerator +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.fuzzing.Configuration import org.utbot.fuzzing.Control import org.utbot.fuzzing.Description @@ -20,6 +23,8 @@ import org.utbot.python.newtyping.PythonSubtypeChecker import org.utbot.python.newtyping.PythonTypeStorage import org.utbot.python.newtyping.general.Type import org.utbot.python.newtyping.pythonTypeRepresentation +import java.util.* +import java.util.concurrent.atomic.AtomicLong private val logger = KotlinLogging.logger {} @@ -37,6 +42,17 @@ class PythonMethodDescription( val tracer: Trie, ) : Description(parameters) +sealed interface FuzzingExecutionFeedback +class ValidExecution(val utFuzzedExecution: UtFuzzedExecution): FuzzingExecutionFeedback +class InvalidExecution(val utError: UtError): FuzzingExecutionFeedback +class TypeErrorFeedback(val message: String) : FuzzingExecutionFeedback +class ArgumentsTypeErrorFeedback(val message: String) : FuzzingExecutionFeedback + +data class PythonExecutionResult( + val fuzzingExecutionFeedback: FuzzingExecutionFeedback, + val fuzzingPlatformFeedback: PythonFeedback +) + data class PythonFeedback( override val control: Control = Control.CONTINUE, val result: Trie.Node = Trie.emptyNode(), @@ -62,7 +78,7 @@ fun pythonDefaultValueProviders(idGenerator: IdGenerator) = listOf( UnionValueProvider, BytesValueProvider, BytearrayValueProvider, - ReduceValueProvider(idGenerator), + ReduceValueProvider, ConstantValueProvider, TypeAliasValueProvider ) @@ -71,6 +87,7 @@ class PythonFuzzing( private val pythonTypeStorage: PythonTypeStorage, val execute: suspend (description: PythonMethodDescription, values: List) -> PythonFeedback, ) : Fuzzing { + private fun generateDefault(description: PythonMethodDescription, type: Type, idGenerator: IdGenerator): Sequence> { var providers = emptyList>().asSequence() pythonDefaultValueProviders(idGenerator).asSequence().forEach { provider -> @@ -122,12 +139,19 @@ class PythonFuzzing( } } -class PythonIdGenerator : IdGenerator { - private var _id: Long = 0 +class PythonIdGenerator(lowerBound: Long = DEFAULT_LOWER_BOUND) : IdentityPreservingIdGenerator { + private val lastId: AtomicLong = AtomicLong(lowerBound) + private val cache: IdentityHashMap = IdentityHashMap() + + override fun getOrCreateIdForValue(value: Any): Long { + return cache.getOrPut(value) { createId() } + } override fun createId(): Long { - _id += 1 - return _id + return lastId.incrementAndGet() } + companion object { + const val DEFAULT_LOWER_BOUND: Long = 1500_000_000 + } } 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 90ed8046a0..cb5ef0c9c4 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 @@ -1,20 +1,18 @@ package org.utbot.python.fuzzing.provider -import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider import org.utbot.python.framework.api.python.PythonClassId import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.toPythonRepr import org.utbot.python.fuzzing.PythonFuzzedValue import org.utbot.python.fuzzing.PythonMethodDescription import org.utbot.python.newtyping.* import org.utbot.python.newtyping.general.FunctionType import org.utbot.python.newtyping.general.Type -class ReduceValueProvider( - private val idGenerator: IdGenerator -) : ValueProvider { +object ReduceValueProvider : ValueProvider { private val unsupportedTypes = listOf( "builtins.list", "builtins.set", @@ -73,7 +71,7 @@ class ReduceValueProvider( modifications.addAll(fields.map { field -> Routine.Call(listOf(field.type)) { instance, arguments -> val obj = instance.tree as PythonTree.ReduceNode - obj.state[field.meta.name] = arguments.first().tree + obj.state[field.meta.name.toPythonRepr()] = arguments.first().tree } }) yieldAll(callConstructors(type, it, modifications.asSequence())) @@ -94,7 +92,6 @@ class ReduceValueProvider( construct = Routine.Create(nonSelfArgs) { v -> PythonFuzzedValue( PythonTree.ReduceNode( - idGenerator.createId(), PythonClassId(type.pythonTypeName()), PythonClassId(type.pythonTypeName()), v.map { it.tree }, diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt index 8b6ca3b691..04c39449ca 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt @@ -28,8 +28,10 @@ object StrValueProvider : ValueProvider Boolean, @@ -38,7 +42,7 @@ class BaselineAlgorithm( ) { val generalRating = createGeneralTypeRating(hintCollectorResult, storage) val initialState = getInitialState(hintCollectorResult, generalRating) - val states: MutableSet = mutableSetOf(initialState) + val states: MutableList = mutableListOf(initialState) val fileForMypyRuns = TemporaryFileManager.assignTemporaryFile(tag = "mypy.py") run breaking@ { @@ -52,16 +56,15 @@ class BaselineAlgorithm( logger.info("Checking ${newState.signature.pythonTypeRepresentation()}") if (checkSignature(newState.signature as FunctionType, fileForMypyRuns, configFile)) { logger.debug("Found new state!") - annotationHandler(newState.signature) - states.add(newState) - /* +// annotationHandler(newState.signature) +// states.add(newState) when (annotationHandler(newState.signature)) { SuccessFeedback -> { states.add(newState) + state.children += 1 } InvalidTypeFeedback -> {} } - */ } } else { states.remove(state) @@ -87,9 +90,9 @@ class BaselineAlgorithm( ) } - // TODO: something smarter? - private fun chooseState(states: Set): BaselineAlgorithmState { - return states.random() + private fun chooseState(states: List): BaselineAlgorithmState { + val weights = states.map { 1.0 / (it.children * it.children + 1) } + return weightedRandom(states, weights, random) } private fun getInitialState( diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/Structures.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/Structures.kt index f97d2b16b7..dc9552ad68 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/Structures.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/Structures.kt @@ -37,4 +37,5 @@ class BaselineAlgorithmState( get() = nodes.find { it.isRoot }!!.partialType val anyNodes: List = nodes.mapNotNull { it as? AnyTypeNode } val candidateGraph = CandidateGraph(anyNodes, generalRating, typeStorage) + var children: Int = 0 } \ No newline at end of file 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 f3faa2d296..bf6c906b98 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,5 +1,13 @@ package org.utbot.python.newtyping.utils +import org.utbot.fuzzing.utils.chooseOne +import kotlin.random.Random + fun getOffsetLine(sourceFileContent: String, offset: Int): Int { return sourceFileContent.take(offset).count { it == '\n' } + 1 } + +fun weightedRandom(elems: List, weights: List, random: Random): T { + val index = random.chooseOne(weights.toDoubleArray()) + return elems[index] +} \ No newline at end of file diff --git a/utbot-python/src/main/resources/preprocessed_values.json b/utbot-python/src/main/resources/preprocessed_values.json index 577bdb9595..19f47bdf4d 100644 --- a/utbot-python/src/main/resources/preprocessed_values.json +++ b/utbot-python/src/main/resources/preprocessed_values.json @@ -1,518 +1,24 @@ [ - { - "name": "builtins.int", - "instances": [ - "-1", - "-3", - "0", - "1", - "10", - "100", - "10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104", - "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - "1144132807", - "123", - "1267650600228229401496703205376", - "1431655765", - "23", - "291", - "3", - "314", - "4", - "4294967296", - "4294967297", - "4886718345", - "83", - "9" - ] - }, - { - "name": "builtins.bool", - "instances": [ - "True", - "False" - ] - }, - { - "name": "builtins.str", - "instances": [ - "''", - "'(1+3j)'", - "'(1.5+3.5j)'", - "'(3+0j)'", - "'(3.2+0j)'", - "'-123456789'", - "'-1234567890'", - "'1001'", - "'10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376'", - "'3'", - "'3j'", - "'500'", - "'6442450944'", - "''", - "'False'", - "'True'", - "'a = 1'", - "'abcdefghijklmnopqrst'", - "'foo'", - "'global'", - "'inf'", - "'pythön.org.'", - "'strings are converted to unicode'", - "'unicode remains unicode'", - "'€'" - ] - }, { "name": "builtins.float", "instances": [ - "-1.0", - "-2.1", - "-2100.0", - "-2147483647.0", - "-2147483648.0", - "-7.3", - "-9.223372036854776e+18", "float('-inf')", - "0.0", - "0.5", - "1.0", - "1.23e+300", - "1.4", - "1.8446744073709552e+19", - "17179869184.0", - "1970.0", - "1e+23", - "2.1", - "2.5", - "2147483647.0", - "2147483648.0", - "3.14", - "3000.0", - "314.0", - "3200.0", - "4294967296.0", - "7.3", - "9.223372036854776e+18", "float('inf')", "float('nan')" ] }, - { - "name": "builtins.range", - "instances": [ - "range(127, 256)", - "range(24)", - "range(0, 2 ** 100 + 1, 2)", - "range(1, 25 + 1)", - "range(0, 3)", - "range(-10, 10)", - "range(10, -11, -1)", - "range(240)", - "range(2 ** 200, 2 ** 201, 2 ** 100)", - "range(200)", - "range(50, 400)", - "range(150)", - "range(9, -1, -2)", - "range(3 * 5 * 7 * 11)", - "range(4, 16)", - "range(0, 2 ** 100 - 1, 2)", - "range(0, 55296)", - "range(101)", - "range(5000)", - "range(65536, 1114112)", - "range((1 << 16) - 1)", - "range(1500)", - "range(1, 9)", - "range(512)", - "range(0, -20, -1)", - "range(32, 127)", - "range(52, 64)", - "range(1 << 1000)", - "range(70000)" - ] - }, - { - "name": "builtins.complex", - "instances": [ - "complex(1.0, float('inf'))", - "complex(0, 1)", - "complex(1.0, 10.0)", - "complex(0.0, 3.14)", - "complex(1, 2)", - "complex(float('inf'), float('inf'))", - "complex('1' * 500)", - "complex(float('inf'), -1)", - "complex(0.0, float('nan'))", - "complex(10.0)", - "complex(float('nan'), 1)", - "complex(0, 0)", - "complex(1.3, 2.2)", - "complex(3.14, 0.0)", - "complex(float('nan'), -1)", - "complex(0.0, -float('inf'))", - "complex(-1e500, 1.8e308')", - "complex(float('inf'), 0)", - "complex(0, float('nan'))", - "complex(1e500, 0)", - "complex(0, -1e-500j)", - "complex(10, 0)", - "complex(1, 10)", - "complex(3.14, 0.0)", - "complex(1, float('inf'))", - "complex(314, 0)", - "complex(-1e-500, 1e-500')", - "complex(3.14, 0.0)", - "complex(-float('inf'), float('inf'))", - "complex(5.3, 9.8)", - "complex(1.0, -float('inf'))", - "complex(0, -1e500)", - "complex(0.0, 3.14)", - "complex(0.0, -1.0)", - "complex(1e-200, 1e-200)", - "complex(314)", - "complex(-0.0, -1.0)", - "complex(float('inf'), 0.0)", - "complex(0, -float('inf'))", - "complex()", - "complex(0.0, 1.0)", - "complex(1, 10)", - "complex(0, float('inf'))", - "complex(float('nan'), float('nan'))", - "complex(real=17, imag=23)", - "complex(1e+200, 1e+200)", - "complex(0.0, 3.0)", - "complex(1.0, 10)", - "complex(-0.0, 2.0)", - "complex(float('inf'), 1)", - "complex(real=1, imag=3)", - "complex(1, 10.0)", - "complex(1, float('nan'))", - "complex(0.0, 3.14)", - "complex(0.0, 3.14)", - "complex(1.0, -0.0)" - ] - }, { "name": "builtins.BaseException", "instances": [ "BaseException()" ] }, - { - "name": "types.NoneType", - "instances": [ - "None" - ] - }, - { - "name": "builtins.bytearray", - "instances": [ - "bytearray(b'a')", - "bytearray(100)", - "bytearray(b'\\x00' * 100)", - "bytearray(range(1, 10))", - "bytearray(b'\\x07\\x7f\\x7f')", - "bytearray(b'mutable')", - "bytearray([1, 2])", - "bytearray(range(256))", - "bytearray(b'hell')", - "bytearray([5, 6, 7, 8, 9])", - "bytearray(b'memoryview')", - "bytearray(b'a:b::c:::d')", - "bytearray(b'b')", - "bytearray(b'cd')", - "bytearray(b'world')", - "bytearray(b'[emoryvie]')", - "bytearray(b'x' * 5)", - "bytearray([0, 1, 254, 255])", - "bytearray(b'*$')", - "bytearray(b'abc\\xe9\\x00')", - "bytearray(b'a\\xffb')", - "bytearray(range(100))", - "bytearray(b'[abracadabra]')", - "bytearray([0, 1, 2, 100, 101, 7, 8, 9])", - "bytearray(b'Mary')", - "bytearray(b'baz')", - "bytearray(b'\\xff')", - "bytearray(128 * 1024)", - "bytearray([100, 101])", - "bytearray(b'1')", - "bytearray([1, 2, 3])", - "bytearray(b'')", - "bytearray(b'one')", - "bytearray(b'nul:\\x00')", - "bytearray(1024)", - "bytearray(10)", - "bytearray(b'abcdefgh')", - "bytearray(b'123')", - "bytearray(b'\\x80')", - "bytearray(b'01 had a 9')", - "bytearray(2)", - "bytearray(range(10))", - "bytearray(b'a\\x80b')", - "bytearray(b':a:b::c')", - "bytearray(b'\\x00' * 15 + b'\\x01')", - "bytearray(b'hash this!')", - "bytearray(b'bar')", - "bytearray(b'x' * 4)", - "bytearray([0, 1, 2, 42, 42, 42, 3, 4, 5, 6, 7, 8, 9])", - "bytearray(b'----')", - "bytearray([i for i in range(256)])", - "bytearray(b'bytearray')", - "bytearray(b'spam')", - "bytearray([10, 100, 200])", - "bytearray(b'abcdefghijk')", - "bytearray(b'msssspp')", - "bytearray(b'no error')", - "bytearray(b'YWJj\\n')", - "bytearray([1, 1, 1, 1, 1, 5, 6, 7, 8, 9])", - "bytearray(b'foobaz')", - "bytearray([0])", - "bytearray(b'little lamb---')", - "bytearray(b'abc\\xe9\\x00xxx')", - "bytearray(b'def')", - "bytearray(b'eggs\\n')", - "bytearray(b'foo')", - "bytearray(b'foobar')", - "bytearray(128)", - "bytearray(b'key')", - "bytearray(16)", - "bytearray(b'file.py')", - "bytearray(b'ab')", - "bytearray(b'this is a random bytearray object')", - "bytearray(b'x' * 8)", - "bytearray(b' world\\n\\n\\n')", - "bytearray([1, 100, 200])", - "bytearray([102, 111, 111, 111, 111])", - "bytearray(range(1, 9))", - "bytearray([126, 127, 128, 129])", - "bytearray(5)", - "bytearray([0, 1, 2, 102, 111, 111])", - "bytearray(b'\\x00python\\x00test\\x00')", - "bytearray(9)", - "bytearray(b'abcde')", - "bytearray(b'x')", - "bytearray(b'0123456789')", - "bytearray([102, 111, 111, 102, 111, 111])", - "bytearray(2 ** 16)", - "bytearray(b'python')", - "bytearray(8192)", - "bytearray(list(range(8)) + list(range(256)))", - "bytearray(b'\\xff\\x00\\x00')", - "bytearray(b'hello1')", - "bytearray(range(16))", - "bytearray(b'xyz')", - "bytearray(b'\\xaaU\\xaaU')", - "bytearray(b'Z')", - "bytearray(8)", - "bytearray([1, 1, 1, 1, 1])", - "bytearray([0, 1, 2, 3, 4])", - "bytearray(b'ghi')", - "bytearray(b'[ytearra]')", - "bytearray(b'abc')", - "bytearray(b'this is a test')", - "bytearray(b'xxx')", - "bytearray()", - "bytearray(b'abcdefghijklmnopqrstuvwxyz')", - "bytearray(b'my dog has fleas')", - "bytearray([1, 100, 3])", - "bytearray(b'g\\xfcrk')", - "bytearray(b'hello world')", - "bytearray([26, 43, 48])", - "bytearray([1, 2, 3, 4, 6, 7, 8])", - "bytearray(b'0102abcdef')", - "bytearray(1)", - "bytearray(b'--------------')", - "bytearray(20)", - "bytearray(b'hello')" - ] - }, - { - "name": "builtins.bytes", - "instances": [ - "b'ab'", - "b'def'", - "b'abc'", - "b'\\x80\\x81'", - "b'Hello world\\n\\x80\\x81\\xfe\\xff'", - "b'3'", - "b'2'" - ] - }, - { - "name": "builtins.dict", - "instances": [ - "dict()" - ] - }, - { - "name": "builtins.frozenset", - "instances": [ - "frozenset()" - ] - }, - { - "name": "builtins.list", - "instances": [ - "list()" - ] - }, - { - "name": "builtins.memoryview", - "instances": [ - "memoryview(b'a')", - "memoryview(b'1234')", - "memoryview(b'ax = 123')", - "memoryview(b'$23$')", - "memoryview(bytes(range(256)))", - "memoryview(b'hash this!')", - "memoryview(b'12.3')", - "memoryview(b'bytes')", - "memoryview(b'a:b::c:::d')", - "memoryview(b'ab')", - "memoryview(b'123')", - "memoryview(b'ac')", - "memoryview(b'YWJj\\n')", - "memoryview(b'')", - "memoryview(b'*$')", - "memoryview(b'spam\\n')", - "memoryview(b'\\xff\\x00\\x00')", - "memoryview(b' ')", - "memoryview(b'abc')", - "memoryview(b'12.3A')", - "memoryview(b':a:b::c')", - "memoryview(b'foo')", - "memoryview(b'\\x124Vx')", - "memoryview(b'[abracadabra]')", - "memoryview(b'123 ')", - "memoryview(b'spam')", - "memoryview(b'text')", - "memoryview(b'memoryview')", - "memoryview(b'123\\x00')", - "memoryview(b'12.3\\x00')", - "memoryview(b'character buffers are decoded to unicode')", - "memoryview(b'xyz')", - "memoryview(b'12.3 ')", - "memoryview(b'cd')", - "memoryview(b'baz')", - "memoryview(b'0102abcdef')", - "memoryview(b'123A')", - "memoryview(b'\\x07\\x7f\\x7f')", - "memoryview(b'file.py')", - "memoryview(b'\\x1a+0')", - "memoryview(b'12.34')" - ] - }, { "name": "builtins.object", "instances": [ "object()" ] }, - { - "name": "builtins.set", - "instances": [ - "set()" - ] - }, - { - "name": "builtins.tuple", - "instances": [ - "tuple()" - ] - }, - { - "name": "builtins.slice", - "instances": [ - "slice(0, 2, 1)", - "slice(2)", - "slice(1, 3)", - "slice(4)", - "slice(0, 10)", - "slice(0, 1, 1)", - "slice(0, 1, 2)", - "slice(0, 2)", - "slice(1, 1)", - "slice(0)", - "slice(0, 1, 2)", - "slice(0, 2, 1)", - "slice(3, 5, 1)", - "slice(0, 1, 1)", - "slice(0, 1, 0)", - "slice(0, 8, 1)", - "slice(0, 2, 0)", - "slice(1, 18, 2)", - "slice(1)", - "slice(0, 10, 1)", - "slice(None, 10, -1)", - "slice(None, -10)", - "slice(None, -11, -1)", - "slice(None, 9)", - "slice(100, -100, -1)", - "slice(None, 10)", - "slice(None)", - "slice(-100, 100)", - "slice(None, None, -1)", - "slice(None, -9)", - "slice(None, 9, -1)", - "slice(0.0, 10, 1)", - "slice(0, 10, 0)", - "slice(10, 20, 3)", - "slice(0, 10, 1.0)", - "slice(1, 2, 4)", - "slice(1, 2)", - "slice(None, None, -2)", - "slice(-100, 100, 2)", - "slice(0, 10.0, 1)", - "slice(3, None, -2)", - "slice(1, None, 2)", - "slice(None, -10, -1)", - "slice(None, 11)", - "slice(1, 2, 3)", - "slice(None, -12, -1)", - "slice(5)", - "slice(None, 8, -1)", - "slice(None, -11)", - "slice(None, None, 2)", - "slice(0, 10, 2)", - "slice(2, 3)", - "slice(0, 10)", - "slice(0, 1, 5)", - "slice(0, 10, 0)", - "slice(2, 10, 3)", - "slice(0, 2)", - "slice(1, 3)", - "slice(1, 2)", - "slice(2, 2)", - "slice(0, 1)", - "slice(0, 0)", - "slice(2, 1)", - "slice(2000, 1000)", - "slice(0, 1000)", - "slice(0, 3)", - "slice(1000, 1000)", - "slice(2, 4)", - "slice(1, 2)", - "slice(1, 2, 3)", - "slice(0, 1)", - "slice(0, 0)", - "slice(2, 3)", - "slice(None, 42)", - "slice(None, 24, None)", - "slice(2, 1024, 10)", - "slice(None, 42, None)", - "slice(0, 2)", - "slice(1, 2)", - "slice(0, 1)", - "slice(3, 5)", - "slice(0, 10, 0)", - "slice(0, 3)", - "slice(1, 10, 2)", - "slice(2, 2, 2)", - "slice(1, 1, 1)" - ] - }, { "name": "builtins.type", "instances": [ diff --git a/utbot-python/src/main/resources/requirements.txt b/utbot-python/src/main/resources/requirements.txt index 3836042d0b..c2fb069681 100644 --- a/utbot-python/src/main/resources/requirements.txt +++ b/utbot-python/src/main/resources/requirements.txt @@ -1,4 +1,4 @@ mypy==1.0.0 coverage -utbot-executor==0.2.8 +utbot-executor==1.1.26 utbot-mypy-runner==0.2.8