Skip to content

Commit 145da7d

Browse files
authored
Disable test generation for synthetic methods of data classes #1191 (#1192)
1 parent 684c56a commit 145da7d

File tree

8 files changed

+70
-31
lines changed

8 files changed

+70
-31
lines changed

utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import org.utbot.framework.plugin.api.CodegenLanguage
1717
import org.utbot.framework.plugin.api.UtMethodTestSet
1818
import org.utbot.framework.plugin.api.util.UtContext
1919
import org.utbot.framework.plugin.api.util.isAbstract
20+
import org.utbot.framework.plugin.api.util.isSynthetic
2021
import org.utbot.framework.plugin.api.util.withUtContext
21-
import org.utbot.framework.util.isKnownSyntheticMethod
22+
import org.utbot.framework.util.isKnownImplicitlyDeclaredMethod
2223
import org.utbot.sarif.SarifReport
2324
import org.utbot.sarif.SourceFindingStrategyDefault
2425
import java.nio.file.Files
@@ -50,8 +51,8 @@ class GenerateTestsCommand :
5051
help = "Specifies source code file for a generated test"
5152
)
5253
.required()
53-
.check("Must exist and ends with *.java suffix") {
54-
it.endsWith(".java") && Files.exists(Paths.get(it))
54+
.check("Must exist and end with .java or .kt suffix") {
55+
(it.endsWith(".java") || it.endsWith(".kt")) && Files.exists(Paths.get(it))
5556
}
5657

5758
private val projectRoot by option(
@@ -97,7 +98,9 @@ class GenerateTestsCommand :
9798
withUtContext(UtContext(classLoader)) {
9899
val classIdUnderTest = ClassId(targetClassFqn)
99100
val targetMethods = classIdUnderTest.targetMethods()
100-
.filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) { !isKnownSyntheticMethod(it) }
101+
.filterWhen(UtSettings.skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods) {
102+
!it.isSynthetic && !it.isKnownImplicitlyDeclaredMethod
103+
}
101104
.filterNot { it.isAbstract }
102105
val testCaseGenerator = initializeGenerator(workingDirectory)
103106

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.utbot.framework
33
import com.jetbrains.rd.util.LogLevel
44
import mu.KotlinLogging
55
import org.utbot.common.AbstractSettings
6+
import java.lang.reflect.Executable
67
private val logger = KotlinLogging.logger {}
78

89
/**
@@ -361,9 +362,9 @@ object UtSettings : AbstractSettings(
361362
var singleSelector by getBooleanProperty(true)
362363

363364
/**
364-
* Flag that indicates whether tests for synthetic methods (values, valueOf in enums) should be generated, or not
365+
* Flag that indicates whether tests for synthetic (see [Executable.isSynthetic]) and implicitly declared methods (like values, valueOf in enums) should be generated, or not
365366
*/
366-
var skipTestGenerationForSyntheticMethods by getBooleanProperty(true)
367+
var skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods by getBooleanProperty(true)
367368

368369
/**
369370
* Flag that indicates whether should we branch on and set static fields from trusted libraries or not.

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.utbot.framework.plugin.api.util
22

3-
import org.utbot.common.withAccessibility
43
import org.utbot.framework.plugin.api.BuiltinClassId
54
import org.utbot.framework.plugin.api.BuiltinConstructorId
65
import org.utbot.framework.plugin.api.BuiltinMethodId
@@ -394,6 +393,9 @@ val ClassId.isIterableOrMap: Boolean
394393
val ClassId.isEnum: Boolean
395394
get() = jClass.isEnum
396395

396+
val ClassId.isData: Boolean
397+
get() = kClass.isData
398+
397399
fun ClassId.findFieldByIdOrNull(fieldId: FieldId): Field? {
398400
if (isNotSubtypeOf(fieldId.declaringClass)) {
399401
return null

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ModifierUtil.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.utbot.framework.plugin.api.util
33
import org.utbot.framework.plugin.api.ClassId
44
import org.utbot.framework.plugin.api.ExecutableId
55
import org.utbot.framework.plugin.api.FieldId
6+
import org.utbot.framework.plugin.api.MethodId
67
import java.lang.reflect.Modifier
78

89
class ModifierFactory private constructor(
@@ -77,6 +78,9 @@ val ExecutableId.isPackagePrivate: Boolean
7778
val ExecutableId.isAbstract: Boolean
7879
get() = Modifier.isAbstract(modifiers)
7980

81+
val ExecutableId.isSynthetic: Boolean
82+
get() = (this is MethodId) && method.isSynthetic
83+
8084
// FieldIds
8185

8286
val FieldId.isStatic: Boolean
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.utbot.framework.util
2+
3+
import org.utbot.framework.plugin.api.ExecutableId
4+
import org.utbot.framework.plugin.api.util.isData
5+
import org.utbot.framework.plugin.api.util.isEnum
6+
7+
/**
8+
* Returns whether this method could be implicitly generated by compiler, or not.
9+
*
10+
* Note that here we can only judge this by method name and kind of class (data class, enum, etc).
11+
* There seems to be no (at least, easy) way to check from bytecode if this method was actually overridden by user,
12+
* so this function will return true even if the matching method is not autogenerated but written explicitly by user.
13+
*/
14+
val ExecutableId.isKnownImplicitlyDeclaredMethod: Boolean
15+
get() =
16+
when {
17+
classId.isEnum -> name in KnownImplicitlyDeclaredMethods.enumImplicitlyDeclaredMethodNames
18+
classId.isData -> KnownImplicitlyDeclaredMethods.dataClassImplicitlyDeclaredMethodNameRegexps.any { it.matches(name) }
19+
else -> false
20+
}
21+
22+
/**
23+
* Contains names of methods that are always autogenerated by compiler and thus it is unlikely that
24+
* one would want to generate tests for them.
25+
*/
26+
private object KnownImplicitlyDeclaredMethods {
27+
/** List with names of enum methods that are generated by compiler */
28+
val enumImplicitlyDeclaredMethodNames = listOf("values", "valueOf")
29+
30+
/** List with regexps that match names of methods that are generated by Kotlin compiler for data classes */
31+
val dataClassImplicitlyDeclaredMethodNameRegexps = listOf(
32+
"equals",
33+
"hashCode",
34+
"toString",
35+
"copy",
36+
"component[1-9][0-9]*"
37+
).map { it.toRegex() }
38+
}

utbot-framework/src/main/kotlin/org/utbot/framework/util/SyntheticMethods.kt

Lines changed: 0 additions & 20 deletions
This file was deleted.

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.intellij.testIntegration.TestIntegrationUtils
99
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
1010
import org.jetbrains.kotlin.asJava.elements.isGetter
1111
import org.jetbrains.kotlin.asJava.elements.isSetter
12+
import org.jetbrains.kotlin.psi.KtClass
1213
import org.utbot.common.filterWhen
1314
import org.utbot.framework.UtSettings
1415

@@ -22,8 +23,15 @@ private val PsiMember.isKotlinGetterOrSetter: Boolean
2223
return isGetter || isSetter
2324
}
2425

25-
private fun Iterable<MemberInfo>.filterTestableMethods(): List<MemberInfo> = this
26-
.filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) { it.member !is SyntheticElement }
26+
// By now, we think that method in Kotlin is autogenerated iff navigation to its declaration leads to its declaring class
27+
// rather than the method itself (because such methods don't have bodies that we can navigate to)
28+
private val PsiMember.isKotlinAutogeneratedMethod: Boolean
29+
get() = this is KtLightMethod && navigationElement is KtClass
30+
31+
fun Iterable<MemberInfo>.filterTestableMethods(): List<MemberInfo> = this
32+
.filterWhen(UtSettings.skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods) {
33+
it.member !is SyntheticElement && !it.member.isKotlinAutogeneratedMethod
34+
}
2735
.filterNot { it.member.isAbstract }
2836
.filterNot { it.member.isKotlinGetterOrSetter }
2937

utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import org.utbot.framework.plugin.api.util.jClass
2929
import org.utbot.framework.plugin.api.util.utContext
3030
import org.utbot.framework.plugin.api.util.withUtContext
3131
import org.utbot.framework.plugin.services.JdkInfoService
32-
import org.utbot.framework.util.isKnownSyntheticMethod
32+
import org.utbot.framework.util.isKnownImplicitlyDeclaredMethod
3333
import org.utbot.fuzzer.UtFuzzedExecution
3434
import org.utbot.instrumentation.ConcreteExecutor
3535
import org.utbot.instrumentation.ConcreteExecutorPool
@@ -60,6 +60,7 @@ import kotlinx.coroutines.newSingleThreadContext
6060
import kotlinx.coroutines.runBlocking
6161
import kotlinx.coroutines.withTimeoutOrNull
6262
import kotlinx.coroutines.yield
63+
import org.utbot.framework.plugin.api.util.isSynthetic
6364

6465
internal const val junitVersion = 4
6566
private val logger = KotlinLogging.logger {}
@@ -402,7 +403,9 @@ private fun prepareClass(javaClazz: Class<*>, methodNameFilter: String?): List<E
402403
val classFilteredMethods = methodsToGenerate
403404
.map { it.executableId }
404405
.filter { methodNameFilter?.equals(it.name) ?: true }
405-
.filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) { !isKnownSyntheticMethod(it) }
406+
.filterWhen(UtSettings.skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods) {
407+
!it.isSynthetic && !it.isKnownImplicitlyDeclaredMethod
408+
}
406409
.toList()
407410

408411

0 commit comments

Comments
 (0)