diff --git a/utbot-go/go-samples/go.mod b/utbot-go/go-samples/go.mod index 96da2c797d..8f59b4476d 100644 --- a/utbot-go/go-samples/go.mod +++ b/utbot-go/go-samples/go.mod @@ -2,10 +2,12 @@ module go-samples go 1.19 -require github.com/stretchr/testify v1.8.1 +require ( + github.com/pmezard/go-difflib v1.0.0 + github.com/stretchr/testify v1.8.1 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/utbot-go/go-samples/simple/supported_types.go b/utbot-go/go-samples/simple/supported_types.go index 1826169c72..db69c21e7b 100644 --- a/utbot-go/go-samples/simple/supported_types.go +++ b/utbot-go/go-samples/simple/supported_types.go @@ -3,6 +3,7 @@ package simple import ( "errors" "github.com/pmezard/go-difflib/difflib" + dif "github.com/pmezard/go-difflib/difflib" "math" ) @@ -172,3 +173,7 @@ func returnErrorOrNil(n int) error { func ExternalStruct(match difflib.Match, structure Structure) Structure { return structure } + +func ExternalStructWithAlias(match dif.Match) difflib.Match { + return match +} diff --git a/utbot-go/go-samples/simple/supported_types_go_ut_test.go b/utbot-go/go-samples/simple/supported_types_go_ut_test.go index 8612f59590..c8068b7579 100644 --- a/utbot-go/go-samples/simple/supported_types_go_ut_test.go +++ b/utbot-go/go-samples/simple/supported_types_go_ut_test.go @@ -226,3 +226,9 @@ func TestExternalStructByUtGoFuzzer(t *testing.T) { assert.Equal(t, Structure{int: -1, int8: 1, int16: -32768, int32: 2147483647, int64: 1, uint: 1, uint8: 1, uint16: 1, uint32: 1, uint64: 18446744073709551615, uintptr: 18446744073709551615, float32: 0.009224832, float64: 0.9644868606768501, complex64: complex(float32(0.009224832), float32(0.009224832)), complex128: complex(0.9644868606768501, 0.9644868606768501), byte: 1, rune: 0, string: "", bool: false}, actualVal) } + +func TestExternalStructWithAliasByUtGoFuzzer(t *testing.T) { + actualVal := ExternalStructWithAlias(difflib.Match{A: 9223372036854775807, B: -1, Size: -9223372036854775808}) + + assert.Equal(t, difflib.Match{A: 9223372036854775807, B: -1, Size: -9223372036854775808}, actualVal) +} \ No newline at end of file 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 e3aba5cd2d..469879dbfe 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt @@ -8,6 +8,7 @@ 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.worker.GoWorker @@ -16,14 +17,13 @@ import org.utbot.go.worker.convertRawExecutionResultToExecutionResult 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 val logger = KotlinLogging.logger {} class GoEngine( - private val methodUnderTest: GoUtFunction, + private val functionUnderTest: GoUtFunction, private val sourceFile: GoUtFile, private val goExecutableAbsolutePath: String, private val eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig, @@ -36,13 +36,27 @@ class GoEngine( 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, - methodUnderTest, + functionUnderTest, eachExecutionTimeoutsMillisConfig, - serverSocket.localPort + serverSocket.localPort, + imports + ) + fileWithModifiedFunction = GoWorkerCodeGenerationHelper.createFileWithModifiedFunction( + sourceFile, functionUnderTest ) + + // starting worker process val testFunctionName = GoWorkerCodeGenerationHelper.workerTestFunctionName val command = listOf( goExecutableAbsolutePath, "test", "-run", testFunctionName @@ -50,49 +64,53 @@ class GoEngine( val sourceFileDir = File(sourceFile.absoluteDirectoryPath) val processStartTime = System.currentTimeMillis() val process = executeCommandByNewProcessOrFailWithoutWaiting(command, sourceFileDir) - 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") - } - logger.debug { "Worker connected - completed in ${System.currentTimeMillis() - processStartTime} ms" } + try { - val worker = GoWorker(workerSocket) - if (methodUnderTest.parameters.isEmpty()) { - worker.sendFuzzedParametersValues(listOf()) + // 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") + } + 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( - methodUnderTest.getPackageName(), rawExecutionResult, - methodUnderTest.resultTypes, - eachExecutionTimeoutsMillisConfig[methodUnderTest], + functionUnderTest.resultTypes, + eachExecutionTimeoutsMillisConfig[functionUnderTest], ) - val fuzzedFunction = GoUtFuzzedFunction(methodUnderTest, listOf()) + val fuzzedFunction = GoUtFuzzedFunction(functionUnderTest, emptyList()) emit(fuzzedFunction to executionResult) } else { - runGoFuzzing(methodUnderTest) { description, values -> + val aliases = imports.filter { it.alias != null }.associate { it.goPackage to it.alias } + runGoFuzzing(functionUnderTest) { description, values -> if (timeoutExceededOrIsCanceled()) { return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.STOP) } - val fuzzedFunction = GoUtFuzzedFunction(methodUnderTest, values) - worker.sendFuzzedParametersValues(values) + val fuzzedFunction = GoUtFuzzedFunction(functionUnderTest, values) + worker.sendFuzzedParametersValues(values, aliases) val rawExecutionResult = worker.receiveRawExecutionResult() val executionResult = convertRawExecutionResultToExecutionResult( - methodUnderTest.getPackageName(), rawExecutionResult, - methodUnderTest.resultTypes, - eachExecutionTimeoutsMillisConfig[methodUnderTest], + functionUnderTest.resultTypes, + eachExecutionTimeoutsMillisConfig[functionUnderTest], ) if (executionResult.trace.isEmpty()) { - logger.error { "Coverage is empty for [${methodUnderTest.name}] with $values}" } + logger.error { "Coverage is empty for [${functionUnderTest.name}] with $values}" } if (executionResult is GoUtPanicFailure) { logger.error { "Execution completed with panic: ${executionResult.panicValue}" } } @@ -102,8 +120,7 @@ class GoEngine( if (trieNode.count > 1) { if (++attempts >= attemptsLimit) { return@runGoFuzzing BaseFeedback( - result = Trie.emptyNode(), - control = Control.STOP + result = Trie.emptyNode(), control = Control.STOP ) } return@runGoFuzzing BaseFeedback(result = trieNode, control = Control.CONTINUE) @@ -122,13 +139,13 @@ class GoEngine( val processOutput = InputStreamReader(process.inputStream).readText() throw RuntimeException( StringBuilder() - .append("Execution of ${"function [${methodUnderTest.name}] from $sourceFile"} in child process failed with non-zero exit code = $exitCode: ") - .append("\n$processOutput") - .toString() + .append("Execution of ${"function [${functionUnderTest.name}] from $sourceFile"} in child process failed with non-zero exit code = $exitCode: ") + .appendLine() + .append(processOutput).toString() ) } } - } catch (e: SocketException) { + } catch (e: Exception) { val processHasExited = process.waitFor(timeoutMillis, TimeUnit.MILLISECONDS) if (!processHasExited) { process.destroy() @@ -139,14 +156,15 @@ class GoEngine( val processOutput = InputStreamReader(process.inputStream).readText() throw RuntimeException( StringBuilder() - .append("Execution of ${"function [${methodUnderTest.name}] from $sourceFile"} in child process failed with non-zero exit code = $exitCode: ") - .append("\n$processOutput") - .toString() + .append("Execution of ${"function [${functionUnderTest.name}] from $sourceFile"} in child process failed with non-zero exit code = $exitCode: ") + .appendLine() + .append(processOutput).toString() ) } } } finally { fileToExecute?.delete() + fileWithModifiedFunction?.delete() } } } diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/GoTypesApi.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/GoTypesApi.kt index eaa3a620c4..fc7247bcb0 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/api/GoTypesApi.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/GoTypesApi.kt @@ -1,16 +1,16 @@ package org.utbot.go.api import org.utbot.go.framework.api.go.GoFieldId +import org.utbot.go.framework.api.go.GoPackage import org.utbot.go.framework.api.go.GoTypeId /** * Represents real Go primitive type. */ class GoPrimitiveTypeId(name: String) : GoTypeId(name) { - override val packageName: String = "" override val canonicalName: String = simpleName - override fun getRelativeName(packageName: String): String = simpleName + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String = simpleName override fun equals(other: Any?): Boolean { if (this === other) return true @@ -25,16 +25,22 @@ class GoPrimitiveTypeId(name: String) : GoTypeId(name) { class GoStructTypeId( name: String, implementsError: Boolean, - override val packageName: String, - val packagePath: String, + override val sourcePackage: GoPackage, val fields: List, ) : GoTypeId(name, implementsError = implementsError) { - override val canonicalName: String = "$packageName.$name" - - override fun getRelativeName(packageName: String): String = if (this.packageName != packageName) { - canonicalName - } else { - simpleName + val packageName: String = sourcePackage.packageName + val packagePath: String = sourcePackage.packagePath + override val canonicalName: String = "${sourcePackage.packageName}.$name" + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String { + val alias = aliases[sourcePackage] + return if (sourcePackage == destinationPackage || alias == ".") { + simpleName + } else if (alias == null) { + "${packageName}.${simpleName}" + } else { + "${alias}.${simpleName}" + } } override fun equals(other: Any?): Boolean { @@ -53,14 +59,12 @@ class GoStructTypeId( } class GoArrayTypeId( - name: String, - elementTypeId: GoTypeId, - val length: Int + name: String, elementTypeId: GoTypeId, val length: Int ) : GoTypeId(name, elementTypeId = elementTypeId) { override val canonicalName: String = "[$length]${elementTypeId.canonicalName}" - override fun getRelativeName(packageName: String): String = - "[$length]${elementTypeId!!.getRelativeName(packageName)}" + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String = + "[$length]${elementTypeId!!.getRelativeName(destinationPackage, aliases)}" override fun equals(other: Any?): Boolean { if (this === other) return true @@ -75,19 +79,25 @@ class GoArrayTypeId( class GoInterfaceTypeId( name: String, implementsError: Boolean, - override val packageName: String, - val packagePath: String, + override val sourcePackage: GoPackage, ) : GoTypeId(name, implementsError = implementsError) { + val packageName: String = sourcePackage.packageName + val packagePath: String = sourcePackage.packagePath override val canonicalName: String = if (packageName != "") { "$packageName.$name" } else { simpleName } - override fun getRelativeName(packageName: String): String = if (this.packageName != packageName) { - canonicalName - } else { - simpleName + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String { + val alias = aliases[sourcePackage] + return if (sourcePackage == destinationPackage || alias == ".") { + simpleName + } else if (alias == null) { + "${packageName}.${simpleName}" + } else { + "${alias}.${simpleName}" + } } override fun equals(other: Any?): Boolean { diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtFunctionApi.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtFunctionApi.kt index dfcbac38ca..9b211338da 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtFunctionApi.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtFunctionApi.kt @@ -1,12 +1,13 @@ package org.utbot.go.api -import org.utbot.fuzzer.FuzzedConcreteValue +import org.utbot.go.framework.api.go.GoImport +import org.utbot.go.framework.api.go.GoPackage import org.utbot.go.framework.api.go.GoTypeId import org.utbot.go.framework.api.go.GoUtModel import java.io.File import java.nio.file.Paths -data class GoUtFile(val absolutePath: String, val packageName: String) { +data class GoUtFile(val absolutePath: String, val sourcePackage: GoPackage) { val fileName: String get() = File(absolutePath).name val fileNameWithoutExtension: String get() = File(absolutePath).nameWithoutExtension val absoluteDirectoryPath: String get() = Paths.get(absolutePath).parent.toString() @@ -19,11 +20,14 @@ data class GoUtFunction( val modifiedName: String, val parameters: List, val resultTypes: List, + val requiredImports: List, val modifiedFunctionForCollectingTraces: String, val numberOfAllStatements: Int, val sourceFile: GoUtFile ) { - fun getPackageName(): String = sourceFile.packageName + val sourcePackage: GoPackage = sourceFile.sourcePackage + + fun getPackageName(): String = sourceFile.sourcePackage.packageName } data class GoUtFuzzedFunction(val function: GoUtFunction, val parametersValues: List) diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtModelsApi.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtModelsApi.kt index d9337f51a8..71c3691e62 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtModelsApi.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtModelsApi.kt @@ -6,6 +6,7 @@ import org.utbot.go.api.util.goDefaultValueModel import org.utbot.go.api.util.goFloat64TypeId import org.utbot.go.api.util.goStringTypeId import org.utbot.go.api.util.neverRequiresExplicitCast +import org.utbot.go.framework.api.go.GoPackage import org.utbot.go.framework.api.go.GoTypeId import org.utbot.go.framework.api.go.GoUtFieldModel import org.utbot.go.framework.api.go.GoUtModel @@ -18,117 +19,73 @@ enum class ExplicitCastMode { open class GoUtPrimitiveModel( val value: Any, typeId: GoPrimitiveTypeId, - val explicitCastMode: ExplicitCastMode = - if (typeId.neverRequiresExplicitCast) { - ExplicitCastMode.NEVER - } else { - ExplicitCastMode.DEPENDS - }, - private val requiredImports: Set = emptySet(), + val explicitCastMode: ExplicitCastMode = if (typeId.neverRequiresExplicitCast) { + ExplicitCastMode.NEVER + } else { + ExplicitCastMode.DEPENDS + }, + private val requiredPackages: Set = emptySet(), ) : GoUtModel(typeId) { override val typeId: GoPrimitiveTypeId get() = super.typeId as GoPrimitiveTypeId - override fun getRequiredImports(): Set = requiredImports + override fun getRequiredPackages(destinationPackage: GoPackage): Set = requiredPackages override fun isComparable(): Boolean = true - override fun toString(): String = when (explicitCastMode) { - ExplicitCastMode.REQUIRED -> toCastedValueGoCode() - ExplicitCastMode.DEPENDS, ExplicitCastMode.NEVER -> toValueGoCode() - } - - open fun toValueGoCode(): String = if (typeId == goStringTypeId) "\"$value\"" else "$value" - fun toCastedValueGoCode(): String = "$typeId(${toValueGoCode()})" + override fun toString(): String = if (typeId == goStringTypeId) "\"${value}\"" else "$value" } -abstract class GoUtCompositeModel( - typeId: GoTypeId, - val packageName: String, -) : GoUtModel(typeId) - class GoUtStructModel( val value: List, typeId: GoStructTypeId, - packageName: String, -) : GoUtCompositeModel(typeId, packageName) { +) : GoUtModel(typeId) { override val typeId: GoStructTypeId get() = super.typeId as GoStructTypeId - override fun getRequiredImports(): Set { - val imports = if (typeId.packageName != packageName) { - mutableSetOf(typeId.packagePath) - } else { - mutableSetOf() + fun getVisibleFields(destinationPackage: GoPackage): List = + value.filter { typeId.sourcePackage == destinationPackage || it.fieldId.isExported } + + override fun getRequiredPackages(destinationPackage: GoPackage): Set = + getVisibleFields(destinationPackage).fold(setOf(typeId.sourcePackage)) { acc, fieldModel -> + acc + fieldModel.getRequiredPackages(destinationPackage) } - value.filter { packageName == typeId.packageName || it.fieldId.isExported } - .map { it.getRequiredImports() } - .forEach { imports += it } - return imports - } override fun isComparable(): Boolean = value.all { it.isComparable() } - fun toStringWithoutStructName(): String = - value.filter { packageName == typeId.packageName || it.fieldId.isExported } - .joinToString(prefix = "{", postfix = "}") { "${it.fieldId.name}: ${it.model}" } - - override fun toString(): String = - "${typeId.getRelativeName(packageName)}${toStringWithoutStructName()}" + override fun toString(): String = value.joinToString(prefix = "$typeId{", postfix = "}") { + "${it.fieldId.name}: ${it.model}" + } } class GoUtArrayModel( val value: MutableMap, typeId: GoArrayTypeId, - packageName: String, -) : GoUtCompositeModel(typeId, packageName) { +) : GoUtModel(typeId) { val length: Int = typeId.length override val typeId: GoArrayTypeId get() = super.typeId as GoArrayTypeId - override fun getRequiredImports(): Set { + override fun getRequiredPackages(destinationPackage: GoPackage): Set { val elementStructTypeId = typeId.elementTypeId as? GoStructTypeId - val imports = if (elementStructTypeId != null && elementStructTypeId.packageName != packageName) { - mutableSetOf(elementStructTypeId.packagePath) + val imports = if (elementStructTypeId != null && elementStructTypeId.sourcePackage != destinationPackage) { + mutableSetOf(elementStructTypeId.sourcePackage) } else { mutableSetOf() } - value.values.map { it.getRequiredImports() }.forEach { imports += it } + value.values.map { it.getRequiredPackages(destinationPackage) }.forEach { imports += it } return imports } override fun isComparable(): Boolean = value.values.all { it.isComparable() } - fun getElements(typeId: GoTypeId): List = (0 until length).map { - value[it] ?: typeId.goDefaultValueModel(packageName) + fun getElements(): List = (0 until length).map { + value[it] ?: typeId.elementTypeId!!.goDefaultValueModel() } - fun toStringWithoutTypeName(): String = when (val typeId = typeId.elementTypeId!!) { - is GoStructTypeId -> getElements(typeId).joinToString(prefix = "{", postfix = "}") { - (it as GoUtStructModel).toStringWithoutStructName() - } - - is GoArrayTypeId -> getElements(typeId).joinToString(prefix = "{", postfix = "}") { - (it as GoUtArrayModel).toStringWithoutTypeName() - } - - else -> getElements(typeId).joinToString(prefix = "{", postfix = "}") - } - - override fun toString(): String = when (val typeId = typeId.elementTypeId!!) { - is GoStructTypeId -> getElements(typeId) - .joinToString(prefix = "[$length]${typeId.getRelativeName(packageName)}{", postfix = "}") { - (it as GoUtStructModel).toStringWithoutStructName() - } - - is GoArrayTypeId -> getElements(typeId) - .joinToString(prefix = "[$length]${typeId.getRelativeName(packageName)}{", postfix = "}") { - (it as GoUtArrayModel).toStringWithoutTypeName() - } - - else -> getElements(typeId) - .joinToString(prefix = "[$length]${typeId.getRelativeName(packageName)}{", postfix = "}") + override fun toString(): String = getElements().joinToString(prefix = "$typeId{", postfix = "}") { + it.toString() } } @@ -142,14 +99,13 @@ class GoUtFloatNaNModel( } else { ExplicitCastMode.NEVER }, - requiredImports = setOf("math"), + requiredPackages = setOf(GoPackage("math", "math")), ) { override fun isComparable(): Boolean = false } class GoUtFloatInfModel( - val sign: Int, - typeId: GoPrimitiveTypeId + val sign: Int, typeId: GoPrimitiveTypeId ) : GoUtPrimitiveModel( "math.Inf($sign)", typeId, @@ -158,7 +114,7 @@ class GoUtFloatInfModel( } else { ExplicitCastMode.NEVER }, - requiredImports = setOf("math"), + requiredPackages = setOf(GoPackage("math", "math")), ) class GoUtComplexModel( @@ -168,11 +124,12 @@ class GoUtComplexModel( ) : GoUtPrimitiveModel( "complex($realValue, $imagValue)", typeId, - requiredImports = realValue.getRequiredImports() + imagValue.getRequiredImports(), explicitCastMode = ExplicitCastMode.NEVER ) { + override fun getRequiredPackages(destinationPackage: GoPackage): Set = + realValue.getRequiredPackages(destinationPackage) + imagValue.getRequiredPackages(destinationPackage) + override fun isComparable(): Boolean = realValue.isComparable() && imagValue.isComparable() - override fun toValueGoCode(): String = "complex($realValue, $imagValue)" } class GoUtNilModel( diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoTypesApiUtil.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoTypesApiUtil.kt index 1740891fb2..7427a294e9 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoTypesApiUtil.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoTypesApiUtil.kt @@ -92,7 +92,7 @@ val GoPrimitiveTypeId.correspondingKClass: KClass else -> String::class // default way to hold GoUtPrimitiveModel's value is to use String } -fun GoTypeId.goDefaultValueModel(packageName: String): GoUtModel = when (this) { +fun GoTypeId.goDefaultValueModel(): GoUtModel = when (this) { is GoPrimitiveTypeId -> when (this) { goBoolTypeId -> GoUtPrimitiveModel(false, this) goRuneTypeId, goIntTypeId, goInt8TypeId, goInt16TypeId, goInt32TypeId, goInt64TypeId -> GoUtPrimitiveModel( @@ -107,8 +107,8 @@ fun GoTypeId.goDefaultValueModel(packageName: String): GoUtModel = when (this) { goFloat32TypeId, goFloat64TypeId -> GoUtPrimitiveModel(0.0, this) goComplex64TypeId, goComplex128TypeId -> GoUtComplexModel( - goFloat64TypeId.goDefaultValueModel(packageName) as GoUtPrimitiveModel, - goFloat64TypeId.goDefaultValueModel(packageName) as GoUtPrimitiveModel, + goFloat64TypeId.goDefaultValueModel() as GoUtPrimitiveModel, + goFloat64TypeId.goDefaultValueModel() as GoUtPrimitiveModel, this ) @@ -118,7 +118,20 @@ fun GoTypeId.goDefaultValueModel(packageName: String): GoUtModel = when (this) { else -> error("Go primitive ${this.javaClass} is not supported") } - is GoStructTypeId -> GoUtStructModel(listOf(), this, packageName) - is GoArrayTypeId -> GoUtArrayModel(hashMapOf(), this, packageName) + is GoStructTypeId -> GoUtStructModel(listOf(), this) + is GoArrayTypeId -> GoUtArrayModel(hashMapOf(), this) else -> GoUtNilModel(this) -} \ No newline at end of file +} + +fun GoTypeId.getAllStructTypes(): Set = when (this) { + is GoStructTypeId -> fields.fold(setOf(this)) { acc: Set, field -> + acc + (field.declaringType).getAllStructTypes() + } + + is GoArrayTypeId -> elementTypeId!!.getAllStructTypes() + else -> emptySet() +} + +fun List.getAllStructTypes(): Set = this.fold(emptySet()) { acc, type -> + acc + type.getAllStructTypes() +} diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoUtModelsApiUtil.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoUtModelsApiUtil.kt index 602fb24c01..556c4edd81 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoUtModelsApiUtil.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoUtModelsApiUtil.kt @@ -1,9 +1,12 @@ package org.utbot.go.api.util -import org.utbot.go.api.GoUtComplexModel -import org.utbot.go.api.GoUtFloatInfModel -import org.utbot.go.api.GoUtFloatNaNModel +import org.utbot.go.api.* +import org.utbot.go.framework.api.go.GoPackage import org.utbot.go.framework.api.go.GoUtModel +import org.utbot.go.worker.ArrayValue +import org.utbot.go.worker.PrimitiveValue +import org.utbot.go.worker.RawValue +import org.utbot.go.worker.StructValue fun GoUtModel.isNaNOrInf(): Boolean = this is GoUtFloatNaNModel || this is GoUtFloatInfModel @@ -13,4 +16,33 @@ fun GoUtModel.doesNotContainNaNOrInf(): Boolean { return !(asComplexModel.realValue.isNaNOrInf() || asComplexModel.imagValue.isNaNOrInf()) } -fun GoUtModel.containsNaNOrInf(): Boolean = !this.doesNotContainNaNOrInf() \ No newline at end of file +fun GoUtModel.containsNaNOrInf(): Boolean = !this.doesNotContainNaNOrInf() + +fun GoUtModel.convertToRawValue(destinationPackage: GoPackage, aliases: Map): RawValue = when (val model = this) { + is GoUtComplexModel -> PrimitiveValue( + model.typeId.getRelativeName(destinationPackage, aliases), + "${model.realValue}@${model.imagValue}" + ) + + is GoUtArrayModel -> ArrayValue( + model.typeId.getRelativeName(destinationPackage, aliases), + model.typeId.elementTypeId!!.getRelativeName(destinationPackage, aliases), + model.length, + model.getElements().map { it.convertToRawValue(destinationPackage, aliases) } + ) + + is GoUtStructModel -> StructValue( + model.typeId.getRelativeName(destinationPackage, aliases), + model.value.map { + StructValue.FieldValue( + it.fieldId.name, + it.model.convertToRawValue(destinationPackage, aliases), + it.fieldId.isExported + ) + } + ) + + is GoUtPrimitiveModel -> PrimitiveValue(model.typeId.name, model.value.toString()) + + else -> error("Converting ${model.javaClass} to RawValue is not supported") +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/framework/api/go/GoApi.kt b/utbot-go/src/main/kotlin/org/utbot/go/framework/api/go/GoApi.kt index 424e611318..c2f0ed3ce2 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/framework/api/go/GoApi.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/framework/api/go/GoApi.kt @@ -10,11 +10,11 @@ abstract class GoTypeId( val elementTypeId: GoTypeId? = null, val implementsError: Boolean = false ) { - open val packageName: String = "" + open val sourcePackage: GoPackage = GoPackage("", "") val simpleName: String = name abstract val canonicalName: String - abstract fun getRelativeName(packageName: String): String + abstract fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String override fun toString(): String = canonicalName } @@ -26,7 +26,7 @@ abstract class GoTypeId( abstract class GoUtModel( open val typeId: GoTypeId, ) { - open fun getRequiredImports(): Set = emptySet() + open fun getRequiredPackages(destinationPackage: GoPackage): Set = emptySet() abstract fun isComparable(): Boolean } @@ -37,7 +37,9 @@ class GoUtFieldModel( val model: GoUtModel, val fieldId: GoFieldId, ) : GoUtModel(fieldId.declaringType) { - override fun getRequiredImports(): Set = model.getRequiredImports() + override fun getRequiredPackages(destinationPackage: GoPackage): Set = + model.getRequiredPackages(destinationPackage) + override fun isComparable(): Boolean = model.isComparable() } @@ -49,3 +51,26 @@ class GoFieldId( val name: String, val isExported: Boolean ) + +/** + * Class for Go package. + */ +data class GoPackage( + val packageName: String, + val packagePath: String +) + +/** + * Class for Go import. + */ +data class GoImport( + val goPackage: GoPackage, + val alias: String? = null +) { + override fun toString(): String { + if (alias == null) { + return "\"${goPackage.packagePath}\"" + } + return "$alias \"${goPackage.packagePath}\"" + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoArrayValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoArrayValueProvider.kt index f47e78c12a..ccc7317cde 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoArrayValueProvider.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoArrayValueProvider.kt @@ -15,14 +15,12 @@ object GoArrayValueProvider : ValueProvider override fun generate(description: GoDescription, type: GoTypeId): Sequence> = sequence { type.let { it as GoArrayTypeId }.also { arrayType -> - val packageName = description.methodUnderTest.getPackageName() yield( Seed.Collection( construct = Routine.Collection { GoUtArrayModel( value = hashMapOf(), typeId = arrayType, - packageName = packageName ) }, modify = Routine.ForEach(listOf(arrayType.elementTypeId!!)) { self, i, values -> diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoStructValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoStructValueProvider.kt index 9fc47d5693..229f6e4a25 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoStructValueProvider.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoStructValueProvider.kt @@ -16,9 +16,10 @@ object GoStructValueProvider : ValueProvider override fun generate(description: GoDescription, type: GoTypeId): Sequence> = sequence { type.let { it as GoStructTypeId }.also { structType -> - val packageName = description.methodUnderTest.getPackageName() + val function = description.methodUnderTest + val destinationPackage = function.sourcePackage val fields = structType.fields - .filter { structType.packageName == packageName || it.isExported } + .filter { structType.sourcePackage == destinationPackage || it.isExported } yield(Seed.Recursive( construct = Routine.Create(fields.map { it.declaringType }) { values -> GoUtStructModel( @@ -26,7 +27,6 @@ object GoStructValueProvider : ValueProvider GoUtFieldModel(value, field) }, typeId = structType, - packageName = packageName, ) }, modify = sequence { @@ -42,7 +42,6 @@ object GoStructValueProvider : ValueProvider GoUtStructModel( value = emptyList(), typeId = structType, - packageName = packageName ) } )) diff --git a/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisResults.kt b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisResults.kt index 9f47c3b177..1f0eb8a246 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisResults.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisResults.kt @@ -8,6 +8,8 @@ import org.utbot.go.api.GoPrimitiveTypeId import org.utbot.go.api.GoStructTypeId import org.utbot.go.api.util.goPrimitives import org.utbot.go.framework.api.go.GoFieldId +import org.utbot.go.framework.api.go.GoImport +import org.utbot.go.framework.api.go.GoPackage import org.utbot.go.framework.api.go.GoTypeId import kotlin.reflect.KClass @@ -21,8 +23,7 @@ data class AnalyzedInterfaceType( GoInterfaceTypeId( name = simpleName, implementsError = implementsError, - packageName = packageName, - packagePath = packagePath + sourcePackage = GoPackage(packageName, packagePath) ) private val simpleName: String = name.replaceFirst("interface ", "") @@ -49,8 +50,7 @@ data class AnalyzedStructType( override fun toGoTypeId(): GoTypeId = GoStructTypeId( name = name, - packageName = packageName, - packagePath = packagePath, + sourcePackage = GoPackage(packageName, packagePath), implementsError = implementsError, fields = fields.map { field -> GoFieldId(field.type.toGoTypeId(), field.name, field.isExported) } ) @@ -94,13 +94,14 @@ internal data class AnalyzedFunction( val modifiedName: String, val parameters: List, val resultTypes: List, + val requiredImports: List, val modifiedFunctionForCollectingTraces: String, val numberOfAllStatements: Int ) internal data class AnalysisResult( val absoluteFilePath: String, - val packageName: String, + val sourcePackage: GoPackage, val analyzedFunctions: List, val notSupportedFunctionsNames: List, val notFoundFunctionsNames: List diff --git a/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/GoSourceCodeAnalyzer.kt b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/GoSourceCodeAnalyzer.kt index cc394dc2a5..836a40bb5b 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/GoSourceCodeAnalyzer.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/GoSourceCodeAnalyzer.kt @@ -65,7 +65,7 @@ object GoSourceCodeAnalyzer { val analysisResults = parseFromJsonOrFail(analysisResultsFile) GoPrimitivesValueProvider.intSize = analysisResults.intSize return analysisResults.results.map { analysisResult -> - GoUtFile(analysisResult.absoluteFilePath, analysisResult.packageName) to analysisResult + GoUtFile(analysisResult.absoluteFilePath, analysisResult.sourcePackage) to analysisResult }.associateBy({ (sourceFile, _) -> sourceFile }) { (sourceFile, analysisResult) -> val functions = analysisResult.analyzedFunctions.map { analyzedFunction -> val parameters = analyzedFunction.parameters.map { analyzedFunctionParameter -> @@ -80,6 +80,7 @@ object GoSourceCodeAnalyzer { analyzedFunction.modifiedName, parameters, resultTypes, + analyzedFunction.requiredImports, analyzedFunction.modifiedFunctionForCollectingTraces, analyzedFunction.numberOfAllStatements, sourceFile @@ -115,7 +116,14 @@ object GoSourceCodeAnalyzer { } private fun getGoCodeAnalyzerSourceFilesNames(): List { - return listOf("main.go", "analyzer_core.go", "analysis_targets.go", "analysis_results.go", "cover.go") + return listOf( + "main.go", + "analyzer_core.go", + "analysis_targets.go", + "analysis_results.go", + "function_modifier.go", + "imports_collector.go" + ) } private fun createAnalysisTargetsFileName(): String { diff --git a/utbot-go/src/main/kotlin/org/utbot/go/imports/GoImportsResolver.kt b/utbot-go/src/main/kotlin/org/utbot/go/imports/GoImportsResolver.kt new file mode 100644 index 0000000000..9fff1c5edc --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/imports/GoImportsResolver.kt @@ -0,0 +1,56 @@ +package org.utbot.go.imports + +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.framework.api.go.GoTypeId + +object GoImportsResolver { + + fun resolveImportsBasedOnTypes( + types: List, + sourcePackage: GoPackage, + busyImports: Set = emptySet() + ): Set { + val structTypes = types.getAllStructTypes() + val result = busyImports.toMutableSet() + val busyAliases = busyImports.map { it.alias ?: it.goPackage.packageName }.toMutableSet() + structTypes.map { it.sourcePackage }.distinct().filter { it != sourcePackage }.forEach { goPackage -> + val alias = if (goPackage.packageName in busyAliases) { + var n = 1 + while (goPackage.packageName + n in busyAliases) { + n++ + } + goPackage.packageName + n + } else { + null + } + busyAliases += alias ?: goPackage.packageName + result += GoImport(goPackage, alias) + } + return result + } + + fun resolveImportsBasedOnRequiredPackages( + requiredPackages: Set, + sourcePackage: GoPackage, + busyImports: Set = emptySet() + ): Set { + val result = busyImports.toMutableSet() + val busyAliases = busyImports.map { it.alias ?: it.goPackage.packageName }.toMutableSet() + requiredPackages.distinct().filter { it != sourcePackage }.forEach { goPackage -> + val alias = if (goPackage.packageName in busyAliases) { + var n = 1 + while (goPackage.packageName + n in busyAliases) { + n++ + } + goPackage.packageName + n + } else { + null + } + busyAliases += alias ?: goPackage.packageName + result += GoImport(goPackage, alias) + } + return result + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoCodeGenerationUtil.kt b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoCodeGenerationUtil.kt deleted file mode 100644 index 8042203c6c..0000000000 --- a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoCodeGenerationUtil.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.utbot.go.simplecodegeneration - -import org.utbot.go.api.ExplicitCastMode -import org.utbot.go.api.GoPrimitiveTypeId -import org.utbot.go.api.GoUtFuzzedFunction -import org.utbot.go.api.GoUtPrimitiveModel -import org.utbot.go.framework.api.go.GoUtModel - - -fun generateFuzzedFunctionCall(functionName: String, parameters: List): String { - val fuzzedParametersToString = parameters.joinToString(prefix = "(", postfix = ")") { it.toString() } - return "$functionName$fuzzedParametersToString" -} - -fun generateVariablesDeclarationTo(variablesNames: List, expression: String): String { - val variables = variablesNames.joinToString() - return "$variables := $expression" -} - -fun generateFuzzedFunctionCallSavedToVariables( - variablesNames: List, - fuzzedFunction: GoUtFuzzedFunction -): String = generateVariablesDeclarationTo( - variablesNames, - generateFuzzedFunctionCall(fuzzedFunction.function.name, fuzzedFunction.parametersValues) -) - -fun generateCastIfNeed(toTypeId: GoPrimitiveTypeId, expressionType: GoPrimitiveTypeId, expression: String): String { - return if (expressionType != toTypeId) { - "${toTypeId.name}($expression)" - } else { - expression - } -} - -fun generateCastedValueIfPossible(model: GoUtPrimitiveModel): String { - return if (model.explicitCastMode == ExplicitCastMode.NEVER) { - model.toValueGoCode() - } else { - model.toCastedValueGoCode() - } -} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoFileCodeBuilder.kt b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoFileCodeBuilder.kt index 53538c618d..d4040c713e 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoFileCodeBuilder.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoFileCodeBuilder.kt @@ -1,22 +1,26 @@ package org.utbot.go.simplecodegeneration +import org.utbot.go.framework.api.go.GoImport +import org.utbot.go.framework.api.go.GoPackage + class GoFileCodeBuilder( - packageName: String, - importNames: Set, + sourcePackage: GoPackage, + imports: Set, ) { - private val packageLine: String = "package $packageName" - private val importLines: String = importLines(importNames) + private val packageLine: String = "package ${sourcePackage.packageName}" + private val importLines: String = importLines(imports) private val topLevelElements: MutableList = mutableListOf() - private fun importLines(importNames: Set): String { - val sortedImportNames = importNames.toList().sorted() - if (sortedImportNames.isEmpty()) return "" - if (sortedImportNames.size == 1) { - return "import ${sortedImportNames.first()}" - } - return sortedImportNames.joinToString(separator = "", prefix = "import (\n", postfix = ")") { - "\t\"$it\"\n" + private fun importLines(imports: Set): String { + if (imports.isEmpty()) return "" + if (imports.size == 1) { + return "import ${imports.first()}" } + + return imports.sortedWith(compareBy { it.goPackage.packagePath }.thenBy { it.alias }) + .joinToString(separator = "", prefix = "import (\n", postfix = ")") { + "\t$it\n" + } } fun buildCodeString(): String { diff --git a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoTestCasesCodeGenerator.kt b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoTestCasesCodeGenerator.kt index 639984aca3..04357e628e 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoTestCasesCodeGenerator.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoTestCasesCodeGenerator.kt @@ -5,41 +5,52 @@ import org.utbot.go.api.util.containsNaNOrInf import org.utbot.go.api.util.doesNotContainNaNOrInf import org.utbot.go.api.util.goFloat64TypeId import org.utbot.go.api.util.goStringTypeId +import org.utbot.go.framework.api.go.GoImport +import org.utbot.go.framework.api.go.GoPackage import org.utbot.go.framework.api.go.GoUtModel +import org.utbot.go.imports.GoImportsResolver object GoTestCasesCodeGenerator { + private val alwaysRequiredImports = setOf( + GoImport(GoPackage("assert", "github.com/stretchr/testify/assert")), GoImport(GoPackage("testing", "testing")) + ) + fun generateTestCasesFileCode(sourceFile: GoUtFile, testCases: List): String { - val imports = if (testCases.isNotEmpty()) { - mutableSetOf("github.com/stretchr/testify/assert", "testing") - } else { - mutableSetOf() + val destinationPackage = sourceFile.sourcePackage + if (testCases.isEmpty() || testCases.all { it.executionResult is GoUtTimeoutExceeded }) { + return GoFileCodeBuilder(destinationPackage, emptySet()).buildCodeString() } + val requiredPackages = mutableSetOf() testCases.forEach { testCase -> testCase.parametersValues.forEach { - imports += it.getRequiredImports() + requiredPackages += it.getRequiredPackages(destinationPackage) } when (val executionResult = testCase.executionResult) { - is GoUtExecutionCompleted -> { - executionResult.models.forEach { - imports += it.getRequiredImports() - } + is GoUtExecutionCompleted -> executionResult.models.forEach { + requiredPackages += it.getRequiredPackages(destinationPackage) } - is GoUtPanicFailure -> { - imports += executionResult.panicValue.getRequiredImports() - } + is GoUtPanicFailure -> requiredPackages += executionResult.panicValue.getRequiredPackages( + destinationPackage + ) } } - val fileBuilder = GoFileCodeBuilder(sourceFile.packageName, imports) + val imports = GoImportsResolver.resolveImportsBasedOnRequiredPackages( + requiredPackages, destinationPackage, alwaysRequiredImports + ) + val fileBuilder = GoFileCodeBuilder(destinationPackage, imports) + val aliases = imports.associate { (goPackage, alias) -> goPackage to alias } + val goUtModelToCodeConverter = GoUtModelToCodeConverter(destinationPackage, aliases) fun List.generateTestFunctions( - generateTestFunctionForTestCase: (GoUtFuzzedFunctionTestCase, Int?) -> String + generateTestFunctionForTestCase: (GoUtFuzzedFunctionTestCase, Int?, GoUtModelToCodeConverter) -> String, ) { this.forEachIndexed { testIndex, testCase -> val testIndexToShow = if (this.size == 1) null else testIndex + 1 - val testFunctionCode = generateTestFunctionForTestCase(testCase, testIndexToShow) + val testFunctionCode = + generateTestFunctionForTestCase(testCase, testIndexToShow, goUtModelToCodeConverter) fileBuilder.addTopLevelElements(testFunctionCode) } } @@ -57,7 +68,7 @@ object GoTestCasesCodeGenerator { } private fun generateTestFunctionForCompletedExecutionTestCase( - testCase: GoUtFuzzedFunctionTestCase, testIndexToShow: Int? + testCase: GoUtFuzzedFunctionTestCase, testIndexToShow: Int?, goUtModelToCodeConverter: GoUtModelToCodeConverter ): String { val (fuzzedFunction, executionResult) = testCase val function = fuzzedFunction.function @@ -72,14 +83,13 @@ object GoTestCasesCodeGenerator { "func Test${function.name.replaceFirstChar(Char::titlecaseChar)}${testFunctionNamePostfix}ByUtGoFuzzer$testIndexToShowString(t *testing.T)" if (function.resultTypes.isEmpty()) { - val actualFunctionCall = - generateFuzzedFunctionCall(fuzzedFunction.function.name, fuzzedFunction.parametersValues) + val actualFunctionCall = generateFuzzedFunctionCall( + fuzzedFunction.function.name, fuzzedFunction.parametersValues, goUtModelToCodeConverter + ) val testFunctionBody = "\tassert.NotPanics(t, func() { $actualFunctionCall })\n" return "$testFunctionSignatureDeclaration {\n$testFunctionBody}" } - val testFunctionBodySb = StringBuilder() - val resultTypes = function.resultTypes val doResultTypesImplementError = resultTypes.map { it.implementsError } val actualResultVariablesNames = run { @@ -96,9 +106,11 @@ object GoTestCasesCodeGenerator { } } } - val actualFunctionCall = generateFuzzedFunctionCallSavedToVariables(actualResultVariablesNames, fuzzedFunction) - testFunctionBodySb.append("\t$actualFunctionCall\n\n") + val actualFunctionCall = generateFuzzedFunctionCallSavedToVariables( + actualResultVariablesNames, fuzzedFunction, goUtModelToCodeConverter + ) + val testFunctionBodySb = StringBuilder("\t$actualFunctionCall\n\n") val expectedModels = (executionResult as GoUtExecutionCompleted).models val (assertionName, assertionTParameter) = if (expectedModels.size > 1 || expectedModels.any { it.isComplexModelAndNeedsSeparateAssertions() }) { testFunctionBodySb.append("\tassertMultiple := assert.New(t)\n") @@ -113,7 +125,11 @@ object GoTestCasesCodeGenerator { val assertionCalls = mutableListOf() fun generateAssertionCallHelper(refinedExpectedModel: GoUtModel, actualResultCode: String) { val code = generateCompletedExecutionAssertionCall( - refinedExpectedModel, actualResultCode, doesResultTypeImplementError, assertionTParameter + refinedExpectedModel, + actualResultCode, + doesResultTypeImplementError, + assertionTParameter, + goUtModelToCodeConverter ) assertionCalls.add(code) } @@ -139,7 +155,8 @@ object GoTestCasesCodeGenerator { expectedModel: GoUtModel, actualResultCode: String, doesReturnTypeImplementError: Boolean, - assertionTParameter: String + assertionTParameter: String, + goUtModelToCodeConverter: GoUtModelToCodeConverter ): String { if (expectedModel is GoUtNilModel) { return "Nil($assertionTParameter$actualResultCode)" @@ -156,27 +173,28 @@ object GoTestCasesCodeGenerator { return "True(${assertionTParameter}math.IsInf($castedActualResultCode, ${expectedModel.sign}))" } val prefix = if (!expectedModel.isComparable()) "Not" else "" - val castedExpectedResultCode = - if (expectedModel is GoUtPrimitiveModel) { - generateCastedValueIfPossible(expectedModel) - } else { - expectedModel.toString() - } + val castedExpectedResultCode = if (expectedModel is GoUtPrimitiveModel) { + generateCastedValueIfPossible(expectedModel, goUtModelToCodeConverter) + } else { + goUtModelToCodeConverter.toGoCode(expectedModel) + } + return "${prefix}Equal($assertionTParameter$castedExpectedResultCode, $actualResultCode)" } private fun generateTestFunctionForPanicFailureTestCase( - testCase: GoUtFuzzedFunctionTestCase, testIndexToShow: Int? + testCase: GoUtFuzzedFunctionTestCase, testIndexToShow: Int?, goUtModelToCodeConverter: GoUtModelToCodeConverter ): String { val (fuzzedFunction, executionResult) = testCase val function = fuzzedFunction.function val testIndexToShowString = testIndexToShow ?: "" val testFunctionSignatureDeclaration = - "func Test${function.name.capitalize()}PanicsByUtGoFuzzer$testIndexToShowString(t *testing.T)" + "func Test${function.name.replaceFirstChar(Char::titlecaseChar)}PanicsByUtGoFuzzer$testIndexToShowString(t *testing.T)" - val actualFunctionCall = - generateFuzzedFunctionCall(fuzzedFunction.function.name, fuzzedFunction.parametersValues) + val actualFunctionCall = generateFuzzedFunctionCall( + fuzzedFunction.function.name, fuzzedFunction.parametersValues, goUtModelToCodeConverter + ) val actualFunctionCallLambda = "func() { $actualFunctionCall }" val (expectedPanicValue, isErrorMessage) = (executionResult as GoUtPanicFailure) val isPrimitiveWithOkEquals = @@ -185,9 +203,9 @@ object GoTestCasesCodeGenerator { "\tassert.PanicsWithError(t, $expectedPanicValue, $actualFunctionCallLambda)" } else if (isPrimitiveWithOkEquals || expectedPanicValue is GoUtNilModel) { val expectedPanicValueCode = if (expectedPanicValue is GoUtNilModel) { - "$expectedPanicValue" + goUtModelToCodeConverter.toGoCode(expectedPanicValue) } else { - generateCastedValueIfPossible(expectedPanicValue as GoUtPrimitiveModel) + generateCastedValueIfPossible(expectedPanicValue as GoUtPrimitiveModel, goUtModelToCodeConverter) } "\tassert.PanicsWithValue(t, $expectedPanicValueCode, $actualFunctionCallLambda)" } else { @@ -198,12 +216,61 @@ object GoTestCasesCodeGenerator { } private fun generateTestFunctionForTimeoutExceededTestCase( - testCase: GoUtFuzzedFunctionTestCase, @Suppress("UNUSED_PARAMETER") testIndexToShow: Int? + testCase: GoUtFuzzedFunctionTestCase, + @Suppress("UNUSED_PARAMETER") testIndexToShow: Int?, + goUtModelToCodeConverter: GoUtModelToCodeConverter ): String { val (fuzzedFunction, executionResult) = testCase - val actualFunctionCall = - generateFuzzedFunctionCall(fuzzedFunction.function.name, fuzzedFunction.parametersValues) + val actualFunctionCall = generateFuzzedFunctionCall( + fuzzedFunction.function.name, fuzzedFunction.parametersValues, goUtModelToCodeConverter + ) val exceededTimeoutMillis = (executionResult as GoUtTimeoutExceeded).timeoutMillis return "// $actualFunctionCall exceeded $exceededTimeoutMillis ms timeout" } + + private fun generateFuzzedFunctionCall( + functionName: String, parameters: List, goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String { + val fuzzedParametersToString = parameters.joinToString(prefix = "(", postfix = ")") { + goUtModelToCodeConverter.toGoCode(it) + } + return "$functionName$fuzzedParametersToString" + } + + private fun generateVariablesDeclarationTo(variablesNames: List, expression: String): String { + val variables = variablesNames.joinToString() + return "$variables := $expression" + } + + private fun generateFuzzedFunctionCallSavedToVariables( + variablesNames: List, + fuzzedFunction: GoUtFuzzedFunction, + goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String = generateVariablesDeclarationTo( + variablesNames, + generateFuzzedFunctionCall( + fuzzedFunction.function.name, fuzzedFunction.parametersValues, goUtModelToCodeConverter + ) + ) + + private fun generateCastIfNeed( + toTypeId: GoPrimitiveTypeId, expressionType: GoPrimitiveTypeId, expression: String + ): String { + return if (expressionType != toTypeId) { + "${toTypeId.name}($expression)" + } else { + expression + } + } + + fun generateCastedValueIfPossible( + model: GoUtPrimitiveModel, + goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String { + return if (model.explicitCastMode == ExplicitCastMode.NEVER) { + goUtModelToCodeConverter.primitiveModelToValueGoCode(model) + } else { + goUtModelToCodeConverter.primitiveModelToCastedValueGoCode(model) + } + } } \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoUtModelToCodeConverter.kt b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoUtModelToCodeConverter.kt new file mode 100644 index 0000000000..78cc5c07ae --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoUtModelToCodeConverter.kt @@ -0,0 +1,85 @@ +package org.utbot.go.simplecodegeneration + +import org.utbot.go.api.* +import org.utbot.go.api.util.goStringTypeId +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoUtModel + +class GoUtModelToCodeConverter( + private val destinationPackage: GoPackage, + private val aliases: Map +) { + + fun toGoCode(model: GoUtModel): String = when (model) { + is GoUtNilModel -> "nil" + + is GoUtPrimitiveModel -> when (model.explicitCastMode) { + ExplicitCastMode.REQUIRED -> primitiveModelToCastedValueGoCode(model) + ExplicitCastMode.DEPENDS, ExplicitCastMode.NEVER -> primitiveModelToValueGoCode(model) + } + + is GoUtStructModel -> { + val typeName = model.typeId.getRelativeName(destinationPackage, aliases) + "$typeName${structModelToGoCodeWithoutStructName(model)}" + } + + is GoUtArrayModel -> arrayModelToGoCode(model) + + else -> error("Converting a $javaClass to Go code isn't supported") + } + + fun primitiveModelToValueGoCode(model: GoUtPrimitiveModel): String = when (model) { + is GoUtComplexModel -> complexModeToValueGoCode(model) + else -> if (model.typeId == goStringTypeId) "\"${model.value}\"" else "${model.value}" + } + + fun primitiveModelToCastedValueGoCode(model: GoUtPrimitiveModel): String = + "${model.typeId}(${primitiveModelToValueGoCode(model)})" + + private fun complexModeToValueGoCode(model: GoUtComplexModel) = + "complex(${toGoCode(model.realValue)}, ${toGoCode(model.imagValue)})" + + private fun structModelToGoCodeWithoutStructName(model: GoUtStructModel): String = + model.getVisibleFields(destinationPackage).joinToString(prefix = "{", postfix = "}") { + "${it.fieldId.name}: ${toGoCode(it.model)}" + } + + private fun arrayModelToGoCode(model: GoUtArrayModel): String = + when (val elementType = model.typeId.elementTypeId!!) { + is GoStructTypeId -> model.getElements().joinToString( + prefix = "[${model.length}]${ + elementType.getRelativeName(destinationPackage, aliases) + }{", + postfix = "}" + ) { + structModelToGoCodeWithoutStructName(it as GoUtStructModel) + } + + is GoArrayTypeId -> model.getElements().joinToString( + prefix = "[${model.length}]${ + elementType.getRelativeName(destinationPackage, aliases) + }{", + postfix = "}" + ) { + arrayModelToGoCodeWithoutTypeName(it as GoUtArrayModel) + } + + else -> model.getElements().joinToString( + prefix = "[${model.length}]${elementType.getRelativeName(destinationPackage, aliases)}{", + postfix = "}" + ) + } + + private fun arrayModelToGoCodeWithoutTypeName(model: GoUtArrayModel): String = + when (model.typeId.elementTypeId!!) { + is GoStructTypeId -> model.getElements().joinToString(prefix = "{", postfix = "}") { + structModelToGoCodeWithoutStructName(it as GoUtStructModel) + } + + is GoArrayTypeId -> model.getElements().joinToString(prefix = "{", postfix = "}") { + arrayModelToGoCodeWithoutTypeName(it as GoUtArrayModel) + } + + else -> model.getElements().joinToString(prefix = "{", postfix = "}") + } +} 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 261d16dbc2..b5f4752b48 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 @@ -1,10 +1,11 @@ package org.utbot.go.worker import org.utbot.go.api.GoStructTypeId +import org.utbot.go.framework.api.go.GoPackage object GoCodeTemplates { - private val traces = """ + val traces = """ var __traces__ []int """.trimIndent() @@ -207,7 +208,11 @@ object GoCodeTemplates { } """.trimIndent() - private fun convertStringToReflectType(structTypes: Set, packageName: String) = """ + private fun convertStringToReflectType( + structTypes: Set, + destinationPackage: GoPackage, + aliases: Map + ) = """ func __convertStringToReflectType__(typeName string) (reflect.Type, error) { var result reflect.Type @@ -274,7 +279,27 @@ object GoCodeTemplates { result = reflect.TypeOf("") case "uintptr": result = reflect.TypeOf(uintptr(0)) - ${structTypes.joinToString(separator = "\n") { "case \"${it.canonicalName}\": result = reflect.TypeOf(${it.getRelativeName(packageName)}{})" }} + ${ + structTypes.joinToString(separator = "\n") { + "case \"${ + if (it.sourcePackage == destinationPackage || aliases[it.sourcePackage] == ".") { + it.simpleName + } else if (aliases[it.sourcePackage] == null) { + "${it.packageName}.${it.simpleName}" + } else { + "${aliases[it.sourcePackage]}.${it.simpleName}" + } + }\": result = reflect.TypeOf(${ + if (it.sourcePackage == destinationPackage || aliases[it.sourcePackage] == ".") { + it.simpleName + } else if (aliases[it.sourcePackage] == null) { + "${it.packageName}.${it.simpleName}" + } else { + "${aliases[it.sourcePackage]}.${it.simpleName}" + } + }{})" + } + } default: return nil, fmt.Errorf("type '%s' not supported", typeName) } @@ -661,8 +686,11 @@ object GoCodeTemplates { } """.trimIndent() - fun getTopLevelHelperStructsAndFunctionsForWorker(structTypes: Set, packageName: String) = listOf( - traces, + fun getTopLevelHelperStructsAndFunctionsForWorker( + structTypes: Set, + destinationPackage: GoPackage, + aliases: Map + ) = listOf( rawValueInterface, primitiveValueStruct, primitiveValueToReflectValueMethod, @@ -671,7 +699,7 @@ object GoCodeTemplates { structValueToReflectValueMethod, arrayValueStruct, arrayValueToReflectValueMethod, - convertStringToReflectType(structTypes, packageName), + convertStringToReflectType(structTypes, destinationPackage, aliases), panicMessageStruct, rawExecutionResultStruct, checkErrorFunction, 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 902c3acf55..9070c6e61a 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 @@ -1,10 +1,9 @@ package org.utbot.go.worker import com.beust.klaxon.Klaxon -import org.utbot.go.api.GoUtArrayModel -import org.utbot.go.api.GoUtComplexModel -import org.utbot.go.api.GoUtPrimitiveModel -import org.utbot.go.api.GoUtStructModel +import org.utbot.go.api.GoUtFunction +import org.utbot.go.api.util.convertToRawValue +import org.utbot.go.framework.api.go.GoPackage import org.utbot.go.framework.api.go.GoUtModel import org.utbot.go.util.convertObjectToJsonString import java.io.BufferedReader @@ -15,41 +14,13 @@ import java.net.Socket class GoWorker( socket: Socket, + val function: GoUtFunction ) { private val reader: BufferedReader = BufferedReader(InputStreamReader(socket.getInputStream())) private val writer: BufferedWriter = BufferedWriter(OutputStreamWriter(socket.getOutputStream())) - private fun GoUtModel.convertToRawValue(): RawValue = when (val model = this) { - is GoUtComplexModel -> PrimitiveValue( - model.typeId.name, - "${model.realValue.toValueGoCode()}@${model.imagValue.toValueGoCode()}" - ) - - is GoUtArrayModel -> ArrayValue( - model.typeId.name, - model.typeId.elementTypeId!!.canonicalName, - model.length, - model.getElements(model.typeId.elementTypeId!!).map { it.convertToRawValue() } - ) - - is GoUtStructModel -> StructValue( - model.typeId.canonicalName, - model.value.map { - StructValue.FieldValue( - it.fieldId.name, - it.model.convertToRawValue(), - it.fieldId.isExported - ) - } - ) - - is GoUtPrimitiveModel -> PrimitiveValue(model.typeId.name, model.value.toString()) - - else -> error("Converting ${model.javaClass} to RawValue is not supported") - } - - fun sendFuzzedParametersValues(parameters: List) { - val rawValues = parameters.map { it.convertToRawValue() } + fun sendFuzzedParametersValues(parameters: List, aliases: Map) { + val rawValues = parameters.map { it.convertToRawValue(function.sourcePackage, aliases) } val json = convertObjectToJsonString(rawValues) writer.write(json) writer.flush() @@ -59,6 +30,6 @@ class GoWorker( val length = reader.readLine().toInt() val buffer = CharArray(length) reader.read(buffer) - return Klaxon().parse(String(buffer)) ?: error("") + return Klaxon().parse(String(buffer)) ?: error("Error with parsing json as raw execution result") } } \ No newline at end of file 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 e5c71f9d92..2eea2fcfde 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 @@ -1,7 +1,10 @@ package org.utbot.go.worker -import org.utbot.go.api.* -import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.api.GoUtFile +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 @@ -10,79 +13,97 @@ internal object GoWorkerCodeGenerationHelper { const val workerTestFunctionName = "TestGoFileFuzzedFunctionsByUtGoWorker" - private val alwaysRequiredImports = setOf( - "io", - "context", - "encoding/json", - "errors", - "fmt", - "log", - "math", - "net", - "reflect", - "strconv", - "strings", - "testing", - "time", - "unsafe" - ) + val alwaysRequiredImports = setOf( + GoPackage("io", "io"), + GoPackage("context", "context"), + GoPackage("json", "encoding/json"), + GoPackage("errors", "errors"), + GoPackage("fmt", "fmt"), + GoPackage("log", "log"), + GoPackage("math", "math"), + GoPackage("net", "net"), + GoPackage("reflect", "reflect"), + GoPackage("strconv", "strconv"), + GoPackage("strings", "strings"), + GoPackage("testing", "testing"), + GoPackage("time", "time"), + GoPackage("unsafe", "unsafe") + ).map { GoImport(it) }.toSet() fun createFileToExecute( sourceFile: GoUtFile, function: GoUtFunction, eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig, - port: Int + port: Int, + imports: Set ): File { val fileToExecuteName = createFileToExecuteName(sourceFile) val sourceFileDir = File(sourceFile.absoluteDirectoryPath) val fileToExecute = sourceFileDir.resolve(fileToExecuteName) - val fileToExecuteGoCode = generateWorkerTestFileGoCode( - sourceFile, function, eachExecutionTimeoutsMillisConfig, port - ) + val fileToExecuteGoCode = + generateWorkerTestFileGoCode(function, eachExecutionTimeoutsMillisConfig, port, imports) fileToExecute.writeText(fileToExecuteGoCode) return fileToExecute } + fun createFileWithModifiedFunction( + sourceFile: GoUtFile, + function: GoUtFunction + ): File { + val fileWithModifiedFunctionName = createFileWithModifiedFunctionName() + val sourceFileDir = File(sourceFile.absoluteDirectoryPath) + val fileWithModifiedFunction = sourceFileDir.resolve(fileWithModifiedFunctionName) + + val fileWithModifiedFunctionGoCode = generateFileWithModifiedFunctionGoCode(function) + fileWithModifiedFunction.writeText(fileWithModifiedFunctionGoCode) + return fileWithModifiedFunction + } + 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 generateWorkerTestFileGoCode( - sourceFile: GoUtFile, function: GoUtFunction, eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig, - port: Int + port: Int, + imports: Set ): String { - fun GoTypeId.getAllStructTypes(): Set = when (this) { - is GoStructTypeId -> fields.fold(setOf(this)) { acc: Set, field -> - acc + (field.declaringType).getAllStructTypes() - } + val destinationPackage = function.sourcePackage + val fileCodeBuilder = GoFileCodeBuilder(destinationPackage, imports) - is GoArrayTypeId -> elementTypeId!!.getAllStructTypes() - else -> emptySet() - } + val workerTestFunctionCode = generateWorkerTestFunctionCode(function, eachExecutionTimeoutsMillisConfig, port) - val structTypes = - function.parameters.fold(emptySet()) { acc: Set, functionParameter: GoUtFunctionParameter -> - acc + functionParameter.type.getAllStructTypes() - } - val imports = structTypes.fold(emptySet()) { acc: Set, goStructTypeId -> - if (goStructTypeId.packageName != sourceFile.packageName) acc + goStructTypeId.packagePath else acc - } - val fileCodeBuilder = GoFileCodeBuilder(sourceFile.packageName, alwaysRequiredImports + imports) + val types = function.parameters.map { it.type } + val structTypes = types.getAllStructTypes() + val aliases = imports.associate { it.goPackage to it.alias } - val workerTestFunctionCode = generateWorkerTestFunctionCode(function, eachExecutionTimeoutsMillisConfig, port) - val modifiedFunction = function.modifiedFunctionForCollectingTraces fileCodeBuilder.addTopLevelElements( GoCodeTemplates.getTopLevelHelperStructsAndFunctionsForWorker( - structTypes, sourceFile.packageName - ) + modifiedFunction + workerTestFunctionCode + structTypes, + destinationPackage, + aliases + ) + workerTestFunctionCode ) return fileCodeBuilder.buildCodeString() } + private fun generateFileWithModifiedFunctionGoCode(function: GoUtFunction): String { + val destinationPackage = function.sourcePackage + val imports = function.requiredImports.toSet() + val fileCodeBuilder = GoFileCodeBuilder(destinationPackage, imports) + fileCodeBuilder.addTopLevelElements( + listOf(GoCodeTemplates.traces) + function.modifiedFunctionForCollectingTraces + ) + return fileCodeBuilder.buildCodeString() + } + private fun generateWorkerTestFunctionCode( function: GoUtFunction, eachExecutionTimeoutsMillisConfig: EachExecutionTimeoutsMillisConfig, port: Int ): String { @@ -111,7 +132,7 @@ internal object GoWorkerCodeGenerationHelper { function := reflect.ValueOf(${function.modifiedName}) executionResult := __executeFunction__($timeoutMillis*time.Millisecond, func() []__RawValue__ { - __traces__ = []int{} + __traces__ = make([]int, 0, 1000) return __wrapResultValuesForUtBotGoWorker__(function.Call(parameters)) }) diff --git a/utbot-go/src/main/kotlin/org/utbot/go/worker/RawExecutionResults.kt b/utbot-go/src/main/kotlin/org/utbot/go/worker/RawExecutionResults.kt index 068ee06499..174fbe79cf 100644 --- a/utbot-go/src/main/kotlin/org/utbot/go/worker/RawExecutionResults.kt +++ b/utbot-go/src/main/kotlin/org/utbot/go/worker/RawExecutionResults.kt @@ -119,7 +119,6 @@ private object RawValuesCodes { } fun convertRawExecutionResultToExecutionResult( - packageName: String, rawExecutionResult: RawExecutionResult, functionResultTypes: List, timeoutMillis: Long @@ -159,7 +158,7 @@ fun convertRawExecutionResultToExecutionResult( if (resultType.implementsError && rawResultValue != null) { executedWithNonNilErrorString = true } - createGoUtModelFromRawValue(rawResultValue, resultType, packageName) + createGoUtModelFromRawValue(rawResultValue, resultType) } return if (executedWithNonNilErrorString) { GoUtExecutionWithNonNilError(resultValues, rawExecutionResult.trace) @@ -169,7 +168,7 @@ fun convertRawExecutionResultToExecutionResult( } private fun createGoUtModelFromRawValue( - rawValue: RawValue?, typeId: GoTypeId, packageName: String + rawValue: RawValue?, typeId: GoTypeId ): GoUtModel = when (typeId) { // Only for error interface is GoInterfaceTypeId -> if (rawValue == null) { @@ -178,9 +177,9 @@ private fun createGoUtModelFromRawValue( GoUtPrimitiveModel((rawValue as PrimitiveValue).value, goStringTypeId) } - is GoStructTypeId -> createGoUtStructModelFromRawValue(rawValue as StructValue, typeId, packageName) + is GoStructTypeId -> createGoUtStructModelFromRawValue(rawValue as StructValue, typeId) - is GoArrayTypeId -> createGoUtArrayModelFromRawValue(rawValue as ArrayValue, typeId, packageName) + is GoArrayTypeId -> createGoUtArrayModelFromRawValue(rawValue as ArrayValue, typeId) is GoPrimitiveTypeId -> createGoUtPrimitiveModelFromRawValue(rawValue as PrimitiveValue, typeId) @@ -237,19 +236,19 @@ private fun convertRawFloatValueToGoUtPrimitiveModel( } private fun createGoUtStructModelFromRawValue( - resultValue: StructValue, resultTypeId: GoStructTypeId, packageName: String + resultValue: StructValue, resultTypeId: GoStructTypeId ): GoUtStructModel { val value = resultValue.value.zip(resultTypeId.fields).map { (value, fieldId) -> - GoUtFieldModel(createGoUtModelFromRawValue(value.value, fieldId.declaringType, packageName), fieldId) + GoUtFieldModel(createGoUtModelFromRawValue(value.value, fieldId.declaringType), fieldId) } - return GoUtStructModel(value, resultTypeId, packageName) + return GoUtStructModel(value, resultTypeId) } private fun createGoUtArrayModelFromRawValue( - resultValue: ArrayValue, resultTypeId: GoArrayTypeId, packageName: String + resultValue: ArrayValue, resultTypeId: GoArrayTypeId ): GoUtArrayModel { val value = (0 until resultTypeId.length).associateWith { index -> - createGoUtModelFromRawValue(resultValue.value[index], resultTypeId.elementTypeId!!, packageName) + createGoUtModelFromRawValue(resultValue.value[index], resultTypeId.elementTypeId!!) }.toMutableMap() - return GoUtArrayModel(value, resultTypeId, packageName) + return GoUtArrayModel(value, resultTypeId) } \ No newline at end of file diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/analysis_results.go b/utbot-go/src/main/resources/go_source_code_analyzer/analysis_results.go index 79bccdc080..3ebe331923 100644 --- a/utbot-go/src/main/resources/go_source_code_analyzer/analysis_results.go +++ b/utbot-go/src/main/resources/go_source_code_analyzer/analysis_results.go @@ -63,6 +63,7 @@ type AnalyzedFunction struct { ModifiedName string `json:"modifiedName"` Parameters []AnalyzedFunctionParameter `json:"parameters"` ResultTypes []AnalyzedType `json:"resultTypes"` + RequiredImports []Import `json:"requiredImports"` ModifiedFunctionForCollectingTraces string `json:"modifiedFunctionForCollectingTraces"` NumberOfAllStatements int `json:"numberOfAllStatements"` position token.Pos @@ -70,7 +71,7 @@ type AnalyzedFunction struct { type AnalysisResult struct { AbsoluteFilePath string `json:"absoluteFilePath"` - PackageName string `json:"packageName"` + SourcePackage Package `json:"sourcePackage"` AnalyzedFunctions []AnalyzedFunction `json:"analyzedFunctions"` NotSupportedFunctionsNames []string `json:"notSupportedFunctionsNames"` NotFoundFunctionsNames []string `json:"notFoundFunctionsNames"` diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/analyzer_core.go b/utbot-go/src/main/resources/go_source_code_analyzer/analyzer_core.go index 3d650c5710..117aaa35ab 100644 --- a/utbot-go/src/main/resources/go_source_code_analyzer/analyzer_core.go +++ b/utbot-go/src/main/resources/go_source_code_analyzer/analyzer_core.go @@ -169,7 +169,17 @@ func checkIsSupported(signature *types.Signature) bool { return true } -func collectTargetAnalyzedFunctions(fset *token.FileSet, info *types.Info, targetFunctionsNames []string) ( +func createNewFunctionName(funcName string) string { + return "__" + funcName + "__" +} + +func collectTargetAnalyzedFunctions( + fset *token.FileSet, + info *types.Info, + targetFunctionsNames []string, + allImportsInFile map[Import]bool, + sourcePackage Package, +) ( analyzedFunctions []AnalyzedFunction, notSupportedFunctionsNames []string, notFoundFunctionsNames []string, @@ -178,29 +188,34 @@ func collectTargetAnalyzedFunctions(fset *token.FileSet, info *types.Info, targe notSupportedFunctionsNames = []string{} notFoundFunctionsNames = []string{} - selectAll := len(targetFunctionsNames) == 0 foundTargetFunctionsNamesMap := map[string]bool{} for _, functionName := range targetFunctionsNames { foundTargetFunctionsNamesMap[functionName] = false } + var blankImports []Import + for i := range allImportsInFile { + if i.Alias == "_" { + blankImports = append(blankImports, i) + } + } + for ident, obj := range info.Defs { switch typedObj := obj.(type) { case *types.Func: analyzedFunction := AnalyzedFunction{ - Name: typedObj.Name(), - ModifiedName: createNewFunctionName(typedObj.Name()), - Parameters: []AnalyzedFunctionParameter{}, - ResultTypes: []AnalyzedType{}, - position: typedObj.Pos(), + Name: typedObj.Name(), + ModifiedName: createNewFunctionName(typedObj.Name()), + Parameters: []AnalyzedFunctionParameter{}, + ResultTypes: []AnalyzedType{}, + RequiredImports: []Import{}, + position: typedObj.Pos(), } - if !selectAll { - if isFound, ok := foundTargetFunctionsNamesMap[analyzedFunction.Name]; !ok || isFound { - continue - } else { - foundTargetFunctionsNamesMap[analyzedFunction.Name] = true - } + if isFound, ok := foundTargetFunctionsNamesMap[analyzedFunction.Name]; !ok || isFound { + continue + } else { + foundTargetFunctionsNamesMap[analyzedFunction.Name] = true } signature := typedObj.Type().(*types.Signature) @@ -219,7 +234,8 @@ func collectTargetAnalyzedFunctions(fset *token.FileSet, info *types.Info, targe AnalyzedFunctionParameter{ Name: parameter.Name(), Type: parameterType, - }) + }, + ) } } if results := signature.Results(); results != nil { @@ -236,11 +252,19 @@ func collectTargetAnalyzedFunctions(fset *token.FileSet, info *types.Info, targe funcDecl := ident.Obj.Decl.(*ast.FuncDecl) funcDecl.Name = ast.NewIdent(analyzedFunction.ModifiedName) - visitor := Visitor{ - counter: 0, - newFunctionName: analyzedFunction.ModifiedName, + functionModifier := FunctionModifier{lineCounter: 0} + ast.Walk(&functionModifier, funcDecl) + + importsCollector := ImportsCollector{ + info: info, + requiredImports: map[Import]bool{}, + allImportsInFile: allImportsInFile, + sourcePackage: sourcePackage, + } + ast.Walk(&importsCollector, funcDecl) + for _, i := range blankImports { + importsCollector.requiredImports[i] = true } - ast.Walk(&visitor, funcDecl) var modifiedFunction bytes.Buffer cfg := printer.Config{ @@ -251,8 +275,12 @@ func collectTargetAnalyzedFunctions(fset *token.FileSet, info *types.Info, targe err := cfg.Fprint(&modifiedFunction, fset, funcDecl) checkError(err) + for i := range importsCollector.requiredImports { + analyzedFunction.RequiredImports = append(analyzedFunction.RequiredImports, i) + } analyzedFunction.ModifiedFunctionForCollectingTraces = modifiedFunction.String() - analyzedFunction.NumberOfAllStatements = visitor.counter + analyzedFunction.NumberOfAllStatements = functionModifier.lineCounter + analyzedFunctions = append(analyzedFunctions, analyzedFunction) } } diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/cover.go b/utbot-go/src/main/resources/go_source_code_analyzer/function_modifier.go similarity index 84% rename from utbot-go/src/main/resources/go_source_code_analyzer/cover.go rename to utbot-go/src/main/resources/go_source_code_analyzer/function_modifier.go index 0050e2f4af..ee7439252a 100644 --- a/utbot-go/src/main/resources/go_source_code_analyzer/cover.go +++ b/utbot-go/src/main/resources/go_source_code_analyzer/function_modifier.go @@ -6,12 +6,11 @@ import ( "strconv" ) -type Visitor struct { - counter int - newFunctionName string +type FunctionModifier struct { + lineCounter int } -func (v *Visitor) Visit(node ast.Node) ast.Visitor { +func (v *FunctionModifier) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockStmt: if n == nil { @@ -103,7 +102,7 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor { return v } -func (v *Visitor) addLinesWithLoggingInTraceBeforeFirstReturnStatement(stmts []ast.Stmt) []ast.Stmt { +func (v *FunctionModifier) addLinesWithLoggingInTraceBeforeFirstReturnStatement(stmts []ast.Stmt) []ast.Stmt { if len(stmts) == 0 { return []ast.Stmt{v.newLineWithLoggingInTrace()} } @@ -120,8 +119,8 @@ func (v *Visitor) addLinesWithLoggingInTraceBeforeFirstReturnStatement(stmts []a return newList } -func (v *Visitor) newLineWithLoggingInTrace() ast.Stmt { - v.counter++ +func (v *FunctionModifier) newLineWithLoggingInTrace() ast.Stmt { + v.lineCounter++ traces := ast.NewIdent("__traces__") return &ast.AssignStmt{ @@ -130,12 +129,8 @@ func (v *Visitor) newLineWithLoggingInTrace() ast.Stmt { Rhs: []ast.Expr{ &ast.CallExpr{ Fun: ast.NewIdent("append"), - Args: []ast.Expr{traces, ast.NewIdent(strconv.Itoa(v.counter))}, + Args: []ast.Expr{traces, ast.NewIdent(strconv.Itoa(v.lineCounter))}, }, }, } } - -func createNewFunctionName(funcName string) string { - return "__" + funcName + "__" -} diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/imports_collector.go b/utbot-go/src/main/resources/go_source_code_analyzer/imports_collector.go new file mode 100644 index 0000000000..cee799b3fe --- /dev/null +++ b/utbot-go/src/main/resources/go_source_code_analyzer/imports_collector.go @@ -0,0 +1,70 @@ +package main + +import ( + "go/ast" + "go/types" +) + +type Package struct { + PackageName string `json:"packageName"` + PackagePath string `json:"packagePath"` +} + +type Import struct { + Package Package `json:"goPackage"` + Alias string `json:"alias"` +} + +type ImportsCollector struct { + info *types.Info + prevPkg *Package // for handling alias "." + requiredImports map[Import]bool + allImportsInFile map[Import]bool + sourcePackage Package +} + +func (i *ImportsCollector) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.Ident: + if obj := i.info.ObjectOf(n); obj != nil && obj.Pkg() != nil { + var typesPkg *types.Package + + switch o := obj.(type) { + case *types.PkgName: + typesPkg = o.Imported() + case *types.Func, *types.TypeName: + if i.prevPkg == nil { + var nextImport = Import{ + Package: Package{ + PackageName: o.Pkg().Name(), + PackagePath: o.Pkg().Path(), + }, + Alias: ".", + } + + if _, ok := i.allImportsInFile[nextImport]; ok { + i.requiredImports[nextImport] = true + } + } + i.prevPkg = nil + return i + default: + return i + } + + var pkg = Package{ + PackageName: typesPkg.Name(), + PackagePath: typesPkg.Path(), + } + i.prevPkg = &pkg + + var alias = "" + if n.Name != pkg.PackageName { + alias = n.Name + } + var nextImport = Import{Package: pkg, Alias: alias} + i.requiredImports[nextImport] = true + } + } + return i +} diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/main.go b/utbot-go/src/main/resources/go_source_code_analyzer/main.go index c44074294e..a1dae5fd48 100644 --- a/utbot-go/src/main/resources/go_source_code_analyzer/main.go +++ b/utbot-go/src/main/resources/go_source_code_analyzer/main.go @@ -38,23 +38,68 @@ func analyzeTarget(target AnalysisTarget) (*AnalysisResult, error) { dir, _ := filepath.Split(target.AbsoluteFilePath) cfg := packages.Config{ - Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedDeps | packages.NeedImports, - Dir: dir, + Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedDeps | + packages.NeedImports | packages.NeedSyntax | packages.NeedFiles | packages.NeedCompiledGoFiles, + Dir: dir, } - pkgs, err := packages.Load(&cfg, "") + pkgs, err := packages.Load(&cfg, fmt.Sprintf("file=%s", target.AbsoluteFilePath)) if err != nil { return nil, err } for _, pkg := range pkgs { if pkg.Name == packageName { + if len(pkg.CompiledGoFiles) != len(pkg.Syntax) { + return nil, fmt.Errorf("parsing returned nil for some files") + } + index := 0 + for ; index < len(pkg.CompiledGoFiles); index++ { + p1, err := filepath.Abs(pkg.CompiledGoFiles[index]) + checkError(err) + p2, err := filepath.Abs(target.AbsoluteFilePath) + checkError(err) + + if p1 == p2 { + break + } + } + if index == len(pkg.CompiledGoFiles) { + return nil, fmt.Errorf("target file not found in compiled go files") + } + + allImportsInFile := map[Import]bool{} + for _, i := range pkg.Syntax[index].Imports { + packagePath := i.Path.Value[1 : len(i.Path.Value)-1] + pkgName := pkg.Imports[packagePath].Name + p := Package{ + PackageName: pkgName, + PackagePath: packagePath, + } + + var alias string + if i.Name.String() != "" { + alias = i.Name.String() + } + + allImportsInFile[Import{Package: p, Alias: alias}] = true + } + // collect required info about selected functions analyzedFunctions, notSupportedFunctionsNames, notFoundFunctionsNames := - collectTargetAnalyzedFunctions(pkg.Fset, pkg.TypesInfo, target.TargetFunctionsNames) + collectTargetAnalyzedFunctions( + pkg.Fset, + pkg.TypesInfo, + target.TargetFunctionsNames, + allImportsInFile, + Package{ + PackageName: pkg.Name, + PackagePath: pkg.PkgPath, + }, + ) return &AnalysisResult{ AbsoluteFilePath: target.AbsoluteFilePath, - PackageName: packageName, + SourcePackage: Package{PackageName: packageName, PackagePath: pkg.PkgPath}, AnalyzedFunctions: analyzedFunctions, NotSupportedFunctionsNames: notSupportedFunctionsNames, NotFoundFunctionsNames: notFoundFunctionsNames,