From 5f4a4fd8aeb479e692251fc7ffac6f09765a00ca Mon Sep 17 00:00:00 2001 From: Abduqodiri Qurbonzoda Date: Thu, 11 Mar 2021 07:33:03 +0300 Subject: [PATCH] Introduce assertContentEquals in kotlin-test #KT-32996 --- libraries/kotlin.test/common/build.gradle | 2 +- .../kotlin/test/AssertContentEqualsImpl.kt | 110 ++++++++++++ .../src/main/kotlin/kotlin/test/Assertions.kt | 154 +++++++++++++++++ .../test/tests/AssertContentEqualsTest.kt | 162 ++++++++++++++++++ .../kotlin/test/tests/BasicAssertionsTest.kt | 2 +- libraries/kotlin.test/jvm/build.gradle | 2 + 6 files changed, 430 insertions(+), 2 deletions(-) create mode 100644 libraries/kotlin.test/common/src/main/kotlin/kotlin/test/AssertContentEqualsImpl.kt create mode 100644 libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/AssertContentEqualsTest.kt diff --git a/libraries/kotlin.test/common/build.gradle b/libraries/kotlin.test/common/build.gradle index cf0b36a35f0..fb64ba6973f 100644 --- a/libraries/kotlin.test/common/build.gradle +++ b/libraries/kotlin.test/common/build.gradle @@ -17,7 +17,7 @@ configureSourcesJar() configureJavadocJar() tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinCompile) { - kotlinOptions.freeCompilerArgs += "-Xallow-kotlin-package" + kotlinOptions.freeCompilerArgs += ["-Xallow-kotlin-package", "-Xopt-in=kotlin.RequiresOptIn"] } compileKotlinCommon { diff --git a/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/AssertContentEqualsImpl.kt b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/AssertContentEqualsImpl.kt new file mode 100644 index 00000000000..62a1869821f --- /dev/null +++ b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/AssertContentEqualsImpl.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.test + +import kotlin.contracts.contract + +internal fun assertIterableContentEquals( + typeName: String, + message: String?, + expected: T?, + actual: T?, + iterator: T.() -> Iterator<*> +) { + if (checkReferenceAndNullEquality(typeName, message, expected, actual, Any?::toString)) return + + var index = 0 + val expectedIt = expected.iterator() + val actualIt = actual.iterator() + + while (expectedIt.hasNext() && actualIt.hasNext()) { + val expectedElement = expectedIt.next() + val actualElement = actualIt.next() + + if (expectedElement != actualElement) { + fail(messagePrefix(message) + elementsDifferMessage(typeName, index, expectedElement, actualElement)) + } + + index++ + } + + if (expectedIt.hasNext()) { + check(!actualIt.hasNext()) + + fail(messagePrefix(message) + "$typeName lengths differ. Expected length is bigger than $index, actual length is $index.") + } + + if (actualIt.hasNext()) { + check(!expectedIt.hasNext()) + + fail(messagePrefix(message) + "$typeName lengths differ. Expected length is $index, actual length is bigger than $index.") + } +} + +internal fun assertArrayContentEquals( + message: String?, + expected: T?, + actual: T?, + size: (T) -> Int, + get: T.(Int) -> Any?, + contentToString: T?.() -> String, + contentEquals: T?.(T?) -> Boolean +) { + if (expected.contentEquals(actual)) return + + val typeName = "Array" + + if (checkReferenceAndNullEquality(typeName, message, expected, actual, contentToString)) return + + val expectedSize = size(expected) + val actualSize = size(actual) + + if (expectedSize != actualSize) { + val sizesDifferMessage = "$typeName sizes differ. Expected size is $expectedSize, actual size is $actualSize." + val toString = "Expected <${expected.contentToString()}>, actual <${actual.contentToString()}>." + + fail(messagePrefix(message) + sizesDifferMessage + "\n" + toString) + } + + for (index in 0 until expectedSize) { + val expectedElement = expected.get(index) + val actualElement = actual.get(index) + + if (expectedElement != actualElement) { + val elementsDifferMessage = elementsDifferMessage(typeName, index, expectedElement, actualElement) + val toString = "Expected <${expected.contentToString()}>, actual <${actual.contentToString()}>." + + fail(messagePrefix(message) + elementsDifferMessage + "\n" + toString) + } + } +} + +private fun checkReferenceAndNullEquality( + typeName: String, + message: String?, + expected: T?, + actual: T?, + contentToString: T?.() -> String +): Boolean { + contract { + returns(false) implies (expected != null && actual != null) + } + + if (expected === actual) { + return true + } + if (expected == null) { + fail(messagePrefix(message) + "Expected $typeName, actual <${actual.contentToString()}>.") + } + if (actual == null) { + fail(messagePrefix(message) + "Expected non-null $typeName <${expected.contentToString()}>, actual .") + } + + return false +} + +private fun elementsDifferMessage(typeName: String, index: Int, expectedElement: Any?, actualElement: Any?): String = + "$typeName elements differ at index $index. Expected element <$expectedElement>, actual element <${actualElement}>." \ No newline at end of file diff --git a/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt index 352a8886a0d..c5a874b181f 100644 --- a/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt +++ b/libraries/kotlin.test/common/src/main/kotlin/kotlin/test/Assertions.kt @@ -96,6 +96,160 @@ fun assertNull(actual: Any?, message: String? = null) { asserter.assertNull(message, actual) } +/** + * Asserts that the [expected] iterable is *structurally* equal to the [actual] iterable, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + * + * The elements are compared for equality with the [equals][Any.equals] function. + * For floating point numbers it means that `NaN` is equal to itself and `-0.0` is not equal to `0.0`. + */ +@SinceKotlin("1.5") +fun <@OnlyInputTypes T> assertContentEquals(expected: Iterable?, actual: Iterable?, message: String? = null) { + assertIterableContentEquals("Iterable", message, expected, actual, Iterable<*>::iterator) +} + +/** + * Asserts that the [expected] sequence is *structurally* equal to the [actual] sequence, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + * + * The elements are compared for equality with the [equals][Any.equals] function. + * For floating point numbers it means that `NaN` is equal to itself and `-0.0` is not equal to `0.0`. + */ +@SinceKotlin("1.5") +fun <@OnlyInputTypes T> assertContentEquals(expected: Sequence?, actual: Sequence?, message: String? = null) { + assertIterableContentEquals("Sequence", message, expected, actual, Sequence<*>::iterator) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + * + * The elements are compared for equality with the [equals][Any.equals] function. + * For floating point numbers it means that `NaN` is equal to itself and `-0.0` is not equal to `0.0`. + */ +@SinceKotlin("1.5") +fun <@OnlyInputTypes T> assertContentEquals(expected: Array?, actual: Array?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, Array<*>::get, Array<*>?::contentToString, Array<*>?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +fun assertContentEquals(expected: ByteArray?, actual: ByteArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, ByteArray::get, ByteArray?::contentToString, ByteArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +fun assertContentEquals(expected: ShortArray?, actual: ShortArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, ShortArray::get, ShortArray?::contentToString, ShortArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +fun assertContentEquals(expected: IntArray?, actual: IntArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, IntArray::get, IntArray?::contentToString, IntArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +fun assertContentEquals(expected: LongArray?, actual: LongArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, LongArray::get, LongArray?::contentToString, LongArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + * + * The elements are compared for equality with the [equals][Any.equals] function. + * For floating point numbers it means that `NaN` is equal to itself and `-0.0` is not equal to `0.0`. + */ +@SinceKotlin("1.5") +fun assertContentEquals(expected: FloatArray?, actual: FloatArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, FloatArray::get, FloatArray?::contentToString, FloatArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + * + * The elements are compared for equality with the [equals][Any.equals] function. + * For floating point numbers it means that `NaN` is equal to itself and `-0.0` is not equal to `0.0`. + */ +@SinceKotlin("1.5") +fun assertContentEquals(expected: DoubleArray?, actual: DoubleArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, DoubleArray::get, DoubleArray?::contentToString, DoubleArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +fun assertContentEquals(expected: BooleanArray?, actual: BooleanArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, BooleanArray::get, BooleanArray?::contentToString, BooleanArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +fun assertContentEquals(expected: CharArray?, actual: CharArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, CharArray::get, CharArray?::contentToString, CharArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +@OptIn(ExperimentalUnsignedTypes::class) +fun assertContentEquals(expected: UByteArray?, actual: UByteArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, UByteArray::get, UByteArray?::contentToString, UByteArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +@OptIn(ExperimentalUnsignedTypes::class) +fun assertContentEquals(expected: UShortArray?, actual: UShortArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, UShortArray::get, UShortArray?::contentToString, UShortArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +@OptIn(ExperimentalUnsignedTypes::class) +fun assertContentEquals(expected: UIntArray?, actual: UIntArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, UIntArray::get, UIntArray?::contentToString, UIntArray?::contentEquals) +} + +/** + * Asserts that the [expected] array is *structurally* equal to the [actual] array, + * i.e. contains the same number of the same elements in the same order, with an optional [message]. + */ +@SinceKotlin("1.5") +@OptIn(ExperimentalUnsignedTypes::class) +fun assertContentEquals(expected: ULongArray?, actual: ULongArray?, message: String? = null) { + assertArrayContentEquals(message, expected, actual, { it.size }, ULongArray::get, ULongArray?::contentToString, ULongArray?::contentEquals) +} + /** 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) diff --git a/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/AssertContentEqualsTest.kt b/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/AssertContentEqualsTest.kt new file mode 100644 index 00000000000..bd286f1812b --- /dev/null +++ b/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/AssertContentEqualsTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.test.tests + +import kotlin.test.* + +class AssertContentEqualsTest { + + private fun testFailureMessage(expected: String, block: () -> Unit) { + val exception = checkFailedAssertion(block) + assertEquals(expected, exception.message, "Wrong assertion message") + } + + @Test + fun testAssertContentEqualsIterable() { + val list: Iterable = listOf(1, 2, 3) + val range: Iterable = 1..3 + + assertContentEquals(list, list) // ref equality + assertContentEquals(list, range) // elements equal + + assertContentEquals(null as Iterable?, null as Iterable?) // null equality + + testFailureMessage("Expected Iterable, actual <[1, 2, 3]>.") { + assertContentEquals(null, list) + } + testFailureMessage("Expected non-null Iterable <1..3>, actual .") { + assertContentEquals(range, null) + } + testFailureMessage("Iterable elements differ at index 1. Expected element <2>, actual element <3>.") { + assertContentEquals(range, listOf(1, 3, 2)) + } + testFailureMessage("Iterable lengths differ. Expected length is bigger than 3, actual length is 3.") { + assertContentEquals(1..4, list) + } + testFailureMessage("Iterable lengths differ. Expected length is 3, actual length is bigger than 3.") { + assertContentEquals(list, 1..4) + } + } + + @Test + fun testAssertContentEqualsSequence() { + val sequence1: Sequence = object : Sequence { + override fun iterator(): Iterator = listOf(1, 2, 3).iterator() + override fun toString(): String = "[1, 2, 3]" + } + val sequence2: Sequence = object : Sequence { + override fun iterator(): Iterator = (1..3).iterator() + override fun toString(): String = "1..3" + } + + assertContentEquals(sequence1, sequence1) // ref equality + assertContentEquals(sequence1, sequence2) // elements equal + + assertContentEquals(null as Iterable?, null as Iterable?) // null equality + + testFailureMessage("Expected Sequence, actual <[1, 2, 3]>.") { + assertContentEquals(null, sequence1) + } + testFailureMessage("Expected non-null Sequence <1..3>, actual .") { + assertContentEquals(sequence2, null) + } + testFailureMessage("Sequence elements differ at index 1. Expected element <2>, actual element <3>.") { + assertContentEquals(sequence2, sequenceOf(1, 3, 2)) + } + testFailureMessage("Sequence lengths differ. Expected length is bigger than 3, actual length is 3.") { + assertContentEquals((1..4).asSequence(), sequence1) + } + testFailureMessage("Sequence lengths differ. Expected length is 3, actual length is bigger than 3.") { + assertContentEquals(sequence1, (1..4).asSequence()) + } + } + + @Test + fun testAssertContentEqualsArray() { + val array1: Array = arrayOf(1, 2, 3) + val array2: Array = arrayOf(1, 2, 3) + + assertContentEquals(array1, array1) // ref equality + assertContentEquals(array1, array2) // elements equal + + assertContentEquals(null as Array?, null as Array?) // null equality + + testFailureMessage("Expected Array, actual <[1, 2, 3]>.") { + assertContentEquals(null, array1) + } + testFailureMessage("Expected non-null Array <[1, 2, 3]>, actual .") { + assertContentEquals(array2, null) + } + testFailureMessage("Array elements differ at index 1. Expected element <2>, actual element <3>.\nExpected <[1, 2, 3]>, actual <[1, 3, 2]>.") { + assertContentEquals(array2, arrayOf(1, 3, 2)) + } + testFailureMessage("Array sizes differ. Expected size is 4, actual size is 3.\nExpected <[0, -1, -2, -3]>, actual <[1, 2, 3]>.") { + assertContentEquals(Array(4) { -it }, array1) + } + testFailureMessage("Array sizes differ. Expected size is 3, actual size is 4.\nExpected <[1, 2, 3]>, actual <[0, -1, -2, -3]>.") { + assertContentEquals(array1, Array(4) { -it }) + } + } + + @Test + fun testAssertContentEqualsDoubleArray() { + val array1: DoubleArray = doubleArrayOf(1.0, Double.NaN, 3.0) + val array2: DoubleArray = doubleArrayOf(1.0, Double.NaN, 3.0) + + assertContentEquals(array1, array1) // ref equality + assertContentEquals(array1, array2) // elements equal + + assertContentEquals(null as DoubleArray?, null as DoubleArray?) // null equality + + testFailureMessage("Expected Array, actual <${array1.contentToString()}>.") { + assertContentEquals(null, array1) + } + testFailureMessage("Expected non-null Array <${array2.contentToString()}>, actual .") { + assertContentEquals(array2, null) + } + + val sameSizeArray = doubleArrayOf(1.0, Double.NaN, 2.0) + testFailureMessage("Array elements differ at index 2. Expected element <${array2[2]}>, actual element <${sameSizeArray[2]}>.\nExpected <${array2.contentToString()}>, actual <${sameSizeArray.contentToString()}>.") { + assertContentEquals(array2, sameSizeArray) + } + + val largerArray = DoubleArray(4) { -it.toDouble() } + testFailureMessage("Array sizes differ. Expected size is 4, actual size is 3.\nExpected <${largerArray.contentToString()}>, actual <${array1.contentToString()}>.") { + assertContentEquals(largerArray, array1) + } + testFailureMessage("Array sizes differ. Expected size is 3, actual size is 4.\nExpected <${array1.contentToString()}>, actual <${largerArray.contentToString()}>.") { + assertContentEquals(array1, largerArray) + } + } + + @OptIn(ExperimentalUnsignedTypes::class) + @Test + fun testAssertContentEqualsULongArray() { + val array1: ULongArray = ulongArrayOf(1u, 2u, 3u) + val array2: ULongArray = ulongArrayOf(1u, 2u, 3u) + + assertContentEquals(array1, array1) // ref equality + assertContentEquals(array1, array2) // elements equal + + assertContentEquals(null as ULongArray?, null as ULongArray?) // null equality + + testFailureMessage("Expected Array, actual <[1, 2, 3]>.") { + assertContentEquals(null, array1) + } + testFailureMessage("Expected non-null Array <[1, 2, 3]>, actual .") { + assertContentEquals(array2, null) + } + testFailureMessage("Array elements differ at index 1. Expected element <2>, actual element <3>.\nExpected <[1, 2, 3]>, actual <[1, 3, 2]>.") { + assertContentEquals(array2, ulongArrayOf(1u, 3u, 2u)) + } + testFailureMessage("Array sizes differ. Expected size is 4, actual size is 3.\nExpected <[4, 3, 2, 1]>, actual <[1, 2, 3]>.") { + assertContentEquals(ULongArray(4) { 4uL - it.toUInt() }, array1) + } + testFailureMessage("Array sizes differ. Expected size is 3, actual size is 4.\nExpected <[1, 2, 3]>, actual <[4, 3, 2, 1]>.") { + assertContentEquals(array1, ULongArray(4) { 4uL - it.toUInt() }) + } + } +} \ No newline at end of file diff --git a/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt b/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt index 6d541f3c8c8..1780275817b 100644 --- a/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt +++ b/libraries/kotlin.test/common/src/test/kotlin/kotlin/test/tests/BasicAssertionsTest.kt @@ -208,7 +208,7 @@ class BasicAssertionsTest { } -private fun checkFailedAssertion(assertion: () -> Unit): AssertionError { +internal fun checkFailedAssertion(assertion: () -> Unit): AssertionError { return assertFailsWith { withDefaultAsserter(assertion) } } diff --git a/libraries/kotlin.test/jvm/build.gradle b/libraries/kotlin.test/jvm/build.gradle index 64dc8941f1f..4aecb1e7b80 100644 --- a/libraries/kotlin.test/jvm/build.gradle +++ b/libraries/kotlin.test/jvm/build.gradle @@ -46,6 +46,7 @@ compileKotlin { "-Xnormalize-constructor-calls=enable", "-Xopt-in=kotlin.contracts.ExperimentalContracts", "-Xsuppress-deprecated-jvm-target-warning", + "-Xopt-in=kotlin.RequiresOptIn", ] kotlinOptions.moduleName = project.archivesBaseName } @@ -54,6 +55,7 @@ compileTestKotlin { kotlinOptions.freeCompilerArgs = [ "-Xallow-kotlin-package", "-Xsuppress-deprecated-jvm-target-warning", + "-Xopt-in=kotlin.RequiresOptIn", ] }