@@ -6,17 +6,20 @@ import com.github.ajalt.clikt.parameters.options.*
66import com.github.ajalt.clikt.parameters.types.choice
77import com.github.ajalt.clikt.parameters.types.long
88import mu.KotlinLogging
9+ import org.parsers.python.PythonParser
910import org.utbot.framework.codegen.domain.TestFramework
10- import org.utbot.python.PythonMethod
11+ import org.utbot.python.PythonMethodHeader
1112import org.utbot.python.PythonTestGenerationProcessor
1213import org.utbot.python.PythonTestGenerationProcessor.processTestGeneration
13- import org.utbot.python.code.PythonClass
1414import org.utbot.python.code.PythonCode
15+ import org.utbot.python.framework.api.python.PythonClassId
1516import org.utbot.python.framework.codegen.model.Pytest
1617import org.utbot.python.framework.codegen.model.Unittest
18+ import org.utbot.python.newtyping.ast.parseClassDefinition
19+ import org.utbot.python.newtyping.ast.parseFunctionDefinition
20+ import org.utbot.python.utils.*
1721import org.utbot.python.utils.RequirementsUtils.installRequirements
1822import org.utbot.python.utils.RequirementsUtils.requirements
19- import org.utbot.python.utils.getModuleName
2023import java.io.File
2124import java.nio.file.Paths
2225
@@ -80,11 +83,6 @@ class PythonGenerateTestsCommand : CliktCommand(
8083 help = " Turn off Python requirements check (to speed up)."
8184 ).flag(default = false )
8285
83- private val visitOnlySpecifiedSource by option(
84- " --visit-only-specified-source" ,
85- help = " Do not search for classes and imported modules in other Python files from sys.path."
86- ).flag(default = false )
87-
8886 private val timeout by option(
8987 " -t" , " --timeout" ,
9088 help = " Specify the maximum time in milliseconds to spend on generating tests ($DEFAULT_TIMEOUT_IN_MILLIS by default)."
@@ -107,36 +105,33 @@ class PythonGenerateTestsCommand : CliktCommand(
107105 else -> error(" Not reachable" )
108106 }
109107
110- private fun findCurrentPythonModule (): Optional <String > {
111- directoriesForSysPath.forEach { path ->
112- val module = getModuleName(path.toAbsolutePath(), sourceFile.toAbsolutePath())
113- if (module != null )
114- return Success (module)
115- }
116- return Fail (" Couldn't find path for $sourceFile in --sys-path option. Please, specify it." )
117- }
118-
119108 private val forbiddenMethods = listOf (" __init__" , " __new__" )
120109
121- private fun getClassMethods ( pythonClassFromSources : PythonClass ): List <PythonMethod > =
122- pythonClassFromSources.methods.filter { method -> method.name !in forbiddenMethods }
110+ private fun getPythonMethods ( ): Optional < List <PythonMethodHeader >> {
111+ val parsedModule = PythonParser (sourceFileContent). Module ()
123112
124- private fun getPythonMethods (sourceCodeContent : String , currentModule : String ): Optional <List <PythonMethod >> {
125- val code = PythonCode .getFromString(
126- sourceCodeContent,
127- sourceFile.toAbsolutePath(),
128- pythonModule = currentModule
129- )
130- ? : return Fail (" Couldn't parse source file. Maybe it contains syntax error?" )
113+ val topLevelFunctions = PythonCode .getTopLevelFunctions(parsedModule)
114+ val topLevelClasses = PythonCode .getTopLevelClasses(parsedModule)
131115
132- val topLevelFunctions = code.getToplevelFunctions()
133- val topLevelClasses = code.getToplevelClasses()
134116 val selectedMethods = methods
135117 if (pythonClass == null && methods == null ) {
136118 return if (topLevelFunctions.isNotEmpty())
137- Success (topLevelFunctions)
119+ Success (
120+ topLevelFunctions
121+ .mapNotNull { parseFunctionDefinition(it) }
122+ .map { PythonMethodHeader (it.name.toString(), sourceFile, null ) }
123+ )
138124 else {
139- val topLevelClassMethods = topLevelClasses.flatMap { getClassMethods(it) }
125+ val topLevelClassMethods = topLevelClasses
126+ .mapNotNull { parseClassDefinition(it) }
127+ .flatMap { cls ->
128+ PythonCode .getClassMethods(cls.body)
129+ .mapNotNull { parseFunctionDefinition(it) }
130+ .map { function ->
131+ val parsedClassName = PythonClassId (cls.name.toString())
132+ PythonMethodHeader (function.name.toString(), sourceFile, parsedClassName)
133+ }
134+ }
140135 if (topLevelClassMethods.isNotEmpty()) {
141136 Success (topLevelClassMethods)
142137 } else
@@ -145,19 +140,29 @@ class PythonGenerateTestsCommand : CliktCommand(
145140 } else if (pythonClass == null && selectedMethods != null ) {
146141 val pythonMethodsOpt = selectedMethods.map { functionName ->
147142 topLevelFunctions
143+ .mapNotNull { parseFunctionDefinition(it) }
144+ .map { PythonMethodHeader (it.name.toString(), sourceFile, null ) }
148145 .find { it.name == functionName }
149146 ?.let { Success (it) }
150147 ? : Fail (" Couldn't find top-level function $functionName in the source file." )
151148 }
152149 return pack(* pythonMethodsOpt.toTypedArray())
153150 }
154151
155- val pythonClassFromSources = code.getToplevelClasses().find { it.name == pythonClass }
152+ val pythonClassFromSources = topLevelClasses
153+ .mapNotNull { parseClassDefinition(it) }
154+ .find { it.name.toString() == pythonClass }
156155 ?.let { Success (it) }
157156 ? : Fail (" Couldn't find class $pythonClass in the source file." )
158157
159- val methods = bind(pythonClassFromSources) {
160- val fineMethods: List <PythonMethod > = it.methods.filter { method -> method.name !in forbiddenMethods }
158+ val methods = bind(pythonClassFromSources) { parsedClass ->
159+ val parsedClassId = PythonClassId (parsedClass.name.toString())
160+ val methods = PythonCode .getClassMethods(parsedClass.body).mapNotNull { parseFunctionDefinition(it) }
161+ val fineMethods = methods
162+ .filter { ! forbiddenMethods.contains(it.name.toString()) }
163+ .map {
164+ PythonMethodHeader (it.name.toString(), sourceFile, parsedClassId)
165+ }
161166 if (fineMethods.isNotEmpty())
162167 Success (fineMethods)
163168 else
@@ -178,18 +183,18 @@ class PythonGenerateTestsCommand : CliktCommand(
178183 }
179184
180185 private lateinit var currentPythonModule: String
181- private lateinit var pythonMethods: List <PythonMethod >
186+ private lateinit var pythonMethods: List <PythonMethodHeader >
182187 private lateinit var sourceFileContent: String
183188
184189 @Suppress(" UNCHECKED_CAST" )
185190 private fun calculateValues (): Optional <Unit > {
186- val currentPythonModuleOpt = findCurrentPythonModule()
191+ val currentPythonModuleOpt = findCurrentPythonModule(directoriesForSysPath, sourceFile )
187192 sourceFileContent = File (sourceFile).readText()
188- val pythonMethodsOpt = bind(currentPythonModuleOpt) { getPythonMethods(sourceFileContent, it ) }
193+ val pythonMethodsOpt = bind(currentPythonModuleOpt) { getPythonMethods() }
189194
190195 return bind(pack(currentPythonModuleOpt, pythonMethodsOpt)) {
191196 currentPythonModule = it[0 ] as String
192- pythonMethods = it[1 ] as List <PythonMethod >
197+ pythonMethods = it[1 ] as List <PythonMethodHeader >
193198 Success (Unit )
194199 }
195200 }
@@ -235,12 +240,12 @@ class PythonGenerateTestsCommand : CliktCommand(
235240 timeout = timeout,
236241 testFramework = testFramework,
237242 timeoutForRun = timeoutForRun,
238- withMinimization = ! doNotMinimize,
239- doNotCheckRequirements = doNotCheckRequirements,
240- visitOnlySpecifiedSource = visitOnlySpecifiedSource,
241243 writeTestTextToFile = { generatedCode ->
242244 writeToFileAndSave(output, generatedCode)
243245 },
246+ pythonRunRoot = Paths .get(" " ).toAbsolutePath(),
247+ doNotCheckRequirements = doNotCheckRequirements,
248+ withMinimization = ! doNotMinimize,
244249 checkingRequirementsAction = {
245250 logger.info(" Checking requirements..." )
246251 },
@@ -263,17 +268,12 @@ class PythonGenerateTestsCommand : CliktCommand(
263268 )
264269 },
265270 processMypyWarnings = { messages -> messages.forEach { println (it) } },
266- finishedAction = {
267- logger.info(" Finished test generation for the following functions: ${it.joinToString()} " )
268- },
269271 processCoverageInfo = { coverageReport ->
270272 val output = coverageOutput ? : return @processTestGeneration
271273 writeToFileAndSave(output, coverageReport)
272- },
273- pythonRunRoot = Paths .get(" " ).toAbsolutePath()
274- )
274+ }
275+ ) {
276+ logger.info(" Finished test generation for the following functions: ${it.joinToString()} " )
277+ }
275278 }
276-
277- private fun String.toAbsolutePath (): String =
278- File (this ).canonicalPath
279279}
0 commit comments