Skip to content

Commit c7f2ac4

Browse files
authored
UTBot Python updates from SBFT version (#2725)
1 parent fb60ec0 commit c7f2ac4

File tree

104 files changed

+3391
-1754
lines changed

Some content is hidden

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

104 files changed

+3391
-1754
lines changed

utbot-cli-python/src/README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@
66

77
- Required Java version: 11.
88

9-
- Prefered Python version: 3.8+.
9+
- Preferred Python version: 3.10-3.11 (3.9 also supported but with limited functionality).
1010

1111
Make sure that your Python has `pip` installed (this is usually the case). [Read more about pip installation](https://pip.pypa.io/en/stable/installation/).
1212

1313
Before running utbot install pip requirements (or use `--install-requirements` flag in `generate_python` command):
1414

15-
python -m pip install mypy==1.0 utbot_executor==0.4.31 utbot_mypy_runner==0.2.8
15+
python -m pip install mypy==1.0 utbot_executor==0.9.19 utbot_mypy_runner==0.2.16
1616

1717
## Basic usage
1818

1919
Generate tests:
2020

21-
java -jar utbot-cli.jar generate_python dir/file_with_sources.py -p <PYTHON_PATH> -o generated_tests.py -s dir
21+
java -jar utbot-cli-python.jar generate_python dir/file_with_sources.py -p <PYTHON_PATH> -o generated_tests.py -s dir
2222

2323
This will generate tests for top-level functions from `file_with_sources.py`.
2424

2525
Run generated tests:
2626

27-
java -jar utbot-cli.jar run_python generated_tests.py -p <PYTHON_PATH>
27+
java -jar utbot-cli-python.jar run_python generated_tests.py -p <PYTHON_PATH>
2828

2929
### `generate_python` options
3030

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
package org.utbot.cli.language.python
22

3-
import mu.KLogger
3+
import mu.KotlinLogging
44
import org.utbot.python.PythonTestGenerationConfig
55
import org.utbot.python.PythonTestGenerationProcessor
66
import org.utbot.python.PythonTestSet
77

8+
private val logger = KotlinLogging.logger {}
9+
810
class PythonCliProcessor(
911
override val configuration: PythonTestGenerationConfig,
10-
private val output: String,
11-
private val logger: KLogger,
12+
private val testWriter: TestWriter,
1213
private val coverageOutput: String?,
1314
private val executionCounterOutput: String?,
1415
) : PythonTestGenerationProcessor() {
1516

1617
override fun saveTests(testsCode: String) {
17-
writeToFileAndSave(output, testsCode)
18+
testWriter.addTestCode(testsCode)
19+
// writeToFileAndSave(output, testsCode)
1820
}
1921

2022
override fun notGeneratedTestsAction(testedFunctions: List<String>) {

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

+220-155
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.utbot.cli.language.python
2+
3+
class TestWriter {
4+
private val testCode: MutableList<String> = mutableListOf()
5+
6+
fun addTestCode(code: String) {
7+
testCode.add(code)
8+
}
9+
10+
fun generateTestCode(): String {
11+
val (importLines, code) = testCode.fold(mutableListOf<String>() to StringBuilder()) { acc, s ->
12+
val lines = s.split(System.lineSeparator())
13+
val firstClassIndex = lines.indexOfFirst { it.startsWith("class") }
14+
lines.take(firstClassIndex).forEach { line -> if (line !in acc.first) acc.first.add(line) }
15+
lines.drop(firstClassIndex).forEach { line -> acc.second.append(line + System.lineSeparator()) }
16+
acc.first to acc.second
17+
}
18+
val codeBuilder = StringBuilder()
19+
importLines.filter { it.isNotEmpty() }.forEach {
20+
codeBuilder.append(it)
21+
codeBuilder.append(System.lineSeparator())
22+
}
23+
codeBuilder.append(System.lineSeparator())
24+
codeBuilder.append(code)
25+
return codeBuilder.toString()
26+
}
27+
}

utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonDialogProcessor.kt

+36-36
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,18 @@ object PythonDialogProcessor {
7272
private fun runIndicatorWithTimeHandler(indicator: ProgressIndicator, range: ProgressRange, text: String, globalCount: Int, globalShift: Int, timeout: Long): ScheduledFuture<*> {
7373
val startTime = System.currentTimeMillis()
7474
return AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay({
75-
val innerTimeoutRatio =
76-
((System.currentTimeMillis() - startTime).toDouble() / timeout)
77-
.coerceIn(0.0, 1.0)
78-
updateIndicator(
79-
indicator,
80-
range,
81-
text,
82-
innerTimeoutRatio,
83-
globalCount,
84-
globalShift,
85-
)
86-
}, 0, 100, TimeUnit.MILLISECONDS)
75+
val innerTimeoutRatio =
76+
((System.currentTimeMillis() - startTime).toDouble() / timeout)
77+
.coerceIn(0.0, 1.0)
78+
updateIndicator(
79+
indicator,
80+
range,
81+
text,
82+
innerTimeoutRatio,
83+
globalCount,
84+
globalShift,
85+
)
86+
}, 0, 100, TimeUnit.MILLISECONDS)
8787
}
8888

8989
private fun updateIndicatorTemplate(
@@ -163,28 +163,28 @@ object PythonDialogProcessor {
163163

164164
private fun findSelectedPythonMethods(model: PythonTestLocalModel): List<PythonMethodHeader> {
165165
return ReadAction.nonBlocking<List<PythonMethodHeader>> {
166-
model.selectedElements
167-
.filter { model.selectedElements.contains(it) }
168-
.flatMap {
169-
when (it) {
170-
is PyFunction -> listOf(it)
171-
is PyClass -> it.methods.toList()
172-
else -> emptyList()
173-
}
174-
}
175-
.filter { fineFunction(it) }
176-
.mapNotNull {
177-
val functionName = it.name ?: return@mapNotNull null
178-
val moduleFilename = it.containingFile.virtualFile?.canonicalPath ?: ""
179-
val containingClassId = it.containingClass?.qualifiedName?.let{ cls -> PythonClassId(cls) }
180-
PythonMethodHeader(
181-
functionName,
182-
moduleFilename,
183-
containingClassId,
184-
)
166+
model.selectedElements
167+
.filter { model.selectedElements.contains(it) }
168+
.flatMap {
169+
when (it) {
170+
is PyFunction -> listOf(it)
171+
is PyClass -> it.methods.toList()
172+
else -> emptyList()
185173
}
186-
.toSet()
187-
.toList()
174+
}
175+
.filter { fineFunction(it) }
176+
.mapNotNull {
177+
val functionName = it.name ?: return@mapNotNull null
178+
val moduleFilename = it.containingFile.virtualFile?.canonicalPath ?: ""
179+
val containingClassId = it.containingClass?.qualifiedName?.let{ cls -> PythonClassId(cls) }
180+
PythonMethodHeader(
181+
functionName,
182+
moduleFilename,
183+
containingClassId,
184+
)
185+
}
186+
.toSet()
187+
.toList()
188188
}.executeSynchronously() ?: emptyList()
189189
}
190190

@@ -287,7 +287,7 @@ object PythonDialogProcessor {
287287

288288
localUpdateIndicator(ProgressRange.ANALYZE, "Analyze module ${model.currentPythonModule}", 0.5)
289289

290-
val (mypyStorage, _) = processor.sourceCodeAnalyze()
290+
val mypyConfig = processor.sourceCodeAnalyze()
291291

292292
localUpdateIndicator(ProgressRange.ANALYZE, "Analyze module ${model.currentPythonModule}", 1.0)
293293

@@ -300,7 +300,7 @@ object PythonDialogProcessor {
300300
model.timeout,
301301
)
302302
try {
303-
val testSets = processor.testGenerate(mypyStorage)
303+
val testSets = processor.testGenerate(mypyConfig)
304304
timerHandler.cancel(true)
305305
if (testSets.isEmpty()) return@forEachIndexed
306306

@@ -312,7 +312,7 @@ object PythonDialogProcessor {
312312

313313
logger.info(
314314
"Finished test generation for the following functions: ${
315-
testSets.joinToString { it.method.name }
315+
testSets.map { it.method.name }.toSet().joinToString()
316316
}"
317317
)
318318
} finally {

utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/Utils.kt

+6-12
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import org.utbot.intellij.plugin.python.table.UtPyClassItem
1313
import org.utbot.intellij.plugin.python.table.UtPyFunctionItem
1414
import org.utbot.intellij.plugin.python.table.UtPyTableItem
1515
import org.utbot.python.utils.RequirementsUtils
16-
import kotlin.random.Random
1716

1817
inline fun <reified T : PsiElement> getContainingElement(
1918
element: PsiElement,
@@ -37,13 +36,6 @@ fun getContentRoot(project: Project, file: VirtualFile): VirtualFile {
3736
.getContentRootForFile(file) ?: error("Source file lies outside of a module")
3837
}
3938

40-
fun generateRandomString(length: Int): String {
41-
val charPool = ('a'..'z') + ('A'..'Z') + ('0'..'9')
42-
return (0..length)
43-
.map { Random.nextInt(0, charPool.size).let { charPool[it] } }
44-
.joinToString("")
45-
}
46-
4739
fun VirtualFile.isProjectSubmodule(ancestor: VirtualFile?): Boolean {
4840
return VfsUtil.isUnder(this, setOf(ancestor).toMutableSet())
4941
}
@@ -52,10 +44,12 @@ fun checkModuleIsInstalled(pythonPath: String, moduleName: String): Boolean {
5244
return RequirementsUtils.requirementsAreInstalled(pythonPath, listOf(moduleName))
5345
}
5446

55-
fun fineFunction(function: PyFunction): Boolean =
56-
!listOf("__init__", "__new__").contains(function.name) &&
57-
function.decoratorList?.decorators?.isNotEmpty() != true // TODO: add processing of simple decorators
58-
//(function.parent !is PyDecorator || (function.parent as PyDecorator).isBuiltin)
47+
fun fineFunction(function: PyFunction): Boolean {
48+
val hasNotConstructorName = !listOf("__init__", "__new__").contains(function.name)
49+
val decoratorNames = function.decoratorList?.decorators?.mapNotNull { it?.qualifiedName }
50+
val knownDecorators = decoratorNames?.all { it.toString() in listOf("staticmethod") } ?: true
51+
return hasNotConstructorName && knownDecorators
52+
}
5953

6054
fun fineClass(pyClass: PyClass): Boolean =
6155
getAncestors(pyClass).dropLast(1).all { it !is PyClass && it !is PyFunction } &&

utbot-python-executor/.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ env/
2323
venv/
2424
.mypy_cache/
2525
.dmypy.json
26-
dmypy.json
26+
dmypy.json
27+
utbot_executor.iml

utbot-python-executor/src/main/python/utbot_executor/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ $ python -m utbot_executor <hostname> <port> <logfile> [<loglevel DEBUG | INFO |
2929
"argumentsIds": ["1", "2"],
3030
"kwargumentsIds": ["4", "5"],
3131
"serializedMemory": "string",
32+
"memoryMode": "REDUCE",
3233
"filepath": ["/home/user/my_project/my_module/submod1.py"],
3334
"coverageId": "1"
3435
}
@@ -41,6 +42,7 @@ $ python -m utbot_executor <hostname> <port> <logfile> [<loglevel DEBUG | INFO |
4142
* `argumentsIds` - list of argument's ids
4243
* `kwargumentsIds` - list of keyword argument's ids
4344
* `serializedMemory` - serialized memory throw `deep_serialization` algorithm
45+
* `memoryMode` - serialization mode (`PICKLE`, `REDUCE`)
4446
* `filepath` - path to the tested function's containing file
4547
* `coverageId` - special id witch will be used for sending information about covered lines
4648

utbot-python-executor/src/main/python/utbot_executor/pyproject.toml

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "utbot-executor"
3-
version = "1.8.6"
3+
version = "1.9.19"
44
description = ""
55
authors = ["Vyacheslav Tamarin <[email protected]>"]
66
readme = "README.md"
@@ -19,3 +19,8 @@ build-backend = "poetry.core.masonry.api"
1919
[tool.poetry.scripts]
2020
utbot-executor = "utbot_executor:utbot_executor"
2121

22+
[tool.pytest.ini_options]
23+
log_cli = true
24+
log_cli_level = "DEBUG"
25+
log_cli_format = "%(asctime)s [%(levelname)6s] (%(filename)s:%(lineno)s) %(message)s"
26+
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import random
2+
3+
from utbot_executor.config import CoverageConfig, HostConfig
4+
from utbot_executor.executor import PythonExecutor
5+
from utbot_executor.parser import ExecutionRequest, ExecutionSuccessResponse, MemoryMode
6+
from utbot_executor.utils import TraceMode
7+
8+
9+
def test_execution():
10+
executor = PythonExecutor(
11+
CoverageConfig(HostConfig("localhost", random.randint(10 ** 5, 10 ** 6)), TraceMode.Instructions, True), False)
12+
id_ = '1500926645'
13+
serialized_arg = (r'{"objects":{"1500926644":{"strategy":"repr","id":"1500926644","typeinfo":{"module":"builtins",'
14+
r'"kind":"int"},"comparable":true,"value":"170141183460469231731687303715749887999"},'
15+
r'"1500926652":{"strategy":"list","id":"1500926652","typeinfo":{"module":"builtins",'
16+
r'"kind":"list"},"comparable":true,"items":["1500926644"]},"1500926650":{"strategy":"repr",'
17+
r'"id":"1500926650","typeinfo":{"module":"builtins","kind":"str"},"comparable":true,'
18+
r'"value":"\"x\""},"1500926646":{"strategy":"repr","id":"1500926646","typeinfo":{'
19+
r'"module":"builtins","kind":"int"},"comparable":true,"value":"1"},"1500926651":{'
20+
r'"strategy":"dict","id":"1500926651","typeinfo":{"module":"builtins","kind":"dict"},'
21+
r'"comparable":true,"items":{"1500926650":"1500926646"}},"1500926653":{"strategy":"list",'
22+
r'"id":"1500926653","typeinfo":{"module":"builtins","kind":"list"},"comparable":true,'
23+
r'"items":[]},"1500926654":{"strategy":"dict","id":"1500926654","typeinfo":{'
24+
r'"module":"builtins","kind":"dict"},"comparable":true,"items":{}},"1500926645":{'
25+
r'"strategy":"reduce","id":"1500926645","typeinfo":{"module":"my_func","kind":"A"},'
26+
r'"comparable":true,"constructor":{"module":"my_func","kind":"A"},"args":"1500926652",'
27+
r'"state":"1500926651","listitems":"1500926653","dictitems":"1500926654"}}}')
28+
request = ExecutionRequest(
29+
'f',
30+
'my_func',
31+
['my_func'],
32+
['./'],
33+
[id_],
34+
{},
35+
serialized_arg,
36+
MemoryMode.REDUCE,
37+
'my_func.py',
38+
'0x1',
39+
)
40+
response = executor.run_reduce_function(request)
41+
42+
assert isinstance(response, ExecutionSuccessResponse)
43+
44+
assert response.status == "success"
45+
assert response.is_exception is False
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"functionName":"im_list","functionModule":"src.foo.foo","imports":["typing","builtins","src.foo.foo"],"syspaths":["/home/vyacheslav/PycharmProjects/pythonProject/src","/home/vyacheslav/PycharmProjects/pythonProject"],"argumentsIds":["1500000001"],"kwargumentsIds":{},"serializedMemory":"{\"objects\":{\"1500000002\":{\"strategy\":\"repr\",\"id\":\"1500000002\",\"typeinfo\":{\"module\":\"builtins\",\"kind\":\"int\"},\"comparable\":true,\"value\":\"9\"},\"1500000003\":{\"strategy\":\"repr\",\"id\":\"1500000003\",\"typeinfo\":{\"module\":\"builtins\",\"kind\":\"int\"},\"comparable\":true,\"value\":\"1\"},\"1500000004\":{\"strategy\":\"repr\",\"id\":\"1500000004\",\"typeinfo\":{\"module\":\"builtins\",\"kind\":\"int\"},\"comparable\":true,\"value\":\"0\"},\"1500000001\":{\"strategy\":\"iterator\",\"id\":\"1500000001\",\"typeinfo\":{\"module\":\"typing\",\"kind\":\"Iterator\"},\"comparable\":true,\"items\":[\"1500000002\",\"1500000003\",\"1500000004\"],\"exception\":{\"module\":\"builtins\",\"kind\":\"StopIteration\"}}}}","memoryMode":"REDUCE","filepath":"/home/vyacheslav/PycharmProjects/pythonProject/src/foo/foo.py","coverageId":"59682f01"}

utbot-python-executor/src/main/python/utbot_executor/tests/pytest.ini

-2
This file was deleted.

0 commit comments

Comments
 (0)