Skip to content

Include utbot-executor and fix bugs #2548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ if (pythonIde.split(",").contains(ideType)) {
include("utbot-intellij-python")
include("utbot-python-parser")
include("utbot-python-types")
include("utbot-python-executor")
}

include("utbot-spring-sample")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class PythonGenerateTestsCommand : CliktCommand(
help = "File with Python code to generate tests for."
)

private fun absSourceFile() = sourceFile.toAbsolutePath()

private val pythonClass by option(
"-c", "--class",
help = "Specify top-level (ordinary, not nested) class under test. " +
Expand Down Expand Up @@ -132,7 +134,7 @@ class PythonGenerateTestsCommand : CliktCommand(
Success(
topLevelFunctions
.mapNotNull { parseFunctionDefinition(it) }
.map { PythonMethodHeader(it.name.toString(), sourceFile, null) }
.map { PythonMethodHeader(it.name.toString(), absSourceFile(), null) }
)
else {
val topLevelClassMethods = topLevelClasses
Expand All @@ -142,7 +144,7 @@ class PythonGenerateTestsCommand : CliktCommand(
.mapNotNull { parseFunctionDefinition(it) }
.map { function ->
val parsedClassName = PythonClassId(cls.name.toString())
PythonMethodHeader(function.name.toString(), sourceFile, parsedClassName)
PythonMethodHeader(function.name.toString(), absSourceFile(), parsedClassName)
}
}
if (topLevelClassMethods.isNotEmpty()) {
Expand All @@ -154,7 +156,7 @@ class PythonGenerateTestsCommand : CliktCommand(
val pythonMethodsOpt = selectedMethods.map { functionName ->
topLevelFunctions
.mapNotNull { parseFunctionDefinition(it) }
.map { PythonMethodHeader(it.name.toString(), sourceFile, null) }
.map { PythonMethodHeader(it.name.toString(), absSourceFile(), null) }
.find { it.name == functionName }
?.let { Success(it) }
?: Fail("Couldn't find top-level function $functionName in the source file.")
Expand All @@ -174,7 +176,7 @@ class PythonGenerateTestsCommand : CliktCommand(
val fineMethods = methods
.filter { !forbiddenMethods.contains(it.name.toString()) }
.map {
PythonMethodHeader(it.name.toString(), sourceFile, parsedClassId)
PythonMethodHeader(it.name.toString(), absSourceFile(), parsedClassId)
}
if (fineMethods.isNotEmpty())
Success(fineMethods)
Expand All @@ -201,8 +203,8 @@ class PythonGenerateTestsCommand : CliktCommand(

@Suppress("UNCHECKED_CAST")
private fun calculateValues(): Optional<Unit> {
val currentPythonModuleOpt = findCurrentPythonModule(directoriesForSysPath, sourceFile)
sourceFileContent = File(sourceFile).readText()
val currentPythonModuleOpt = findCurrentPythonModule(directoriesForSysPath, absSourceFile())
sourceFileContent = File(absSourceFile()).readText()
val pythonMethodsOpt = bind(currentPythonModuleOpt) { getPythonMethods() }

return bind(pack(currentPythonModuleOpt, pythonMethodsOpt)) {
Expand Down Expand Up @@ -232,7 +234,7 @@ class PythonGenerateTestsCommand : CliktCommand(

val config = PythonTestGenerationConfig(
pythonPath = pythonPath,
testFileInformation = TestFileInformation(sourceFile.toAbsolutePath(), sourceFileContent, currentPythonModule.dropInitFile()),
testFileInformation = TestFileInformation(absSourceFile(), sourceFileContent, currentPythonModule.dropInitFile()),
sysPathDirectories = directoriesForSysPath.map { it.toAbsolutePath() } .toSet(),
testedMethods = pythonMethods,
timeout = timeout,
Expand Down
26 changes: 26 additions & 0 deletions utbot-python-executor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
dist/
__pycache__/
build/
develop-eggs/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
.pytest_cache/
poetry.lock
.env/
.venv/
env/
venv/
.mypy_cache/
.dmypy.json
dmypy.json
109 changes: 109 additions & 0 deletions utbot-python-executor/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
val kotlinLoggingVersion: String? by rootProject

val utbotExecutorVersion = File(project.projectDir, "src/main/resources/utbot_executor_version").readText()
// these two properties --- from GRADLE_USER_HOME/gradle.properties
val pypiToken: String? by project
val pythonInterpreter: String? by project
val utbotExecutorPath = File(project.projectDir, "src/main/python/utbot_executor")
val localUtbotExecutorPath = File(utbotExecutorPath, "dist")

tasks.register("cleanDist") {
group = "python"
delete(localUtbotExecutorPath.canonicalPath)
}

val installPoetry =
if (pythonInterpreter != null) {
tasks.register<Exec>("installPoetry") {
group = "python"
workingDir = utbotExecutorPath
commandLine(pythonInterpreter, "-m", "pip", "install", "poetry")
}
} else {
null
}

val setExecutorVersion =
if (pythonInterpreter != null) {
tasks.register<Exec>("setVersion") {
dependsOn(installPoetry!!)
group = "python"
workingDir = utbotExecutorPath
commandLine(pythonInterpreter, "-m", "poetry", "version", utbotExecutorVersion)
}
} else {
null
}

val buildExecutor =
if (pythonInterpreter != null) {
tasks.register<Exec>("buildUtbotExecutor") {
dependsOn(setExecutorVersion!!)
group = "python"
workingDir = utbotExecutorPath
commandLine(pythonInterpreter, "-m", "poetry", "build")
}
} else {
null
}

if (pythonInterpreter != null && pypiToken != null) {
tasks.register<Exec>("publishUtbotExecutor") {
dependsOn(buildExecutor!!)
group = "python"
workingDir = utbotExecutorPath
commandLine(
pythonInterpreter,
"-m",
"poetry",
"publish",
"-u",
"__token__",
"-p",
pypiToken
)
}
}

val installExecutor =
if (pythonInterpreter != null) {
tasks.register<Exec>("installUtbotExecutor") {
dependsOn(buildExecutor!!)
group = "python"
environment("PIP_FIND_LINKS" to localUtbotExecutorPath.canonicalPath)
commandLine(
pythonInterpreter,
"-m",
"pip",
"install",
"utbot_executor==$utbotExecutorVersion"
)
}
} else {
null
}

val installPytest =
if (pythonInterpreter != null) {
tasks.register<Exec>("installPytest") {
group = "pytest"
workingDir = utbotExecutorPath
commandLine(pythonInterpreter, "-m", "pip", "install", "pytest")
}
} else {
null
}

if (pythonInterpreter != null) {
tasks.register<Exec>("runTests") {
dependsOn(installExecutor!!)
dependsOn(installPytest!!)
group = "pytest"
workingDir = utbotExecutorPath
commandLine(
pythonInterpreter,
"-m",
"pytest",
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.utbot.python

class UtbotExecutor {
}
127 changes: 127 additions & 0 deletions utbot-python-executor/src/main/python/utbot_executor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# UtBot Executor

Util for python code execution and state serialization.

## Installation

You can install module from [PyPI](https://pypi.org/project/utbot-executor/):

```bash
python -m pip install utbot-executor
```

## Usage

### From console with socket listener

Run with your `<hostname>` and `<port>` for socket connection
```bash
$ python -m utbot_executor <hostname> <port> <logfile> [<loglevel DEBUG | INFO | ERROR>] <coverage_hostname> <coverage_port>
```

### Request format
```json
{
"functionName": "f",
"functionModule": "my_module.submod1",
"imports": ["sys", "math", "json"],
"syspaths": ["/home/user/my_project/"],
"argumentsIds": ["1", "2"],
"kwargumentsIds": ["4", "5"],
"serializedMemory": "string",
"filepath": ["/home/user/my_project/my_module/submod1.py"],
"coverageId": "1"
}
```

* `functionName` - name of the tested function
* `functionModule` - name of the module of the tested function
* `imports` - all modules which need to run function with current arguments
* `syspaths` - all syspaths which need to import modules (usually it is a project root)
* `argumentsIds` - list of argument's ids
* `kwargumentsIds` - list of keyword argument's ids
* `serializedMemory` - serialized memory throw `deep_serialization` algorithm
* `filepath` - path to the tested function's containing file
* `coverageId` - special id witch will be used for sending information about covered lines

### Response format:

If execution is successful:
```json
{
"status": "success",
"isException": false,
"statements": [1, 2, 3],
"missedStatements": [4, 5],
"stateInit": "string",
"stateBefore": "string",
"stateAfter": "string",
"diffIds": ["3", "4"],
"argsIds": ["1", "2", "3"],
"kwargs": ["4", "5", "6"],
"resultId": "7"
}
```

* `status` - always "success"
* `isException` - boolean value, if it is `true`, execution ended with an exception
* `statements` - list of the numbers of covered rows
* `missedStatements` - list of numbers of uncovered rows
* `stateInit` - serialized states from request
* `stateBefore` - serialized states of arguments before execution
* `stateAfter` - serialized states of arguments after execution
* `diffIds` - ids of the objects which have been changed
* `argsIds` - ids of the function's arguments
* `kwargsIds` - ids of the function's keyword arguments
* `resultId` - id of the returned value

or error format if there was exception in running algorith:

```json
{
"status": "fail",
"exception": "stacktrace"
}
```
* `status` - always "fail"
* `exception` - string representation of the exception stack trace

### Submodule `deep_serialization`

JSON serializer and deserializer for python objects

#### States memory json-format

```json
{
"objects": {
"id": {
"id": "1",
"strategy": "strategy name",
"typeinfo": {
"module": "builtins",
"kind": "int"
},
"comparable": true,

// iff strategy is 'repr'
"value": "1",

// iff strategy is 'list' or 'dict'
"items": ["3", "2"],

// iff strategy = 'reduce'
"constructor": "mymod.A.__new__",
"args": ["mymod.A"],
"state": {"a": "4", "b": "5"},
"listitems": ["7", "8"],
"dictitems": {"ka": "10"}
}
}
}
```


## Source

GitHub [repository](https://github.com/tamarinvs19/utbot_executor)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[tool.poetry]
name = "utbot-executor"
version = "1.4.41"
description = ""
authors = ["Vyacheslav Tamarin <vyacheslav.tamarin@yandex.ru>"]
readme = "README.md"
packages = [{include = "utbot_executor"}]

[tool.poetry.dependencies]
python = "^3.8"

[tool.poetry.dev-dependencies]
pytest = "^7.3"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.scripts]
utbot-executor = "utbot_executor:utbot_executor"

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
python_files = test_*.py *_test.py *_tests.py
Loading