From 02fb8d3eb13aac5f19f857a315486f5f6201a3bc Mon Sep 17 00:00:00 2001 From: Ilya Matveev Date: Fri, 15 Sep 2017 11:45:29 +0300 Subject: [PATCH] unit-tests: Assert library --- .../backend/konan/lower/TestProcessor.kt | 4 +- runtime/src/main/kotlin/konan/Annotations.kt | 7 +- .../main/kotlin/konan/test/TestListener.kt | 4 +- .../src/main/kotlin/konan/test/TestRunner.kt | 21 +- .../src/main/kotlin/konan/test/TestSuite.kt | 8 +- .../src/main/kotlin/kotlin/test/Annotation.kt | 3 +- .../src/main/kotlin/kotlin/test/Assertions.kt | 239 ++++++++++++++++++ .../kotlin/kotlin/test/DefaultAsserter.kt | 13 + runtime/src/main/kotlin/kotlin/test/Utils.kt | 9 + 9 files changed, 286 insertions(+), 22 deletions(-) create mode 100644 runtime/src/main/kotlin/kotlin/test/Assertions.kt create mode 100644 runtime/src/main/kotlin/kotlin/test/DefaultAsserter.kt create mode 100644 runtime/src/main/kotlin/kotlin/test/Utils.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt index 96a30ed4b69..6e141fca820 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt @@ -532,9 +532,9 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas }) } - + // TODO: Get tests from Kotlin/JVM // TODO: Support tests in objects - // TODO: Support beforeTest/afterTest for companions. + // TODO: Check beforeTest/afterTest for companions. private fun createTestSuites(irFile: IrFile, annotationCollector: AnnotationCollector) { annotationCollector.testClasses.filter { it.value.functions.any { it.kind == FunctionKind.TEST } diff --git a/runtime/src/main/kotlin/konan/Annotations.kt b/runtime/src/main/kotlin/konan/Annotations.kt index 5134f24cd95..d142ccea2c6 100644 --- a/runtime/src/main/kotlin/konan/Annotations.kt +++ b/runtime/src/main/kotlin/konan/Annotations.kt @@ -92,4 +92,9 @@ public annotation class Fixme public annotation class Escapes(val who: Int) -public annotation class PointsTo(vararg val onWhom: Int) \ No newline at end of file +public annotation class PointsTo(vararg val onWhom: Int) + +/** + * Need to be fixed because of header/impl notation + */ +public annotation class FixmeMultiplatform diff --git a/runtime/src/main/kotlin/konan/test/TestListener.kt b/runtime/src/main/kotlin/konan/test/TestListener.kt index 0bc9160c38a..ae922be8035 100644 --- a/runtime/src/main/kotlin/konan/test/TestListener.kt +++ b/runtime/src/main/kotlin/konan/test/TestListener.kt @@ -9,8 +9,8 @@ interface TestListener { fun startSuite(suite: TestSuite) fun endSuite(suite: TestSuite) + fun start(testCase: TestCase) fun pass(testCase: TestCase) - fun fail(testCase: TestCase, e: AssertionError) - fun error(testCase: TestCase, e: Throwable) + fun fail(testCase: TestCase, e: Throwable) fun ignore(testCase: TestCase) } \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/test/TestRunner.kt b/runtime/src/main/kotlin/konan/test/TestRunner.kt index a5a375618fb..2967eb3755c 100644 --- a/runtime/src/main/kotlin/konan/test/TestRunner.kt +++ b/runtime/src/main/kotlin/konan/test/TestRunner.kt @@ -1,20 +1,21 @@ package konan.test -import kotlin.AssertionError - object TestRunner { object SimpleTestListener: TestListener { - override fun startTesting(runner: TestRunner) = println("Starting testing\n") - override fun endTesting(runner: TestRunner) = println("Testing finished\n") + override fun startTesting(runner: TestRunner) = println("Starting testing") + override fun endTesting(runner: TestRunner) = println("Testing finished") - override fun startSuite(suite: TestSuite) = println("Starting test suite: $suite\n") - override fun endSuite(suite: TestSuite) = println("Test suite finished: $suite\n") + override fun startSuite(suite: TestSuite) = println("Starting test suite: $suite") + override fun endSuite(suite: TestSuite) = println("Test suite finished: $suite") - override fun pass(testCase: TestCase) = println("Pass: $testCase\n") - override fun fail(testCase: TestCase, e: AssertionError) = println("Fail: $testCase (Exception: ${e.message})\n") - override fun error(testCase: TestCase, e: Throwable) = println("Error: $testCase (Exception: ${e.message})\n") - override fun ignore(testCase: TestCase) = println("Ignore: $testCase\n") + override fun start(testCase: TestCase) = println("Start test case: $testCase") + override fun pass(testCase: TestCase) = println("Pass: $testCase") + override fun fail(testCase: TestCase, e: Throwable) { + println("Fail: $testCase. Exception:") + e.printStackTrace() + } + override fun ignore(testCase: TestCase) = println("Ignore: $testCase") } private val _suites = mutableListOf() diff --git a/runtime/src/main/kotlin/konan/test/TestSuite.kt b/runtime/src/main/kotlin/konan/test/TestSuite.kt index 275b6ce461d..fa82398a385 100644 --- a/runtime/src/main/kotlin/konan/test/TestSuite.kt +++ b/runtime/src/main/kotlin/konan/test/TestSuite.kt @@ -29,7 +29,7 @@ abstract class AbstractTestSuite>(override val name: String): override val suite: AbstractTestSuite, val testFunction: F ) : TestCase { - override fun toString(): String = "$suite.$name" + override fun toString(): String = "$name ($suite)" } private val _testCases = mutableMapOf>() @@ -53,13 +53,11 @@ abstract class AbstractTestSuite>(override val name: String): doBeforeClass() testCases.values.forEach { try { + listener.start(it) doTest(it) listener.pass(it) - // TODO: merge catches? - } catch (e: AssertionError) { - listener.fail(it, e) } catch (e: Throwable) { - listener.error(it, e) + listener.fail(it, e) } } diff --git a/runtime/src/main/kotlin/kotlin/test/Annotation.kt b/runtime/src/main/kotlin/kotlin/test/Annotation.kt index 8385425081c..637ede1e71f 100644 --- a/runtime/src/main/kotlin/kotlin/test/Annotation.kt +++ b/runtime/src/main/kotlin/kotlin/test/Annotation.kt @@ -1,7 +1,5 @@ package kotlin.test -// TODO: Move test code in a separate library. - @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.FUNCTION) annotation class Test @@ -14,6 +12,7 @@ annotation class BeforeClass @Target(AnnotationTarget.FUNCTION) annotation class AfterClass +// TODO: Rename Before -> BeforeEach, After -> AfterEach @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.FUNCTION) annotation class Before diff --git a/runtime/src/main/kotlin/kotlin/test/Assertions.kt b/runtime/src/main/kotlin/kotlin/test/Assertions.kt new file mode 100644 index 00000000000..7404079a7c9 --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/test/Assertions.kt @@ -0,0 +1,239 @@ +/* + * Copyright 2010-2016 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. + */ + +/** + * A number of common helper methods for writing unit tests. Copied from Kotlin repo. + */ +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + +package kotlin.test + +import konan.FixmeMultiplatform +import konan.FixmeReflection +import kotlin.internal.InlineOnly +import kotlin.internal.OnlyInputTypes + +/** + * Current adapter providing assertion implementations + */ +val asserter: Asserter + get() = _asserter ?: lookupAsserter() + +/** Used to override current asserter internally */ +internal var _asserter: Asserter? = null + +/** 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): Nothing { + 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) +} + +/** Asserts that given function [block] fails by throwing an exception. */ +fun assertFails(block: () -> Unit): Throwable = assertFails(null, block) + +/** Asserts that given function [block] fails by throwing an exception. */ +@SinceKotlin("1.1") +fun assertFails(message: String?, block: () -> Unit): Throwable { + try { + block() + } catch (e: Throwable) { + assertEquals(e.message, e.message) // success path assertion for qunit + return e + } + asserter.fail(messagePrefix(message) + "Expected an exception to be thrown, but was completed successfully.") +} + +/** 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. + */ +@InlineOnly +@FixmeReflection // TODO: Implement as in Kotlin/JVM when KClass is available. +// TODO: An incorrect llvm module is generated if this method is called. Uncomment it when +@Suppress("UNUSED_PARAMETER") +inline fun assertFailsWith(message: String? = null, noinline block: () -> Unit) /*: T*/ { +// try { +// block() +// } catch (e: Throwable) { +// if (e is T) { +// return e +// } +// +// @Suppress("INVISIBLE_MEMBER") +// asserter.fail(messagePrefix(message) + "Unxepected exception type: $e") +// } +// +// @Suppress("INVISIBLE_MEMBER") +// val msg = messagePrefix(message) +// asserter.fail(msg + "Expected an exception to be thrown, but was completed successfully.") +} + +/** Asserts that a [block] fails with a specific exception of type [exceptionClass] being thrown. */ +//@FixmeReflection +//fun assertFailsWith(exceptionClass: KClass, block: () -> Unit): T = assertFailsWith(exceptionClass, null, block) + + +// From AssertionsH.kt +/** + * 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 + */ +@FixmeMultiplatform +@Suppress("UNUSED_PARAMETER") +/*header */ inline fun todo(block: () -> Unit) { + println("TODO") +} + +/** Asserts that a [block] fails with a specific exception of type [exceptionClass] being thrown. */ +//@FixmeMultiplatform +//@FixmeReflection +//header fun assertFailsWith(exceptionClass: KClass, message: String?, block: () -> Unit): T + +// From Assertions.kt +/** + * Abstracts the logic for performing assertions. + */ +interface Asserter { + /** + * Fails the current test with the specified message. + * + * @param message the message to report. + */ + fun fail(message: String?): Nothing + + /** + * 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({ messagePrefix(message) + "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({ messagePrefix(message) + "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({ messagePrefix(message) + "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({ messagePrefix(message) + "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? +} + diff --git a/runtime/src/main/kotlin/kotlin/test/DefaultAsserter.kt b/runtime/src/main/kotlin/kotlin/test/DefaultAsserter.kt new file mode 100644 index 00000000000..a727d90a0e5 --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/test/DefaultAsserter.kt @@ -0,0 +1,13 @@ +package kotlin.test + +/** + * Default [Asserter] implementation for Kotlin/Native. + */ +object DefaultAsserter : Asserter { + override fun fail(message: String?): Nothing { + if (message == null) + throw AssertionError() + else + throw AssertionError(message) + } +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/test/Utils.kt b/runtime/src/main/kotlin/kotlin/test/Utils.kt new file mode 100644 index 00000000000..ddb5ef84d88 --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/test/Utils.kt @@ -0,0 +1,9 @@ +package kotlin.test + +@PublishedApi +internal fun messagePrefix(message: String?) = if (message == null) "" else "$message. " + +internal fun lookupAsserter(): Asserter = DefaultAsserter + +@PublishedApi // required to get stable name as it's called from box tests +internal fun overrideAsserter(value: Asserter?): Asserter? = _asserter.also { _asserter = value } \ No newline at end of file