From e14caadf6f1d5dd4b504e0c6d8d50d24c8cd923d Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 22 Dec 2023 13:42:24 -0800 Subject: [PATCH 1/5] Make sure that `toBe("blah")` fails eagerly when the test fails, and doesn't wait for the writing to fail because the writing happens too late. --- .../main/kotlin/com/diffplug/selfie/junit5/WriteTracker.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/WriteTracker.kt b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/WriteTracker.kt index b7424ea3..170b6beb 100644 --- a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/WriteTracker.kt +++ b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/WriteTracker.kt @@ -101,6 +101,10 @@ internal class DiskWriteTracker : WriteTracker() { internal class InlineWriteTracker : WriteTracker>() { fun record(call: CallStack, literalValue: LiteralValue<*>, layout: SnapshotFileLayout) { recordInternal(call.location, literalValue, call, layout) + if (literalValue.expected != null) { + throw UnsupportedOperationException( + "`.toBe() didn't match! Change to `toBe_TODO()` to record a new value until https://github.com/diffplug/selfie/pull/49 is merged.") + } } fun hasWrites(): Boolean = writes.isNotEmpty() From 51d7a97c5a40cf20d42f8861b6118c927e881065 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 22 Dec 2023 13:49:57 -0800 Subject: [PATCH 2/5] First cut at fixing up inline facet testing. --- .../main/kotlin/com/diffplug/selfie/Selfie.kt | 36 +++++++++++----- .../diffplug/selfie/junit5/InlineFacetTest.kt | 25 +++++++++++ .../undertest/junit5/UT_InlineFacetTest.kt | 41 +++++++++++++++++++ 3 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 selfie-runner-junit5/src/test/kotlin/com/diffplug/selfie/junit5/InlineFacetTest.kt create mode 100644 undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt diff --git a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt index b9655d29..044f925f 100644 --- a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt +++ b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt @@ -52,7 +52,9 @@ object Selfie { check(onlyFacets.all { it == "" || actual.facets.containsKey(it) }) { "The following facets were not found in the snapshot: ${onlyFacets.filter { actual.subjectOrFacetMaybe(it) == null }}\navailable facets are: ${actual.facets.keys}" } - check(onlyFacets.size > 1) { "Must have at least one facet to display, this was empty." } + check(onlyFacets.isNotEmpty()) { + "Must have at least one facet to display, this was empty." + } } } /** Extract a single facet from a snapshot in order to do an inline snapshot. */ @@ -60,10 +62,9 @@ object Selfie { /** Extract a multiple facets from a snapshot in order to do an inline snapshot. */ fun facets(vararg facets: String) = LiteralStringSelfie(actual, facets.toList()) private fun actualString(): String { - if ((onlyFacets == null && actual.facets.isEmpty()) || actual.facets.size == 1) { + if ((onlyFacets == null && actual.facets.isEmpty()) || onlyFacets!!.size == 1) { // single value doesn't have to worry about escaping at all - val onlyValue = - actual.run { if (facets.isEmpty()) subject else facets[onlyFacets!!.first()]!! } + val onlyValue = actual.subjectOrFacet(onlyFacets?.first() ?: "") return if (onlyValue.isBinary) { TODO("BASE64") } else onlyValue.valueString() @@ -72,7 +73,7 @@ object Selfie { val snapshotToWrite = if (onlyFacets == null) actual else Snapshot.ofEntries(onlyFacets.map { entry(it, actual.subjectOrFacet(it)) }) - return serialize(snapshotToWrite, onlyFacets != null && !onlyFacets.contains("")) + return serializeMultiple(snapshotToWrite, onlyFacets != null && !onlyFacets.contains("")) } } fun toBe_TODO() = toBeDidntMatch(null, actualString(), LiteralString) @@ -84,7 +85,9 @@ object Selfie { } @JvmStatic - fun expectSelfie(actual: T, camera: Camera) = DiskSelfie(camera.snapshot(actual)) + fun expectSelfie(actual: T, camera: Camera) = expectSelfie(camera.snapshot(actual)) + + @JvmStatic fun expectSelfie(actual: Snapshot) = DiskSelfie(actual) @JvmStatic fun expectSelfie(actual: String) = DiskSelfie(Snapshot.of(actual)) @@ -166,12 +169,12 @@ internal class ExpectedActual(val expected: Snapshot?, val actual: Snapshot) { val includeRoot = mismatchInExpected.containsKey("") throw AssertionFailedError( "Snapshot failure", - serialize(Snapshot.ofEntries(mismatchInExpected.entries), !includeRoot), - serialize(Snapshot.ofEntries(mismatchInActual.entries), !includeRoot)) + serializeMultiple(Snapshot.ofEntries(mismatchInExpected.entries), !includeRoot), + serializeMultiple(Snapshot.ofEntries(mismatchInActual.entries), !includeRoot)) } } } -private fun serialize(actual: Snapshot, removeEmptySubject: Boolean): String { +private fun serializeMultiple(actual: Snapshot, removeEmptySubject: Boolean): String { if (removeEmptySubject) { check(actual.subject.valueString().isEmpty()) { "The subject was expected to be empty, was '${actual.subject.valueString()}'" @@ -181,6 +184,17 @@ private fun serialize(actual: Snapshot, removeEmptySubject: Boolean): String { file.snapshots = ArrayMap.of(mutableListOf("" to actual)) val buf = StringBuilder() file.serialize(buf::append) - val str = buf.toString() - return if (removeEmptySubject) str else str + + check(buf.startsWith(EMPTY_SUBJECT)) + check(buf.endsWith(EOF)) + buf.setLength(buf.length - EOF.length) + val str = buf.substring(EMPTY_SUBJECT.length) + return if (!removeEmptySubject) str + else { + check(str[0] == '\n') + str.substring(1) + } } + +private const val EMPTY_SUBJECT = "╔═ ═╗\n" +private const val EOF = "\n╔═ [end of file] ═╗\n" diff --git a/selfie-runner-junit5/src/test/kotlin/com/diffplug/selfie/junit5/InlineFacetTest.kt b/selfie-runner-junit5/src/test/kotlin/com/diffplug/selfie/junit5/InlineFacetTest.kt new file mode 100644 index 00000000..1e79b05b --- /dev/null +++ b/selfie-runner-junit5/src/test/kotlin/com/diffplug/selfie/junit5/InlineFacetTest.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 DiffPlug + * + * 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 + * + * https://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. + */ +package com.diffplug.selfie.junit5 + +import kotlin.test.Test + +class InlineFacetTest : Harness("undertest-junit5") { + @Test + fun readPasses() { + gradleReadSS() + } +} diff --git a/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt b/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt new file mode 100644 index 00000000..4aabfb4d --- /dev/null +++ b/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt @@ -0,0 +1,41 @@ +package undertest.junit5 + +import com.diffplug.selfie.Selfie.expectSelfie +import com.diffplug.selfie.Snapshot +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class UT_InlineFacetTest { + @Test fun singleFacet() { + val zero = Snapshot.of("no facets") + expectSelfie(zero).facet("").toBe("no facets") + expectSelfie(zero).facets("").toBe("no facets") + assertThrows { expectSelfie(zero).facet("").toBe("WRONG") } + assertThrows { expectSelfie(zero).facets("").toBe("WRONG") } + + val one = Snapshot.of("subject").plusFacet("facet", "facetValue") + expectSelfie(one).facet("").toBe("subject") + expectSelfie(one).facets("").toBe("subject") + expectSelfie(one).facet("facet").toBe("facetValue") + expectSelfie(one).facets("facet").toBe("facetValue") + + assertThrows { expectSelfie(one).facet("").toBe("WRONG") } + assertThrows { expectSelfie(one).facets("").toBe("WRONG") } + assertThrows { expectSelfie(one).facet("facet").toBe("WRONG") } + assertThrows { expectSelfie(one).facets("facet").toBe("WRONG") } + } + + @Test fun multipleFacets() { + val multiple = + Snapshot.of("subject") + .plusFacet("facet1", "facetValue1") + .plusFacet("facet2", "facetValue2") + .plusFacet("facet3", "facetValue3") + expectSelfie(multiple) + .facets("", "facet1") + .toBe("subject\n" + "╔═ [facet1] ═╗\n" + "facetValue1") + expectSelfie(multiple) + .facets("facet3", "facet2") + .toBe("╔═ [facet2] ═╗\n" + "facetValue2\n" + "╔═ [facet3] ═╗\n" + "facetValue3") + } +} From d5226be9e97b7d35519f12a43faed54bc34672e8 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 22 Dec 2023 13:56:56 -0800 Subject: [PATCH 3/5] Code cleanup. --- .../src/main/kotlin/com/diffplug/selfie/Selfie.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt index 044f925f..6ad4046c 100644 --- a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt +++ b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt @@ -46,7 +46,10 @@ object Selfie { } open class LiteralStringSelfie - internal constructor(protected val actual: Snapshot, val onlyFacets: Collection? = null) { + internal constructor( + protected val actual: Snapshot, + private val onlyFacets: Collection? = null + ) { init { if (onlyFacets != null) { check(onlyFacets.all { it == "" || actual.facets.containsKey(it) }) { @@ -71,9 +74,8 @@ object Selfie { } else { // multiple values might need our SnapshotFile escaping, we'll use it just in case val snapshotToWrite = - if (onlyFacets == null) actual - else Snapshot.ofEntries(onlyFacets.map { entry(it, actual.subjectOrFacet(it)) }) - return serializeMultiple(snapshotToWrite, onlyFacets != null && !onlyFacets.contains("")) + Snapshot.ofEntries(onlyFacets.map { entry(it, actual.subjectOrFacet(it)) }) + return serializeMultiple(snapshotToWrite, !onlyFacets.contains("")) } } fun toBe_TODO() = toBeDidntMatch(null, actualString(), LiteralString) From cfbc15be72b571fba2c7188dbfe5a0437954d44d Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 22 Dec 2023 17:37:48 -0800 Subject: [PATCH 4/5] Fix some bugs. --- .../src/main/kotlin/com/diffplug/selfie/Selfie.kt | 12 +++++++++--- .../kotlin/undertest/junit5/UT_InlineFacetTest.kt | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt index 6ad4046c..4c008d0b 100644 --- a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt +++ b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt @@ -65,7 +65,7 @@ object Selfie { /** Extract a multiple facets from a snapshot in order to do an inline snapshot. */ fun facets(vararg facets: String) = LiteralStringSelfie(actual, facets.toList()) private fun actualString(): String { - if ((onlyFacets == null && actual.facets.isEmpty()) || onlyFacets!!.size == 1) { + if (actual.facets.isEmpty() || onlyFacets?.size == 1) { // single value doesn't have to worry about escaping at all val onlyValue = actual.subjectOrFacet(onlyFacets?.first() ?: "") return if (onlyValue.isBinary) { @@ -73,9 +73,15 @@ object Selfie { } else onlyValue.valueString() } else { // multiple values might need our SnapshotFile escaping, we'll use it just in case + val facetsToCheck = + onlyFacets + ?: buildList { + add("") + addAll(actual.facets.keys) + } val snapshotToWrite = - Snapshot.ofEntries(onlyFacets.map { entry(it, actual.subjectOrFacet(it)) }) - return serializeMultiple(snapshotToWrite, !onlyFacets.contains("")) + Snapshot.ofEntries(facetsToCheck.map { entry(it, actual.subjectOrFacet(it)) }) + return serializeMultiple(snapshotToWrite, !facetsToCheck.contains("")) } } fun toBe_TODO() = toBeDidntMatch(null, actualString(), LiteralString) diff --git a/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt b/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt index 4aabfb4d..505a61ce 100644 --- a/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt +++ b/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt @@ -8,8 +8,10 @@ import org.junit.jupiter.api.assertThrows class UT_InlineFacetTest { @Test fun singleFacet() { val zero = Snapshot.of("no facets") + expectSelfie(zero).toBe("no facets") expectSelfie(zero).facet("").toBe("no facets") expectSelfie(zero).facets("").toBe("no facets") + assertThrows { expectSelfie(zero).toBe("WRONG") } assertThrows { expectSelfie(zero).facet("").toBe("WRONG") } assertThrows { expectSelfie(zero).facets("").toBe("WRONG") } @@ -31,11 +33,23 @@ class UT_InlineFacetTest { .plusFacet("facet1", "facetValue1") .plusFacet("facet2", "facetValue2") .plusFacet("facet3", "facetValue3") + expectSelfie(multiple) + .toBe( + "subject\n" + + "╔═ [facet1] ═╗\n" + + "facetValue1\n" + + "╔═ [facet2] ═╗\n" + + "facetValue2\n" + + "╔═ [facet3] ═╗\n" + + "facetValue3") + assertThrows { expectSelfie(multiple).toBe("WRONG") } expectSelfie(multiple) .facets("", "facet1") .toBe("subject\n" + "╔═ [facet1] ═╗\n" + "facetValue1") + assertThrows { expectSelfie(multiple).facets("", "facet1").toBe("WRONG") } expectSelfie(multiple) .facets("facet3", "facet2") .toBe("╔═ [facet2] ═╗\n" + "facetValue2\n" + "╔═ [facet3] ═╗\n" + "facetValue3") + assertThrows { expectSelfie(multiple).facets("facet3", "facet2").toBe("WRONG") } } } From 62a5611d3af5584046f4d02b8ac0876069e1542f Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Fri, 22 Dec 2023 17:41:42 -0800 Subject: [PATCH 5/5] Add a test which should change later. --- .../src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt b/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt index 505a61ce..5ac94fdb 100644 --- a/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt +++ b/undertest-junit5/src/test/kotlin/undertest/junit5/UT_InlineFacetTest.kt @@ -50,6 +50,11 @@ class UT_InlineFacetTest { expectSelfie(multiple) .facets("facet3", "facet2") .toBe("╔═ [facet2] ═╗\n" + "facetValue2\n" + "╔═ [facet3] ═╗\n" + "facetValue3") +// TODO: order of lenses should matter assertThrows { expectSelfie(multiple).facets("facet3", "facet2").toBe("WRONG") } + expectSelfie(multiple) + .facets("facet1", "") + .toBe("subject\n" + "╔═ [facet1] ═╗\n" + "facetValue1") + assertThrows { expectSelfie(multiple).facets("facet1", "").toBe("WRONG") } } }