|
| 1 | +package org.utbot.python.framework.external |
| 2 | + |
| 3 | +import mu.KLogger |
| 4 | +import mu.KotlinLogging |
| 5 | +import org.utbot.common.PathUtil.toPath |
| 6 | +import org.utbot.framework.UtSettings |
| 7 | +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour |
| 8 | +import org.utbot.framework.codegen.domain.TestFramework |
| 9 | +import org.utbot.python.* |
| 10 | +import org.utbot.python.framework.api.python.PythonClassId |
| 11 | +import org.utbot.python.framework.codegen.model.Pytest |
| 12 | +import org.utbot.python.framework.codegen.model.Unittest |
| 13 | +import org.utbot.python.utils.RequirementsInstaller |
| 14 | +import org.utbot.python.utils.Success |
| 15 | +import org.utbot.python.utils.findCurrentPythonModule |
| 16 | +import java.io.File |
| 17 | + |
| 18 | +object PythonUtBotJavaApi { |
| 19 | + private val logger: KLogger = KotlinLogging.logger {} |
| 20 | + |
| 21 | + /** |
| 22 | + * Generate test sets |
| 23 | + * |
| 24 | + * @param testMethods methods for test generation |
| 25 | + * @param pythonPath a path to the Python executable file |
| 26 | + * @param pythonRunRoot a path to the directory where test sets will be executed |
| 27 | + * @param directoriesForSysPath a collection of strings that specifies the additional search path for modules, usually it is only project root |
| 28 | + * @param timeout a timeout to the test generation process (in milliseconds) |
| 29 | + * @param executionTimeout a timeout to one concrete execution |
| 30 | + */ |
| 31 | + @JvmStatic |
| 32 | + fun generateTestSets ( |
| 33 | + testMethods: List<PythonTestMethodInfo>, |
| 34 | + pythonPath: String, |
| 35 | + pythonRunRoot: String, |
| 36 | + directoriesForSysPath: Collection<String>, |
| 37 | + timeout: Long, |
| 38 | + executionTimeout: Long = UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, |
| 39 | + ): List<PythonTestSet> { |
| 40 | + logger.info("Checking requirements...") |
| 41 | + |
| 42 | + val installer = RequirementsInstaller() |
| 43 | + RequirementsInstaller.checkRequirements( |
| 44 | + installer, |
| 45 | + pythonPath, |
| 46 | + emptyList() |
| 47 | + ) |
| 48 | + val processor = initPythonTestGeneratorProcessor( |
| 49 | + testMethods, |
| 50 | + pythonPath, |
| 51 | + pythonRunRoot, |
| 52 | + directoriesForSysPath.toSet(), |
| 53 | + timeout, |
| 54 | + executionTimeout, |
| 55 | + ) |
| 56 | + logger.info("Loading information about Python types...") |
| 57 | + val (mypyStorage, _) = processor.sourceCodeAnalyze() |
| 58 | + logger.info("Generating tests...") |
| 59 | + return processor.testGenerate(mypyStorage) |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Generate test sets code |
| 64 | + * |
| 65 | + * @param testSets a list of test sets |
| 66 | + * @param pythonRunRoot a path to the directory where test sets will be executed |
| 67 | + * @param directoriesForSysPath a collection of strings that specifies the additional search path for modules, usually it is only project root |
| 68 | + * @param testFramework a test framework (Unittest or Pytest) |
| 69 | + */ |
| 70 | + @JvmStatic |
| 71 | + fun renderTestSets ( |
| 72 | + testSets: List<PythonTestSet>, |
| 73 | + pythonRunRoot: String, |
| 74 | + directoriesForSysPath: Collection<String>, |
| 75 | + testFramework: TestFramework = Unittest, |
| 76 | + ): String { |
| 77 | + if (testSets.isEmpty()) return "" |
| 78 | + |
| 79 | + require(testFramework is Unittest || testFramework is Pytest) { "TestFramework should be Unittest or Pytest" } |
| 80 | + |
| 81 | + testSets.map { it.method.containingPythonClass } .toSet().let { |
| 82 | + require(it.size == 1) { "All test methods should be from one class or only top level" } |
| 83 | + it.first() |
| 84 | + } |
| 85 | + |
| 86 | + val containingFile = testSets.map { it.method.moduleFilename } .toSet().let { |
| 87 | + require(it.size == 1) { "All test methods should be from one module" } |
| 88 | + it.first() |
| 89 | + } |
| 90 | + val moduleUnderTest = findCurrentPythonModule(directoriesForSysPath, containingFile) |
| 91 | + require(moduleUnderTest is Success) |
| 92 | + |
| 93 | + val testMethods = testSets.map { it.method.toPythonMethodInfo() }.toSet().toList() |
| 94 | + |
| 95 | + val processor = initPythonTestGeneratorProcessor( |
| 96 | + testMethods = testMethods, |
| 97 | + pythonRunRoot = pythonRunRoot, |
| 98 | + directoriesForSysPath = directoriesForSysPath.toSet(), |
| 99 | + testFramework = testFramework, |
| 100 | + ) |
| 101 | + return processor.testCodeGenerate(testSets) |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * Generate test sets and render code |
| 106 | + * |
| 107 | + * @param testMethods methods for test generation |
| 108 | + * @param pythonPath a path to the Python executable file |
| 109 | + * @param pythonRunRoot a path to the directory where test sets will be executed |
| 110 | + * @param directoriesForSysPath a collection of strings that specifies the additional search path for modules, usually it is only project root |
| 111 | + * @param timeout a timeout to the test generation process (in milliseconds) |
| 112 | + * @param executionTimeout a timeout to one concrete execution |
| 113 | + * @param testFramework a test framework (Unittest or Pytest) |
| 114 | + */ |
| 115 | + @JvmStatic |
| 116 | + fun generate( |
| 117 | + testMethods: List<PythonTestMethodInfo>, |
| 118 | + pythonPath: String, |
| 119 | + pythonRunRoot: String, |
| 120 | + directoriesForSysPath: Collection<String>, |
| 121 | + timeout: Long, |
| 122 | + executionTimeout: Long = UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, |
| 123 | + testFramework: TestFramework = Unittest, |
| 124 | + ): String { |
| 125 | + val testSets = |
| 126 | + generateTestSets(testMethods, pythonPath, pythonRunRoot, directoriesForSysPath, timeout, executionTimeout) |
| 127 | + return renderTestSets(testSets, pythonRunRoot, directoriesForSysPath, testFramework) |
| 128 | + } |
| 129 | + |
| 130 | + private fun initPythonTestGeneratorProcessor ( |
| 131 | + testMethods: List<PythonTestMethodInfo>, |
| 132 | + pythonPath: String = "", |
| 133 | + pythonRunRoot: String, |
| 134 | + directoriesForSysPath: Set<String>, |
| 135 | + timeout: Long = 60_000, |
| 136 | + timeoutForRun: Long = 2_000, |
| 137 | + testFramework: TestFramework = Unittest, |
| 138 | + ): PythonTestGenerationProcessor { |
| 139 | + |
| 140 | + val pythonFilePath = testMethods.map { it.moduleFilename }.let { |
| 141 | + require(it.size == 1) {"All test methods should be from one file"} |
| 142 | + it.first() |
| 143 | + } |
| 144 | + val contentFile = File(pythonFilePath) |
| 145 | + val pythonFileContent = contentFile.readText() |
| 146 | + |
| 147 | + val pythonModule = testMethods.map { it.methodName.moduleName }.let { |
| 148 | + require(it.size == 1) {"All test methods should be from one module"} |
| 149 | + it.first() |
| 150 | + } |
| 151 | + |
| 152 | + val pythonMethods = testMethods.map { |
| 153 | + PythonMethodHeader( |
| 154 | + it.methodName.name, |
| 155 | + it.moduleFilename, |
| 156 | + it.containingClassName?.let { objName -> |
| 157 | + PythonClassId(objName.moduleName, objName.name) |
| 158 | + }) |
| 159 | + } |
| 160 | + |
| 161 | + return JavaApiProcessor( |
| 162 | + PythonTestGenerationConfig( |
| 163 | + pythonPath, |
| 164 | + TestFileInformation(pythonFilePath, pythonFileContent, pythonModule), |
| 165 | + directoriesForSysPath, |
| 166 | + pythonMethods, |
| 167 | + timeout, |
| 168 | + timeoutForRun, |
| 169 | + testFramework, |
| 170 | + pythonRunRoot.toPath(), |
| 171 | + true, |
| 172 | + { false }, |
| 173 | + RuntimeExceptionTestsBehaviour.FAIL |
| 174 | + ) |
| 175 | + ) |
| 176 | + } |
| 177 | +} |
0 commit comments