diff --git a/libraries/examples/browser-example-with-library/pom.xml b/libraries/examples/browser-example-with-library/pom.xml index e617c0dc727..9711598e1f6 100644 --- a/libraries/examples/browser-example-with-library/pom.xml +++ b/libraries/examples/browser-example-with-library/pom.xml @@ -25,6 +25,12 @@ kotlin-js-library-example ${project.version} + + org.jetbrains.kotlin + kotlin.test.jvm + ${project.version} + test + diff --git a/libraries/examples/browser-example/pom.xml b/libraries/examples/browser-example/pom.xml index 311e84498d7..0e307f4a03c 100644 --- a/libraries/examples/browser-example/pom.xml +++ b/libraries/examples/browser-example/pom.xml @@ -20,6 +20,12 @@ kotlin-js-library ${project.version} + + org.jetbrains.kotlin + kotlin.test.jvm + ${project.version} + test + diff --git a/libraries/kotlin.test/js/pom.xml b/libraries/kotlin.test/js/pom.xml new file mode 100644 index 00000000000..e6a4e3213c6 --- /dev/null +++ b/libraries/kotlin.test/js/pom.xml @@ -0,0 +1,90 @@ + + + + kotlin.test + org.jetbrains.kotlin + 0.1-SNAPSHOT + + 4.0.0 + + kotlin.test.js + + + src/main/kotlin + src/test/kotlin + + + + maven-dependency-plugin + 2.10 + + + + unpack-dependencies + + generate-test-resources + + test + jar + ${project.basedir}/target/test-js + *.js + *.meta.js + + + + + + + com.github.eirslett + frontend-maven-plugin + + + + install node and npm + + none + + install-node-and-npm + + + + + + + npm install + + none + + npm + + + + install + + + + + javascript tests + + karma + + + none + + ${project.basedir}/src/test/karma/karma.conf.js + + + + + + v0.12.7 + 2.14.4 + + ${project.basedir}/src/test/karma + + + + + diff --git a/libraries/kotlin.test/js/src/test/karma/karma.conf.js b/libraries/kotlin.test/js/src/test/karma/karma.conf.js new file mode 100644 index 00000000000..8c41577ab37 --- /dev/null +++ b/libraries/kotlin.test/js/src/test/karma/karma.conf.js @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +module.exports = function (config) { + config.set({ + frameworks: ['qunit'], + reporters: ['progress', 'junit'], + files: [ + '../../../target/test-js/kotlin.js', + '../../../target/test-js/*.js', + '../../../target/classes/*.js' + ], + exclude: [], + port: 9876, + runnerPort: 9100, + colors: true, + autoWatch: false, + browsers: [ + 'PhantomJS' + ], + captureTimeout: 5000, + //singleRun: false, + singleRun: true, + reportSlowerThan: 500, + + junitReporter: { + outputFile: '../../../target/reports/test-results.xml', + suite: '' + }, + preprocessors: { + '**/*.js': ['sourcemap'] + } + } + ) +}; \ No newline at end of file diff --git a/libraries/kotlin.test/js/src/test/karma/package.json b/libraries/kotlin.test/js/src/test/karma/package.json new file mode 100644 index 00000000000..0e19fa9b39a --- /dev/null +++ b/libraries/kotlin.test/js/src/test/karma/package.json @@ -0,0 +1,14 @@ +{ + "name": "karma-tests", + "version": "0.0.0", + "devDependencies": { + "karma": "*", + "qunitjs": "*", + "karma-qunit": "*", + "karma-junit-reporter": "~0.2", + "karma-sourcemap-loader": "~0.3", + "karma-teamcity-reporter": "*", + "karma-phantomjs-launcher": "*", + "phantomjs": "1.9.13" + } +} \ No newline at end of file diff --git a/libraries/kotlin.test/jvm/pom.xml b/libraries/kotlin.test/jvm/pom.xml new file mode 100644 index 00000000000..193b8736020 --- /dev/null +++ b/libraries/kotlin.test/jvm/pom.xml @@ -0,0 +1,107 @@ + + + + kotlin.test + org.jetbrains.kotlin + 0.1-SNAPSHOT + + 4.0.0 + + kotlin.test.jvm + + + + junit + junit + (4,5) + + + org.jetbrains.kotlin + kotlin.test.shared + ${project.version} + + + + org.jetbrains.kotlin + kotlin-js-library + + + + + + + src/main/kotlin + src/test/kotlin + + + + maven-assembly-plugin + + + package + + single + + + + + + src/assembly/sources.xml + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + true + ${project.build.outputDirectory} + + + + ${user.name} + + JetBrains s.r.o. + ${project.version} + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + compile + compile + + compile + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/main/kotlin.jvm + + + + + + test-compile + test-compile + + test-compile + + + + ${project.basedir}/src/test/kotlin + ${project.basedir}/src/test/kotlin.jvm + + + + + + + + diff --git a/libraries/kotlin.test/jvm/src/assembly/sources.xml b/libraries/kotlin.test/jvm/src/assembly/sources.xml new file mode 100644 index 00000000000..0b85e85e290 --- /dev/null +++ b/libraries/kotlin.test/jvm/src/assembly/sources.xml @@ -0,0 +1,46 @@ + + sources + + jar + + + / + + + + true + unix + + ../shared/src/main/kotlin + + + + + **/*.kt + + + + true + unix + + ../shared/src/main/kotlin.jvm + + + + **/*.kt + + + + true + unix + + src/main/kotlin + + + + **/*.kt + + + + \ No newline at end of file diff --git a/libraries/kotlin.test/jvm/src/main/kotlin/JUnitSupport.kt b/libraries/kotlin.test/jvm/src/main/kotlin/JUnitSupport.kt new file mode 100644 index 00000000000..68e78497a2e --- /dev/null +++ b/libraries/kotlin.test/jvm/src/main/kotlin/JUnitSupport.kt @@ -0,0 +1,41 @@ +package kotlin.test.junit + +import org.junit.* +import kotlin.test.* + +class JUnitContributor : AsserterContributor { + override fun contribute(): Asserter? { + for (stackFrame in currentStackTrace()) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + val className = stackFrame.className as java.lang.String + + if (className.startsWith("org.junit.") || className.startsWith("junit.")) { + return JUnitAsserter + } + } + + return null + } +} + +object JUnitAsserter : Asserter { + override fun assertEquals(message : String?, expected : Any?, actual : Any?) { + Assert.assertEquals(message, expected, actual) + } + + override fun assertNotEquals(message : String?, illegal : Any?, actual : Any?) { + Assert.assertNotEquals(message, illegal, actual) + } + + override fun assertNotNull(message : String?, actual : Any?) { + Assert.assertNotNull(message ?: "actual value is null", actual) + } + + override fun assertNull(message : String?, actual : Any?) { + Assert.assertNull(message ?: "actual value is not null", actual) + } + + override fun fail(message : String?) { + Assert.fail(message) + } +} diff --git a/libraries/kotlin.test/jvm/src/main/resources/META-INF/services/kotlin.test.AsserterContributor b/libraries/kotlin.test/jvm/src/main/resources/META-INF/services/kotlin.test.AsserterContributor new file mode 100644 index 00000000000..e32ef6c66d9 --- /dev/null +++ b/libraries/kotlin.test/jvm/src/main/resources/META-INF/services/kotlin.test.AsserterContributor @@ -0,0 +1 @@ +kotlin.test.junit.JUnitContributor \ No newline at end of file diff --git a/libraries/kotlin.test/jvm/src/test/kotlin/JUnitContributorTest.kt b/libraries/kotlin.test/jvm/src/test/kotlin/JUnitContributorTest.kt new file mode 100644 index 00000000000..a9c04819ed6 --- /dev/null +++ b/libraries/kotlin.test/jvm/src/test/kotlin/JUnitContributorTest.kt @@ -0,0 +1,27 @@ +package kotlinx.testing.tests + +import org.junit.* +import java.util.concurrent.* + +class JUnitContributorTest { + @Test + fun smokeTest() { + Assert.assertEquals("JUnitAsserter", asserter.`class`.simpleName) + } + + @Test + fun `should fail to contribute if it was run outside of junit`() { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + val q = ArrayBlockingQueue(1) + + Thread { + q.put(asserter) + }.start() + + Assert.assertEquals("DefaultAsserter", q.take().`class`.simpleName) + } + + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + private val asserter: java.lang.Object + get() = Class.forName("kotlin.test.TestAssertionsKt").getMethod("getAsserter").invoke(null) as java.lang.Object +} diff --git a/libraries/kotlin.test/pom.xml b/libraries/kotlin.test/pom.xml new file mode 100644 index 00000000000..ca4e10db239 --- /dev/null +++ b/libraries/kotlin.test/pom.xml @@ -0,0 +1,114 @@ + + + 4.0.0 + + + org.jetbrains.kotlin + kotlin-project + 0.1-SNAPSHOT + + + kotlin.test + pom + + + shared + jvm + + + + + + Sergey Mashkov + JetBrains s.r.o. + + developer + + + + + + + + + + + org.jetbrains.kotlin + kotlin-runtime + ${project.version} + + + org.jetbrains.kotlin + kotlin-stdlib + ${project.version} + + + org.jetbrains.kotlin + kotlin-reflect + ${project.version} + + + org.jetbrains.kotlin + kotlin-js-library + ${project.version} + + + junit + junit + 4.12 + + + + + + + + + maven-resources-plugin + 2.7 + + + + maven-assembly-plugin + 2.6 + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${project.version} + + + + maven-surefire-plugin + 2.18.1 + + 2C + true + + + + + com.github.eirslett + frontend-maven-plugin + 0.0.26 + + + + + + + + kotlin-jvm + + true + + + + kotlin-js + + false + + + + diff --git a/libraries/kotlin.test/shared/pom.xml b/libraries/kotlin.test/shared/pom.xml new file mode 100644 index 00000000000..72f58fc2d1b --- /dev/null +++ b/libraries/kotlin.test/shared/pom.xml @@ -0,0 +1,148 @@ + + + + kotlin.test + org.jetbrains.kotlin + 0.1-SNAPSHOT + + 4.0.0 + + kotlin.test.shared + + + src/main/kotlin + src/test/kotlin + + + + maven-assembly-plugin + + + package + + single + + + + + + src/assembly/sources.xml + + + + + + + + + kotlin-js + + false + + + + + org.jetbrains.kotlin + kotlin-js-library + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + test-compile-js + test-compile + + test-js + + + + ${project.basedir}/src/test/kotlin + ${project.basedir}/src/test/kotlin.js + + + + + + js-compile + compile + + js + + + ${project.build.outputDirectory}/${project.artifactId}.js + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/main/kotlin.js + + + + + + + + + + kotlin-jvm + + true + + + + + org.jetbrains.kotlin + kotlin-runtime + + + junit + junit + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + compile + compile + + compile + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/main/kotlin.jvm + + + + + + test-compile + test-compile + + test-compile + + + + ${project.basedir}/src/test/kotlin + ${project.basedir}/src/test/kotlin.jvm + + + + + + + + + + diff --git a/libraries/kotlin.test/shared/src/assembly/sources.xml b/libraries/kotlin.test/shared/src/assembly/sources.xml new file mode 100644 index 00000000000..09920bce308 --- /dev/null +++ b/libraries/kotlin.test/shared/src/assembly/sources.xml @@ -0,0 +1,46 @@ + + sources + + jar + + + / + + + + true + unix + + src/main/kotlin + + + + + **/*.kt + + + + true + unix + + src/main/kotlin.jvm + + + + **/*.kt + + + + true + unix + + src/main/kotlin.js + + + + **/*.kt + + + + \ No newline at end of file diff --git a/libraries/kotlin.test/shared/src/main/kotlin.jvm/kotlin/test/AsserterLookup.kt b/libraries/kotlin.test/shared/src/main/kotlin.jvm/kotlin/test/AsserterLookup.kt new file mode 100644 index 00000000000..961c00404e5 --- /dev/null +++ b/libraries/kotlin.test/shared/src/main/kotlin.jvm/kotlin/test/AsserterLookup.kt @@ -0,0 +1,53 @@ +package kotlin.test + +import java.util.* +import java.util.concurrent.atomic.* +import java.util.concurrent.locks.* + +private val inited = AtomicBoolean() +private val lock = ReentrantLock() +private val contributors = ArrayList() + +internal fun lookup(): Asserter { + initContributorsIfNeeded() + + for (contributor in contributors) { + val asserter = contributor.contribute() + if (asserter != null) { + return asserter + } + } + + return DefaultAsserter() +} + +private fun initContributors() { + contributors.clear() + @Suppress("DEPRECATION") + val loader = ServiceLoader.load(AsserterContributor::class.javaClass) + + for (contributor in loader) { + if (contributor != null) { + contributors.add(contributor) + } + } +} + +private fun initContributorsIfNeeded() { + if (!inited.get()) { + lock.withLock { + if (inited.compareAndSet(false, true)) { + initContributors() + } + } + } +} + +private inline fun Lock.withLock(block: () -> Unit) { + lockInterruptibly() + try { + block() + } finally { + unlock() + } +} \ No newline at end of file diff --git a/libraries/kotlin.test/shared/src/main/kotlin.jvm/kotlin/test/TestAssertionsJVM.kt b/libraries/kotlin.test/shared/src/main/kotlin.jvm/kotlin/test/TestAssertionsJVM.kt new file mode 100644 index 00000000000..babc601baa7 --- /dev/null +++ b/libraries/kotlin.test/shared/src/main/kotlin.jvm/kotlin/test/TestAssertionsJVM.kt @@ -0,0 +1,68 @@ +@file:kotlin.jvm.JvmMultifileClass +@file:kotlin.jvm.JvmName("TestAssertionsKt") +package kotlin.test + +import kotlin.jvm.internal.* +import kotlin.reflect.* + +@Deprecated("Use assertFailsWith instead.", ReplaceWith("assertFailsWith(exceptionClass, block)"), kotlin.DeprecationLevel.ERROR) +fun failsWith(exceptionClass: Class, block: () -> Any): T = assertFailsWith(exceptionClass, { block() }) + +/** Asserts that a [block] fails with a specific exception being thrown. */ +fun assertFailsWith(exceptionClass: Class, block: () -> Unit): T = assertFailsWith(exceptionClass, null, block) + +/** Asserts that a [block] fails with a specific exception being thrown. */ +fun assertFailsWith(exceptionClass: Class, message: String?, block: () -> Unit): T { + try { + block() + } catch (e: Throwable) { + if (exceptionClass.isInstance(e)) { + @Suppress("UNCHECKED_CAST") + return e as T + } + asserter.fail((message?.let { "$it. " } ?: "") + "Expected an exception of type $exceptionClass to be thrown, but was $e") + throw e + } + val msg = message?.let { "$it. " } ?: "" + asserter.fail(msg + "Expected an exception of type $exceptionClass to be thrown, but was completed successfully.") + throw IllegalStateException(msg + "Should have failed.") +} + +/** Asserts that a [block] fails with a specific exception of type [exceptionClass] being thrown. */ +@Suppress("DEPRECATION") +fun assertFailsWith(exceptionClass: KClass, message: String? = null, block: () -> Unit): T = assertFailsWith(exceptionClass.javaClass, message, block) + +/** Asserts that a [block] fails with a specific exception of type [T] being thrown. + * Since inline method doesn't allow to trace where it was invoked, it is required to pass a [message] to distinguish this method call from others. + */ +@Suppress("DEPRECATION") +inline fun assertFailsWith(message: String, noinline block: () -> Unit): T = assertFailsWith(T::class.javaClass, message, block) + + +/** + * Comments out a [block] of test code until it is implemented while keeping a link to the code + * to implement in your unit test output + */ +inline fun todo(@Suppress("UNUSED_PARAMETER") block: () -> Unit) { + System.out.println("TODO at " + currentStackTrace()[1]) +} + +@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE") +inline fun currentStackTrace() = (java.lang.Exception() as java.lang.Throwable).stackTrace + +/** + * The active implementation of [Asserter]. An implementation of [Asserter] can be provided + * using the [Java service loader](http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html) mechanism. + */ +val asserter: Asserter + get() = lookup() + + +/** + * Returns a Java [Class] instance corresponding to the given [KClass] instance. + */ +@Suppress("UNCHECKED_CAST") +@Intrinsic("kotlin.KClass.java.property") +@Deprecated("This is for internal use only", ReplaceWith("this.java"), kotlin.DeprecationLevel.WARNING) +val KClass.javaClass: Class + get() = (this as ClassBasedDeclarationContainer).jClass as Class diff --git a/libraries/kotlin.test/shared/src/main/kotlin/kotlin/internal/internalAnnottaions.kt b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/internal/internalAnnottaions.kt new file mode 100644 index 00000000000..71404811ec6 --- /dev/null +++ b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/internal/internalAnnottaions.kt @@ -0,0 +1,10 @@ +package kotlin.internal + + +/** + * The value of this type parameter should be mentioned in input types (argument types, receiver type or expected type). + */ +@Target(AnnotationTarget.TYPE_PARAMETER) +@Retention(AnnotationRetention.BINARY) +internal annotation class OnlyInputTypes + diff --git a/libraries/kotlin.test/shared/src/main/kotlin/kotlin/jvm/annotations.kt b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/jvm/annotations.kt new file mode 100644 index 00000000000..bc99c9c8c36 --- /dev/null +++ b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/jvm/annotations.kt @@ -0,0 +1,25 @@ +package kotlin.jvm + + +/** + * Specifies the name for the Java class or method + * which is generated from this element. + * See the [Kotlin language documentation](http://kotlinlang.org/docs/reference/java-interop.html#handling-signature-clashes-with-platformname) + * for more information. + * @property name the name of the element. + */ +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FILE) +@Retention(AnnotationRetention.BINARY) +@MustBeDocumented +public annotation class JvmName(public val name: String) + +/** + * Instructs the Kotlin compiler to generate a multifile class with top-level functions and properties declared in this file as one of its parts. + * Name of the corresponding multifile class is provided by the [JvmName] annotation. + */ +@Target(AnnotationTarget.FILE) +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +public annotation class JvmMultifileClass + + diff --git a/libraries/kotlin.test/shared/src/main/kotlin/kotlin/test/CollectionAssertions.kt b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/test/CollectionAssertions.kt new file mode 100644 index 00000000000..12aba09dcd2 --- /dev/null +++ b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/test/CollectionAssertions.kt @@ -0,0 +1,133 @@ +package kotlin.test + +import java.util.* + +class CollectionAssertionSession>(val collection: C) + +inline fun > assert(collection: C, block: CollectionAssertionSession.() -> Unit) { + CollectionAssertionSession(collection).block() +} + +fun > CollectionAssertionSession<*, C>.sizeShouldBe(expectedSize: Int, message: String? = null) { + assertEquals(expectedSize, collection.size, message ?: "collection should have size $expectedSize but it is ${collection.size}") +} + +fun CollectionAssertionSession.elementAtShouldBe(position: Int, expected: T, message: String? = null) { + assertEquals(expected, collection.elementAt(position), message ?: "element at $position should be $expected") +} + +fun > CollectionAssertionSession.elementAtShouldComply(position: Int, message: String? = null, predicate: (T) -> Boolean) { + assertTrue(message) { predicate(collection.elementAt(position)) } +} + +fun CollectionAssertionSession.lastElementShouldBe(expected: T, message: String? = null) { + assertEquals(expected, collection.last(), message ?: "the last element should be $expected") +} + +fun CollectionAssertionSession.containsAll(vararg elements: T) { + for (e in elements) { + assertTrue(message = "Element $e is missing in the collection") { e in collection } + } +} + +fun > CollectionAssertionSession.shouldBe(expectedElements: Iterable, message: String? = null) { + val actual = collection.iterator() + val expected = expectedElements.iterator() + + while (actual.hasNext() && expected.hasNext()) { + assertEquals(expected.next(), actual.next(), message) + } + + if (actual.hasNext()) { + fail("Actual collection is longer than expected. Extra elements are: ${actual.remaining()}") + } + if (expected.hasNext()) { + fail("Actual collection is shorter than expected. Missing elements are: ${expected.remaining()}") + } +} + +fun > CollectionAssertionSession.shouldBeSet(other: Set, message: String? = null) { + for (e in other) { + if (e !in collection) { + fail(message ?: "Element $e in not in the collection $collection") + } + } + for (e in collection) { + if (e !in other) { + fail(message ?: "Element $e is not expected") + } + } +} + +fun > CollectionAssertionSession.shouldBeSet(vararg other: T) { + val otherSet = HashSet() + for (e in other) { + otherSet.add(e) + } + + shouldBeSet(otherSet) +} + +private operator fun Iterable.contains(e: T): Boolean { + if (this is Set) { + return contains(e) + } + for (it in this) { + if (it == e) { + return true + } + } + return false +} + +private fun Iterable.last(): T { + if (this is List) { + if (this.isEmpty()) { + throw NoSuchElementException() + } + + return this[size - 1] + } + + val it = iterator() + var result: T = iterator().next() + + while (it.hasNext()) { + result = it.next() + } + + return result +} + +private fun Iterable.elementAt(position: Int): T { + if (position < 0) { + throw IllegalArgumentException("position shouldn't be negative: $position") + } + if (this is List) { + return this[position] + } + + val iterator = iterator() + var idx = 0 + do { + if (!iterator.hasNext()) { + throw IndexOutOfBoundsException("index $position is out of the collection bounds [0; $idx)") + } + val result = iterator.next() + if (idx == position) { + return result + } + + idx++ + } while (true) + + throw IllegalStateException() +} + +private fun Iterator.remaining(): List { + val result = ArrayList() + while (hasNext()) { + result.add(next()) + } + return result +} \ No newline at end of file diff --git a/libraries/kotlin.test/shared/src/main/kotlin/kotlin/test/DefaultAsserter.kt b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/test/DefaultAsserter.kt new file mode 100644 index 00000000000..eed8581329a --- /dev/null +++ b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/test/DefaultAsserter.kt @@ -0,0 +1,14 @@ +package kotlin.test + +/** + * Default [Asserter] implementation to avoid dependency on JUnit or TestNG. + */ +class DefaultAsserter() : Asserter { + + override fun fail(message: String?) { + if (message == null) + throw AssertionError() + else + throw AssertionError(message) + } +} \ No newline at end of file diff --git a/libraries/kotlin.test/shared/src/main/kotlin/kotlin/test/TestAssertions.kt b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/test/TestAssertions.kt new file mode 100644 index 00000000000..7983d130e9b --- /dev/null +++ b/libraries/kotlin.test/shared/src/main/kotlin/kotlin/test/TestAssertions.kt @@ -0,0 +1,187 @@ +@file:kotlin.jvm.JvmMultifileClass +@file:kotlin.jvm.JvmName("TestAssertionsKt") + +/** + * A number of helper methods for writing unit tests. + */ +package kotlin.test + +import kotlin.internal.* + + +/** Asserts that the given [block] returns `false`. */ +@Deprecated("Use assertFalse instead.", ReplaceWith("assertFalse(message, block)"), kotlin.DeprecationLevel.ERROR) +fun assertNot(message: String, block: () -> Boolean): Unit = assertFalse(message, block) + +/** Asserts that the given [block] returns `false`. */ +@Deprecated("Use assertFalse instead.", ReplaceWith("assertFalse(null, block)"), kotlin.DeprecationLevel.ERROR) +fun assertNot(block: () -> Boolean): Unit = assertFalse(block = block) + +/** Asserts that the given [block] returns `true`. */ +fun assertTrue(message: String? = null, block: () -> Boolean): Unit = assertTrue(block(), message) + +/** Asserts that the expression is `true` with an optional [message]. */ +fun assertTrue(actual: Boolean, message: String? = null) { + return asserter.assertTrue(message ?: "Expected value to be true.", actual) +} + +/** Asserts that the given [block] returns `false`. */ +fun assertFalse(message: String? = null, block: () -> Boolean): Unit = assertFalse(block(), message) + +/** Asserts that the expression is `false` with an optional [message]. */ +fun assertFalse(actual: Boolean, message: String? = null) { + return asserter.assertTrue(message ?: "Expected value to be false.", !actual) +} + +/** Asserts that the [expected] value is equal to the [actual] value, with an optional [message]. */ +fun <@OnlyInputTypes T> assertEquals(expected: T, actual: T, message: String? = null) { + asserter.assertEquals(message, expected, actual) +} + +/** Asserts that the [actual] value is not equal to the illegal value, with an optional [message]. */ +fun <@OnlyInputTypes T> assertNotEquals(illegal: T, actual: T, message: String? = null) { + asserter.assertNotEquals(message, illegal, actual) +} + +/** Asserts that the [actual] value is not `null`, with an optional [message]. */ +fun assertNotNull(actual: T?, message: String? = null): T { + asserter.assertNotNull(message, actual) + return actual!! +} + +/** Asserts that the [actual] value is not `null`, with an optional [message] and a function [block] to process the not-null value. */ +fun assertNotNull(actual: T?, message: String? = null, block: (T) -> R) { + asserter.assertNotNull(message, actual) + if (actual != null) { + block(actual) + } +} + +/** Asserts that the [actual] value is `null`, with an optional [message]. */ +fun assertNull(actual: Any?, message: String? = null) { + asserter.assertNull(message, actual) +} + +/** Marks a test as having failed if this point in the execution path is reached, with an optional [message]. */ +fun fail(message: String? = null) { + asserter.fail(message) +} + +/** Asserts that given function [block] returns the given [expected] value. */ +fun <@OnlyInputTypes T> expect(expected: T, block: () -> T) { + assertEquals(expected, block()) +} + +/** Asserts that given function [block] returns the given [expected] value and use the given [message] if it fails. */ +fun <@OnlyInputTypes T> expect(expected: T, message: String?, block: () -> T) { + assertEquals(expected, block(), message) +} + +@Deprecated("Use assertFails instead.", ReplaceWith("assertFails(block)"), kotlin.DeprecationLevel.ERROR) +fun fails(block: () -> Unit): Throwable? = assertFails(block) + +/** Asserts that given function [block] fails by throwing an exception. */ +fun assertFails(block: () -> Unit): Throwable? { + var thrown: Throwable? = null + try { + block() + } catch (e: Throwable) { + thrown = e + } + if (thrown == null) + asserter.fail("Expected an exception to be thrown") + return thrown +} + +/** + * Abstracts the logic for performing assertions. Specific implementations of [Asserter] can use JUnit + * or TestNG assertion facilities. + */ +interface Asserter { + /** + * Fails the current test with the specified message. + * + * @param message the message to report. + */ + fun fail(message: String?): Unit + + /** + * Asserts that the specified value is `true`. + * + * @param lazyMessage the function to return a message to report if the assertion fails. + */ + fun assertTrue(lazyMessage: () -> String?, actual: Boolean): Unit { + if (!actual) { + fail(lazyMessage()) + } + } + + /** + * Asserts that the specified value is `true`. + * + * @param message the message to report if the assertion fails. + */ + fun assertTrue(message: String?, actual: Boolean): Unit { + assertTrue({ message }, actual) + } + + /** + * Asserts that the specified values are equal. + * + * @param message the message to report if the assertion fails. + */ + fun assertEquals(message: String?, expected: Any?, actual: Any?): Unit { + assertTrue({ (message?.let { "$it. " } ?: "") + "Expected <$expected>, actual <$actual>." }, actual == expected) + } + + /** + * Asserts that the specified values are not equal. + * + * @param message the message to report if the assertion fails. + */ + fun assertNotEquals(message: String?, illegal: Any?, actual: Any?): Unit { + assertTrue({ (message?.let { "$it. " } ?: "") + "Illegal value: <$actual>." }, actual != illegal) + } + + /** + * Asserts that the specified value is `null`. + * + * @param message the message to report if the assertion fails. + */ + fun assertNull(message: String?, actual: Any?): Unit { + assertTrue({ (message?.let { "$it. " } ?: "") + "Expected value to be null, but was: <$actual>." }, actual == null) + } + + /** + * Asserts that the specified value is not `null`. + * + * @param message the message to report if the assertion fails. + */ + fun assertNotNull(message: String?, actual: Any?): Unit { + assertTrue({ (message?.let { "$it. " } ?: "") + "Expected value to be not null." }, actual != null) + } + +} + +/** + * Checks applicability and provides Asserter instance + */ +interface AsserterContributor { + /** + * Provides [Asserter] instance or `null` depends on the current context. + * + * @return asserter instance or null if it is not applicable now + */ + fun contribute(): Asserter? +} + +internal fun T.let(block: (T) -> R): R = block(this) + +internal fun Iterable.firstOrNull(predicate: (T) -> Boolean): T? { + for (e in this) { + if (predicate(e)) { + return e + } + } + return null +} diff --git a/libraries/kotlin.test/shared/src/test/kotlin.jvm/BasicAssertionsJVMTest.kt b/libraries/kotlin.test/shared/src/test/kotlin.jvm/BasicAssertionsJVMTest.kt new file mode 100644 index 00000000000..e5206fbb19c --- /dev/null +++ b/libraries/kotlin.test/shared/src/test/kotlin.jvm/BasicAssertionsJVMTest.kt @@ -0,0 +1,39 @@ +package kotlinx.testing.tests + +import kotlin.test.* +import org.junit.* + +class BasicAssertionsJVMTest { + @Test + fun testFailsWith() { + assertFailsWith(IllegalArgumentException::class) { + throw IllegalArgumentException() + } + } + + @Test(expected = AssertionError::class) + fun testFailsWithFails() { + assertFailsWith(IllegalArgumentException::class) { + throw IllegalStateException() + } + } + + @Test + fun testFailsWithMessage() { + assertFailsWith("") { + throw IllegalArgumentException() + } + } + + @Test + fun testToDo() { + todo { + fail("Shouldn't pass here") + } + } + + @Test + fun testCurrentStackTrace() { + assertEquals("BasicAssertionsJVMTest.kt", currentStackTrace()[0].fileName) + } +} diff --git a/libraries/kotlin.test/shared/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt b/libraries/kotlin.test/shared/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt new file mode 100644 index 00000000000..224152dab20 --- /dev/null +++ b/libraries/kotlin.test/shared/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt @@ -0,0 +1,131 @@ +package kotlin.test.tests + +import org.junit.* +import kotlin.test.* + +class BasicAssertionsTest { + @Test + fun testAssertEquals() { + assertEquals(1, 1) + } + + @Test + fun testAssertEqualsString() { + assertEquals("a", "a") + } + + @Test(expected = AssertionError::class) + fun testAssertEqualsFails() { + assertEquals(1, 2) + } + + @Test + fun testAssertTrue() { + assertTrue(true) + } + + @Test + fun testAssertTrueWithLambda() { + assertTrue { true } + } + + @Test(expected = AssertionError::class) + fun testAssertTrueFails() { + assertTrue(false) + } + + @Test(expected = AssertionError::class) + fun testAssertTrueWithLambdaFails() { + assertTrue { false } + } + + @Test + fun testAssertFalse() { + assertFalse(false) + } + + @Test + fun testAssertFalseLambda() { + assertFalse { false } + } + + @Test(expected = AssertionError::class) + fun testAssertFalseFails() { + assertFalse(true) + } + + @Test(expected = AssertionError::class) + fun testAssertFalseWithLambdaFails() { + assertFalse { true } + } + + @Test + fun testAssertFails() { + assertFails { throw IllegalStateException() } + } + + @Test(expected = AssertionError::class) + fun testAssertFailsFails() { + assertFails { } + Assert.fail("Shouldn't pass here") + } + + + @Test + fun testAssertNotEquals() { + assertNotEquals(1, 2) + } + + @Test(expected = AssertionError::class) + fun testAssertNotEqualsFails() { + assertNotEquals(1, 1) + } + + @Test + fun testAssertNotNull() { + assertNotNull(true) + } + + @Test(expected = AssertionError::class) + fun testAssertNotNullFails() { + assertNotNull(null) + } + + @Test + fun testAssertNotNullLambda() { + assertNotNull("") { assertEquals("", it) } + } + + @Test(expected = AssertionError::class) + fun testAssertNotNullLambdaFails() { + assertNotNull(null) { + @Suppress("UNREACHABLE_CODE") + assertNotNull(it) + } + } + + @Test + fun testAssertNull() { + assertNull(null) + } + + @Test(expected = AssertionError::class) + fun testAssertNullFails() { + assertNull(true) + } + + @Test(expected = AssertionError::class) + fun testFail() { + fail("should fail") + } + + @Test + fun testExpect() { + expect(1) { 1 } + } + + @Test(expected = AssertionError::class) + fun testExpectFails() { + expect(1) { 2 } + } +} \ No newline at end of file diff --git a/libraries/kotlin.test/shared/src/test/kotlin/kotlin/test/tests/CollectionAssertionTest.kt b/libraries/kotlin.test/shared/src/test/kotlin/kotlin/test/tests/CollectionAssertionTest.kt new file mode 100644 index 00000000000..c0f55ed772c --- /dev/null +++ b/libraries/kotlin.test/shared/src/test/kotlin/kotlin/test/tests/CollectionAssertionTest.kt @@ -0,0 +1,136 @@ +package kotlin.test.tests + +import org.junit.* +import java.util.* +import kotlin.test.* + +class CollectionAssertionTest { + @Test + fun testList() { + assert(listOf(1, 2, 3)) { + sizeShouldBe(3) + elementAtShouldBe(0, 1) + elementAtShouldComply(0) { it > 0 } + lastElementShouldBe(3) + containsAll(1, 2) + + shouldBe(listOf(1, 2, 3)) + } + } + + @Test + fun testSet() { + assert(setOf(1, 2, 3)) { + sizeShouldBe(3) + elementAtShouldBe(0, 1) + elementAtShouldComply(0) { it > 0 } + lastElementShouldBe(3) + containsAll(1, 2) + + shouldBeSet(setOf(1, 2, 3)) + } + } + + @Test(expected = AssertionError::class) + fun testSizeShouldBeFails() { + assert(listOf(1, 2, 3)) { + sizeShouldBe(1) + } + } + + @Test(expected = AssertionError::class) + fun testElementAtShouldBeFail() { + assert(listOf(1, 2, 3)) { + elementAtShouldBe(0, 0) + } + } + + @Test(expected = AssertionError::class) + fun testElementAtShouldComplyFail() { + assert(listOf(1, 2, 3)) { + elementAtShouldComply(0) { it < 0 } + } + } + + @Test(expected = AssertionError::class) + fun testLastElementFail() { + assert(listOf(1, 2, 3)) { + lastElementShouldBe(0) + } + } + + @Test(expected = NoSuchElementException::class) + fun testLastElementOnEmptyFail() { + assert(listOf()) { + lastElementShouldBe(0) + } + } + + @Test(expected = AssertionError::class) + fun testContainsAll() { + assert(listOf(1, 2, 3)) { + containsAll(1, 8) + } + } + + @Test(expected = AssertionError::class) + fun testContainsAllWithSet() { + assert(setOf(1, 2, 3)) { + containsAll(1, 8) + } + } + + @Test + fun testShouldBeLess() { + try { + assert(listOf(1, 2, 3)) { + shouldBe(listOf(1, 2, 3, 4)) + } + Assert.fail("It shouldn't pass here") + } catch (e: AssertionError) { + assertTrue { "[4]" in e.message!! } + assertTrue { "shorter" in e.message!! } + } + } + + @Test + fun testShouldBeLonger() { + try { + assert(listOf(1, 2, 3)) { + shouldBe(listOf(1, 2)) + } + Assert.fail("It shouldn't pass here") + } catch (e: AssertionError) { + assertTrue { "[3]" in e.message!! } + assertTrue { "longer" in e.message!! } + } + } + + @Test(expected = AssertionError::class) + fun testShouldBeSetExtra() { + assert(setOf(1, 2, 3)) { + shouldBeSet(setOf(1, 2)) + } + } + + @Test + fun testShouldBeSetExact() { + assert(setOf(1, 2, 3)) { + shouldBeSet(setOf(1, 2, 3)) + } + } + + @Test + fun testShouldBeSetExactVararg() { + assert(setOf(1, 2, 3)) { + shouldBeSet(1, 2, 3) + } + } + + @Test(expected = AssertionError::class) + fun testShouldBeSetMissing() { + assert(setOf(1, 2, 3)) { + shouldBeSet(setOf(1, 2, 3, 4)) + } + } +} diff --git a/libraries/kotlin.test/shared/src/test/kotlin/kotlin/test/tests/util.kt b/libraries/kotlin.test/shared/src/test/kotlin/kotlin/test/tests/util.kt new file mode 100644 index 00000000000..80f09691e6a --- /dev/null +++ b/libraries/kotlin.test/shared/src/test/kotlin/kotlin/test/tests/util.kt @@ -0,0 +1,24 @@ +package kotlin.test.tests + +import java.util.* + +internal fun listOf(vararg elements: T): List { + val result = ArrayList(elements.size) + for (e in elements) { + result.add(e) + } + + return result +} + +internal fun setOf(vararg elements: T): Set { + val result = HashSet(elements.size) + for (e in elements) { + result.add(e) + } + + return result +} + +internal operator fun String.contains(sub: String) = (this as java.lang.String).contains(sub) + diff --git a/libraries/pom.xml b/libraries/pom.xml index d7a7eeb78fe..b164b4af3a9 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -97,6 +97,8 @@ tools/kotlin-js-tests tools/kotlin-js-tests-junit + kotlin.test + examples/kotlin-java-example examples/js-example examples/kotlin-js-library-example diff --git a/libraries/stdlib/pom.xml b/libraries/stdlib/pom.xml index 6eca2f5aa98..9335016ad6f 100644 --- a/libraries/stdlib/pom.xml +++ b/libraries/stdlib/pom.xml @@ -19,6 +19,12 @@ kotlin-runtime ${project.version} + + org.jetbrains.kotlin + kotlin.test.jvm + ${project.version} + test + diff --git a/libraries/tools/kotlin-js-tests-junit/pom.xml b/libraries/tools/kotlin-js-tests-junit/pom.xml index 42dfe702848..b15be51fb73 100644 --- a/libraries/tools/kotlin-js-tests-junit/pom.xml +++ b/libraries/tools/kotlin-js-tests-junit/pom.xml @@ -21,6 +21,11 @@ kotlin-stdlib ${project.version} + + org.jetbrains.kotlin + kotlin.test.jvm + ${project.version} + org.jetbrains.kotlin kotlin-js-tests