diff --git a/gradle.properties b/gradle.properties index 749730f691..750abc314b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,6 +35,9 @@ eclipse_aether_version=1.1.0 maven_wagon_version=3.5.1 maven_plugin_api_version=3.8.5 maven_plugin_tools_version=3.6.4 +maven_plugin_testing_version=3.3.0 +maven_resolver_api_version=1.8.0 +sisu_plexus_version=0.3.5 javacpp_version=1.5.3 jsoup_version=1.7.2 djl_api_version=0.17.0 diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt index c36307ad0f..29d4d51139 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt @@ -214,8 +214,12 @@ object UtExecutionInstrumentation : Instrumentation { return UtTimeoutException(exception) } val instrs = traceHandler.computeInstructionList() - val isNested = instrs.first().callId != instrs.last().callId - return if (instrs.last().instructionData is ExplicitThrowInstruction) { + val isNested = if (instrs.isEmpty()) { + false + } else { + instrs.first().callId != instrs.last().callId + } + return if (instrs.isNotEmpty() && instrs.last().instructionData is ExplicitThrowInstruction) { UtExplicitlyThrownException(exception, isNested) } else { UtImplicitlyThrownException(exception, isNested) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/TestUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/TestUtils.kt index b94fca37b5..c8778f8cf6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/TestUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/TestUtils.kt @@ -63,6 +63,8 @@ data class GeneratedSarif(val text: String) { fun hasCodeFlows(): Boolean = text.contains("codeFlows") fun codeFlowsIsNotEmpty(): Boolean = text.contains("threadFlows") + + fun contains(value: String): Boolean = text.contains(value) } fun compileClassAndGetClassPath(classNameToSource: Pair): Pair { diff --git a/utbot-maven/build.gradle b/utbot-maven/build.gradle index b14ec01f18..bdf943a261 100644 --- a/utbot-maven/build.gradle +++ b/utbot-maven/build.gradle @@ -13,18 +13,23 @@ dependencies { // `compile` because `api` dependencies are not included in pom.xml by `install` task compile project(':utbot-framework') - implementation "org.apache.maven:maven-core:${maven_plugin_api_version}" - implementation "org.apache.maven:maven-plugin-api:${maven_plugin_api_version}" - compileOnly "org.apache.maven.plugin-tools:maven-plugin-annotations:${maven_plugin_tools_version}" - implementation "io.github.microutils:kotlin-logging:${kotlin_logging_version}" - - mavenEmbedder "org.apache.maven:maven-embedder:${maven_plugin_api_version}" - mavenEmbedder "org.apache.maven:maven-compat:${maven_plugin_api_version}" - mavenEmbedder "org.slf4j:slf4j-simple:${slf4j_version}" - mavenEmbedder "org.eclipse.aether:aether-connector-basic:${eclipse_aether_version}" - mavenEmbedder "org.eclipse.aether:aether-transport-wagon:${eclipse_aether_version}" - mavenEmbedder "org.apache.maven.wagon:wagon-http:${maven_wagon_version}" - mavenEmbedder "org.apache.maven.wagon:wagon-provider-api:${maven_wagon_version}" + implementation "org.apache.maven:maven-core:$maven_plugin_api_version" + implementation "org.apache.maven:maven-plugin-api:$maven_plugin_api_version" + compileOnly "org.apache.maven.plugin-tools:maven-plugin-annotations:$maven_plugin_tools_version" + implementation "io.github.microutils:kotlin-logging:$kotlin_logging_version" + + implementation "org.eclipse.sisu:org.eclipse.sisu.plexus:$sisu_plexus_version" + testImplementation "org.apache.maven.plugin-testing:maven-plugin-testing-harness:$maven_plugin_testing_version" + testImplementation "org.apache.maven:maven-compat:$maven_plugin_api_version" + testImplementation "org.apache.maven.resolver:maven-resolver-api:$maven_resolver_api_version" + + mavenEmbedder "org.apache.maven:maven-embedder:$maven_plugin_api_version" + mavenEmbedder "org.apache.maven:maven-compat:$maven_plugin_api_version" + mavenEmbedder "org.slf4j:slf4j-simple:$slf4j_version" + mavenEmbedder "org.eclipse.aether:aether-connector-basic:$eclipse_aether_version" + mavenEmbedder "org.eclipse.aether:aether-transport-wagon:$eclipse_aether_version" + mavenEmbedder "org.apache.maven.wagon:wagon-http:$maven_wagon_version" + mavenEmbedder "org.apache.maven.wagon:wagon-provider-api:$maven_wagon_version" } /** diff --git a/utbot-maven/src/main/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojo.kt b/utbot-maven/src/main/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojo.kt index 8d2768970f..3660af2576 100644 --- a/utbot-maven/src/main/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojo.kt +++ b/utbot-maven/src/main/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojo.kt @@ -34,8 +34,11 @@ internal val logger = KotlinLogging.logger {} ) class GenerateTestsAndSarifReportMojo : AbstractMojo() { + /** + * The maven project for which we are creating a SARIF report. + */ @Parameter(defaultValue = "\${project}", readonly = true) - private lateinit var mavenProject: MavenProject + lateinit var mavenProject: MavenProject /** * Classes for which the SARIF report will be created. @@ -116,12 +119,23 @@ class GenerateTestsAndSarifReportMojo : AbstractMojo() { @Parameter(defaultValue = "") internal var classesToMockAlways: List = listOf() + /** + * Provides configuration needed to create a SARIF report. + */ + val sarifProperties: SarifMavenConfigurationProvider + get() = SarifMavenConfigurationProvider(this) + + /** + * Contains information about the maven project for which we are creating a SARIF report. + */ + lateinit var rootMavenProjectWrapper: MavenProjectWrapper + /** * Entry point: called when the user starts this maven task. */ override fun execute() { - val rootMavenProjectWrapper = try { - MavenProjectWrapper(mavenProject, sarifProperties) + try { + rootMavenProjectWrapper = MavenProjectWrapper(mavenProject, sarifProperties) } catch (t: Throwable) { logger.error(t) { "Unexpected error while configuring the maven task" } return @@ -140,9 +154,6 @@ class GenerateTestsAndSarifReportMojo : AbstractMojo() { // internal - private val sarifProperties: SarifMavenConfigurationProvider - get() = SarifMavenConfigurationProvider(this) - /** * Generates tests and a SARIF report for classes in the [mavenProjectWrapper] and in all its child projects. */ diff --git a/utbot-maven/src/test/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojoTest.kt b/utbot-maven/src/test/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojoTest.kt new file mode 100644 index 0000000000..c2ff1de7fb --- /dev/null +++ b/utbot-maven/src/test/kotlin/org/utbot/maven/plugin/GenerateTestsAndSarifReportMojoTest.kt @@ -0,0 +1,73 @@ +package org.utbot.maven.plugin + +import org.apache.maven.plugin.testing.AbstractMojoTestCase +import org.apache.maven.project.MavenProject +import org.junit.jupiter.api.* +import org.utbot.common.PathUtil.toPath +import org.utbot.framework.util.GeneratedSarif +import java.io.File + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class GenerateTestsAndSarifReportMojoTest : AbstractMojoTestCase() { + + @BeforeAll + override fun setUp() { + super.setUp() + } + + @AfterAll + override fun tearDown() { + super.tearDown() + } + + @Test + fun `test directory exists and not empty`() { + val testsRelativePath = sarifReportMojo.sarifProperties.generatedTestsRelativeRoot + val testDirectory = testMavenProject.projectBaseDir.resolve(testsRelativePath) + assert(directoryExistsAndNotEmpty(testDirectory)) + } + + @Test + fun `sarif directory exists and not empty`() { + val reportsRelativePath = sarifReportMojo.sarifProperties.sarifReportsRelativeRoot + val sarifDirectory = testMavenProject.projectBaseDir.resolve(reportsRelativePath) + assert(directoryExistsAndNotEmpty(sarifDirectory)) + } + + @Test + fun `sarif report contains all required results`() { + val sarifReportFile = sarifReportMojo.rootMavenProjectWrapper.sarifReportFile + val sarifReportText = sarifReportFile.readText() + GeneratedSarif(sarifReportText).apply { + assert(hasSchema()) + assert(hasVersion()) + assert(hasRules()) + assert(hasResults()) + assert(hasCodeFlows()) + assert(codeFlowsIsNotEmpty()) + assert(contains("ArithmeticException")) + } + } + + // internal + + private val testMavenProject: TestMavenProject = + TestMavenProject("src/test/resources/project-to-test".toPath()) + + private val sarifReportMojo by lazy { + configureSarifReportMojo(testMavenProject.mavenProject).apply { + this.execute() + } + } + + private fun configureSarifReportMojo(mavenProject: MavenProject): GenerateTestsAndSarifReportMojo { + val generateTestsAndSarifReportMojo = configureMojo( + GenerateTestsAndSarifReportMojo(), "utbot-maven", mavenProject.file + ) as GenerateTestsAndSarifReportMojo + generateTestsAndSarifReportMojo.mavenProject = mavenProject + return generateTestsAndSarifReportMojo + } + + private fun directoryExistsAndNotEmpty(directory: File): Boolean = + directory.exists() && directory.isDirectory && directory.list()?.isNotEmpty() == true +} \ No newline at end of file diff --git a/utbot-maven/src/test/kotlin/org/utbot/maven/plugin/TestMavenProject.kt b/utbot-maven/src/test/kotlin/org/utbot/maven/plugin/TestMavenProject.kt new file mode 100644 index 0000000000..6c6bac1ddb --- /dev/null +++ b/utbot-maven/src/test/kotlin/org/utbot/maven/plugin/TestMavenProject.kt @@ -0,0 +1,38 @@ +package org.utbot.maven.plugin + +import org.apache.commons.lang3.reflect.FieldUtils +import org.apache.maven.model.io.xpp3.MavenXpp3Reader +import org.apache.maven.project.MavenProject +import org.codehaus.plexus.util.FileUtils +import java.io.File +import java.io.FileReader +import java.nio.file.Path + +/** + * Wrapper for the maven project stored in the test resources. + */ +class TestMavenProject(pathToProject: Path) { + + /** + * Path to the copied maven project. + */ + val projectBaseDir = + File("build/resources/${pathToProject.fileName}") + + init { + projectBaseDir.deleteRecursively() + // copying the project to the build directory to change it there + FileUtils.copyDirectoryStructure(pathToProject.toFile(), projectBaseDir) + } + + val mavenProject: MavenProject = run { + val pomFile = File(projectBaseDir, "pom.xml") + val model = MavenXpp3Reader().read(FileReader(pomFile)) + val mavenProject = MavenProject(model) + mavenProject.setPomFile(pomFile) + mavenProject.collectedProjects = listOf() // no child modules + mavenProject.addCompileSourceRoot(mavenProject.build.sourceDirectory) + FieldUtils.writeField(mavenProject, "basedir", projectBaseDir, true) + mavenProject + } +} \ No newline at end of file diff --git a/utbot-maven/src/test/kotlin/org/utbot/maven/plugin/extension/SarifMavenConfigurationProviderTest.kt b/utbot-maven/src/test/kotlin/org/utbot/maven/plugin/extension/SarifMavenConfigurationProviderTest.kt new file mode 100644 index 0000000000..a9bcc6317f --- /dev/null +++ b/utbot-maven/src/test/kotlin/org/utbot/maven/plugin/extension/SarifMavenConfigurationProviderTest.kt @@ -0,0 +1,119 @@ +package org.utbot.maven.plugin.extension + +import org.apache.maven.plugin.testing.AbstractMojoTestCase +import org.apache.maven.project.MavenProject +import org.junit.jupiter.api.* +import org.utbot.common.PathUtil.toPath +import org.utbot.engine.Mocker +import org.utbot.framework.codegen.* +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.maven.plugin.GenerateTestsAndSarifReportMojo +import org.utbot.maven.plugin.TestMavenProject +import java.io.File + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SarifMavenConfigurationProviderTest : AbstractMojoTestCase() { + + @BeforeAll + override fun setUp() { + super.setUp() + } + + @AfterAll + override fun tearDown() { + super.tearDown() + } + + @Test + fun `targetClasses should be provided from the configuration`() { + Assertions.assertEquals(listOf("Main"), configurationProvider.targetClasses) + } + + @Test + fun `projectRoot should be provided from the configuration`() { + Assertions.assertEquals(File("build/resources/project-to-test"), configurationProvider.projectRoot) + } + + @Test + fun `generatedTestsRelativeRoot should be provided from the configuration`() { + Assertions.assertEquals("target/generated/test", configurationProvider.generatedTestsRelativeRoot) + } + + @Test + fun `sarifReportsRelativeRoot should be provided from the configuration`() { + Assertions.assertEquals("target/generated/sarif", configurationProvider.sarifReportsRelativeRoot) + } + + @Test + fun `markGeneratedTestsDirectoryAsTestSourcesRoot should be provided from the configuration`() { + Assertions.assertEquals(true, configurationProvider.markGeneratedTestsDirectoryAsTestSourcesRoot) + } + + @Test + fun `testFramework should be provided from the configuration`() { + Assertions.assertEquals(Junit5, configurationProvider.testFramework) + } + + + @Test + fun `mockFramework should be provided from the configuration`() { + Assertions.assertEquals(MockFramework.MOCKITO, configurationProvider.mockFramework) + } + + + @Test + fun `generationTimeout should be provided from the configuration`() { + Assertions.assertEquals(10000, configurationProvider.generationTimeout) + } + + @Test + fun `codegenLanguage should be provided from the configuration`() { + Assertions.assertEquals(CodegenLanguage.JAVA, configurationProvider.codegenLanguage) + } + + @Test + fun `mockStrategy should be provided from the configuration`() { + Assertions.assertEquals(MockStrategyApi.OTHER_PACKAGES, configurationProvider.mockStrategy) + } + + @Test + fun `staticsMocking should be provided from the configuration`() { + Assertions.assertEquals(MockitoStaticMocking, configurationProvider.staticsMocking) + } + + @Test + fun `forceStaticMocking should be provided from the configuration`() { + Assertions.assertEquals(ForceStaticMocking.FORCE, configurationProvider.forceStaticMocking) + } + + @Test + fun `classesToMockAlways should be provided from the configuration`() { + val expectedClassesToMockAlways = + (Mocker.defaultSuperClassesToMockAlwaysNames + "java.io.File").map(::ClassId).toSet() + Assertions.assertEquals(expectedClassesToMockAlways, configurationProvider.classesToMockAlways) + } + + // internal + + private val testMavenProject: TestMavenProject = + TestMavenProject("src/test/resources/project-to-test".toPath()) + + private val sarifReportMojo by lazy { + configureSarifReportMojo(testMavenProject.mavenProject) + } + + private val configurationProvider by lazy { + sarifReportMojo.sarifProperties + } + + private fun configureSarifReportMojo(mavenProject: MavenProject): GenerateTestsAndSarifReportMojo { + val generateTestsAndSarifReportMojo = configureMojo( + GenerateTestsAndSarifReportMojo(), "utbot-maven", mavenProject.file + ) as GenerateTestsAndSarifReportMojo + generateTestsAndSarifReportMojo.mavenProject = mavenProject + return generateTestsAndSarifReportMojo + } +} \ No newline at end of file diff --git a/utbot-maven/src/test/resources/project-to-test/pom.xml b/utbot-maven/src/test/resources/project-to-test/pom.xml new file mode 100644 index 0000000000..2783607f96 --- /dev/null +++ b/utbot-maven/src/test/resources/project-to-test/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + org.example + project-to-test + 1.0 + + + 8 + 8 + + + + + build/resources/project-to-test/src/main/java + build/resources/project-to-test/target/classes + + + + org.utbot + utbot-maven + 1.0-SNAPSHOT + + + Main + + build/resources/project-to-test + target/generated/test + target/generated/sarif + true + junit5 + mockito + 10000 + java + package-based + mock-statics + force + + java.io.File + + + + + + \ No newline at end of file diff --git a/utbot-maven/src/test/resources/project-to-test/src/main/java/Main.java b/utbot-maven/src/test/resources/project-to-test/src/main/java/Main.java new file mode 100644 index 0000000000..3e0dea0fc4 --- /dev/null +++ b/utbot-maven/src/test/resources/project-to-test/src/main/java/Main.java @@ -0,0 +1,5 @@ +public class Main { + public int f(int a) { + return 1 / a; + } +} diff --git a/utbot-maven/src/test/resources/project-to-test/target/classes/Main.class b/utbot-maven/src/test/resources/project-to-test/target/classes/Main.class new file mode 100644 index 0000000000..ae1e4ec1ea Binary files /dev/null and b/utbot-maven/src/test/resources/project-to-test/target/classes/Main.class differ