-
Notifications
You must be signed in to change notification settings - Fork 45
Update time management of python test generation #1893
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
b002d99
dd863d1
f56ffaf
b0cbb92
310c71b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,7 +31,6 @@ class PythonEngine( | |
private val pythonPath: String, | ||
private val fuzzedConcreteValues: List<PythonFuzzedConcreteValue>, | ||
private val timeoutForRun: Long, | ||
private val initialCoveredLines: Set<Int>, | ||
private val pythonTypeStorage: PythonTypeStorage, | ||
) { | ||
|
||
|
@@ -141,17 +140,20 @@ class PythonEngine( | |
|
||
fun fuzzing(parameters: List<Type>, isCancelled: () -> Boolean, until: Long): Flow<FuzzingExecutionFeedback> = flow { | ||
val additionalModules = parameters.flatMap { it.pythonModules() } | ||
val coveredLines = initialCoveredLines.toMutableSet() | ||
|
||
ServerSocket(0).use { serverSocket -> | ||
logger.info { "Server port: ${serverSocket.localPort}" } | ||
val manager = PythonWorkerManager( | ||
serverSocket, | ||
pythonPath, | ||
until, | ||
{ constructEvaluationInput(it) }, | ||
timeoutForRun.toInt() | ||
) | ||
val manager = try { | ||
PythonWorkerManager( | ||
serverSocket, | ||
pythonPath, | ||
until, | ||
{ constructEvaluationInput(it) }, | ||
timeoutForRun.toInt() | ||
) | ||
} catch (_: TimeoutException) { | ||
return@flow | ||
} | ||
logger.info { "Executor manager was created successfully" } | ||
|
||
fun fuzzingResultHandler( | ||
|
@@ -195,7 +197,6 @@ class PythonEngine( | |
|
||
is PythonEvaluationSuccess -> { | ||
val coveredInstructions = evaluationResult.coverage.coveredInstructions | ||
coveredInstructions.forEach { coveredLines.add(it.lineNumber) } | ||
|
||
val summary = arguments | ||
.zip(methodUnderTest.arguments) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On line 215 (sorry, that I comment another line, because I was not able to find how to comment 215) there's no check that:
Returns the node the was already found. You can check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
|
@@ -277,7 +278,6 @@ class PythonEngine( | |
cache.add(pair, result) | ||
emit(result.fuzzingExecutionFeedback) | ||
return@PythonFuzzing result.fuzzingPlatformFeedback | ||
|
||
}.fuzz(pmd) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,15 +22,14 @@ import org.utbot.python.newtyping.mypy.MypyReportLine | |
import org.utbot.python.newtyping.mypy.getErrorNumber | ||
import org.utbot.python.newtyping.utils.getOffsetLine | ||
import org.utbot.python.typing.MypyAnnotations | ||
import org.utbot.python.utils.ExecutionWithTimeoutMode | ||
import org.utbot.python.utils.GenerationLimitManager | ||
import org.utbot.python.utils.PriorityCartesianProduct | ||
import org.utbot.python.utils.TimeoutMode | ||
import java.io.File | ||
|
||
private val logger = KotlinLogging.logger {} | ||
|
||
private const val COVERAGE_LIMIT = 150 | ||
private const val ADDITIONAL_LIMIT = 5 | ||
private const val INVALID_EXECUTION_LIMIT = 10 | ||
|
||
class PythonTestCaseGenerator( | ||
private val withMinimization: Boolean = true, | ||
private val directoriesForSysPath: Set<String>, | ||
|
@@ -120,6 +119,10 @@ class PythonTestCaseGenerator( | |
|
||
fun generate(method: PythonMethod, until: Long): PythonTestSet { | ||
storageForMypyMessages.clear() | ||
val limitManager = GenerationLimitManager( | ||
ExecutionWithTimeoutMode, | ||
until, | ||
) | ||
|
||
val typeStorage = PythonTypeStorage.get(mypyStorage) | ||
|
||
|
@@ -135,10 +138,6 @@ class PythonTestCaseGenerator( | |
val coveredLines = mutableSetOf<Int>() | ||
var generated = 0 | ||
|
||
var additionalLimit = ADDITIONAL_LIMIT | ||
val typeInferenceCancellation = | ||
{ isCancelled() || System.currentTimeMillis() >= until || additionalLimit <= 0 } | ||
|
||
logger.info("Start test generation for ${method.name}") | ||
substituteTypeParameters(method, typeStorage).forEach { newMethod -> | ||
inferAnnotations( | ||
|
@@ -148,7 +147,7 @@ class PythonTestCaseGenerator( | |
hintCollector, | ||
mypyReportLine, | ||
mypyConfigFile, | ||
typeInferenceCancellation | ||
limitManager, | ||
) { functionType -> | ||
val args = (functionType as FunctionType).arguments | ||
|
||
|
@@ -161,22 +160,12 @@ class PythonTestCaseGenerator( | |
pythonPath, | ||
constants, | ||
timeoutForRun, | ||
coveredLines, | ||
PythonTypeStorage.get(mypyStorage) | ||
) | ||
|
||
var invalidExecutionLimit = INVALID_EXECUTION_LIMIT | ||
var coverageLimit = COVERAGE_LIMIT | ||
var coveredBefore = coveredLines.size | ||
|
||
var feedback: InferredTypeFeedback = SuccessFeedback | ||
|
||
val fuzzerCancellation = { | ||
typeInferenceCancellation() | ||
|| coverageLimit == 0 | ||
|| additionalLimit == 0 | ||
|| invalidExecutionLimit == 0 | ||
} | ||
val fuzzerCancellation = { isCancelled() || limitManager.isCancelled() } | ||
val startTime = System.currentTimeMillis() | ||
|
||
engine.fuzzing(args, fuzzerCancellation, until).collect { | ||
|
@@ -186,30 +175,26 @@ class PythonTestCaseGenerator( | |
executions += it.utFuzzedExecution | ||
missingLines = updateCoverage(it.utFuzzedExecution, coveredLines, missingLines) | ||
feedback = SuccessFeedback | ||
limitManager.addSuccessExecution() | ||
} | ||
is InvalidExecution -> { | ||
errors += it.utError | ||
feedback = SuccessFeedback | ||
} | ||
is ArgumentsTypeErrorFeedback -> { | ||
invalidExecutionLimit-- | ||
feedback = InvalidTypeFeedback | ||
limitManager.addInvalidExecution() | ||
} | ||
is TypeErrorFeedback -> { | ||
invalidExecutionLimit-- | ||
feedback = InvalidTypeFeedback | ||
limitManager.addInvalidExecution() | ||
} | ||
} | ||
if (missingLines?.size == 0) { | ||
additionalLimit-- | ||
} | ||
val coveredAfter = coveredLines.size | ||
if (coveredAfter == coveredBefore) { | ||
coverageLimit-- | ||
} | ||
limitManager.missedLines = missingLines?.size | ||
|
||
logger.info { "Time ${System.currentTimeMillis() - startTime}: $generated, $missingLines" } | ||
coveredBefore = coveredAfter | ||
} | ||
limitManager.restart() | ||
feedback | ||
} | ||
} | ||
|
@@ -250,7 +235,7 @@ class PythonTestCaseGenerator( | |
hintCollector: HintCollector, | ||
report: List<MypyReportLine>, | ||
mypyConfigFile: File, | ||
isCancelled: () -> Boolean, | ||
limitManager: GenerationLimitManager, | ||
annotationHandler: suspend (Type) -> InferredTypeFeedback, | ||
) { | ||
val namesInModule = mypyStorage.names | ||
|
@@ -259,6 +244,7 @@ class PythonTestCaseGenerator( | |
.filter { | ||
it.length < 4 || !it.startsWith("__") || !it.endsWith("__") | ||
} | ||
val typeInferenceCancellation = { isCancelled() || limitManager.isCancelled() } | ||
|
||
val algo = BaselineAlgorithm( | ||
typeStorage, | ||
|
@@ -277,14 +263,15 @@ class PythonTestCaseGenerator( | |
) | ||
|
||
runBlocking breaking@{ | ||
if (isCancelled()) { | ||
if (typeInferenceCancellation()) { | ||
return@breaking | ||
} | ||
|
||
algo.run(hintCollector.result, isCancelled, annotationHandler) | ||
val iterationNumber = algo.run(hintCollector.result, typeInferenceCancellation, annotationHandler) | ||
|
||
val existsAnnotation = method.definition.type | ||
if (existsAnnotation.arguments.all { it.pythonTypeName() != "typing.Any" }) { | ||
if (iterationNumber == 1) { | ||
limitManager.mode = TimeoutMode | ||
val existsAnnotation = method.definition.type | ||
annotationHandler(existsAnnotation) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initially this code was needed to run fuzzing with initial types (even when those are not full). Will we run fuzzing with def f(x: list): ... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe. But only if all arguments have not- There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure? Type inference does not run annotationHandler on initial signature. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now I run fuzzing with initial annotations only after type inference) But I can change program and run fuzzing twice with initial annotations, but I think it isn't a good solution There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added handling of an initial annotation after first expansion in BaselineAlgorithm.
|
||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package org.utbot.python.utils | ||
|
||
import kotlin.math.min | ||
|
||
class GenerationLimitManager( | ||
// global settings | ||
var mode: LimitManagerMode, | ||
val until: Long, | ||
|
||
// local settings: one type inference iteration | ||
var executions: Int = 150, | ||
var invalidExecutions: Int = 10, | ||
var additionalExecutions: Int = 5, | ||
var missedLines: Int? = null, | ||
) { | ||
private val initExecution = executions | ||
private val initInvalidExecutions = invalidExecutions | ||
private val initAdditionalExecutions = additionalExecutions | ||
private val initMissedLines = missedLines | ||
|
||
fun restart() { | ||
executions = initExecution | ||
invalidExecutions = initInvalidExecutions | ||
additionalExecutions = initAdditionalExecutions | ||
missedLines = initMissedLines | ||
} | ||
|
||
fun addSuccessExecution() { | ||
executions -= 1 | ||
} | ||
fun addInvalidExecution() { | ||
invalidExecutions -= 1 | ||
} | ||
|
||
fun isCancelled(): Boolean { | ||
return mode.isCancelled(this) | ||
} | ||
} | ||
|
||
interface LimitManagerMode { | ||
fun isCancelled(manager: GenerationLimitManager): Boolean | ||
} | ||
|
||
object MaxCoverageMode : LimitManagerMode { | ||
override fun isCancelled(manager: GenerationLimitManager): Boolean { | ||
return manager.missedLines?.equals(0) == true | ||
} | ||
} | ||
|
||
object TimeoutMode : LimitManagerMode { | ||
override fun isCancelled(manager: GenerationLimitManager): Boolean { | ||
return System.currentTimeMillis() >= manager.until | ||
} | ||
} | ||
|
||
object ExecutionMode : LimitManagerMode { | ||
override fun isCancelled(manager: GenerationLimitManager): Boolean { | ||
if (manager.invalidExecutions <= 0 || manager.executions <= 0) { | ||
return min(manager.invalidExecutions, 0) + min(manager.executions, 0) <= manager.additionalExecutions | ||
} | ||
return false | ||
} | ||
} | ||
|
||
object MaxCoverageWithTimeoutMode : LimitManagerMode { | ||
override fun isCancelled(manager: GenerationLimitManager): Boolean { | ||
return MaxCoverageMode.isCancelled(manager) || TimeoutMode.isCancelled(manager) | ||
} | ||
} | ||
|
||
object ExecutionWithTimeoutMode : LimitManagerMode { | ||
override fun isCancelled(manager: GenerationLimitManager): Boolean { | ||
return ExecutionMode.isCancelled(manager) || TimeoutMode.isCancelled(manager) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.