Skip to content

Commit b775394

Browse files
authored
Include utbot-executor and fix bugs (#2548)
1 parent d8d5197 commit b775394

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2629
-49
lines changed

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ if (pythonIde.split(",").contains(ideType)) {
6666
include("utbot-intellij-python")
6767
include("utbot-python-parser")
6868
include("utbot-python-types")
69+
include("utbot-python-executor")
6970
}
7071

7172
include("utbot-spring-sample")

utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class PythonGenerateTestsCommand : CliktCommand(
3939
help = "File with Python code to generate tests for."
4040
)
4141

42+
private fun absSourceFile() = sourceFile.toAbsolutePath()
43+
4244
private val pythonClass by option(
4345
"-c", "--class",
4446
help = "Specify top-level (ordinary, not nested) class under test. " +
@@ -132,7 +134,7 @@ class PythonGenerateTestsCommand : CliktCommand(
132134
Success(
133135
topLevelFunctions
134136
.mapNotNull { parseFunctionDefinition(it) }
135-
.map { PythonMethodHeader(it.name.toString(), sourceFile, null) }
137+
.map { PythonMethodHeader(it.name.toString(), absSourceFile(), null) }
136138
)
137139
else {
138140
val topLevelClassMethods = topLevelClasses
@@ -142,7 +144,7 @@ class PythonGenerateTestsCommand : CliktCommand(
142144
.mapNotNull { parseFunctionDefinition(it) }
143145
.map { function ->
144146
val parsedClassName = PythonClassId(cls.name.toString())
145-
PythonMethodHeader(function.name.toString(), sourceFile, parsedClassName)
147+
PythonMethodHeader(function.name.toString(), absSourceFile(), parsedClassName)
146148
}
147149
}
148150
if (topLevelClassMethods.isNotEmpty()) {
@@ -154,7 +156,7 @@ class PythonGenerateTestsCommand : CliktCommand(
154156
val pythonMethodsOpt = selectedMethods.map { functionName ->
155157
topLevelFunctions
156158
.mapNotNull { parseFunctionDefinition(it) }
157-
.map { PythonMethodHeader(it.name.toString(), sourceFile, null) }
159+
.map { PythonMethodHeader(it.name.toString(), absSourceFile(), null) }
158160
.find { it.name == functionName }
159161
?.let { Success(it) }
160162
?: Fail("Couldn't find top-level function $functionName in the source file.")
@@ -174,7 +176,7 @@ class PythonGenerateTestsCommand : CliktCommand(
174176
val fineMethods = methods
175177
.filter { !forbiddenMethods.contains(it.name.toString()) }
176178
.map {
177-
PythonMethodHeader(it.name.toString(), sourceFile, parsedClassId)
179+
PythonMethodHeader(it.name.toString(), absSourceFile(), parsedClassId)
178180
}
179181
if (fineMethods.isNotEmpty())
180182
Success(fineMethods)
@@ -201,8 +203,8 @@ class PythonGenerateTestsCommand : CliktCommand(
201203

202204
@Suppress("UNCHECKED_CAST")
203205
private fun calculateValues(): Optional<Unit> {
204-
val currentPythonModuleOpt = findCurrentPythonModule(directoriesForSysPath, sourceFile)
205-
sourceFileContent = File(sourceFile).readText()
206+
val currentPythonModuleOpt = findCurrentPythonModule(directoriesForSysPath, absSourceFile())
207+
sourceFileContent = File(absSourceFile()).readText()
206208
val pythonMethodsOpt = bind(currentPythonModuleOpt) { getPythonMethods() }
207209

208210
return bind(pack(currentPythonModuleOpt, pythonMethodsOpt)) {
@@ -232,7 +234,7 @@ class PythonGenerateTestsCommand : CliktCommand(
232234

233235
val config = PythonTestGenerationConfig(
234236
pythonPath = pythonPath,
235-
testFileInformation = TestFileInformation(sourceFile.toAbsolutePath(), sourceFileContent, currentPythonModule.dropInitFile()),
237+
testFileInformation = TestFileInformation(absSourceFile(), sourceFileContent, currentPythonModule.dropInitFile()),
236238
sysPathDirectories = directoriesForSysPath.map { it.toAbsolutePath() } .toSet(),
237239
testedMethods = pythonMethods,
238240
timeout = timeout,

utbot-python-executor/.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
dist/
2+
__pycache__/
3+
build/
4+
develop-eggs/
5+
eggs/
6+
.eggs/
7+
lib/
8+
lib64/
9+
parts/
10+
sdist/
11+
var/
12+
wheels/
13+
share/python-wheels/
14+
*.egg-info/
15+
.installed.cfg
16+
*.egg
17+
MANIFEST
18+
.pytest_cache/
19+
poetry.lock
20+
.env/
21+
.venv/
22+
env/
23+
venv/
24+
.mypy_cache/
25+
.dmypy.json
26+
dmypy.json
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
val kotlinLoggingVersion: String? by rootProject
2+
3+
val utbotExecutorVersion = File(project.projectDir, "src/main/resources/utbot_executor_version").readText()
4+
// these two properties --- from GRADLE_USER_HOME/gradle.properties
5+
val pypiToken: String? by project
6+
val pythonInterpreter: String? by project
7+
val utbotExecutorPath = File(project.projectDir, "src/main/python/utbot_executor")
8+
val localUtbotExecutorPath = File(utbotExecutorPath, "dist")
9+
10+
tasks.register("cleanDist") {
11+
group = "python"
12+
delete(localUtbotExecutorPath.canonicalPath)
13+
}
14+
15+
val installPoetry =
16+
if (pythonInterpreter != null) {
17+
tasks.register<Exec>("installPoetry") {
18+
group = "python"
19+
workingDir = utbotExecutorPath
20+
commandLine(pythonInterpreter, "-m", "pip", "install", "poetry")
21+
}
22+
} else {
23+
null
24+
}
25+
26+
val setExecutorVersion =
27+
if (pythonInterpreter != null) {
28+
tasks.register<Exec>("setVersion") {
29+
dependsOn(installPoetry!!)
30+
group = "python"
31+
workingDir = utbotExecutorPath
32+
commandLine(pythonInterpreter, "-m", "poetry", "version", utbotExecutorVersion)
33+
}
34+
} else {
35+
null
36+
}
37+
38+
val buildExecutor =
39+
if (pythonInterpreter != null) {
40+
tasks.register<Exec>("buildUtbotExecutor") {
41+
dependsOn(setExecutorVersion!!)
42+
group = "python"
43+
workingDir = utbotExecutorPath
44+
commandLine(pythonInterpreter, "-m", "poetry", "build")
45+
}
46+
} else {
47+
null
48+
}
49+
50+
if (pythonInterpreter != null && pypiToken != null) {
51+
tasks.register<Exec>("publishUtbotExecutor") {
52+
dependsOn(buildExecutor!!)
53+
group = "python"
54+
workingDir = utbotExecutorPath
55+
commandLine(
56+
pythonInterpreter,
57+
"-m",
58+
"poetry",
59+
"publish",
60+
"-u",
61+
"__token__",
62+
"-p",
63+
pypiToken
64+
)
65+
}
66+
}
67+
68+
val installExecutor =
69+
if (pythonInterpreter != null) {
70+
tasks.register<Exec>("installUtbotExecutor") {
71+
dependsOn(buildExecutor!!)
72+
group = "python"
73+
environment("PIP_FIND_LINKS" to localUtbotExecutorPath.canonicalPath)
74+
commandLine(
75+
pythonInterpreter,
76+
"-m",
77+
"pip",
78+
"install",
79+
"utbot_executor==$utbotExecutorVersion"
80+
)
81+
}
82+
} else {
83+
null
84+
}
85+
86+
val installPytest =
87+
if (pythonInterpreter != null) {
88+
tasks.register<Exec>("installPytest") {
89+
group = "pytest"
90+
workingDir = utbotExecutorPath
91+
commandLine(pythonInterpreter, "-m", "pip", "install", "pytest")
92+
}
93+
} else {
94+
null
95+
}
96+
97+
if (pythonInterpreter != null) {
98+
tasks.register<Exec>("runTests") {
99+
dependsOn(installExecutor!!)
100+
dependsOn(installPytest!!)
101+
group = "pytest"
102+
workingDir = utbotExecutorPath
103+
commandLine(
104+
pythonInterpreter,
105+
"-m",
106+
"pytest",
107+
)
108+
}
109+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package org.utbot.python
2+
3+
class UtbotExecutor {
4+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# UtBot Executor
2+
3+
Util for python code execution and state serialization.
4+
5+
## Installation
6+
7+
You can install module from [PyPI](https://pypi.org/project/utbot-executor/):
8+
9+
```bash
10+
python -m pip install utbot-executor
11+
```
12+
13+
## Usage
14+
15+
### From console with socket listener
16+
17+
Run with your `<hostname>` and `<port>` for socket connection
18+
```bash
19+
$ python -m utbot_executor <hostname> <port> <logfile> [<loglevel DEBUG | INFO | ERROR>] <coverage_hostname> <coverage_port>
20+
```
21+
22+
### Request format
23+
```json
24+
{
25+
"functionName": "f",
26+
"functionModule": "my_module.submod1",
27+
"imports": ["sys", "math", "json"],
28+
"syspaths": ["/home/user/my_project/"],
29+
"argumentsIds": ["1", "2"],
30+
"kwargumentsIds": ["4", "5"],
31+
"serializedMemory": "string",
32+
"filepath": ["/home/user/my_project/my_module/submod1.py"],
33+
"coverageId": "1"
34+
}
35+
```
36+
37+
* `functionName` - name of the tested function
38+
* `functionModule` - name of the module of the tested function
39+
* `imports` - all modules which need to run function with current arguments
40+
* `syspaths` - all syspaths which need to import modules (usually it is a project root)
41+
* `argumentsIds` - list of argument's ids
42+
* `kwargumentsIds` - list of keyword argument's ids
43+
* `serializedMemory` - serialized memory throw `deep_serialization` algorithm
44+
* `filepath` - path to the tested function's containing file
45+
* `coverageId` - special id witch will be used for sending information about covered lines
46+
47+
### Response format:
48+
49+
If execution is successful:
50+
```json
51+
{
52+
"status": "success",
53+
"isException": false,
54+
"statements": [1, 2, 3],
55+
"missedStatements": [4, 5],
56+
"stateInit": "string",
57+
"stateBefore": "string",
58+
"stateAfter": "string",
59+
"diffIds": ["3", "4"],
60+
"argsIds": ["1", "2", "3"],
61+
"kwargs": ["4", "5", "6"],
62+
"resultId": "7"
63+
}
64+
```
65+
66+
* `status` - always "success"
67+
* `isException` - boolean value, if it is `true`, execution ended with an exception
68+
* `statements` - list of the numbers of covered rows
69+
* `missedStatements` - list of numbers of uncovered rows
70+
* `stateInit` - serialized states from request
71+
* `stateBefore` - serialized states of arguments before execution
72+
* `stateAfter` - serialized states of arguments after execution
73+
* `diffIds` - ids of the objects which have been changed
74+
* `argsIds` - ids of the function's arguments
75+
* `kwargsIds` - ids of the function's keyword arguments
76+
* `resultId` - id of the returned value
77+
78+
or error format if there was exception in running algorith:
79+
80+
```json
81+
{
82+
"status": "fail",
83+
"exception": "stacktrace"
84+
}
85+
```
86+
* `status` - always "fail"
87+
* `exception` - string representation of the exception stack trace
88+
89+
### Submodule `deep_serialization`
90+
91+
JSON serializer and deserializer for python objects
92+
93+
#### States memory json-format
94+
95+
```json
96+
{
97+
"objects": {
98+
"id": {
99+
"id": "1",
100+
"strategy": "strategy name",
101+
"typeinfo": {
102+
"module": "builtins",
103+
"kind": "int"
104+
},
105+
"comparable": true,
106+
107+
// iff strategy is 'repr'
108+
"value": "1",
109+
110+
// iff strategy is 'list' or 'dict'
111+
"items": ["3", "2"],
112+
113+
// iff strategy = 'reduce'
114+
"constructor": "mymod.A.__new__",
115+
"args": ["mymod.A"],
116+
"state": {"a": "4", "b": "5"},
117+
"listitems": ["7", "8"],
118+
"dictitems": {"ka": "10"}
119+
}
120+
}
121+
}
122+
```
123+
124+
125+
## Source
126+
127+
GitHub [repository](https://github.com/tamarinvs19/utbot_executor)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[tool.poetry]
2+
name = "utbot-executor"
3+
version = "1.4.41"
4+
description = ""
5+
authors = ["Vyacheslav Tamarin <vyacheslav.tamarin@yandex.ru>"]
6+
readme = "README.md"
7+
packages = [{include = "utbot_executor"}]
8+
9+
[tool.poetry.dependencies]
10+
python = "^3.8"
11+
12+
[tool.poetry.dev-dependencies]
13+
pytest = "^7.3"
14+
15+
[build-system]
16+
requires = ["poetry-core"]
17+
build-backend = "poetry.core.masonry.api"
18+
19+
[tool.poetry.scripts]
20+
utbot-executor = "utbot_executor:utbot_executor"
21+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
python_files = test_*.py *_test.py *_tests.py

0 commit comments

Comments
 (0)