From 89163f985d7765d8a24bfb049c5a6cbce41ff8a3 Mon Sep 17 00:00:00 2001 From: "HyunWoo Lee (Nunu Lee)" <54518925+l2hyunwoo@users.noreply.github.com> Date: Thu, 9 Nov 2023 02:11:06 +0900 Subject: [PATCH] Refactor NonEmptyListTest to kotlin-test (#3231) * Add kotlin test dependency * Refactor NonEmptyList Test to use kotlin test --------- Co-authored-by: Alejandro Serrano --- .../kotlin/arrow/core/NonEmptyListTest.kt | 634 ++++++++++-------- 1 file changed, 336 insertions(+), 298 deletions(-) diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/NonEmptyListTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/NonEmptyListTest.kt index 6ff83ec9b02..5daf09ca177 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/NonEmptyListTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/NonEmptyListTest.kt @@ -1,11 +1,8 @@ package arrow.core -import arrow.core.test.laws.SemigroupLaws import arrow.core.test.nonEmptyList import arrow.core.test.stackSafeIteration -import arrow.core.test.testLaws import io.kotest.assertions.withClue -import io.kotest.core.spec.style.StringSpec import io.kotest.inspectors.shouldForAll import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.nulls.shouldBeNull @@ -14,350 +11,391 @@ import io.kotest.matchers.shouldBe import io.kotest.property.Arb import io.kotest.property.arbitrary.* import io.kotest.property.checkAll +import kotlinx.coroutines.test.runTest import kotlin.math.max import kotlin.math.min - -class NonEmptyListTest : StringSpec({ - - testLaws(SemigroupLaws("NonEmptyList", NonEmptyList::plus, Arb.nonEmptyList(Arb.int()))) - - "iterable.toNonEmptyListOrNull should round trip" { - checkAll(Arb.nonEmptyList(Arb.int())) { nonEmptyList -> - nonEmptyList.all.toNonEmptyListOrNull().shouldNotBeNull() shouldBe nonEmptyList - } +import kotlin.test.Test + +class NonEmptyListTest { + + @Test + fun nonEmptyList() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()) + ) { nel1, nel2, nel3 -> + (nel1 + nel2) + nel3 shouldBe nel1 + (nel2 + nel3) } + } - "iterable.toNonEmptyListOrNull should return null for an empty iterable" { - listOf().toNonEmptyListOrNull().shouldBeNull() + @Test + fun iterableToNonEmptyListOrNullShouldRoundTrip() = runTest { + checkAll(Arb.nonEmptyList(Arb.int())) { nonEmptyList -> + nonEmptyList.all.toNonEmptyListOrNull().shouldNotBeNull() shouldBe nonEmptyList } + } - "iterable.toNonEmptyListOrNull should work correctly when the iterable starts with or contains null" { - checkAll(Arb.nonEmptyList(Arb.int().orNull())) { nonEmptyList -> - nonEmptyList.all.toNonEmptyListOrNull().shouldNotBeNull() shouldBe nonEmptyList - } + @Test + fun iterableToNonEmptyListOrNoneShouldRoundTrip() = runTest { + checkAll(Arb.nonEmptyList(Arb.int())) { nonEmptyList -> + nonEmptyList.all.toNonEmptyListOrNone() shouldBe nonEmptyList.some() } + } - "iterable.toNonEmptyListOrNone should round trip" { - checkAll(Arb.nonEmptyList(Arb.int())) { nonEmptyList -> - nonEmptyList.all.toNonEmptyListOrNone() shouldBe nonEmptyList.some() - } + @Test + fun iterableToNonEmptyListOrNullShouldReturnNullForAnEmptyIterable() = runTest { + listOf().toNonEmptyListOrNull().shouldBeNull() + } + + @Test + fun iterableToNonEmptyListOrNullShouldWorkCorrectlyWhenTheIterableStartsWithOrContainsNull() = runTest { + checkAll(Arb.nonEmptyList(Arb.int().orNull())) { nonEmptyList -> + nonEmptyList.all.toNonEmptyListOrNull().shouldNotBeNull() shouldBe nonEmptyList } + } - "can align lists with different lengths" { - checkAll(Arb.nonEmptyList(Arb.boolean()), Arb.nonEmptyList(Arb.boolean())) { a, b -> - val result = a.align(b) + @Test + fun canAlignListsWithDifferentLengths() = runTest { + checkAll(Arb.nonEmptyList(Arb.boolean()), Arb.nonEmptyList(Arb.boolean())) { a, b -> + val result = a.align(b) - result.size shouldBe max(a.size, b.size) - result.take(min(a.size, b.size)).shouldForAll { - it.isBoth() shouldBe true - } - result.drop(min(a.size, b.size)).shouldForAll { - if (a.size < b.size) { - it.isRight() shouldBe true - } else { - it.isLeft() shouldBe true - } - } + result.size shouldBe max(a.size, b.size) + result.take(min(a.size, b.size)).shouldForAll { + it.isBoth() shouldBe true } - } - - "mapOrAccumulate is stack-safe, and runs in original order" { - val acc = mutableListOf() - val res = (0..stackSafeIteration()) - .toNonEmptyListOrNull()!! - .mapOrAccumulate(String::plus) { - acc.add(it) - it + result.drop(min(a.size, b.size)).shouldForAll { + if (a.size < b.size) { + it.isRight() shouldBe true + } else { + it.isLeft() shouldBe true } - res shouldBe Either.Right(acc) - res shouldBe Either.Right((0..stackSafeIteration()).toList()) + } } + } + + @Test + fun mapOrAccumulateIsStackSafeAndRunsInOriginalOrder() = runTest { + val acc = mutableListOf() + val res = (0..stackSafeIteration()) + .toNonEmptyListOrNull()!! + .mapOrAccumulate(String::plus) { + acc.add(it) + it + } + res shouldBe Either.Right(acc) + res shouldBe Either.Right((0..stackSafeIteration()).toList()) + } + + @Test + fun mapOrAccumulateAccumulatesErrors() = runTest { + checkAll(Arb.nonEmptyList(Arb.int())) { nel -> + val res = nel.mapOrAccumulate { i -> + if (i % 2 == 0) i else raise(i) + } - "mapOrAccumulate accumulates errors" { - checkAll(Arb.nonEmptyList(Arb.int())) { nel -> - val res = nel.mapOrAccumulate { i -> - if (i % 2 == 0) i else raise(i) - } - - val expected = nel.filterNot { it % 2 == 0 } - .toNonEmptyListOrNull()?.left() ?: nel.filter { it % 2 == 0 }.right() + val expected = nel.filterNot { it % 2 == 0 } + .toNonEmptyListOrNull()?.left() ?: nel.filter { it % 2 == 0 }.right() - res shouldBe expected - } + res shouldBe expected } + } - "mapOrAccumulate accumulates errors with combine function" { - checkAll(Arb.nonEmptyList(Arb.negativeInt())) { nel -> - val res = nel.mapOrAccumulate(String::plus) { i -> - if (i > 0) i else raise("Negative") - } - - res shouldBe nel.map { "Negative" }.joinToString("").left() + @Test + fun mapOrAccumulateAccumulatesErrorsWithCombineFunction() = runTest { + checkAll(Arb.nonEmptyList(Arb.negativeInt())) { nel -> + val res = nel.mapOrAccumulate(String::plus) { i -> + if (i > 0) i else raise("Negative") } + + res shouldBe nel.map { "Negative" }.joinToString("").left() } + } - "padZip" { - checkAll(Arb.nonEmptyList(Arb.int()), Arb.nonEmptyList(Arb.int())) { a, b -> - val result = a.padZip(b) - val left = a + List(max(0, b.size - a.size)) { null } - val right = b + List(max(0, a.size - b.size)) { null } + @Test + fun padZip() = runTest { + checkAll(Arb.nonEmptyList(Arb.int()), Arb.nonEmptyList(Arb.int())) { a, b -> + val result = a.padZip(b) + val left = a + List(max(0, b.size - a.size)) { null } + val right = b + List(max(0, a.size - b.size)) { null } - result shouldBe left.zip(right) - } + result shouldBe left.zip(right) } + } - "padZip with transformation" { - checkAll(Arb.nonEmptyList(Arb.int()), Arb.nonEmptyList(Arb.int())) { a, b -> - val result = a.padZip(b, { it * 2 }, { it * 3 }, { x, y -> x + y }) + @Test + fun padZipWithTransformation() = runTest { + checkAll(Arb.nonEmptyList(Arb.int()), Arb.nonEmptyList(Arb.int())) { a, b -> + val result = a.padZip(b, { it * 2 }, { it * 3 }, { x, y -> x + y }) - val minSize = min(a.size, b.size) - result.size shouldBe max(a.size, b.size) - result.take(minSize) shouldBe a.take(minSize).zip(b.take(minSize)) { x, y -> x + y } + val minSize = min(a.size, b.size) + result.size shouldBe max(a.size, b.size) + result.take(minSize) shouldBe a.take(minSize).zip(b.take(minSize)) { x, y -> x + y } - if (a.size > b.size) - result.drop(minSize) shouldBe a.drop(minSize).map { it * 2 } - else - result.drop(minSize) shouldBe b.drop(minSize).map { it * 3 } - } + if (a.size > b.size) + result.drop(minSize) shouldBe a.drop(minSize).map { it * 2 } + else + result.drop(minSize) shouldBe b.drop(minSize).map { it * 3 } } + } - "unzip is the inverse of zip" { - checkAll(Arb.nonEmptyList(Arb.int())) { nel -> - val zipped = nel.zip(nel) - val left = zipped.map { it.first } - val right = zipped.map { it.second } + @Test + fun unzipIsTheInverseOfZip() = runTest { + checkAll(Arb.nonEmptyList(Arb.int())) { nel -> + val zipped = nel.zip(nel) + val left = zipped.map { it.first } + val right = zipped.map { it.second } - left shouldBe nel - right shouldBe nel - } + left shouldBe nel + right shouldBe nel } + } - "unzip with split function" { - checkAll(Arb.nonEmptyList(Arb.pair(Arb.int(), Arb.int()))) { nel -> - val unzipped = nel.unzip(::identity) + @Test + fun unzipWithSplitFunction() = runTest { + checkAll(Arb.nonEmptyList(Arb.pair(Arb.int(), Arb.int()))) { nel -> + val unzipped = nel.unzip(::identity) - unzipped.first shouldBe nel.map { it.first } - unzipped.second shouldBe nel.map { it.second } - } + unzipped.first shouldBe nel.map { it.first } + unzipped.second shouldBe nel.map { it.second } } - - "zip2" { - checkAll(Arb.nonEmptyList(Arb.int()), Arb.nonEmptyList(Arb.int())) { a, b -> - val result = a.zip(b) - val expected = a.all.zip(b.all).toNonEmptyListOrNull() - result shouldBe expected - } + } + + @Test + fun zip2() = runTest { + checkAll(Arb.nonEmptyList(Arb.int()), Arb.nonEmptyList(Arb.int())) { a, b -> + val result = a.zip(b) + val expected = a.all.zip(b.all).toNonEmptyListOrNull() + result shouldBe expected } - - "zip3" { - checkAll( - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()) - ) { a, b, c -> - val result = a.zip(b, c, ::Triple) - val expected = a.all.zip(b.all, c.all, ::Triple).toNonEmptyListOrNull() - result shouldBe expected - } + } + + @Test + fun zip3() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()) + ) { a, b, c -> + val result = a.zip(b, c, ::Triple) + val expected = a.all.zip(b.all, c.all, ::Triple).toNonEmptyListOrNull() + result shouldBe expected } - - "zip4" { - checkAll( - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()) - ) { a, b, c, d -> - val result = a.zip(b, c, d, ::Tuple4) - val expected = a.all.zip(b.all, c.all, d.all, ::Tuple4).toNonEmptyListOrNull() - result shouldBe expected - } + } + + @Test + fun zip4() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()) + ) { a, b, c, d -> + val result = a.zip(b, c, d, ::Tuple4) + val expected = a.all.zip(b.all, c.all, d.all, ::Tuple4).toNonEmptyListOrNull() + result shouldBe expected } - - "zip5" { - checkAll( - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()) - ) { a, b, c, d, e -> - val result = a.zip(b, c, d, e, ::Tuple5) - val expected = a.all.zip(b.all, c.all, d.all, e.all, ::Tuple5).toNonEmptyListOrNull() - result shouldBe expected - } + } + + @Test + fun zip5() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()) + ) { a, b, c, d, e -> + val result = a.zip(b, c, d, e, ::Tuple5) + val expected = a.all.zip(b.all, c.all, d.all, e.all, ::Tuple5).toNonEmptyListOrNull() + result shouldBe expected } - - "zip6" { - checkAll( - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()) - ) { a, b, c, d, e, f -> - val result = a.zip(b, c, d, e, f, ::Tuple6) - val expected = - a.all.zip(b.all, c.all, d.all, e.all, f.all, ::Tuple6).toNonEmptyListOrNull() - result shouldBe expected - } + } + + @Test + fun zip6() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()) + ) { a, b, c, d, e, f -> + val result = a.zip(b, c, d, e, f, ::Tuple6) + val expected = + a.all.zip(b.all, c.all, d.all, e.all, f.all, ::Tuple6).toNonEmptyListOrNull() + result shouldBe expected } - - "zip7" { - checkAll( - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()) - ) { a, b, c, d, e, f, g -> - val result = a.zip(b, c, d, e, f, g, ::Tuple7) - val expected = - a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, ::Tuple7).toNonEmptyListOrNull() - result shouldBe expected - } + } + + @Test + fun zip7() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()) + ) { a, b, c, d, e, f, g -> + val result = a.zip(b, c, d, e, f, g, ::Tuple7) + val expected = + a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, ::Tuple7).toNonEmptyListOrNull() + result shouldBe expected } - - "zip8" { - checkAll( - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()) - ) { a, b, c, d, e, f, g, h -> - val result = a.zip(b, c, d, e, f, g, h, ::Tuple8) - val expected = a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, h.all, ::Tuple8) - .toNonEmptyListOrNull() - result shouldBe expected - } + } + + @Test + fun zip8() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()) + ) { a, b, c, d, e, f, g, h -> + val result = a.zip(b, c, d, e, f, g, h, ::Tuple8) + val expected = a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, h.all, ::Tuple8) + .toNonEmptyListOrNull() + result shouldBe expected } - - "zip9" { - checkAll( - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()), - Arb.nonEmptyList(Arb.int()) - ) { a, b, c, d, e, f, g, h, i -> - val result = a.zip(b, c, d, e, f, g, h, i, ::Tuple9) - val expected = a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, h.all, i.all, ::Tuple9) - .toNonEmptyListOrNull() - result shouldBe expected - } + } + + @Test + fun zip9() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()), + Arb.nonEmptyList(Arb.int()) + ) { a, b, c, d, e, f, g, h, i -> + val result = a.zip(b, c, d, e, f, g, h, i, ::Tuple9) + val expected = a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, h.all, i.all, ::Tuple9) + .toNonEmptyListOrNull() + result shouldBe expected } - - "max element" { - checkAll( - Arb.nonEmptyList(Arb.int()) - ) { a -> - val result = a.max() - val expected = a.maxOrNull() - result shouldBe expected - } + } + + @Test + fun maxElement() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()) + ) { a -> + val result = a.max() + val expected = a.maxOrNull() + result shouldBe expected } - - "maxBy element" { - checkAll( - Arb.nonEmptyList(Arb.int()) - ) { a -> - val result = a.maxBy(::identity) - val expected = a.maxByOrNull(::identity) - result shouldBe expected - } + } + + @Test + fun maxByElement() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()) + ) { a -> + val result = a.maxBy(::identity) + val expected = a.maxByOrNull(::identity) + result shouldBe expected } - - "min element" { - checkAll( - Arb.nonEmptyList(Arb.int()) - ) { a -> - val result = a.min() - val expected = a.minOrNull() - result shouldBe expected - } + } + + @Test + fun minElement() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()) + ) { a -> + val result = a.min() + val expected = a.minOrNull() + result shouldBe expected } - - "minBy element" { - checkAll( - Arb.nonEmptyList(Arb.int()) - ) { a -> - val result = a.minBy(::identity) - val expected = a.minByOrNull(::identity) - result shouldBe expected - } + } + + @Test + fun minByElement() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()) + ) { a -> + val result = a.minBy(::identity) + val expected = a.minByOrNull(::identity) + result shouldBe expected } - - "NonEmptyList equals List" { - checkAll( - Arb.nonEmptyList(Arb.int()) - ) { a -> - withClue("$a should be equal to ${a.all}") { - // `shouldBe` doesn't use the `equals` methods on `Iterable` - (a == a.all).shouldBeTrue() - } + } + + @Test + fun nonEmptyListEqualsList() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()) + ) { a -> + withClue("$a should be equal to ${a.all}") { + // `shouldBe` doesn't use the `equals` methods on `Iterable` + (a == a.all).shouldBeTrue() } } - - "lastOrNull" { - checkAll( - Arb.nonEmptyList(Arb.int()) - ) { a -> - val result = a.lastOrNull() - val expected = a.last() - result shouldBe expected - } + } + + @Test + fun lastOrNull() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()) + ) { a -> + val result = a.lastOrNull() + val expected = a.last() + result shouldBe expected } - - "extract" { - checkAll( - Arb.nonEmptyList(Arb.int()) - ) { a -> - val result = a.extract() - val expected = a.head - result shouldBe expected - } + } + + @Test + fun extract() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()) + ) { a -> + val result = a.extract() + val expected = a.head + result shouldBe expected } - - "plus" { - checkAll( - Arb.nonEmptyList(Arb.int()), - Arb.int() - ) { a, b -> - val result = a + b - val expected = a.all + b - result shouldBe expected - } + } + + @Test + fun plus() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.int() + ) { a, b -> + val result = a + b + val expected = a.all + b + result shouldBe expected } - - "coflatMap should retain the same length as the original list" { - checkAll( - Arb.nonEmptyList(Arb.int()) - ) { a -> - val result = a.coflatMap { it.all } - val expected = a.all - result.size shouldBe expected.size - } + } + + @Test + fun coflatMapKeepsLength() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()) + ) { a -> + val result = a.coflatMap { it.all } + val expected = a.all + result.size shouldBe expected.size } - - "foldLeft should sum up correctly for addition" { - checkAll( - Arb.nonEmptyList(Arb.int()), - Arb.int() - ) { list, initial -> - val result = list.foldLeft(initial) { acc, i -> acc + i } - val expected = initial + list.all.sum() - result shouldBe expected - } + } + + @Test + fun foldLeftAddition() = runTest { + checkAll( + Arb.nonEmptyList(Arb.int()), + Arb.int() + ) { list, initial -> + val result = list.foldLeft(initial) { acc, i -> acc + i } + val expected = initial + list.all.sum() + result shouldBe expected } -}) + } +}