diff --git a/utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt b/utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt index 89728e7844..38ba2d34dc 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt @@ -3,171 +3,75 @@ package org.utbot.go import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import mu.KotlinLogging -import org.utbot.framework.plugin.api.TimeoutException import org.utbot.fuzzing.BaseFeedback import org.utbot.fuzzing.Control import org.utbot.fuzzing.utils.Trie -import org.utbot.go.api.* -import org.utbot.go.imports.GoImportsResolver -import org.utbot.go.logic.EachExecutionTimeoutsMillisConfig -import org.utbot.go.util.executeCommandByNewProcessOrFailWithoutWaiting +import org.utbot.go.api.GoUtExecutionResult +import org.utbot.go.api.GoUtFunction +import org.utbot.go.api.GoUtFuzzedFunction +import org.utbot.go.api.GoUtPanicFailure +import org.utbot.go.framework.api.go.GoPackage import org.utbot.go.worker.GoWorker -import org.utbot.go.worker.GoWorkerCodeGenerationHelper import org.utbot.go.worker.convertRawExecutionResultToExecutionResult -import java.io.File -import java.io.InputStreamReader -import java.net.ServerSocket -import java.net.SocketTimeoutException -import java.util.concurrent.TimeUnit val logger = KotlinLogging.logger {} class GoEngine( + private val worker: GoWorker, private val functionUnderTest: GoUtFunction, - private val sourceFile: GoUtFile, + private val aliases: Map, private val intSize: Int, - private val goExecutableAbsolutePath: String, - private val eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig, + private val eachExecutionTimeoutMillis: Long, private val timeoutExceededOrIsCanceled: () -> Boolean, - private val timeoutMillis: Long = 10000 ) { fun fuzzing(): Flow> = flow { var attempts = 0 val attemptsLimit = Int.MAX_VALUE - ServerSocket(0).use { serverSocket -> - var fileToExecute: File? = null - var fileWithModifiedFunction: File? = null - try { - // creating files for worker - val types = functionUnderTest.parameters.map { it.type } - val imports = GoImportsResolver.resolveImportsBasedOnTypes( - types, - functionUnderTest.sourcePackage, - GoWorkerCodeGenerationHelper.alwaysRequiredImports - ) - fileToExecute = GoWorkerCodeGenerationHelper.createFileToExecute( - sourceFile, - functionUnderTest, - eachExecutionTimeoutsMillisConfig, - serverSocket.localPort, - imports - ) - fileWithModifiedFunction = GoWorkerCodeGenerationHelper.createFileWithModifiedFunction( - sourceFile, functionUnderTest - ) - - // starting worker process - val testFunctionName = GoWorkerCodeGenerationHelper.workerTestFunctionName - val command = listOf( - goExecutableAbsolutePath, "test", "-run", testFunctionName + if (functionUnderTest.parameters.isEmpty()) { + worker.sendFuzzedParametersValues(functionUnderTest, emptyList(), emptyMap()) + val rawExecutionResult = worker.receiveRawExecutionResult() + val executionResult = convertRawExecutionResultToExecutionResult( + rawExecutionResult, + functionUnderTest.resultTypes, + intSize, + eachExecutionTimeoutMillis, + ) + val fuzzedFunction = GoUtFuzzedFunction(functionUnderTest, emptyList()) + emit(fuzzedFunction to executionResult) + } else { + val notCoveredLines = (1..functionUnderTest.numberOfAllStatements).toMutableSet() + runGoFuzzing(functionUnderTest, intSize) { description, values -> + if (timeoutExceededOrIsCanceled() || notCoveredLines.isEmpty()) { + return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.STOP) + } + val fuzzedFunction = GoUtFuzzedFunction(functionUnderTest, values) + worker.sendFuzzedParametersValues(functionUnderTest, values, aliases) + val rawExecutionResult = worker.receiveRawExecutionResult() + val executionResult = convertRawExecutionResultToExecutionResult( + rawExecutionResult, + functionUnderTest.resultTypes, + intSize, + eachExecutionTimeoutMillis, ) - val sourceFileDir = File(sourceFile.absoluteDirectoryPath) - val processStartTime = System.currentTimeMillis() - val process = executeCommandByNewProcessOrFailWithoutWaiting(command, sourceFileDir) - - try { - // connecting to worker - logger.debug { "Trying to connect to worker" } - val workerSocket = try { - serverSocket.soTimeout = timeoutMillis.toInt() - serverSocket.accept() - } catch (e: SocketTimeoutException) { - val processHasExited = process.waitFor(timeoutMillis, TimeUnit.MILLISECONDS) - if (processHasExited) { - val processOutput = InputStreamReader(process.inputStream).readText() - throw TimeoutException("Timeout exceeded: Worker not connected. Process output: $processOutput") - } else { - process.destroy() - } - throw TimeoutException("Timeout exceeded: Worker not connected") + if (executionResult.trace.isEmpty()) { + logger.error { "Coverage is empty for [${functionUnderTest.name}] with $values}" } + if (executionResult is GoUtPanicFailure) { + logger.error { "Execution completed with panic: ${executionResult.panicValue}" } } - val worker = GoWorker(workerSocket, functionUnderTest) - logger.debug { "Worker connected - completed in ${System.currentTimeMillis() - processStartTime} ms" } - - // fuzzing - if (functionUnderTest.parameters.isEmpty()) { - worker.sendFuzzedParametersValues(emptyList(), emptyMap()) - val rawExecutionResult = worker.receiveRawExecutionResult() - val executionResult = convertRawExecutionResultToExecutionResult( - rawExecutionResult, - functionUnderTest.resultTypes, - intSize, - eachExecutionTimeoutsMillisConfig[functionUnderTest], - ) - val fuzzedFunction = GoUtFuzzedFunction(functionUnderTest, emptyList()) - emit(fuzzedFunction to executionResult) - } else { - val aliases = imports.filter { it.alias != null }.associate { it.goPackage to it.alias } - runGoFuzzing(functionUnderTest, intSize) { description, values -> - if (timeoutExceededOrIsCanceled()) { - return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.STOP) - } - val fuzzedFunction = GoUtFuzzedFunction(functionUnderTest, values) - worker.sendFuzzedParametersValues(values, aliases) - val rawExecutionResult = worker.receiveRawExecutionResult() - val executionResult = convertRawExecutionResultToExecutionResult( - rawExecutionResult, - functionUnderTest.resultTypes, - intSize, - eachExecutionTimeoutsMillisConfig[functionUnderTest], - ) - if (executionResult.trace.isEmpty()) { - logger.error { "Coverage is empty for [${functionUnderTest.name}] with $values}" } - if (executionResult is GoUtPanicFailure) { - logger.error { "Execution completed with panic: ${executionResult.panicValue}" } - } - return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) - } - val trieNode = description.tracer.add(executionResult.trace.map { GoInstruction(it) }) - if (trieNode.count > 1) { - if (++attempts >= attemptsLimit) { - return@runGoFuzzing BaseFeedback( - result = Trie.emptyNode(), control = Control.STOP - ) - } - return@runGoFuzzing BaseFeedback(result = trieNode, control = Control.CONTINUE) - } - emit(fuzzedFunction to executionResult) - BaseFeedback(result = trieNode, control = Control.CONTINUE) - } - workerSocket.close() - val processHasExited = process.waitFor(timeoutMillis, TimeUnit.MILLISECONDS) - if (!processHasExited) { - process.destroy() - throw TimeoutException("Timeout exceeded: Worker didn't finish") - } - val exitCode = process.exitValue() - if (exitCode != 0) { - val processOutput = InputStreamReader(process.inputStream).readText() - throw RuntimeException( - StringBuilder() - .append("Execution of ${"function [${functionUnderTest.name}] from $sourceFile"} in child process failed with non-zero exit code = $exitCode: ") - .appendLine() - .append(processOutput).toString() - ) - } - } - } catch (e: Exception) { - val processHasExited = process.waitFor(timeoutMillis, TimeUnit.MILLISECONDS) - if (!processHasExited) { - process.destroy() - throw TimeoutException("Timeout exceeded: Worker didn't finish") - } - val exitCode = process.exitValue() - if (exitCode != 0) { - val processOutput = InputStreamReader(process.inputStream).readText() - throw RuntimeException( - StringBuilder() - .append("Execution of ${"function [${functionUnderTest.name}] from $sourceFile"} in child process failed with non-zero exit code = $exitCode: ") - .appendLine() - .append(processOutput).toString() - ) + return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) + } + val trieNode = description.tracer.add(executionResult.trace.map { GoInstruction(it) }) + if (trieNode.count > 1) { + if (++attempts >= attemptsLimit) { + return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.STOP) } + return@runGoFuzzing BaseFeedback(result = trieNode, control = Control.CONTINUE) + } + if (notCoveredLines.removeAll(executionResult.trace.toSet())) { + emit(fuzzedFunction to executionResult) } - } finally { - fileToExecute?.delete() - fileWithModifiedFunction?.delete() + BaseFeedback(result = trieNode, control = Control.CONTINUE) } } } diff --git a/utbot-go/src/main/kotlin/org/utbot/go/logic/AbstractGoUtTestsGenerationController.kt b/utbot-go/src/main/kotlin/org/utbot/go/logic/AbstractGoUtTestsGenerationController.kt index 899ebf802d..709c550e73 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/logic/AbstractGoUtTestsGenerationController.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/logic/AbstractGoUtTestsGenerationController.kt @@ -23,7 +23,7 @@ abstract class AbstractGoUtTestsGenerationController { val numOfFunctions = analysisResults.values .map { it.functions.size } .reduce { acc, numOfFunctions -> acc + numOfFunctions } - val functionTimeoutStepMillis = testsGenerationConfig.allExecutionTimeoutsMillisConfig / numOfFunctions + val functionTimeoutStepMillis = testsGenerationConfig.allFunctionExecutionTimeoutMillis / numOfFunctions var startTimeMillis = System.currentTimeMillis() val testCasesBySourceFiles = analysisResults.mapValues { (sourceFile, analysisResult) -> val functions = analysisResult.functions @@ -33,7 +33,7 @@ abstract class AbstractGoUtTestsGenerationController { functions, intSize, testsGenerationConfig.goExecutableAbsolutePath, - testsGenerationConfig.eachExecutionTimeoutsMillisConfig + testsGenerationConfig.eachFunctionExecutionTimeoutMillis ) { index -> isCanceled() || System.currentTimeMillis() - (startTimeMillis + (index + 1) * functionTimeoutStepMillis) > 0 } .also { startTimeMillis += functionTimeoutStepMillis * functions.size diff --git a/utbot-go/src/main/kotlin/org/utbot/go/logic/GoTestCasesGenerator.kt b/utbot-go/src/main/kotlin/org/utbot/go/logic/GoTestCasesGenerator.kt index 88ec34dc7c..aaf49f25a6 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/logic/GoTestCasesGenerator.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/logic/GoTestCasesGenerator.kt @@ -3,10 +3,21 @@ package org.utbot.go.logic import kotlinx.coroutines.flow.catch import kotlinx.coroutines.runBlocking import mu.KotlinLogging +import org.utbot.framework.plugin.api.TimeoutException import org.utbot.go.GoEngine import org.utbot.go.api.GoUtFile import org.utbot.go.api.GoUtFunction import org.utbot.go.api.GoUtFuzzedFunctionTestCase +import org.utbot.go.imports.GoImportsResolver +import org.utbot.go.util.executeCommandByNewProcessOrFailWithoutWaiting +import org.utbot.go.worker.GoWorker +import org.utbot.go.worker.GoWorkerCodeGenerationHelper +import java.io.File +import java.io.InputStreamReader +import java.net.ServerSocket +import java.net.SocketException +import java.net.SocketTimeoutException +import java.util.concurrent.TimeUnit import kotlin.system.measureTimeMillis val logger = KotlinLogging.logger {} @@ -18,30 +29,126 @@ object GoTestCasesGenerator { functions: List, intSize: Int, goExecutableAbsolutePath: String, - eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig, - timeoutExceededOrIsCanceled: (index: Int) -> Boolean + eachExecutionTimeoutMillis: Long, + connectionTimeoutMillis: Long = 10000, + endOfWorkerExecutionTimeout: Long = 5000, + timeoutExceededOrIsCanceled: (index: Int) -> Boolean = { false }, ): List = runBlocking { - return@runBlocking functions.flatMapIndexed { index, function -> - val testCases = mutableListOf() - if (timeoutExceededOrIsCanceled(index)) return@flatMapIndexed testCases - val engine = GoEngine( - function, - sourceFile, - intSize, - goExecutableAbsolutePath, - eachExecutionTimeoutsMillisConfig, - { timeoutExceededOrIsCanceled(index) } - ) - logger.info { "Fuzzing for function [${function.name}] - started" } - val totalFuzzingTime = measureTimeMillis { - engine.fuzzing().catch { - logger.error { "Error in flow: ${it.message}" } - }.collect { (fuzzedFunction, executionResult) -> - testCases.add(GoUtFuzzedFunctionTestCase(fuzzedFunction, executionResult)) + ServerSocket(0).use { serverSocket -> + val allTestCases = mutableListOf() + var fileToExecute: File? = null + var fileWithModifiedFunctions: File? = null + try { + // creating files for worker + val types = functions.flatMap { it.parameters }.map { it.type } + val imports = GoImportsResolver.resolveImportsBasedOnTypes( + types, + sourceFile.sourcePackage, + GoWorkerCodeGenerationHelper.alwaysRequiredImports + ) + val aliases = imports.filter { it.alias != null }.associate { it.goPackage to it.alias } + fileToExecute = GoWorkerCodeGenerationHelper.createFileToExecute( + sourceFile, + functions, + eachExecutionTimeoutMillis, + serverSocket.localPort, + imports + ) + fileWithModifiedFunctions = GoWorkerCodeGenerationHelper.createFileWithModifiedFunctions( + sourceFile, functions + ) + + // starting worker process + val testFunctionName = GoWorkerCodeGenerationHelper.workerTestFunctionName + val command = listOf( + goExecutableAbsolutePath, "test", "-run", testFunctionName + ) + val sourceFileDir = File(sourceFile.absoluteDirectoryPath) + val processStartTime = System.currentTimeMillis() + val process = executeCommandByNewProcessOrFailWithoutWaiting(command, sourceFileDir) + + try { + // connecting to worker + logger.debug { "Trying to connect to worker" } + val workerSocket = try { + serverSocket.soTimeout = connectionTimeoutMillis.toInt() + serverSocket.accept() + } catch (e: SocketTimeoutException) { + val processHasExited = process.waitFor(endOfWorkerExecutionTimeout, TimeUnit.MILLISECONDS) + if (processHasExited) { + val processOutput = InputStreamReader(process.inputStream).readText() + throw TimeoutException("Timeout exceeded: Worker not connected. Process output: $processOutput") + } else { + process.destroy() + } + throw TimeoutException("Timeout exceeded: Worker not connected") + } + val worker = GoWorker(workerSocket, sourceFile.sourcePackage) + logger.debug { "Worker connected - completed in ${System.currentTimeMillis() - processStartTime} ms" } + functions.forEachIndexed { index, function -> + if (timeoutExceededOrIsCanceled(index)) return@forEachIndexed + val testCases = mutableListOf() + val engine = GoEngine( + worker, + function, + aliases, + intSize, + eachExecutionTimeoutMillis + ) { timeoutExceededOrIsCanceled(index) } + logger.info { "Fuzzing for function [${function.name}] - started" } + val totalFuzzingTime = measureTimeMillis { + engine.fuzzing().catch { + logger.error { "Error in flow: ${it.message}" } + }.collect { (fuzzedFunction, executionResult) -> + testCases.add(GoUtFuzzedFunctionTestCase(fuzzedFunction, executionResult)) + } + } + logger.info { "Fuzzing for function [${function.name}] - completed in $totalFuzzingTime ms. Generated ${testCases.size} test cases" } + allTestCases += testCases + } + workerSocket.close() + val processHasExited = process.waitFor(endOfWorkerExecutionTimeout, TimeUnit.MILLISECONDS) + if (!processHasExited) { + process.destroy() + throw TimeoutException("Timeout exceeded: Worker didn't finish") + } + val exitCode = process.exitValue() + if (exitCode != 0) { + val processOutput = InputStreamReader(process.inputStream).readText() + throw RuntimeException( + StringBuilder() + .append("Execution of functions from $sourceFile in child process failed with non-zero exit code = $exitCode: ") + .appendLine() + .append(processOutput).toString() + ) + } + } catch (e: TimeoutException) { + logger.error { e.message } + } catch (e: RuntimeException) { + logger.error { e.message } + } catch (e: SocketException) { + val processHasExited = process.waitFor(endOfWorkerExecutionTimeout, TimeUnit.MILLISECONDS) + if (!processHasExited) { + process.destroy() + logger.error { "Timeout exceeded: Worker didn't finish" } + } + val exitCode = process.exitValue() + if (exitCode != 0) { + val processOutput = InputStreamReader(process.inputStream).readText() + logger.error { + StringBuilder() + .append("Execution of functions from $sourceFile in child process failed with non-zero exit code = $exitCode: ") + .appendLine() + .append(processOutput).toString() + } + } + } + } finally { + fileToExecute?.delete() + fileWithModifiedFunctions?.delete() } - logger.info { "Fuzzing for function [${function.name}] - completed in $totalFuzzingTime ms. Generated ${testCases.size} test cases" } - testCases + return@runBlocking allTestCases } } -} \ No newline at end of file +} diff --git a/utbot-go/src/main/kotlin/org/utbot/go/logic/GoUtTestsGenerationConfig.kt b/utbot-go/src/main/kotlin/org/utbot/go/logic/GoUtTestsGenerationConfig.kt index 4418a97b45..f56fb800df 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/logic/GoUtTestsGenerationConfig.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/logic/GoUtTestsGenerationConfig.kt @@ -1,25 +1,13 @@ package org.utbot.go.logic -import org.utbot.go.api.GoUtFunction - -data class EachExecutionTimeoutsMillisConfig(private val eachFunctionExecutionTimeoutMillis: Long) { - @Suppress("UNUSED_PARAMETER") // TODO: support finer tuning - operator fun get(function: GoUtFunction): Long = eachFunctionExecutionTimeoutMillis -} - class GoUtTestsGenerationConfig( val goExecutableAbsolutePath: String, - eachFunctionExecutionTimeoutMillis: Long, - allFunctionExecutionTimeoutMillis: Long + val eachFunctionExecutionTimeoutMillis: Long, + val allFunctionExecutionTimeoutMillis: Long ) { companion object Constants { const val DEFAULT_ALL_EXECUTION_TIMEOUT_MILLIS: Long = 60000 const val DEFAULT_EACH_EXECUTION_TIMEOUT_MILLIS: Long = 1000 } - - val eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig = - EachExecutionTimeoutsMillisConfig(eachFunctionExecutionTimeoutMillis) - - val allExecutionTimeoutsMillisConfig: Long = allFunctionExecutionTimeoutMillis } \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/worker/GoCodeTemplates.kt b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoCodeTemplates.kt index 62ce5e69fa..4b3d6cb810 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/worker/GoCodeTemplates.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoCodeTemplates.kt @@ -9,6 +9,13 @@ object GoCodeTemplates { var __traces__ []int """.trimIndent() + private val testInputStruct = """ + type __TestInput__ struct { + FunctionName string `json:"functionName"` + Arguments []map[string]interface{} `json:"arguments"` + } + """.trimIndent() + private val rawValueInterface = """ type __RawValue__ interface { __toReflectValue__() (reflect.Value, error) @@ -572,34 +579,25 @@ object GoCodeTemplates { } """.trimIndent() - private val parseJsonToRawValuesFunction = """ + private val parseJsonToFunctionNameAndRawValuesFunction = """ //goland:noinspection GoPreferNilSlice - func __parseJsonToRawValues__(decoder *json.Decoder) ([]__RawValue__, error) { - result := []__RawValue__{} - - // read '[' - _, err := decoder.Token() + func __parseJsonToFunctionNameAndRawValues__(decoder *json.Decoder) (string, []__RawValue__, error) { + var testInput __TestInput__ + err := decoder.Decode(&testInput) if err == io.EOF { - return nil, err + return "", nil, err } __checkErrorAndExit__(err) - for decoder.More() { - var p map[string]interface{} - err = decoder.Decode(&p) - __checkErrorAndExit__(err) - - rawValue, err := __convertParsedJsonToRawValue__(p) + result := make([]__RawValue__, 0) + for _, arg := range testInput.Arguments { + rawValue, err := __convertParsedJsonToRawValue__(arg) __checkErrorAndExit__(err) result = append(result, rawValue) } - // read ']' - _, err = decoder.Token() - __checkErrorAndExit__(err) - - return result, nil + return testInput.FunctionName, result, nil } """.trimIndent() @@ -773,6 +771,7 @@ object GoCodeTemplates { destinationPackage: GoPackage, aliases: Map ) = listOf( + testInputStruct, rawValueInterface, primitiveValueStruct, primitiveValueToReflectValueMethod, @@ -792,7 +791,7 @@ object GoCodeTemplates { executeFunctionFunction, wrapResultValuesForWorkerFunction, convertRawValuesToReflectValuesFunction, - parseJsonToRawValuesFunction, + parseJsonToFunctionNameAndRawValuesFunction, convertParsedJsonToRawValueFunction, convertParsedJsonToFieldValueFunction ) diff --git a/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorker.kt b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorker.kt index 9070c6e61a..c83b6dbc71 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorker.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorker.kt @@ -14,14 +14,24 @@ import java.net.Socket class GoWorker( socket: Socket, - val function: GoUtFunction + private val goPackage: GoPackage ) { private val reader: BufferedReader = BufferedReader(InputStreamReader(socket.getInputStream())) private val writer: BufferedWriter = BufferedWriter(OutputStreamWriter(socket.getOutputStream())) - fun sendFuzzedParametersValues(parameters: List, aliases: Map) { - val rawValues = parameters.map { it.convertToRawValue(function.sourcePackage, aliases) } - val json = convertObjectToJsonString(rawValues) + data class TestInput( + val functionName: String, + val arguments: List + ) + + fun sendFuzzedParametersValues( + function: GoUtFunction, + arguments: List, + aliases: Map + ) { + val rawValues = arguments.map { it.convertToRawValue(goPackage, aliases) } + val testCase = TestInput(function.modifiedName, rawValues) + val json = convertObjectToJsonString(testCase) writer.write(json) writer.flush() } diff --git a/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorkerCodeGenerationHelper.kt b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorkerCodeGenerationHelper.kt index 2eea2fcfde..b36835654a 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorkerCodeGenerationHelper.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorkerCodeGenerationHelper.kt @@ -5,7 +5,6 @@ import org.utbot.go.api.GoUtFunction import org.utbot.go.api.util.getAllStructTypes import org.utbot.go.framework.api.go.GoImport import org.utbot.go.framework.api.go.GoPackage -import org.utbot.go.logic.EachExecutionTimeoutsMillisConfig import org.utbot.go.simplecodegeneration.GoFileCodeBuilder import java.io.File @@ -32,8 +31,8 @@ internal object GoWorkerCodeGenerationHelper { fun createFileToExecute( sourceFile: GoUtFile, - function: GoUtFunction, - eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig, + functions: List, + eachExecutionTimeoutMillis: Long, port: Int, imports: Set ): File { @@ -42,44 +41,45 @@ internal object GoWorkerCodeGenerationHelper { val fileToExecute = sourceFileDir.resolve(fileToExecuteName) val fileToExecuteGoCode = - generateWorkerTestFileGoCode(function, eachExecutionTimeoutsMillisConfig, port, imports) + generateWorkerTestFileGoCode(sourceFile, functions, eachExecutionTimeoutMillis, port, imports) fileToExecute.writeText(fileToExecuteGoCode) return fileToExecute } - fun createFileWithModifiedFunction( + fun createFileWithModifiedFunctions( sourceFile: GoUtFile, - function: GoUtFunction + functions: List ): File { - val fileWithModifiedFunctionName = createFileWithModifiedFunctionName() + val fileWithModifiedFunctionsName = createFileWithModifiedFunctionsName() val sourceFileDir = File(sourceFile.absoluteDirectoryPath) - val fileWithModifiedFunction = sourceFileDir.resolve(fileWithModifiedFunctionName) + val fileWithModifiedFunctions = sourceFileDir.resolve(fileWithModifiedFunctionsName) - val fileWithModifiedFunctionGoCode = generateFileWithModifiedFunctionGoCode(function) - fileWithModifiedFunction.writeText(fileWithModifiedFunctionGoCode) - return fileWithModifiedFunction + val fileWithModifiedFunctionsGoCode = generateFileWithModifiedFunctionsGoCode(sourceFile, functions) + fileWithModifiedFunctions.writeText(fileWithModifiedFunctionsGoCode) + return fileWithModifiedFunctions } private fun createFileToExecuteName(sourceFile: GoUtFile): String { return "utbot_go_worker_${sourceFile.fileNameWithoutExtension}_test.go" } - private fun createFileWithModifiedFunctionName(): String { - return "utbot_go_modified_function.go" + private fun createFileWithModifiedFunctionsName(): String { + return "utbot_go_modified_functions.go" } private fun generateWorkerTestFileGoCode( - function: GoUtFunction, - eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig, + sourceFile: GoUtFile, + functions: List, + eachExecutionTimeoutMillis: Long, port: Int, imports: Set ): String { - val destinationPackage = function.sourcePackage + val destinationPackage = sourceFile.sourcePackage val fileCodeBuilder = GoFileCodeBuilder(destinationPackage, imports) - val workerTestFunctionCode = generateWorkerTestFunctionCode(function, eachExecutionTimeoutsMillisConfig, port) + val workerTestFunctionCode = generateWorkerTestFunctionCode(functions, eachExecutionTimeoutMillis, port) - val types = function.parameters.map { it.type } + val types = functions.flatMap { it.parameters }.map { it.type } val structTypes = types.getAllStructTypes() val aliases = imports.associate { it.goPackage to it.alias } @@ -94,20 +94,21 @@ internal object GoWorkerCodeGenerationHelper { return fileCodeBuilder.buildCodeString() } - private fun generateFileWithModifiedFunctionGoCode(function: GoUtFunction): String { - val destinationPackage = function.sourcePackage - val imports = function.requiredImports.toSet() + private fun generateFileWithModifiedFunctionsGoCode(sourceFile: GoUtFile, functions: List): String { + val destinationPackage = sourceFile.sourcePackage + val imports = functions.fold(emptySet()) { acc, function -> + acc + function.requiredImports + } val fileCodeBuilder = GoFileCodeBuilder(destinationPackage, imports) fileCodeBuilder.addTopLevelElements( - listOf(GoCodeTemplates.traces) + function.modifiedFunctionForCollectingTraces + listOf(GoCodeTemplates.traces) + functions.map { it.modifiedFunctionForCollectingTraces } ) return fileCodeBuilder.buildCodeString() } private fun generateWorkerTestFunctionCode( - function: GoUtFunction, eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig, port: Int + functions: List, eachExecutionTimeoutMillis: Long, port: Int ): String { - val timeoutMillis = eachExecutionTimeoutsMillisConfig[function] return """ func $workerTestFunctionName(t *testing.T) { con, err := net.Dial("tcp", ":$port") @@ -122,18 +123,28 @@ internal object GoWorkerCodeGenerationHelper { jsonDecoder := json.NewDecoder(con) for { - rawValues, err := __parseJsonToRawValues__(jsonDecoder) + funcName, rawValues, err := __parseJsonToFunctionNameAndRawValues__(jsonDecoder) if err == io.EOF { break } __checkErrorAndExit__(err) - parameters := __convertRawValuesToReflectValues__(rawValues) - function := reflect.ValueOf(${function.modifiedName}) + arguments := __convertRawValuesToReflectValues__(rawValues) + + var function reflect.Value + switch funcName { + ${ + functions.joinToString(separator = "\n") { function -> + "case \"${function.modifiedName}\": function = reflect.ValueOf(${function.modifiedName})" + } + } + default: + panic(fmt.Sprintf("no function with that name: %s", funcName)) + } - executionResult := __executeFunction__($timeoutMillis*time.Millisecond, func() []__RawValue__ { - __traces__ = make([]int, 0, 1000) - return __wrapResultValuesForUtBotGoWorker__(function.Call(parameters)) + executionResult := __executeFunction__($eachExecutionTimeoutMillis*time.Millisecond, func() []__RawValue__ { + __traces__ = make([]int, 0, 100) + return __wrapResultValuesForUtBotGoWorker__(function.Call(arguments)) }) jsonBytes, toJsonErr := json.MarshalIndent(executionResult, "", " ") diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/function_modifier.go b/utbot-go/src/main/resources/go_source_code_analyzer/function_modifier.go index ee7439252a..16f18bcf30 100644 --- a/utbot-go/src/main/resources/go_source_code_analyzer/function_modifier.go +++ b/utbot-go/src/main/resources/go_source_code_analyzer/function_modifier.go @@ -12,6 +12,8 @@ type FunctionModifier struct { func (v *FunctionModifier) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { + case *ast.FuncDecl: + n.Doc = nil case *ast.BlockStmt: if n == nil { n = &ast.BlockStmt{}