Skip to content

Commit b34129b

Browse files
authored
Add socket channel for coverage collection (#2195)
* Coverage algo update * Update coverage * Update coverage socket algorithm
1 parent b8fc39e commit b34129b

File tree

10 files changed

+146
-4
lines changed

10 files changed

+146
-4
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import time
2+
3+
4+
def long_function(x: int):
5+
x += 4
6+
x /= 2
7+
x += 100
8+
x *= 3
9+
x -= 15
10+
time.sleep(2000)
11+
return x

utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import org.utbot.fuzzing.utils.Trie
1111
import org.utbot.python.evaluation.*
1212
import org.utbot.python.evaluation.serialiation.MemoryDump
1313
import org.utbot.python.evaluation.serialiation.toPythonTree
14+
import org.utbot.python.evaluation.utils.CoverageIdGenerator
15+
import org.utbot.python.evaluation.utils.coveredLinesToInstructions
1416
import org.utbot.python.framework.api.python.PythonTree
1517
import org.utbot.python.framework.api.python.PythonTreeModel
1618
import org.utbot.python.framework.api.python.PythonTreeWrapper
@@ -84,6 +86,7 @@ class PythonEngine(
8486
private fun handleTimeoutResult(
8587
arguments: List<PythonFuzzedValue>,
8688
methodUnderTestDescription: PythonMethodDescription,
89+
coveredLines: Collection<Int>,
8790
): FuzzingExecutionFeedback {
8891
val summary = arguments
8992
.zip(methodUnderTest.arguments)
@@ -100,12 +103,16 @@ class PythonEngine(
100103
val beforeThisObject = beforeThisObjectTree?.let { PythonTreeModel(it.tree) }
101104
val beforeModelList = beforeModelListTree.map { PythonTreeModel(it.tree) }
102105

106+
val coveredInstructions = coveredLinesToInstructions(coveredLines, methodUnderTest)
107+
val coverage = Coverage(coveredInstructions)
108+
103109
val utFuzzedExecution = PythonUtExecution(
104110
stateInit = EnvironmentModels(beforeThisObject, beforeModelList, emptyMap()),
105111
stateBefore = EnvironmentModels(beforeThisObject, beforeModelList, emptyMap()),
106112
stateAfter = EnvironmentModels(beforeThisObject, beforeModelList, emptyMap()),
107113
diffIds = emptyList(),
108114
result = executionResult,
115+
coverage = coverage,
109116
testMethodName = testMethodName.testName?.camelToSnakeCase(),
110117
displayName = testMethodName.displayName,
111118
summary = summary.map { DocRegularStmt(it) }
@@ -218,7 +225,9 @@ class PythonEngine(
218225
methodUnderTest.argumentsNames
219226
)
220227
try {
221-
return when (val evaluationResult = manager.run(functionArguments, localAdditionalModules)) {
228+
val coverageId = CoverageIdGenerator.createId()
229+
return when (val evaluationResult =
230+
manager.runWithCoverage(functionArguments, localAdditionalModules, coverageId)) {
222231
is PythonEvaluationError -> {
223232
val utError = UtError(
224233
"Error evaluation: ${evaluationResult.status}, ${evaluationResult.message}",
@@ -229,8 +238,20 @@ class PythonEngine(
229238
}
230239

231240
is PythonEvaluationTimeout -> {
232-
val utTimeoutException = handleTimeoutResult(arguments, description)
233-
PythonExecutionResult(utTimeoutException, PythonFeedback(control = Control.PASS))
241+
val coveredLines =
242+
manager.coverageReceiver.coverageStorage.getOrDefault(coverageId, mutableSetOf())
243+
val utTimeoutException = handleTimeoutResult(arguments, description, coveredLines)
244+
val coveredInstructions = coveredLinesToInstructions(coveredLines, methodUnderTest)
245+
val trieNode: Trie.Node<Instruction> =
246+
if (coveredInstructions.isEmpty())
247+
Trie.emptyNode()
248+
else description.tracer.add(
249+
coveredInstructions
250+
)
251+
PythonExecutionResult(
252+
utTimeoutException,
253+
PythonFeedback(control = Control.PASS, result = trieNode)
254+
)
234255
}
235256

236257
is PythonEvaluationSuccess -> {

utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ interface PythonCodeExecutor {
1717
additionalModulesToImport: Set<String>
1818
): PythonEvaluationResult
1919

20+
fun runWithCoverage(
21+
fuzzedValues: FunctionArguments,
22+
additionalModulesToImport: Set<String>,
23+
coverageId: String,
24+
): PythonEvaluationResult
25+
2026
fun stop()
2127
}
2228

utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.utbot.python.evaluation.serialiation.FailExecution
1212
import org.utbot.python.evaluation.serialiation.PythonExecutionResult
1313
import org.utbot.python.evaluation.serialiation.SuccessExecution
1414
import org.utbot.python.evaluation.serialiation.serializeObjects
15+
import org.utbot.python.evaluation.utils.CoverageIdGenerator
1516
import org.utbot.python.framework.api.python.util.pythonAnyClassId
1617
import org.utbot.python.newtyping.pythonTypeName
1718
import org.utbot.python.newtyping.pythonTypeRepresentation
@@ -48,6 +49,15 @@ class PythonCodeSocketExecutor(
4849
override fun run(
4950
fuzzedValues: FunctionArguments,
5051
additionalModulesToImport: Set<String>
52+
): PythonEvaluationResult {
53+
val coverageId = CoverageIdGenerator.createId()
54+
return runWithCoverage(fuzzedValues, additionalModulesToImport, coverageId)
55+
}
56+
57+
override fun runWithCoverage(
58+
fuzzedValues: FunctionArguments,
59+
additionalModulesToImport: Set<String>,
60+
coverageId: String
5161
): PythonEvaluationResult {
5262
val (arguments, memory) = serializeObjects(fuzzedValues.allArguments.map { it.tree })
5363

@@ -69,6 +79,7 @@ class PythonCodeSocketExecutor(
6979
emptyMap(), // here can be only-kwargs arguments
7080
memory,
7181
method.moduleFilename,
82+
coverageId,
7283
)
7384
val message = ExecutionRequestSerializer.serializeRequest(request) ?: error("Cannot serialize request to python executor")
7485
try {
@@ -81,6 +92,7 @@ class PythonCodeSocketExecutor(
8192
val (status, response) = UtExecutorThread.run(pythonWorker, executionTimeout)
8293
return when (status) {
8394
UtExecutorThread.Status.TIMEOUT -> {
95+
8496
PythonEvaluationTimeout()
8597
}
8698

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.utbot.python.evaluation
2+
3+
import mu.KotlinLogging
4+
import java.io.IOException
5+
import java.net.DatagramPacket
6+
import java.net.DatagramSocket
7+
import java.net.SocketException
8+
9+
class PythonCoverageReceiver(
10+
private val until: Long,
11+
) : Thread() {
12+
val coverageStorage = mutableMapOf<String, MutableSet<Int>>()
13+
private val socket = DatagramSocket()
14+
private val logger = KotlinLogging.logger {}
15+
16+
fun address(): Pair<String, String> {
17+
return "localhost" to socket.localPort.toString()
18+
}
19+
20+
override fun run() {
21+
try {
22+
while (System.currentTimeMillis() < until) {
23+
val buf = ByteArray(256)
24+
val request = DatagramPacket(buf, buf.size)
25+
socket.receive(request)
26+
val (id, line) = request.data.decodeToString().take(request.length).split(":")
27+
logger.debug { "Got coverage: $id, $line" }
28+
val lineNumber = line.toInt()
29+
coverageStorage.getOrPut(id) { mutableSetOf() } .add(lineNumber)
30+
}
31+
} catch (ex: SocketException) {
32+
logger.error("Socket error: " + ex.message)
33+
} catch (ex: IOException) {
34+
logger.error("IO error: " + ex.message)
35+
}
36+
}
37+
}

utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ class PythonWorkerManager(
2626
private lateinit var workerSocket: Socket
2727
private lateinit var codeExecutor: PythonCodeExecutor
2828

29+
val coverageReceiver = PythonCoverageReceiver(until)
30+
2931
init {
32+
coverageReceiver.start()
3033
connect()
3134
}
3235

@@ -37,6 +40,8 @@ class PythonWorkerManager(
3740
"-m", "utbot_executor",
3841
"localhost",
3942
serverSocket.localPort.toString(),
43+
coverageReceiver.address().first,
44+
coverageReceiver.address().second,
4045
"--logfile", logfile.absolutePath,
4146
"--loglevel", "INFO", // "DEBUG", "INFO", "WARNING", "ERROR"
4247
))
@@ -60,13 +65,32 @@ class PythonWorkerManager(
6065
fun disconnect() {
6166
workerSocket.close()
6267
process.destroy()
68+
coverageReceiver.interrupt()
6369
}
6470

6571
fun reconnect() {
6672
disconnect()
6773
connect()
6874
}
6975

76+
fun runWithCoverage(
77+
fuzzedValues: FunctionArguments,
78+
additionalModulesToImport: Set<String>,
79+
coverageId: String
80+
): PythonEvaluationResult {
81+
val evaluationResult = try {
82+
codeExecutor.runWithCoverage(fuzzedValues, additionalModulesToImport, coverageId)
83+
} catch (_: SocketTimeoutException) {
84+
logger.debug { "Socket timeout" }
85+
reconnect()
86+
PythonEvaluationTimeout()
87+
}
88+
if (evaluationResult is PythonEvaluationError || evaluationResult is PythonEvaluationTimeout) {
89+
reconnect()
90+
}
91+
return evaluationResult
92+
}
93+
7094
fun run(
7195
fuzzedValues: FunctionArguments,
7296
additionalModulesToImport: Set<String>

utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionRequestSerializer.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ data class ExecutionRequest(
2424
val kwargumentsIds: Map<String, String>,
2525
val serializedMemory: String,
2626
val filepath: String,
27+
val coverageId: String,
2728
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.utbot.python.evaluation.utils
2+
3+
import java.util.concurrent.atomic.AtomicLong
4+
5+
object CoverageIdGenerator {
6+
private const val lower_bound: Long = 1500_000_000
7+
8+
private val lastId: AtomicLong = AtomicLong(lower_bound)
9+
10+
fun createId(): String {
11+
return lastId.incrementAndGet().toString(radix = 16)
12+
}
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.utbot.python.evaluation.utils
2+
3+
import org.utbot.framework.plugin.api.Instruction
4+
import org.utbot.python.PythonMethod
5+
import org.utbot.python.framework.api.python.util.pythonAnyClassId
6+
import org.utbot.python.newtyping.pythonTypeRepresentation
7+
8+
fun coveredLinesToInstructions(coveredLines: Collection<Int>, method: PythonMethod): List<Instruction> {
9+
return coveredLines.map {
10+
Instruction(
11+
method.containingPythonClass?.pythonTypeRepresentation() ?: pythonAnyClassId.name,
12+
method.methodSignature(),
13+
it,
14+
it.toLong()
15+
)
16+
}
17+
}

utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package org.utbot.python.utils
33
object RequirementsUtils {
44
val requirements: List<String> = listOf(
55
"mypy==1.0.0",
6-
"utbot-executor==1.4.21",
6+
"utbot-executor==1.4.26",
77
"utbot-mypy-runner==0.2.8",
88
)
99

0 commit comments

Comments
 (0)