Skip to content

Commit 6ed0b4f

Browse files
committed
File and Process API; Installation of mypy; some changes in UI
1 parent b63a285 commit 6ed0b4f

File tree

13 files changed

+170
-119
lines changed

13 files changed

+170
-119
lines changed

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

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ import org.utbot.python.code.PythonCodeGenerator.generateTestCode
2222
import org.utbot.python.code.PythonCodeGenerator.saveToFile
2323
import org.utbot.python.PythonMethod
2424
import org.utbot.python.PythonTestCaseGenerator
25-
import org.utbot.python.code.AnnotationProcessor.getTypesFromAnnotation
26-
import org.utbot.python.normalizeAnnotation
25+
import org.utbot.python.typing.MypyAnnotations
2726
import org.utbot.python.typing.PythonTypesStorage
27+
import org.utbot.python.typing.StubFileFinder
28+
import org.utbot.python.utils.FileManager
2829

2930

3031
object PythonDialogProcessor {
@@ -94,15 +95,30 @@ object PythonDialogProcessor {
9495
val pythonPath = model.srcModule.sdk?.homePath ?: error("Couldn't find Python interpreter")
9596
val testSourceRoot = model.testSourceRoot!!.path
9697
val filePath = model.file.virtualFile.path
98+
FileManager.assignTestSourceRoot(testSourceRoot)
9799

98-
// PythonCodeCollector.refreshProjectClassesList(model.project.basePath!!)
99-
PythonTypesStorage.refreshProjectClassesList(
100-
filePath,
101-
pythonPath,
102-
model.project.basePath!!,
103-
model.directoriesForSysPath,
104-
testSourceRoot
105-
)
100+
if (!MypyAnnotations.mypyInstalled(pythonPath) && !indicator.isCanceled) {
101+
indicator.text = "Installing mypy"
102+
MypyAnnotations.installMypy(pythonPath)
103+
if (!MypyAnnotations.mypyInstalled(pythonPath))
104+
error("Something wrong with mypy")
105+
}
106+
107+
if (!indicator.isCanceled) {
108+
indicator.text = "Loading information about Python types"
109+
110+
// PythonCodeCollector.refreshProjectClassesList(model.project.basePath!!)
111+
PythonTypesStorage.refreshProjectClassesList(
112+
filePath,
113+
pythonPath,
114+
model.project.basePath!!,
115+
model.directoriesForSysPath
116+
)
117+
118+
while (!StubFileFinder.isInitialized);
119+
120+
indicator.text = "Generating tests"
121+
}
106122

107123
val pythonMethods = findSelectedPythonMethods(model)
108124

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,14 @@ class PythonDialogWindow(val model: PythonTestsModel): DialogWrapper(model.proje
6060
row("Test source root:") {
6161
component(testSourceFolderField)
6262
}
63+
/*
6364
row("Test framework:") {
6465
makePanelWithHelpTooltip(
6566
testFrameworks as ComboBox<CodeGenerationSettingItem>,
6667
itemsToHelpTooltip[testFrameworks]
6768
)
6869
}
70+
*/
6971
row("Generate test methods for:") {}
7072
row {
7173
scrollPane(functionsTable)

utbot-python/src/main/kotlin/org/utbot/python/PythonEvaluation.kt

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package org.utbot.python
33
import com.beust.klaxon.Klaxon
44
import org.utbot.framework.plugin.api.UtModel
55
import org.utbot.python.code.PythonCodeGenerator
6+
import org.utbot.python.utils.FileManager
7+
import org.utbot.python.utils.runCommand
68
import java.io.File
79

810

@@ -29,44 +31,36 @@ object PythonEvaluation {
2931
moduleToImport: String,
3032
pythonPath: String
3133
): EvaluationResult {
32-
createDirectory(testSourceRoot)
33-
34-
val outputFilename = "$testSourceRoot/__output_utbot_run_${method.name}.txt"
35-
val errorFilename = "$testSourceRoot/__error_utbot_run_${method.name}.txt"
36-
val codeFilename = "$testSourceRoot/__test_utbot_run_${method.name}.py"
37-
38-
val file = PythonCodeGenerator.generateRunFunctionCode(
34+
val outputFile = FileManager.assignTemporaryFile(tag = "output_" + method.name)
35+
val errorFile = FileManager.assignTemporaryFile(tag = "error_" + method.name)
36+
val runCode = PythonCodeGenerator.generateRunFunctionCode(
3937
method,
4038
methodArguments,
41-
outputFilename,
42-
errorFilename,
43-
codeFilename,
39+
outputFile.path,
40+
errorFile.path,
4441
directoriesForSysPath,
4542
moduleToImport
4643
)
47-
48-
val process = Runtime.getRuntime().exec("$pythonPath $codeFilename")
49-
process.waitFor()
50-
var failedEvaluation = process.exitValue() != 0
44+
val fileWithCode = FileManager.createTemporaryFile(runCode, tag = "run_" + method.name)
45+
val result = runCommand(listOf(pythonPath, fileWithCode.path))
46+
var failedEvaluation = result.exitValue != 0
5147

5248
var outputAsString = ""
5349
var isSuccess = false
5450

55-
val resultFile = File(outputFilename)
56-
if (resultFile.exists()) {
57-
outputAsString = resultFile.readText()
58-
resultFile.delete()
51+
if (outputFile.exists()) {
52+
outputAsString = outputFile.readText()
53+
outputFile.delete()
5954
isSuccess = true
6055
} else {
61-
val errorFile = File(errorFilename)
6256
if (errorFile.exists()) {
6357
outputAsString = errorFile.readText()
6458
errorFile.delete()
6559
} else {
6660
failedEvaluation = true
6761
}
6862
}
69-
file.delete()
63+
fileWithCode.delete()
7064

7165
if (failedEvaluation)
7266
return EvaluationError
@@ -76,8 +70,4 @@ object PythonEvaluation {
7670
!isSuccess
7771
)
7872
}
79-
80-
private fun createDirectory(path: String) {
81-
File(path).mkdir()
82-
}
8373
}

utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.utbot.python.code.ArgInfoCollector
66
import org.utbot.python.typing.MypyAnnotations
77
import org.utbot.python.typing.PythonTypesStorage
88
import org.utbot.python.typing.StubFileFinder
9+
import org.utbot.python.utils.annotationToClassId
910
import java.io.File
1011

1112
object PythonTestCaseGenerator {
@@ -42,8 +43,7 @@ object PythonTestCaseGenerator {
4243
pythonPath,
4344
projectRoot,
4445
fileOfMethod,
45-
directoriesForSysPath,
46-
testSourceRoot
46+
directoriesForSysPath
4747
)
4848
}
4949
val argInfoCollector = ArgInfoCollector(method, initialArgumentTypes)
@@ -172,7 +172,6 @@ object PythonTestCaseGenerator {
172172
return MypyAnnotations.getCheckedByMypyAnnotations(
173173
methodUnderTest,
174174
userAnnotations + annotationCombinations,
175-
testSourceRoot,
176175
moduleToImport,
177176
directoriesForSysPath + listOf(File(fileOfMethod).parentFile.path),
178177
pythonPath,

utbot-python/src/main/kotlin/org/utbot/python/code/CodeGen.kt

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import io.github.danielnaczo.python3parser.visitors.prettyprint.ModulePrettyPrin
2929
import org.utbot.framework.plugin.api.UtModel
3030
import org.utbot.framework.plugin.api.pythonAnyClassId
3131
import org.utbot.python.*
32-
import org.utbot.python.typing.StubFileStructures
3332
import java.io.File
3433

3534

@@ -228,10 +227,9 @@ object PythonCodeGenerator {
228227
methodArguments: List<UtModel>,
229228
outputFilename: String,
230229
errorFilename: String,
231-
codeFilename: String,
232230
directoriesForSysPath: List<String>,
233231
moduleToImport: String
234-
): File {
232+
): String {
235233

236234
val importStatements = generateImportFunctionCode(
237235
moduleToImport,
@@ -290,13 +288,11 @@ object PythonCodeGenerator {
290288
listOf(createArguments())
291289
)
292290

293-
val functionCode = toString(
291+
return toString(
294292
Module(
295293
importStatements + listOf(testFunction, runFunction)
296294
)
297295
)
298-
299-
return saveToFile(codeFilename, functionCode)
300296
}
301297

302298
fun saveToFile(filePath: String, code: String): File {
@@ -309,10 +305,9 @@ object PythonCodeGenerator {
309305
fun generateMypyCheckCode(
310306
method: PythonMethod,
311307
methodAnnotations: Map<String, String>,
312-
codeFilename: String,
313308
directoriesForSysPath: List<String>,
314309
moduleToImport: String
315-
): File {
310+
): String {
316311
val importStatements = generateImportFunctionCode(
317312
moduleToImport,
318313
directoriesForSysPath,
@@ -321,7 +316,7 @@ object PythonCodeGenerator {
321316

322317
val parameters = Parameters(
323318
method.arguments.map { argument ->
324-
Parameter("${argument.name}: ${methodAnnotations[argument.name] ?: pythonAnyClassId.name }")
319+
Parameter("${argument.name}: ${methodAnnotations[argument.name] ?: pythonAnyClassId.name}")
325320
},
326321
)
327322

@@ -332,13 +327,11 @@ object PythonCodeGenerator {
332327
method.ast().body
333328
)
334329

335-
val functionCode = toString(
330+
return toString(
336331
Module(
337332
importStatements + listOf(testFunction)
338333
)
339334
)
340-
341-
return saveToFile(codeFilename, functionCode)
342335
}
343336
}
344337

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,119 @@
11
package org.utbot.python.typing
22

33
import org.utbot.framework.plugin.api.ClassId
4+
import org.utbot.python.utils.FileManager
45
import org.utbot.python.PythonMethod
56
import org.utbot.python.code.PythonCodeGenerator.generateMypyCheckCode
7+
import org.utbot.python.utils.runCommand
68
import java.io.File
79

810

911
object MypyAnnotations {
12+
private const val mypyVersion = "0.971"
13+
1014
fun getCheckedByMypyAnnotations(
1115
method: PythonMethod,
1216
functionArgAnnotations: Map<String, List<String>>,
13-
testSourcePath: String,
1417
moduleToImport: String,
1518
directoriesForSysPath: List<String>,
1619
pythonPath: String,
1720
isCancelled: () -> Boolean
1821
) = sequence {
19-
val codeFilename = "${testSourcePath}/__${method.name}__mypy_types.py"
20-
21-
generateMypyCheckCode(
22+
val fileWithCode = FileManager.assignTemporaryFile(tag = "mypy")
23+
val codeWithoutAnnotations = generateMypyCheckCode(
2224
method,
2325
emptyMap(),
24-
codeFilename,
2526
directoriesForSysPath,
2627
moduleToImport.split(".").last(),
2728
)
28-
startMypyDaemon(pythonPath, testSourcePath, directoriesForSysPath)
29-
val defaultOutput = runMypy(pythonPath, codeFilename, testSourcePath)
29+
FileManager.writeToAssignedFile(fileWithCode, codeWithoutAnnotations)
30+
31+
startMypyDaemon(pythonPath, directoriesForSysPath)
32+
val defaultOutput = runMypy(pythonPath, fileWithCode)
3033
val defaultErrorNum = getErrorNumber(defaultOutput)
3134

3235
val candidates = functionArgAnnotations.entries.map { (key, value) ->
3336
value.map {
3437
Pair(key, it)
3538
}
3639
}
37-
3840
if (candidates.any { it.isEmpty() })
3941
return@sequence
4042

4143
val annotationCandidates = PriorityCartesianProduct(candidates).getSequence()
42-
4344
annotationCandidates.forEach checkAnnotations@{
44-
4545
if (isCancelled())
4646
return@checkAnnotations
4747

4848
val annotationMap = it.toMap()
49-
val functionFile = generateMypyCheckCode(
49+
val codeWithAnnotations = generateMypyCheckCode(
5050
method,
5151
annotationMap,
52-
codeFilename,
5352
directoriesForSysPath,
5453
moduleToImport.split(".").last(),
5554
)
56-
val mypyOutput = runMypy(pythonPath, codeFilename, testSourcePath)
55+
FileManager.writeToAssignedFile(fileWithCode, codeWithAnnotations)
56+
val mypyOutput = runMypy(pythonPath, fileWithCode)
5757
val errorNum = getErrorNumber(mypyOutput)
5858
if (errorNum <= defaultErrorNum) {
5959
yield(annotationMap.mapValues { entry ->
6060
ClassId(entry.value)
6161
})
6262
}
63-
functionFile.deleteOnExit()
6463
}
64+
65+
fileWithCode.delete()
6566
}
6667

67-
private fun getConfigFile(testSourcePath: String): File = File(testSourcePath, "mypy.ini")
68+
private const val configFilename = "mypy.ini"
69+
private val configFile = FileManager.assignTemporaryFile(configFilename)
6870

6971
private fun runMypy(
7072
pythonPath: String,
71-
codeFilename: String,
72-
testSourcePath: String,
73+
fileWithCode: File
7374
): String {
74-
val command = "$pythonPath -m mypy.dmypy run $codeFilename -- --config-file ${getConfigFile(testSourcePath).path}"
75-
val process = Runtime.getRuntime().exec(
76-
command
77-
)
78-
process.waitFor()
79-
return process.inputStream.readBytes().decodeToString()
75+
val result = runCommand(listOf(
76+
pythonPath,
77+
"-m",
78+
"mypy.dmypy",
79+
"run",
80+
fileWithCode.path,
81+
"--",
82+
"--config-file",
83+
configFile.path
84+
))
85+
return result.stdout
8086
}
8187

8288
private fun startMypyDaemon(
8389
pythonPath: String,
84-
testSourcePath: String,
8590
directoriesForSysPath: List<String>
8691
): String {
87-
8892
val configContent = "[mypy]\nmypy_path = ${directoriesForSysPath.joinToString(separator = ":")}"
89-
val configFile = getConfigFile(testSourcePath)
90-
configFile.writeText(configContent)
91-
configFile.createNewFile()
93+
FileManager.writeToAssignedFile(configFile, configContent)
9294

93-
val command = "$pythonPath -m mypy.dmypy start"
94-
val process = Runtime.getRuntime().exec(
95-
command
96-
)
97-
process.waitFor()
98-
return process.inputStream.readBytes().decodeToString()
95+
val result = runCommand(listOf(pythonPath, "-m", "mypy.dmypy", "start"))
96+
return result.stdout
9997
}
10098

10199
private fun getErrorNumber(mypyOutput: String): Int {
102100
val regex = Regex("Found ([0-9]*) error")
103101
val match = regex.find(mypyOutput)
104102
return match?.groupValues?.getOrNull(1)?.toInt() ?: 0
105103
}
104+
105+
fun mypyInstalled(pythonPath: String): Boolean {
106+
val result = runCommand(listOf(pythonPath, "-m", "pip", "show", "mypy"))
107+
if (result.exitValue != 0)
108+
return false
109+
val regex = Regex("Version: ([0-9.]*)")
110+
val version = regex.find(result.stdout)?.groupValues?.getOrNull(1) ?: return false
111+
return version == mypyVersion
112+
}
113+
114+
fun installMypy(pythonPath: String): Int {
115+
val result = runCommand(listOf(pythonPath, "-m", "pip", "install", "mypy==" + mypyVersion))
116+
return result.exitValue
117+
}
106118
}
107119

0 commit comments

Comments
 (0)