Skip to content

UtMethod removal #862

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

Merged
merged 7 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.choice
import mu.KotlinLogging
import org.utbot.cli.util.createClassLoader
import org.utbot.engine.Mocker
import org.utbot.framework.plugin.api.ClassId
Expand All @@ -14,9 +15,6 @@ import java.io.File
import java.net.URLClassLoader
import java.nio.file.Paths
import java.time.temporal.ChronoUnit
import kotlin.reflect.KClass
import kotlin.reflect.jvm.jvmName
import mu.KotlinLogging


private val logger = KotlinLogging.logger {}
Expand Down Expand Up @@ -93,16 +91,15 @@ class BunchTestGeneratorCommand : GenerateTestsAbstractCommand(
logger.debug { "Generating test for [$targetClassFqn] - started" }
logger.debug { "Classpath to be used: ${newline()} $classPath ${newline()}" }

val classUnderTest: KClass<*> = loadClassBySpecifiedFqn(targetClassFqn)
val targetMethods = classUnderTest.targetMethods()
if (targetMethods.isEmpty()) return

val testCaseGenerator = initializeGenerator(workingDirectory)

// utContext is used in `generate`, `generateTest`, `generateReport`
// utContext is used in `targetMethods`, `generate`, `generateTest`, `generateReport`
withUtContext(UtContext(classLoader)) {
val classIdUnderTest = ClassId(targetClassFqn)
val targetMethods = classIdUnderTest.targetMethods()
if (targetMethods.isEmpty()) return

val testCaseGenerator = initializeGenerator(workingDirectory)

val testClassName = "${classUnderTest.simpleName}Test"
val testClassName = "${classIdUnderTest.simpleName}Test"

val testSets = generateTestSets(
testCaseGenerator,
Expand All @@ -112,7 +109,7 @@ class BunchTestGeneratorCommand : GenerateTestsAbstractCommand(
.mapTo(mutableSetOf()) { ClassId(it) }
)

val testClassBody = generateTest(classUnderTest, testClassName, testSets)
val testClassBody = generateTest(classIdUnderTest, testClassName, testSets)

val outputArgAsFile = File(output ?: "")
if (!outputArgAsFile.exists()) {
Expand All @@ -121,7 +118,7 @@ class BunchTestGeneratorCommand : GenerateTestsAbstractCommand(

val outputDir = "$outputArgAsFile${File.separator}"

val packageNameAsList = classUnderTest.jvmName.split('.').dropLast(1)
val packageNameAsList = classIdUnderTest.jvmName.split('.').dropLast(1)
val path = Paths.get("${outputDir}${packageNameAsList.joinToString(separator = File.separator)}")
path.toFile().mkdirs()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,20 @@ import org.utbot.framework.codegen.model.CodeGenerator
import org.utbot.framework.codegen.testFrameworkByName
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.MockStrategyApi
import org.utbot.framework.plugin.api.TreatOverflowAsError
import org.utbot.framework.plugin.api.TestCaseGenerator
import org.utbot.framework.plugin.api.UtMethod
import org.utbot.framework.plugin.api.TreatOverflowAsError
import org.utbot.framework.plugin.api.UtMethodTestSet
import org.utbot.framework.plugin.api.util.id
import org.utbot.summary.summarize
import java.io.File
import java.lang.reflect.Method
import java.net.URLClassLoader
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import kotlin.reflect.KCallable
import kotlin.reflect.KClass
import kotlin.reflect.jvm.kotlinFunction

private const val LONG_GENERATION_TIMEOUT = 1_200_000L

Expand Down Expand Up @@ -151,12 +147,9 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) :
return Paths.get(classAbsolutePath)
}

protected fun loadClassBySpecifiedFqn(classFqn: String): KClass<*> =
classLoader.loadClass(classFqn).kotlin

protected fun generateTestSets(
testCaseGenerator: TestCaseGenerator,
targetMethods: List<UtMethod<*>>,
targetMethods: List<ExecutableId>,
sourceCodeFile: Path? = null,
searchDirectory: Path,
chosenClassesToMockAlways: Set<ClassId>
Expand Down Expand Up @@ -186,7 +179,7 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) :
}
}

protected fun generateTest(classUnderTest: KClass<*>, testClassname: String, testSets: List<UtMethodTestSet>): String =
protected fun generateTest(classUnderTest: ClassId, testClassname: String, testSets: List<UtMethodTestSet>): String =
initializeCodeGenerator(
testFramework,
classUnderTest
Expand All @@ -201,31 +194,21 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) :
return TestCaseGenerator(workingDirectory, classPathNormalized, System.getProperty("java.class.path"))
}

private fun initializeCodeGenerator(testFramework: String, classUnderTest: KClass<*>): CodeGenerator {
private fun initializeCodeGenerator(testFramework: String, classUnderTest: ClassId): CodeGenerator {
val generateWarningsForStaticMocking =
forceStaticMocking == ForceStaticMocking.FORCE && staticsMocking is NoStaticMocking
return CodeGenerator(
testFramework = testFrameworkByName(testFramework),
classUnderTest = classUnderTest.id,
classUnderTest = classUnderTest,
codegenLanguage = codegenLanguage,
staticsMocking = staticsMocking,
forceStaticMocking = forceStaticMocking,
generateWarningsForStaticMocking = generateWarningsForStaticMocking,
)
}

protected fun KClass<*>.targetMethods() =
this.java.declaredMethods.mapNotNull {
toUtMethod(it, kClass = this)
}

private fun toUtMethod(method: Method, kClass: KClass<*>): UtMethod<*>? =
method.kotlinFunction?.let {
UtMethod(it as KCallable<*>, kClass)
} ?: run {
logger.info("Method does not have a kotlin function: $method")
null
}
protected fun ClassId.targetMethods(): List<MethodId> =
allMethods.filter { it.classId == this }.toList() // only declared methods

protected fun saveToFile(snippet: String, outputPath: String?) =
outputPath?.let {
Expand Down
34 changes: 17 additions & 17 deletions utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import com.github.ajalt.clikt.parameters.types.choice
import mu.KotlinLogging
import org.utbot.common.PathUtil.toPath
import org.utbot.common.filterWhen
import org.utbot.engine.Mocker
import org.utbot.framework.UtSettings
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.UtMethodTestSet
import org.utbot.framework.plugin.api.util.UtContext
import org.utbot.framework.plugin.api.util.isAbstract
import org.utbot.framework.plugin.api.util.withUtContext
import org.utbot.framework.util.isKnownSyntheticMethod
import org.utbot.sarif.SarifReport
import org.utbot.sarif.SourceFindingStrategyDefault
import java.nio.file.Files
import java.nio.file.Paths
import java.time.temporal.ChronoUnit
import kotlin.reflect.KClass
import mu.KotlinLogging
import org.utbot.common.filterWhen
import org.utbot.framework.UtSettings
import org.utbot.framework.util.isKnownSyntheticMethod


private val logger = KotlinLogging.logger {}
Expand Down Expand Up @@ -93,20 +93,20 @@ class GenerateTestsCommand :
logger.debug { "Generating test for [$targetClassFqn] - started" }
logger.debug { "Classpath to be used: ${newline()} $classPath ${newline()}" }

val classUnderTest: KClass<*> = loadClassBySpecifiedFqn(targetClassFqn)
val targetMethods = classUnderTest.targetMethods()
.filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) { !isKnownSyntheticMethod(it) }
.filterNot { it.callable.isAbstract }
val testCaseGenerator = initializeGenerator(workingDirectory)
// utContext is used in `targetMethods`, `generate`, `generateTest`, `generateReport`
withUtContext(UtContext(classLoader)) {
val classIdUnderTest = ClassId(targetClassFqn)
val targetMethods = classIdUnderTest.targetMethods()
.filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) { !isKnownSyntheticMethod(it) }
.filterNot { it.isAbstract }
val testCaseGenerator = initializeGenerator(workingDirectory)

if (targetMethods.isEmpty()) {
throw Exception("Nothing to process. No methods were provided")
}
// utContext is used in `generate`, `generateTest`, `generateReport`
withUtContext(UtContext(targetMethods.first().clazz.java.classLoader)) {
if (targetMethods.isEmpty()) {
throw Exception("Nothing to process. No methods were provided")
}

val testClassName = output?.toPath()?.toFile()?.nameWithoutExtension
?: "${classUnderTest.simpleName}Test"
?: "${classIdUnderTest.simpleName}Test"
val testSets = generateTestSets(
testCaseGenerator,
targetMethods,
Expand All @@ -115,7 +115,7 @@ class GenerateTestsCommand :
chosenClassesToMockAlways = (Mocker.defaultSuperClassesToMockAlwaysNames + classesToMockAlways)
.mapTo(mutableSetOf()) { ClassId(it) }
)
val testClassBody = generateTest(classUnderTest, testClassName, testSets)
val testClassBody = generateTest(classIdUnderTest, testClassName, testSets)

if (printToStdOut) {
logger.info { testClassBody }
Expand Down
19 changes: 9 additions & 10 deletions utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ import java.time.Duration
import java.util.concurrent.TimeUnit
import java.util.zip.ZipFile
import kotlin.concurrent.thread
import kotlin.reflect.KClass
import kotlin.streams.asSequence
import mu.KotlinLogging

fun KClass<*>.toClassFilePath(): String {
val name = requireNotNull(java.name) { "Class is local or anonymous" }
fun Class<*>.toClassFilePath(): String {
val name = requireNotNull(name) { "Class is local or anonymous" }

return "${name.replace('.', '/')}.class"
}
Expand Down Expand Up @@ -83,12 +82,12 @@ object FileUtil {
* Copy the class file for given [classes] to temporary folder.
* It can be used for Soot analysis.
*/
fun isolateClassFiles(vararg classes: KClass<*>): File {
fun isolateClassFiles(vararg classes: Class<*>): File {
val tempDir = createTempDirectory("generated-").toFile()

for (clazz in classes) {
val path = clazz.toClassFilePath()
val resource = clazz.java.classLoader.getResource(path) ?: error("No such file: $path")
val resource = clazz.classLoader.getResource(path) ?: error("No such file: $path")

if (resource.toURI().scheme == "jar") {
val jarLocation = resource.toURI().extractJarName()
Expand All @@ -107,7 +106,7 @@ object FileUtil {

private fun URI.extractJarName(): URI = URI(this.schemeSpecificPart.substringBefore("!").replace(" ", "%20"))

private fun extractClassFromArchive(archiveFile: Path, clazz: KClass<*>, destPath: Path) {
private fun extractClassFromArchive(archiveFile: Path, clazz: Class<*>, destPath: Path) {
val classFilePath = clazz.toClassFilePath()
ZipFile(archiveFile.toFile()).use { archive ->
val entry = archive.stream().asSequence().filter { it.name.normalizePath() == classFilePath }.single()
Expand All @@ -121,9 +120,9 @@ object FileUtil {
* Locates class path by class.
* It can be used for Soot analysis.
*/
fun locateClassPath(clazz: KClass<*>): File? {
fun locateClassPath(clazz: Class<*>): File? {
val path = clazz.toClassFilePath()
val resource = requireNotNull(clazz.java.classLoader.getResource(path)) { "No such file: $path" }
val resource = requireNotNull(clazz.classLoader.getResource(path)) { "No such file: $path" }
if (resource.toURI().scheme == "jar") return null
val fullPath = resource.path.removeSuffix(path)
return File(fullPath)
Expand All @@ -132,9 +131,9 @@ object FileUtil {
/**
* Locates class and returns class location. Could be directory or jar based.
*/
fun locateClass(clazz: KClass<*>): ClassLocation {
fun locateClass(clazz: Class<*>): ClassLocation {
val path = clazz.toClassFilePath()
val resource = requireNotNull(clazz.java.classLoader.getResource(path)) { "No such file: $path" }
val resource = requireNotNull(clazz.classLoader.getResource(path)) { "No such file: $path" }
return if (resource.toURI().scheme == "jar") {
val jarLocation = resource.toURI().extractJarName()
JarClassLocation(Paths.get(jarLocation))
Expand Down
43 changes: 41 additions & 2 deletions utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package org.utbot.common

import org.utbot.common.Reflection.setModifiers
import sun.misc.Unsafe
import java.lang.reflect.AccessibleObject
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import sun.misc.Unsafe
import java.lang.reflect.Member
import java.lang.reflect.Method
import java.lang.reflect.Modifier

object Reflection {
val unsafe: Unsafe
Expand Down Expand Up @@ -73,3 +74,41 @@ inline fun <reified R> Field.withAccessibility(block: () -> R): R {
setModifiers(this, prevModifiers)
}
}

// utility properties

val Member.isAbstract
get() = Modifier.isAbstract(modifiers)

val Member.isStatic
get() = Modifier.isStatic(modifiers)

val Member.isPrivate
get() = Modifier.isPrivate(modifiers)

val Member.isPublic
get() = Modifier.isPublic(modifiers)

val Member.isFinal
get() = Modifier.isFinal(modifiers)

val Member.isProtected
get() = Modifier.isProtected(modifiers)

val Class<*>.isAbstract
get() = Modifier.isAbstract(modifiers)

val Class<*>.isStatic
get() = Modifier.isStatic(modifiers)

val Class<*>.isPrivate
get() = Modifier.isPrivate(modifiers)

val Class<*>.isPublic
get() = Modifier.isPublic(modifiers)

val Class<*>.isFinal
get() = Modifier.isFinal(modifiers)

val Class<*>.isProtected
get() = Modifier.isProtected(modifiers)
Loading