Skip to content

Commit c03b4d6

Browse files
authored
Fixed test generation for generics (#1846)
* First version of test generation for generics * Fix bug with constructors from different ancestors * Fixed case with type parameters of a function
1 parent a2a2bec commit c03b4d6

File tree

13 files changed

+249
-106
lines changed

13 files changed

+249
-106
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import TypeVar, Generic
2+
from logging import Logger
3+
4+
T = TypeVar('T', bound=bool)
5+
U = TypeVar('U', str, object)
6+
7+
8+
class LoggedVar(Generic[T]):
9+
def __init__(self, value: T, name: str) -> None:
10+
self.name = name
11+
self.value = value
12+
13+
def set(self, new: T) -> None:
14+
self.value = new
15+
16+
def get(self, x: U):
17+
return self.value, x
18+
19+
20+
def func(x: T, y: U):
21+
if x:
22+
return y
23+
else:
24+
return 100

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

Lines changed: 122 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import org.utbot.python.newtyping.*
1313
import org.utbot.python.newtyping.ast.visitor.Visitor
1414
import org.utbot.python.newtyping.ast.visitor.constants.ConstantCollector
1515
import org.utbot.python.newtyping.ast.visitor.hints.HintCollector
16-
import org.utbot.python.newtyping.general.FunctionType
17-
import org.utbot.python.newtyping.general.Type
16+
import org.utbot.python.newtyping.general.*
1817
import org.utbot.python.newtyping.inference.InferredTypeFeedback
1918
import org.utbot.python.newtyping.inference.InvalidTypeFeedback
2019
import org.utbot.python.newtyping.inference.SuccessFeedback
@@ -25,6 +24,7 @@ import org.utbot.python.newtyping.mypy.MypyReportLine
2524
import org.utbot.python.newtyping.mypy.getErrorNumber
2625
import org.utbot.python.newtyping.utils.getOffsetLine
2726
import org.utbot.python.typing.MypyAnnotations
27+
import org.utbot.python.utils.PriorityCartesianProduct
2828
import java.io.File
2929

3030
private val logger = KotlinLogging.logger {}
@@ -44,16 +44,19 @@ class PythonTestCaseGenerator(
4444
private val mypyStorage: MypyAnnotationStorage,
4545
private val mypyReportLine: List<MypyReportLine>,
4646
private val mypyConfigFile: File,
47-
){
47+
) {
4848

4949
private val storageForMypyMessages: MutableList<MypyAnnotations.MypyReportLine> = mutableListOf()
5050

5151
private fun findMethodByDescription(mypyStorage: MypyAnnotationStorage, method: PythonMethodHeader): PythonMethod {
52-
val containingClass = method.containingPythonClassId
53-
val functionDef = if (containingClass == null) {
52+
var containingClass: CompositeType? = null
53+
val containingClassName = method.containingPythonClassId?.simpleName
54+
val functionDef = if (containingClassName == null) {
5455
mypyStorage.definitions[curModule]!![method.name]!!.getUtBotDefinition()!!
5556
} else {
56-
mypyStorage.definitions[curModule]!![containingClass.simpleName]!!.type.asUtBotType.getPythonAttributes().first {
57+
containingClass =
58+
mypyStorage.definitions[curModule]!![containingClassName]!!.getUtBotType() as CompositeType
59+
mypyStorage.definitions[curModule]!![containingClassName]!!.type.asUtBotType.getPythonAttributes().first {
5760
it.meta.name == method.name
5861
}
5962
} as? PythonFunctionDefinition ?: error("Selected method is not a function definition")
@@ -64,7 +67,7 @@ class PythonTestCaseGenerator(
6467
return PythonMethod(
6568
name = method.name,
6669
moduleFilename = method.moduleFilename,
67-
containingPythonClassId = method.containingPythonClassId,
70+
containingPythonClass = containingClass,
6871
codeAsString = funcDef.body.source,
6972
definition = functionDef,
7073
ast = funcDef.body
@@ -84,13 +87,64 @@ class PythonTestCaseGenerator(
8487
} ?: emptyMap()
8588

8689
val namesStorage = GlobalNamesStorage(mypyStorage)
87-
val hintCollector = HintCollector(method.definition, typeStorage, mypyExpressionTypes , namesStorage, curModule)
90+
val hintCollector = HintCollector(method.definition, typeStorage, mypyExpressionTypes, namesStorage, curModule)
8891
val constantCollector = ConstantCollector(typeStorage)
8992
val visitor = Visitor(listOf(hintCollector, constantCollector))
9093
visitor.visit(method.ast)
9194
return Pair(hintCollector, constantCollector)
9295
}
9396

97+
private fun getCandidates(param: TypeParameter, typeStorage: PythonTypeStorage): List<Type> {
98+
val meta = param.pythonDescription() as PythonTypeVarDescription
99+
return when (meta.parameterKind) {
100+
PythonTypeVarDescription.ParameterKind.WithConcreteValues -> {
101+
param.constraints.map { it.boundary }
102+
}
103+
PythonTypeVarDescription.ParameterKind.WithUpperBound -> {
104+
typeStorage.simpleTypes.filter {
105+
if (it.hasBoundedParameters())
106+
return@filter false
107+
val bound = param.constraints.first().boundary
108+
PythonSubtypeChecker.checkIfRightIsSubtypeOfLeft(bound, it, typeStorage)
109+
}
110+
}
111+
}
112+
}
113+
114+
private val maxSubstitutions = 10
115+
116+
private fun generateTypesAfterSubstitution(type: Type, typeStorage: PythonTypeStorage): List<Type> {
117+
val params = type.getBoundedParameters()
118+
return PriorityCartesianProduct(params.map { getCandidates(it, typeStorage) }).getSequence().map { subst ->
119+
DefaultSubstitutionProvider.substitute(type, (params zip subst).associate { it })
120+
}.take(maxSubstitutions).toList()
121+
}
122+
123+
private fun substituteTypeParameters(method: PythonMethod, typeStorage: PythonTypeStorage): List<PythonMethod> {
124+
val newClasses =
125+
if (method.containingPythonClass != null) {
126+
generateTypesAfterSubstitution(method.containingPythonClass, typeStorage)
127+
} else {
128+
listOf(null)
129+
}
130+
return newClasses.flatMap { newClass ->
131+
val funcType = newClass?.getPythonAttributeByName(typeStorage, method.name)?.type as? FunctionType
132+
?: method.definition.type
133+
val newFuncTypes = generateTypesAfterSubstitution(funcType, typeStorage)
134+
newFuncTypes.map { newFuncType ->
135+
val def = PythonFunctionDefinition(method.definition.meta, newFuncType as FunctionType)
136+
PythonMethod(
137+
method.name,
138+
method.moduleFilename,
139+
newClass as? CompositeType,
140+
method.codeAsString,
141+
def,
142+
method.ast
143+
)
144+
}
145+
}.take(maxSubstitutions)
146+
}
147+
94148
fun generate(methodDescription: PythonMethodHeader): PythonTestSet {
95149
storageForMypyMessages.clear()
96150

@@ -108,65 +162,69 @@ class PythonTestCaseGenerator(
108162
var missingLines: Set<Int>? = null
109163
val coveredLines = mutableSetOf<Int>()
110164
var generated = 0
111-
val typeInferenceCancellation = { isCancelled() || System.currentTimeMillis() >= until || missingLines?.size == 0 }
165+
val typeInferenceCancellation =
166+
{ isCancelled() || System.currentTimeMillis() >= until || missingLines?.size == 0 }
112167

113-
inferAnnotations(
114-
method,
115-
mypyStorage,
116-
typeStorage,
117-
hintCollector,
118-
mypyReportLine,
119-
mypyConfigFile,
120-
typeInferenceCancellation
121-
) { functionType ->
122-
val args = (functionType as FunctionType).arguments
123-
124-
logger.info { "Inferred annotations: ${ args.joinToString { it.pythonTypeRepresentation() } }" }
125-
126-
val engine = PythonEngine(
127-
method,
128-
directoriesForSysPath,
129-
curModule,
130-
pythonPath,
131-
constants,
132-
timeoutForRun,
133-
coveredLines,
134-
PythonTypeStorage.get(mypyStorage)
135-
)
136-
137-
var coverageLimit = COVERAGE_LIMIT
138-
var coveredBefore = coveredLines.size
139-
140-
var feedback: InferredTypeFeedback = SuccessFeedback
141-
142-
val fuzzerCancellation = { typeInferenceCancellation() || coverageLimit == 0 } // || feedback is InvalidTypeFeedback }
143-
144-
engine.fuzzing(args, fuzzerCancellation, until).collect {
145-
generated += 1
146-
when (it) {
147-
is ValidExecution -> {
148-
executions += it.utFuzzedExecution
149-
missingLines = updateCoverage(it.utFuzzedExecution, coveredLines, missingLines)
150-
feedback = SuccessFeedback
151-
}
152-
is InvalidExecution -> {
153-
errors += it.utError
154-
feedback = SuccessFeedback
155-
}
156-
is ArgumentsTypeErrorFeedback -> {
157-
feedback = InvalidTypeFeedback
168+
substituteTypeParameters(method, typeStorage).forEach { newMethod ->
169+
inferAnnotations(
170+
newMethod,
171+
mypyStorage,
172+
typeStorage,
173+
hintCollector,
174+
mypyReportLine,
175+
mypyConfigFile,
176+
typeInferenceCancellation
177+
) { functionType ->
178+
val args = (functionType as FunctionType).arguments
179+
180+
logger.info { "Inferred annotations: ${args.joinToString { it.pythonTypeRepresentation() }}" }
181+
182+
val engine = PythonEngine(
183+
newMethod,
184+
directoriesForSysPath,
185+
curModule,
186+
pythonPath,
187+
constants,
188+
timeoutForRun,
189+
coveredLines,
190+
PythonTypeStorage.get(mypyStorage)
191+
)
192+
193+
var coverageLimit = COVERAGE_LIMIT
194+
var coveredBefore = coveredLines.size
195+
196+
var feedback: InferredTypeFeedback = SuccessFeedback
197+
198+
val fuzzerCancellation =
199+
{ typeInferenceCancellation() || coverageLimit == 0 } // || feedback is InvalidTypeFeedback }
200+
201+
engine.fuzzing(args, fuzzerCancellation, until).collect {
202+
generated += 1
203+
when (it) {
204+
is ValidExecution -> {
205+
executions += it.utFuzzedExecution
206+
missingLines = updateCoverage(it.utFuzzedExecution, coveredLines, missingLines)
207+
feedback = SuccessFeedback
208+
}
209+
is InvalidExecution -> {
210+
errors += it.utError
211+
feedback = SuccessFeedback
212+
}
213+
is ArgumentsTypeErrorFeedback -> {
214+
feedback = InvalidTypeFeedback
215+
}
216+
is TypeErrorFeedback -> {
217+
feedback = InvalidTypeFeedback
218+
}
158219
}
159-
is TypeErrorFeedback -> {
160-
feedback = InvalidTypeFeedback
220+
val coveredAfter = coveredLines.size
221+
if (coveredAfter == coveredBefore) {
222+
coverageLimit -= 1
161223
}
224+
coveredBefore = coveredAfter
162225
}
163-
val coveredAfter = coveredLines.size
164-
if (coveredAfter == coveredBefore) {
165-
coverageLimit -= 1
166-
}
167-
coveredBefore = coveredAfter
226+
feedback
168227
}
169-
feedback
170228
}
171229

172230

@@ -231,13 +289,13 @@ class PythonTestCaseGenerator(
231289
mypyConfigFile
232290
)
233291

234-
runBlocking breaking@ {
292+
runBlocking breaking@{
235293
if (isCancelled()) {
236294
return@breaking
237295
}
238296

239297
val existsAnnotation = method.definition.type
240-
if (existsAnnotation.arguments.all {it.pythonTypeName() != "typing.Any"}) {
298+
if (existsAnnotation.arguments.all { it.pythonTypeName() != "typing.Any" }) {
241299
annotationHandler(existsAnnotation)
242300
}
243301

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.utbot.python.framework.api.python.PythonClassId
77
import org.utbot.python.framework.api.python.PythonModel
88
import org.utbot.python.framework.api.python.util.pythonAnyClassId
99
import org.utbot.python.newtyping.*
10+
import org.utbot.python.newtyping.general.CompositeType
1011
import org.utbot.python.typing.MypyAnnotations
1112

1213
data class PythonArgument(val name: String, val annotation: String?)
@@ -20,7 +21,7 @@ class PythonMethodHeader(
2021
class PythonMethod(
2122
val name: String,
2223
val moduleFilename: String,
23-
val containingPythonClassId: PythonClassId?,
24+
val containingPythonClass: CompositeType?,
2425
val codeAsString: String,
2526
var definition: PythonFunctionDefinition,
2627
val ast: Block
@@ -34,7 +35,7 @@ class PythonMethod(
3435
TODO: Now we think that all class methods has `self` argument! We should support `@property` decorator
3536
*/
3637
val hasThisArgument: Boolean
37-
get() = containingPythonClassId != null
38+
get() = containingPythonClass != null
3839

3940
val arguments: List<PythonArgument>
4041
get() {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.utbot.python.FunctionArguments
77
import org.utbot.python.PythonMethod
88
import org.utbot.python.code.PythonCodeGenerator
99
import org.utbot.python.framework.api.python.util.pythonAnyClassId
10+
import org.utbot.python.newtyping.pythonTypeRepresentation
1011
import org.utbot.python.utils.TemporaryFileManager
1112
import org.utbot.python.utils.getResult
1213
import org.utbot.python.utils.startProcess
@@ -63,7 +64,7 @@ class PythonCodeExecutorImpl(
6364
return Coverage(
6465
coveredInstructions=covered.map {
6566
Instruction(
66-
method.containingPythonClassId?.name ?: pythonAnyClassId.name,
67+
method.containingPythonClass?.pythonTypeRepresentation() ?: pythonAnyClassId.name,
6768
method.methodSignature(),
6869
it,
6970
it.toLong()
@@ -72,7 +73,7 @@ class PythonCodeExecutorImpl(
7273
instructionsCount = statements.size.toLong(),
7374
missedInstructions = missedStatements.map {
7475
Instruction(
75-
method.containingPythonClassId?.name ?: pythonAnyClassId.name,
76+
method.containingPythonClass?.pythonTypeRepresentation() ?: pythonAnyClassId.name,
7677
method.methodSignature(),
7778
it,
7879
it.toLong()

utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import org.utbot.python.newtyping.pythonAnyType
4242
import org.utbot.python.newtyping.pythonModules
4343
import org.utbot.python.newtyping.pythonTypeRepresentation
4444
import org.utbot.python.framework.codegen.toPythonRawString
45+
import org.utbot.python.newtyping.pythonDescription
4546

4647
class PythonCodeGenerator(
4748
classUnderTest: ClassId,
@@ -113,7 +114,7 @@ class PythonCodeGenerator(
113114
val executorModuleName = "utbot_executor.executor"
114115
val executorModuleNameAlias = "__utbot_executor"
115116
val executorFunctionName = "$executorModuleNameAlias.run_calculate_function_value"
116-
val failArgumentsFunctionName = "$executorModuleNameAlias.fail_arguments_initialization"
117+
val failArgumentsFunctionName = "$executorModuleNameAlias.fail_argument_initialization"
117118

118119
val importExecutor = PythonUserImport(executorModuleName, alias_ = executorModuleNameAlias)
119120
val importSys = PythonSystemImport("sys")
@@ -133,12 +134,12 @@ class PythonCodeGenerator(
133134
val outputPath = CgLiteral(pythonStrClassId, fileForOutputName.toPythonRawString())
134135
val databasePath = CgLiteral(pythonStrClassId, coverageDatabasePath.toPythonRawString())
135136

136-
val containingClass = method.containingPythonClassId
137+
val containingClass = method.containingPythonClass
137138
var functionTextName =
138139
if (containingClass == null)
139140
method.name
140141
else
141-
"${containingClass.simpleName}.${method.name}"
142+
"${containingClass.pythonDescription().name.name}.${method.name}"
142143
if (functionModule.isNotEmpty()) {
143144
functionTextName = "$functionModule.$functionTextName"
144145
}

utbot-python/src/main/kotlin/org/utbot/python/fuzzing/PythonApi.kt

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,7 @@ import org.utbot.fuzzing.Seed
1313
import org.utbot.fuzzing.Statistic
1414
import org.utbot.fuzzing.utils.Trie
1515
import org.utbot.python.framework.api.python.PythonTree
16-
import org.utbot.python.fuzzing.provider.BoolValueProvider
17-
import org.utbot.python.fuzzing.provider.BytearrayValueProvider
18-
import org.utbot.python.fuzzing.provider.BytesValueProvider
19-
import org.utbot.python.fuzzing.provider.ComplexValueProvider
20-
import org.utbot.python.fuzzing.provider.ConstantValueProvider
21-
import org.utbot.python.fuzzing.provider.DictValueProvider
22-
import org.utbot.python.fuzzing.provider.FloatValueProvider
23-
import org.utbot.python.fuzzing.provider.IntValueProvider
24-
import org.utbot.python.fuzzing.provider.ListValueProvider
25-
import org.utbot.python.fuzzing.provider.NoneValueProvider
26-
import org.utbot.python.fuzzing.provider.ReduceValueProvider
27-
import org.utbot.python.fuzzing.provider.SetValueProvider
28-
import org.utbot.python.fuzzing.provider.StrValueProvider
29-
import org.utbot.python.fuzzing.provider.TupleFixSizeValueProvider
30-
import org.utbot.python.fuzzing.provider.TupleValueProvider
31-
import org.utbot.python.fuzzing.provider.UnionValueProvider
16+
import org.utbot.python.fuzzing.provider.*
3217
import org.utbot.python.fuzzing.provider.utils.isAny
3318
import org.utbot.python.newtyping.PythonProtocolDescription
3419
import org.utbot.python.newtyping.PythonSubtypeChecker
@@ -79,6 +64,7 @@ fun pythonDefaultValueProviders(idGenerator: IdGenerator<Long>) = listOf(
7964
BytearrayValueProvider,
8065
ReduceValueProvider(idGenerator),
8166
ConstantValueProvider,
67+
TypeAliasValueProvider
8268
)
8369

8470
class PythonFuzzing(

0 commit comments

Comments
 (0)