1
1
package org.utbot.python
2
2
3
- import kotlinx.coroutines.flow.Flow
4
- import kotlinx.coroutines.flow.flow
3
+ import kotlinx.coroutines.delay
4
+ import kotlinx.coroutines.launch
5
5
import mu.KotlinLogging
6
+ import org.utbot.common.runBlockingWithCancellationPredicate
6
7
import org.utbot.framework.plugin.api.DocRegularStmt
7
8
import org.utbot.framework.plugin.api.EnvironmentModels
8
9
import org.utbot.framework.plugin.api.UtError
10
+ import org.utbot.framework.plugin.api.UtExecution
9
11
import org.utbot.framework.plugin.api.UtExecutionResult
10
12
import org.utbot.framework.plugin.api.UtExecutionSuccess
11
13
import org.utbot.framework.plugin.api.UtExplicitlyThrownException
@@ -28,11 +30,19 @@ import org.utbot.python.fuzzing.PythonFuzzedValue
28
30
import org.utbot.python.fuzzing.PythonFuzzing
29
31
import org.utbot.python.fuzzing.PythonMethodDescription
30
32
import org.utbot.python.newtyping.PythonTypeStorage
33
+ import org.utbot.python.newtyping.general.FunctionType
31
34
import org.utbot.python.newtyping.general.Type
35
+ import org.utbot.python.newtyping.inference.InferredTypeFeedback
36
+ import org.utbot.python.newtyping.inference.InvalidTypeFeedback
37
+ import org.utbot.python.newtyping.inference.SuccessFeedback
38
+ import org.utbot.python.newtyping.inference.TypeInferenceAlgorithm
32
39
import org.utbot.python.newtyping.pythonModules
33
40
import org.utbot.python.newtyping.pythonTypeRepresentation
34
41
import org.utbot.python.utils.camelToSnakeCase
42
+ import org.utbot.python.utils.updateCoverage
35
43
import org.utbot.summary.fuzzer.names.TestSuggestedInfo
44
+ import java.lang.Exception
45
+ import java.util.Random
36
46
37
47
private val logger = KotlinLogging .logger {}
38
48
@@ -42,16 +52,144 @@ class InvalidExecution(val utError: UtError): FuzzingExecutionFeedback
42
52
class TypeErrorFeedback (val message : String ) : FuzzingExecutionFeedback
43
53
class ArgumentsTypeErrorFeedback (val message : String ) : FuzzingExecutionFeedback
44
54
55
+ private const val COVERAGE_LIMIT = 20
56
+
45
57
class PythonEngine (
46
58
private val methodUnderTest : PythonMethod ,
47
59
private val directoriesForSysPath : Set <String >,
48
60
private val moduleToImport : String ,
49
61
private val pythonPath : String ,
50
62
private val fuzzedConcreteValues : List <PythonFuzzedConcreteValue >,
51
63
private val timeoutForRun : Long ,
52
- private val initialCoveredLines : Set <Int >,
53
64
private val pythonTypeStorage : PythonTypeStorage ,
65
+ private val algorithm : TypeInferenceAlgorithm ,
66
+ private val isCancelled : () -> Boolean ,
67
+ private val until : Long ,
54
68
) {
69
+ val executions: MutableList <UtExecution > = emptyList<UtExecution >().toMutableList()
70
+ val errors: MutableList <UtError > = emptyList<UtError >().toMutableList()
71
+ var missedLines: Set <Int >? = null
72
+
73
+ private val coveredLines: MutableSet <Int > = emptyList<Int >().toMutableSet()
74
+ private val fuzzingManager = FuzzingManager ()
75
+ private var engineIsCancelled = { isCancelled() || System .currentTimeMillis() >= until || missedLines?.size == 0 }
76
+
77
+ suspend fun run (functionType : FunctionType ) {
78
+ if (! fuzzingManager.fuzzers.contains(functionType)) {
79
+ buildFuzzer(functionType)
80
+ }
81
+ fuzzingManager.currentFuzzer = fuzzingManager.fuzzers[functionType]
82
+ runBlockingWithCancellationPredicate(engineIsCancelled) {
83
+ launch {
84
+ try {
85
+ fuzzingManager.currentFuzzer?.fuzz(fuzzingManager.descriptions[functionType]!! )
86
+ } catch (_: Exception ) {
87
+ fuzzingManager.currentFuzzer = null
88
+ }
89
+ }
90
+ launch {
91
+ while (fuzzingManager.currentFuzzer != null ) {
92
+ delay(100 )
93
+ }
94
+ val feedback = fuzzingManager.inferFeedback.getOrDefault(functionType, emptyList<InferredTypeFeedback >().toMutableList()).let {
95
+ if (it.contains(SuccessFeedback )) SuccessFeedback
96
+ else InvalidTypeFeedback
97
+ }
98
+ algorithm.pushFeedback(feedback)
99
+
100
+ val nextAlgorithmType = algorithm.getType(isCancelled)
101
+ val nextHistoryType = fuzzingManager.findTheBestAnnotation()
102
+ val nextType = nextAlgorithmType?.let {
103
+ if (Random ().nextDouble() > 0.3 ) it else null
104
+ } ? : nextHistoryType
105
+
106
+ if (nextType is FunctionType ) {
107
+ run (nextType)
108
+ } else {
109
+ engineIsCancelled = { true }
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ private fun buildFuzzer (functionType : FunctionType ) {
116
+ val arguments = functionType.arguments
117
+ logger.info { " Prepare to fuzzing ${arguments.joinToString { it.pythonTypeRepresentation() }} " }
118
+
119
+ val pmd = PythonMethodDescription (
120
+ methodUnderTest.name,
121
+ arguments,
122
+ fuzzedConcreteValues,
123
+ pythonTypeStorage,
124
+ )
125
+
126
+ var coverageLimit = COVERAGE_LIMIT
127
+ var coveredBefore = coveredLines.size
128
+
129
+ var badExecutionCounter = 0
130
+ var errorsCounter = 0
131
+ var executionCounter = 0
132
+ var coverageCounter = 0
133
+
134
+ val fuzzingCancellation = { isCancelled() || System .currentTimeMillis() > until || coverageLimit <= 0 || fuzzingManager.currentFuzzer == null }
135
+ val fuzzer = getFuzzer(functionType, isCancelled, until, {fuzzingManager.currentFuzzer}) {
136
+ if (fuzzingCancellation()) {
137
+ fuzzingManager.update(
138
+ functionType,
139
+ AnnotationStatistic (
140
+ coveredLines.toSet(),
141
+ executionCounter + badExecutionCounter + errorsCounter,
142
+ executionCounter,
143
+ badExecutionCounter,
144
+ coverageCounter
145
+ )
146
+ )
147
+ fuzzingManager.currentFuzzer = null
148
+ coverageLimit = COVERAGE_LIMIT
149
+ coveredBefore = coveredLines.size
150
+
151
+ badExecutionCounter = 0
152
+ errorsCounter = 0
153
+ executionCounter = 0
154
+ coverageCounter = 0
155
+ } else {
156
+ val feedback = when (it) {
157
+ is ValidExecution -> {
158
+ executions + = it.utFuzzedExecution
159
+ missedLines = updateCoverage(it.utFuzzedExecution, coveredLines, missedLines)
160
+ executionCounter++
161
+ SuccessFeedback
162
+ }
163
+ is InvalidExecution -> {
164
+ errors + = it.utError
165
+ errorsCounter++
166
+ SuccessFeedback
167
+ }
168
+ is ArgumentsTypeErrorFeedback , is TypeErrorFeedback -> {
169
+ badExecutionCounter++
170
+ InvalidTypeFeedback
171
+ }
172
+ }
173
+ fuzzingManager.addInferFeedback(functionType, feedback)
174
+
175
+ val coveredAfter = coveredLines.size
176
+ if (coveredAfter == coveredBefore) {
177
+ coverageLimit - = 1
178
+ }
179
+ if (coveredAfter > coveredBefore) {
180
+ coverageCounter++
181
+ }
182
+ coveredBefore = coveredAfter
183
+ }
184
+ }
185
+ fuzzingManager.fuzzers[functionType] = fuzzer
186
+ fuzzingManager.descriptions[functionType] = pmd
187
+ fuzzingManager.currentFuzzer = fuzzer
188
+
189
+ // fuzzer.fuzz(pmd)
190
+
191
+ logger.info { " Fuzzer created" }
192
+ }
55
193
56
194
private fun suggestExecutionName (
57
195
description : PythonMethodDescription ,
@@ -170,84 +308,80 @@ class PythonEngine(
170
308
)
171
309
}
172
310
173
- fun fuzzing (parameters : List <Type >, isCancelled : () -> Boolean , until : Long ): Flow <FuzzingExecutionFeedback > = flow {
311
+ private fun getFuzzer (
312
+ functionType : FunctionType ,
313
+ isCancelled : () -> Boolean ,
314
+ until : Long ,
315
+ activeFuzzing : suspend () -> PythonFuzzing ? ,
316
+ handleFeedback : suspend (FuzzingExecutionFeedback ) -> Unit ,
317
+ ): PythonFuzzing {
318
+ val parameters = functionType.arguments
174
319
val additionalModules = parameters.flatMap { it.pythonModules() }
175
320
176
- val pmd = PythonMethodDescription (
177
- methodUnderTest.name,
178
- parameters,
179
- fuzzedConcreteValues,
180
- pythonTypeStorage,
181
- )
182
-
183
- val coveredLines = initialCoveredLines.toMutableSet()
184
- var sourceLinesCount = Long .MAX_VALUE
321
+ val coveredLines = coveredLines.toMutableSet()
322
+ var sourceLinesCount: Long? = null
185
323
186
- PythonFuzzing (pmd.pythonTypeStorage) { description, arguments ->
187
- if (isCancelled()) {
188
- logger.info { " Fuzzing process was interrupted" }
189
- return @PythonFuzzing PythonFeedback (control = Control .STOP )
190
- }
191
- if (System .currentTimeMillis() >= until) {
192
- logger.info { " Fuzzing process was interrupted by timeout" }
193
- return @PythonFuzzing PythonFeedback (control = Control .STOP )
194
- }
195
-
196
- val codeExecutor = constructEvaluationInput(arguments, additionalModules)
197
- // val jobResult = evaluationInput.evaluate()
198
-
199
- when (val evaluationResult = codeExecutor.run ()) {
200
- is PythonEvaluationError -> {
201
- val utError = UtError (
202
- " Error evaluation: ${evaluationResult.status} , ${evaluationResult.message} " ,
203
- Throwable (evaluationResult.stackTrace.joinToString(" \n " ))
204
- )
205
- logger.debug(evaluationResult.stackTrace.joinToString(" \n " ))
206
- emit(InvalidExecution (utError))
207
- return @PythonFuzzing PythonFeedback (control = Control .PASS )
324
+ return fuzzingManager.fuzzers.getOrPut(functionType) {
325
+ PythonFuzzing (pythonTypeStorage, activeFuzzing) { description, arguments ->
326
+ logger.info {arguments.first().summary}
327
+ if (isCancelled()) {
328
+ logger.info { " Fuzzing process was interrupted" }
329
+ return @PythonFuzzing PythonFeedback (control = Control .STOP )
208
330
}
209
-
210
- is PythonEvaluationTimeout -> {
211
- val utError = UtError (evaluationResult.message, Throwable ())
212
- emit(InvalidExecution (utError))
213
- return @PythonFuzzing PythonFeedback (control = Control .PASS )
331
+ if (System .currentTimeMillis() >= until) {
332
+ logger.info { " Fuzzing process was interrupted by timeout" }
333
+ return @PythonFuzzing PythonFeedback (control = Control .STOP )
214
334
}
215
335
216
- is PythonEvaluationSuccess -> {
217
- evaluationResult.coverage.coveredInstructions.forEach { coveredLines.add(it.lineNumber) }
218
- val instructionsCount = evaluationResult.coverage.instructionsCount
219
- if (instructionsCount != null ) {
220
- sourceLinesCount = instructionsCount
336
+ val codeExecutor = constructEvaluationInput(arguments, additionalModules)
337
+ when (val evaluationResult = codeExecutor.run ()) {
338
+ is PythonEvaluationError -> {
339
+ val utError = UtError (
340
+ " Error evaluation: ${evaluationResult.status} , ${evaluationResult.message} " ,
341
+ Throwable (evaluationResult.stackTrace.joinToString(" \n " ))
342
+ )
343
+ logger.debug(evaluationResult.stackTrace.joinToString(" \n " ))
344
+ fuzzingManager.addFeedback(functionType, InvalidExecution (utError))
345
+ handleFeedback(InvalidExecution (utError))
346
+ return @PythonFuzzing PythonFeedback (control = Control .PASS )
221
347
}
222
348
223
- val summary = arguments
224
- .zip(methodUnderTest.arguments)
225
- .mapNotNull { it.first.summary?.replace(" %var%" , it.second.name) }
226
-
227
- val hasThisObject = codeExecutor.methodArguments.thisObject != null
228
- val result = handleSuccessResult(parameters, evaluationResult, description, hasThisObject, summary)
229
- emit(result)
230
-
231
- if (coveredLines.size.toLong() == sourceLinesCount) {
232
- return @PythonFuzzing PythonFeedback (control = Control .STOP )
349
+ is PythonEvaluationTimeout -> {
350
+ val utError = UtError (evaluationResult.message, Throwable ())
351
+ handleFeedback(InvalidExecution (utError))
352
+ return @PythonFuzzing PythonFeedback (control = Control .PASS )
233
353
}
234
354
235
- when (result) {
236
- is ValidExecution -> {
237
- return @PythonFuzzing PythonFeedback (control = Control .CONTINUE )
238
- }
239
- is ArgumentsTypeErrorFeedback -> {
240
- return @PythonFuzzing PythonFeedback (control = Control .PASS )
355
+ is PythonEvaluationSuccess -> {
356
+ evaluationResult.coverage.coveredInstructions.forEach { coveredLines.add(it.lineNumber) }
357
+ val instructionsCount = evaluationResult.coverage.instructionsCount
358
+ if (instructionsCount != null ) {
359
+ sourceLinesCount = instructionsCount
241
360
}
242
- is TypeErrorFeedback -> {
243
- return @PythonFuzzing PythonFeedback (control = Control .PASS )
361
+
362
+ val summary = arguments
363
+ .zip(methodUnderTest.arguments)
364
+ .mapNotNull { it.first.summary?.replace(" %var%" , it.second.name) }
365
+
366
+ val hasThisObject = codeExecutor.methodArguments.thisObject != null
367
+ val result = handleSuccessResult(parameters, evaluationResult, description, hasThisObject, summary)
368
+ handleFeedback(result)
369
+
370
+ if (coveredLines.size.toLong() == sourceLinesCount) {
371
+ return @PythonFuzzing PythonFeedback (control = Control .STOP )
244
372
}
245
- is InvalidExecution -> {
246
- return @PythonFuzzing PythonFeedback (control = Control .CONTINUE )
373
+
374
+ when (result) {
375
+ is ValidExecution , is InvalidExecution -> {
376
+ return @PythonFuzzing PythonFeedback (control = Control .CONTINUE )
377
+ }
378
+ is ArgumentsTypeErrorFeedback , is TypeErrorFeedback -> {
379
+ return @PythonFuzzing PythonFeedback (control = Control .PASS )
380
+ }
247
381
}
248
382
}
249
383
}
250
384
}
251
- }.fuzz(pmd)
385
+ }
252
386
}
253
387
}
0 commit comments