diff --git a/kobalt/src/Build.kt b/kobalt/src/Build.kt index ccee7c7d..4ac0298e 100644 --- a/kobalt/src/Build.kt +++ b/kobalt/src/Build.kt @@ -33,8 +33,15 @@ object Versions { val kotlin = "1.1.1" val aether = "1.0.2.v20150114" val testng = "6.11" + + // JUnit 5 + val junit = "4.12" + val junitPlatform = "1.0.0-M4" + val junitJupiter = "5.0.0-M4" + val junitVintageVersion = "$junit.0-M4" } + fun mavenResolver(vararg m: String) = m.map { "org.apache.maven.resolver:maven-resolver-$it:${Versions.mavenResolver}" } .toTypedArray() @@ -117,7 +124,14 @@ val kobaltPluginApi = project { "org.apache.maven:maven-aether-provider:3.3.9", "org.testng.testng-remote:testng-remote:1.3.0", "org.testng:testng:${Versions.testng}", - "commons-io:commons-io:2.5" + "commons-io:commons-io:2.5", + "org.junit.platform:junit-platform-surefire-provider:${Versions.junitPlatform}", + "org.junit.platform:junit-platform-runner:${Versions.junitPlatform}", + "org.junit.platform:junit-platform-engine:${Versions.junitPlatform}", + "org.junit.platform:junit-platform-console:${Versions.junitPlatform}", + "org.junit.jupiter:junit-jupiter-engine:${Versions.junitJupiter}", + "org.junit.vintage:junit-vintage-engine:${Versions.junitVintageVersion}" + ) exclude(*aether("impl", "spi", "util", "api")) } diff --git a/kobalt/wrapper/kobalt-wrapper.properties b/kobalt/wrapper/kobalt-wrapper.properties index 8b029712..89806ea3 100644 --- a/kobalt/wrapper/kobalt-wrapper.properties +++ b/kobalt/wrapper/kobalt-wrapper.properties @@ -1 +1 @@ -kobalt.version=1.0.54 \ No newline at end of file +kobalt.version=1.0.55 \ No newline at end of file diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt index e272e3d5..3a66f980 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/IDependencyManager.kt @@ -61,7 +61,7 @@ interface IDependencyManager { return excluded?.map { it.id }?.contains(dep.id) ?: false } - val accept = dependencies.any { + val accept = dependencies.isEmpty() || dependencies.any { // Is this dependency excluded? val isExcluded = isNodeExcluded(p0, it) || isDepExcluded(p0, project?.excludedDependencies) diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt index 06fc68e2..e10b7939 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt @@ -128,14 +128,16 @@ open class Project( return result } + class Dep(val File: File, val id: String) + /** * @return a list of the transitive dependencies (absolute paths to jar files) for the given dependencies. * Can be used for example as `collect(compileDependencies)`. */ @Directive - fun collect(dependencies: List) : List { - return Kobalt.context?.dependencyManager?.transitiveClosure(dependencies)?.map { it.jarFile.get() } - ?: emptyList() + fun collect(dependencies: List) : List { + return (Kobalt.context?.dependencyManager?.transitiveClosure(dependencies) ?: emptyList()) + .map { Dep(it.jarFile.get(), it.id) } } override fun toString() = "[Project $name]" diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/GenericRunner.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/GenericRunner.kt index 866eb8d4..b1f3dcd9 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/GenericRunner.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/GenericRunner.kt @@ -15,9 +15,13 @@ abstract class GenericTestRunner: ITestRunnerContributor { abstract val dependencyName : String abstract val mainClass: String abstract val annotationPackage: String + abstract val runnerName: String + abstract fun args(project: Project, context: KobaltContext, classpath: List, testConfig: TestConfig) : List + open val extraClasspath: List = emptyList() + open fun filterTestClasses(classes: List) : List = classes override fun run(project: Project, context: KobaltContext, configName: String, @@ -98,7 +102,7 @@ abstract class GenericTestRunner: ITestRunnerContributor { configName: String) : Boolean { var result = false - context.logger.log(project.name, 1, "Running default TestNG runner") + context.logger.log(project.name, 1, "Running tests with " + runnerName) val testConfig = project.testConfigs.firstOrNull { it.name == configName } @@ -144,13 +148,14 @@ abstract class GenericTestRunner: ITestRunnerContributor { */ @VisibleForTesting fun calculateAllJvmArgs(project: Project, context: KobaltContext, - testConfig: TestConfig, classpath: List, pluginInfo: IPluginInfo) : List { + testConfig: TestConfig, classpath: List, pluginInfo: IPluginInfo) : List { + val fullClasspath = classpath.map { it.jarFile.get().absolutePath } + extraClasspath // Default JVM args val jvmFlags = arrayListOf().apply { addAll(testConfig.jvmArgs) add("-ea") add("-classpath") - add(classpath.map { it.jarFile.get().absolutePath }.joinToString(File.pathSeparator)) + add(fullClasspath.joinToString(File.pathSeparator)) } // JVM flags from the contributors diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JUnit5Runner.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JUnit5Runner.kt new file mode 100644 index 00000000..7dd72df7 --- /dev/null +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JUnit5Runner.kt @@ -0,0 +1,151 @@ +package com.beust.kobalt.internal + +import com.beust.jcommander.JCommander +import com.beust.jcommander.Parameter +import com.beust.kobalt.TestConfig +import com.beust.kobalt.api.IAffinity +import com.beust.kobalt.api.IClasspathDependency +import com.beust.kobalt.api.KobaltContext +import com.beust.kobalt.api.Project +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger +import com.google.inject.Inject +import org.junit.platform.engine.TestExecutionResult +import org.junit.platform.engine.discovery.DiscoverySelectors +import org.junit.platform.engine.reporting.ReportEntry +import org.junit.platform.engine.support.descriptor.MethodSource +import org.junit.platform.launcher.LauncherDiscoveryRequest +import org.junit.platform.launcher.TestExecutionListener +import org.junit.platform.launcher.TestIdentifier +import org.junit.platform.launcher.TestPlan +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder +import org.junit.platform.launcher.core.LauncherFactory +import java.io.File +import java.nio.file.Paths + +/** + * Runner for JUnit 5 tests. This class also contains a main() entry point since JUnit 5 no longer supplies one. + */ +class JUnit5Runner @Inject constructor(kFiles: KFiles) : GenericTestRunner() { + + override val dependencyName = "jupiter" + override val annotationPackage = "org.junit.jupiter.api" + override val mainClass = "com.beust.kobalt.internal.JUnit5RunnerKt" + override val runnerName = "JUnit 5" + + override fun affinity(project: Project, context: KobaltContext) : Int { + val result = + if (project.testDependencies.any { it.id.contains("jupiter") }) IAffinity.DEFAULT_POSITIVE_AFFINITY + 100 + else 0 + return result + + } + + override fun args(project: Project, context: KobaltContext, classpath: List, testConfig: TestConfig): List { + val testClassDir = KFiles.joinDir(project.buildDirectory, KFiles.TEST_CLASSES_DIR) + val classDir = KFiles.joinDir(project.buildDirectory, KFiles.CLASSES_DIR) + val args = listOf("--testClassDir", testClassDir, + "--classDir", classDir, + "--log", KobaltLogger.LOG_LEVEL.toString()) + return args + } + + override val extraClasspath = kFiles.kobaltJar +} + +private class Args { + @Parameter(names = arrayOf("--log")) + var log: Int = 1 + + @Parameter(names = arrayOf("--testClassDir")) + var testClassDir: String = "kobaltBuild/test-classes" + + @Parameter(names = arrayOf("--classDir")) + var classDir: String = "kobaltBuild/classes" +} + +fun main(argv: Array) { + val args = Args() + val jc = JCommander(args) + jc.parse(*argv) + + val testClassDir = File(args.testClassDir).absolutePath + val classDir = File(args.classDir).absolutePath + val request : LauncherDiscoveryRequest = LauncherDiscoveryRequestBuilder() + .selectors(DiscoverySelectors.selectClasspathRoots(setOf( + Paths.get(testClassDir), + Paths.get(classDir) + ))) + .selectors(DiscoverySelectors.selectDirectory(testClassDir)) + .build() + + fun testName(id: TestIdentifier) : String? { + val result = + if (id.source.isPresent) { + val source = id.source.get() + if (source is MethodSource) { + source.className + "." + source.methodName + } else { + null + } + } else { + null + } + return result + } + + var passed = 0 + var failed = 0 + var skipped = 0 + var aborted = 0 + + fun log(level: Int, s: String) { + if (level <= args.log) println(s) + } + + val listener = object: TestExecutionListener { + override fun executionFinished(testIdentifier: TestIdentifier, testExecutionResult: TestExecutionResult) { + val testName = testName(testIdentifier) + if (testName != null) { + when(testExecutionResult.status) { + TestExecutionResult.Status.FAILED -> { + log(1, "FAILED: $testName, reason: " + testExecutionResult.throwable.get().toString()) + failed++ + } + TestExecutionResult.Status.ABORTED -> { + log(1, "ABORTED: $testName, reason: " + testExecutionResult.throwable.get().toString()) + aborted++ + } + TestExecutionResult.Status.SUCCESSFUL -> { + log(2, "PASSED: $testName") + passed++ + } else -> { + + } + } + } + } + + override fun executionSkipped(testIdentifier: TestIdentifier, reason: String) { + testName(testIdentifier)?.let { + log(1, "Skipping $it because $reason") + skipped++ + } + } + + override fun executionStarted(testIdentifier: TestIdentifier) { + testName(testIdentifier)?.let { + log(2, "Starting $it") + } + } + + override fun testPlanExecutionStarted(testPlan: TestPlan?) {} + override fun dynamicTestRegistered(testIdentifier: TestIdentifier?) {} + override fun reportingEntryPublished(testIdentifier: TestIdentifier?, entry: ReportEntry?) {} + override fun testPlanExecutionFinished(testPlan: TestPlan?) {} + } + + LauncherFactory.create().execute(request, listener) + + log(1, "TEST RESULTS: $passed PASSED, $failed FAILED, $skipped SKIPPED, $aborted ABORTED") +} \ No newline at end of file diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JUnitRunner.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JUnitRunner.kt index eec22afd..bfe6d800 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JUnitRunner.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JUnitRunner.kt @@ -8,10 +8,9 @@ import com.beust.kobalt.api.Project open class JUnitRunner() : GenericTestRunner() { override val mainClass = "org.junit.runner.JUnitCore" - override val annotationPackage = "org.junit" - override val dependencyName = "junit" + override val runnerName = "JUnit 4" override fun args(project: Project, context: KobaltContext, classpath: List, testConfig: TestConfig) = findTestClasses(project, context, testConfig) diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KotlinTestRunner.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KotlinTestRunner.kt index 90c5d354..b78da1db 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KotlinTestRunner.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KotlinTestRunner.kt @@ -6,6 +6,7 @@ package com.beust.kobalt.internal */ class KotlinTestRunner : JUnitRunner() { override val dependencyName = "io.kotlintest" + override val runnerName = "Kotlin Test" /** * KotlinTestRunner runs tests in the init{} initializer, so ignore all the extra diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/SpekRunner.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/SpekRunner.kt index 2129a315..0c1ddad6 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/SpekRunner.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/SpekRunner.kt @@ -6,5 +6,6 @@ package com.beust.kobalt.internal */ class SpekRunner : JUnitRunner() { override val dependencyName = "org.jetbrains.spek" + override val runnerName = "Spek" } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TestNgRunner.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TestNgRunner.kt index c0d2b122..735b26e1 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TestNgRunner.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TestNgRunner.kt @@ -2,21 +2,25 @@ package com.beust.kobalt.internal import com.beust.kobalt.AsciiArt import com.beust.kobalt.TestConfig -import com.beust.kobalt.api.* +import com.beust.kobalt.api.IClasspathDependency +import com.beust.kobalt.api.KobaltContext +import com.beust.kobalt.api.Project import com.beust.kobalt.maven.aether.AetherDependency import com.beust.kobalt.misc.* import org.testng.remote.RemoteArgs -import org.testng.remote.strprotocol.* +import org.testng.remote.strprotocol.JsonMessageSender +import org.testng.remote.strprotocol.MessageHelper +import org.testng.remote.strprotocol.MessageHub +import org.testng.remote.strprotocol.TestResultMessage import java.io.File import java.io.IOException class TestNgRunner : GenericTestRunner() { override val mainClass = "org.testng.TestNG" - override val dependencyName = "testng" - override val annotationPackage = "org.testng" + override val runnerName = "TestNG" fun defaultOutput(project: Project) = KFiles.joinDir(project.buildDirectory, "test-output") diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/KFiles.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/KFiles.kt index 0760a0a8..4aeafbea 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/KFiles.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/KFiles.kt @@ -16,7 +16,7 @@ import java.util.jar.JarInputStream class KFiles { /** * This actually returns a list of strings because in development mode, we are not pointing to a single - * jar file but to a set of /classes directories. + * jar file but to a set of classes/directories. */ val kobaltJar : List get() { diff --git a/src/main/resources/META-INF/kobalt-core-plugin.xml b/src/main/resources/META-INF/kobalt-core-plugin.xml index 4a2b359b..fbcca6c6 100644 --- a/src/main/resources/META-INF/kobalt-core-plugin.xml +++ b/src/main/resources/META-INF/kobalt-core-plugin.xml @@ -24,6 +24,7 @@ com.beust.kobalt.internal.TestNgRunner com.beust.kobalt.internal.SpekRunner com.beust.kobalt.internal.KotlinTestRunner + com.beust.kobalt.internal.JUnit5Runner com.beust.kobalt.app.KobaltPluginTemplate diff --git a/src/main/resources/kobalt.properties b/src/main/resources/kobalt.properties index e51e68f8..a1379fb1 100644 --- a/src/main/resources/kobalt.properties +++ b/src/main/resources/kobalt.properties @@ -1 +1 @@ -kobalt.version=1.0.54 +kobalt.version=1.0.55