Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions utbot-go/go-samples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
5 changes: 5 additions & 0 deletions utbot-go/go-samples/simple/supported_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package simple
import (
"errors"
"github.com/pmezard/go-difflib/difflib"
dif "github.com/pmezard/go-difflib/difflib"
"math"
)

Expand Down Expand Up @@ -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
}
6 changes: 6 additions & 0 deletions utbot-go/go-samples/simple/supported_types_go_ut_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
100 changes: 59 additions & 41 deletions utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -36,63 +36,81 @@ 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
)
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}" }
}
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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()
}
}
}
Expand Down
52 changes: 31 additions & 21 deletions utbot-go/src/main/kotlin/org/utbot/go/api/GoTypesApi.kt
Original file line number Diff line number Diff line change
@@ -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<GoPackage, String?>): String = simpleName

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -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<GoFieldId>,
) : 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<GoPackage, String?>): 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 {
Expand All @@ -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<GoPackage, String?>): String =
"[$length]${elementTypeId!!.getRelativeName(destinationPackage, aliases)}"

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -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<GoPackage, String?>): 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 {
Expand Down
10 changes: 7 additions & 3 deletions utbot-go/src/main/kotlin/org/utbot/go/api/GoUtFunctionApi.kt
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -19,11 +20,14 @@ data class GoUtFunction(
val modifiedName: String,
val parameters: List<GoUtFunctionParameter>,
val resultTypes: List<GoTypeId>,
val requiredImports: List<GoImport>,
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<GoUtModel>)
Expand Down
Loading