Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contains in order more details draft (#43) #3907

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.kotest.matchers.sequences

import io.kotest.assertions.print.print

internal data class UnorderedCollectionsDifference<T>(
val missingElements: Set<T>,
val extraElements: Set<T>,
val countMismatches: List<CountMismatch<T>>
) {
private val describedMismatches = sequenceOf(
DescribedMismatch(missingElements, "Missing Elements"),
DescribedMismatch(extraElements, "Extra Elements"),
DescribedMismatch(countMismatches, "Count Mismatches")
)

fun isMatch(): Boolean {
return describedMismatches.all { it.elements.isEmpty() }
}

override fun toString(): String {
return if (isMatch()) "" else "\n" + describedMismatches
.map { it.toString() }
.filter { it.isNotEmpty() }
.joinToString("\n")
}

private data class DescribedMismatch<T>(
val elements: Collection<T>,
val description: String
) {
override fun toString(): String {
return if (elements.isEmpty()) "" else
"$description:\n${elements.joinToString("\n") { it.print().value }}"
}
}

companion object {
fun<T> of(expected: List<T>, value: List<T>): UnorderedCollectionsDifference<T> {
val expectedCounts = expected.counted()
val valueCounts = value.counted()
return UnorderedCollectionsDifference(
missingElements = expectedCounts.keys - valueCounts.keys,
extraElements = valueCounts.keys - expectedCounts.keys,
countMismatches = expectedCounts.mapNotNull { expected ->
val valueCount = valueCounts[expected.key]
valueCount?.let {
if (expected.value == valueCount) null else
CountMismatch(expected.key, expected.value, valueCount)
}
}
)
}
}

internal data class CountMismatch<T>(
val value: T,
val expectedCount: Int,
val actualCount: Int
) {
init {
require(expectedCount != actualCount) { "Expected count should be different from actual, but both were: $expectedCount" }
}

override fun toString(): String = " For ${value.print().value}: expected count: <$expectedCount>, but was: <$actualCount>"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,16 @@ fun <T> containAllInAnyOrder(vararg expected: T): Matcher<Sequence<T>?> =
fun <T, C : Sequence<T>> containAllInAnyOrder(expected: C): Matcher<C?> = neverNullMatcher { value ->
val valueAsList = value.toList()
val expectedAsList = expected.toList()
val passed = valueAsList.size == expectedAsList.size && valueAsList.containsAll(expectedAsList)
val comparison = UnorderedCollectionsDifference.of(expectedAsList, valueAsList)
MatcherResult(
passed,
{ "Sequence should contain the values of $expectedAsList in any order, but was $valueAsList" },
comparison.isMatch(),
{ "Sequence should contain the values of $expectedAsList in any order, but was $valueAsList.${comparison}" },
{ "Sequence should not contain the values of $expectedAsList in any order" }
)
}

internal fun<T> List<T>.counted(): Map<T, Int> = this.groupingBy { it }.eachCount()

infix fun <T : Comparable<T>, C : Sequence<T>> C.shouldHaveUpperBound(t: T) = this should haveUpperBound(t)

fun <T : Comparable<T>, C : Sequence<T>> haveUpperBound(t: T) = object : Matcher<C> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ class SequenceMatchersTest : WordSpec() {
sampleData.countup.shouldContainAllInAnyOrder((5..15).asSequence())
}

succeed("for subset, same count with nulls") {
fail("for subset, same count with nulls") {
sampleData.sparse.shouldContainAllInAnyOrder(sampleData.nulls)
}

Expand All @@ -735,6 +735,17 @@ class SequenceMatchersTest : WordSpec() {
fail("for same, different count") {
sampleData.repeating.shouldContainAllInAnyOrder(sampleData.unique)
}

succeed("detect different count of individual elements in collections of same length") {
shouldThrowAny{
sequenceOf(1, 2, 2).shouldContainAllInAnyOrder(sequenceOf(1, 1, 2))
}.shouldHaveMessage("""
|Sequence should contain the values of [1, 1, 2] in any order, but was [1, 2, 2].
|Count Mismatches:
| For 1: expected count: <2>, but was: <1>
| For 2: expected count: <1>, but was: <2>
""".trimMargin())
}
}

"not contain in any order" should {
Expand All @@ -754,7 +765,7 @@ class SequenceMatchersTest : WordSpec() {
sampleData.countup.shouldNotContainAllInAnyOrder((5..15).asSequence())
}

fail("for subset, same count with nulls") {
succeed("for subset, same count with nulls") {
sampleData.sparse.shouldNotContainAllInAnyOrder(sampleData.nulls)
}

Expand All @@ -774,6 +785,9 @@ class SequenceMatchersTest : WordSpec() {
sampleData.repeating.shouldNotContainAllInAnyOrder(sampleData.unique)
}

succeed("detect different count of individual elements in sequences of same length") {
sequenceOf(1, 2, 2).shouldNotContainAllInAnyOrder(sequenceOf(1, 1, 2))
}
}

"contain in order" should {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.sksamuel.kotest.matchers.sequences

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.sequences.UnorderedCollectionsDifference
import io.kotest.matchers.shouldBe

class UnorderedCollectionsDifferenceTest: StringSpec() {
init {
"detect match" {
UnorderedCollectionsDifference.of(
expected = listOf("apple", "orange", "apple"),
value = listOf("apple", "apple", "orange"),
).isMatch() shouldBe true
}
"detect missing elements" {
UnorderedCollectionsDifference.of(
expected = listOf("apple", "orange", "apple", "banana"),
value = listOf("apple", "apple", "orange"),
) shouldBe UnorderedCollectionsDifference(
missingElements = setOf("banana"),
extraElements = setOf(),
countMismatches = listOf()
)
}
"detect extra elements" {
UnorderedCollectionsDifference.of(
expected = listOf("apple", "orange", "apple"),
value = listOf("apple", "apple", "orange", "banana"),
) shouldBe UnorderedCollectionsDifference(
missingElements = setOf(),
extraElements = setOf("banana"),
countMismatches = listOf()
)
}
"detect count mismatch" {
UnorderedCollectionsDifference.of(
expected = listOf("apple", "orange", "apple"),
value = listOf("apple", "apple", "orange", "orange"),
) shouldBe UnorderedCollectionsDifference(
missingElements = setOf(),
extraElements = setOf(),
countMismatches = listOf(
UnorderedCollectionsDifference.CountMismatch(
value = "orange",
expectedCount = 1,
actualCount = 2
)
)
)
}
}
}