Skip to content

Commit

Permalink
Inline assertions on facets (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg authored Dec 23, 2023
2 parents 5880184 + 62a5611 commit 972d8f9
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 14 deletions.
50 changes: 36 additions & 14 deletions selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/Selfie.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,33 +46,42 @@ object Selfie {
}

open class LiteralStringSelfie
internal constructor(protected val actual: Snapshot, val onlyFacets: Collection<String>? = null) {
internal constructor(
protected val actual: Snapshot,
private val onlyFacets: Collection<String>? = null
) {
init {
if (onlyFacets != null) {
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. */
fun facet(facet: String) = LiteralStringSelfie(actual, listOf(facet))
/** 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 (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()
} 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 =
if (onlyFacets == null) actual
else Snapshot.ofEntries(onlyFacets.map { entry(it, actual.subjectOrFacet(it)) })
return serialize(snapshotToWrite, onlyFacets != null && !onlyFacets.contains(""))
Snapshot.ofEntries(facetsToCheck.map { entry(it, actual.subjectOrFacet(it)) })
return serializeMultiple(snapshotToWrite, !facetsToCheck.contains(""))
}
}
fun toBe_TODO() = toBeDidntMatch(null, actualString(), LiteralString)
Expand All @@ -84,7 +93,9 @@ object Selfie {
}

@JvmStatic
fun <T> expectSelfie(actual: T, camera: Camera<T>) = DiskSelfie(camera.snapshot(actual))
fun <T> expectSelfie(actual: T, camera: Camera<T>) = expectSelfie(camera.snapshot(actual))

@JvmStatic fun expectSelfie(actual: Snapshot) = DiskSelfie(actual)

@JvmStatic fun expectSelfie(actual: String) = DiskSelfie(Snapshot.of(actual))

Expand Down Expand Up @@ -166,12 +177,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()}'"
Expand All @@ -181,6 +192,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"
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ internal class DiskWriteTracker : WriteTracker<String, Snapshot>() {
internal class InlineWriteTracker : WriteTracker<CallLocation, LiteralValue<*>>() {
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()

Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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).toBe("no facets")
expectSelfie(zero).facet("").toBe("no facets")
expectSelfie(zero).facets("").toBe("no facets")
assertThrows<Throwable> { expectSelfie(zero).toBe("WRONG") }
assertThrows<Throwable> { expectSelfie(zero).facet("").toBe("WRONG") }
assertThrows<Throwable> { 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<Throwable> { expectSelfie(one).facet("").toBe("WRONG") }
assertThrows<Throwable> { expectSelfie(one).facets("").toBe("WRONG") }
assertThrows<Throwable> { expectSelfie(one).facet("facet").toBe("WRONG") }
assertThrows<Throwable> { 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)
.toBe(
"subject\n" +
"╔═ [facet1] ═╗\n" +
"facetValue1\n" +
"╔═ [facet2] ═╗\n" +
"facetValue2\n" +
"╔═ [facet3] ═╗\n" +
"facetValue3")
assertThrows<Throwable> { expectSelfie(multiple).toBe("WRONG") }
expectSelfie(multiple)
.facets("", "facet1")
.toBe("subject\n" + "╔═ [facet1] ═╗\n" + "facetValue1")
assertThrows<Throwable> { expectSelfie(multiple).facets("", "facet1").toBe("WRONG") }
expectSelfie(multiple)
.facets("facet3", "facet2")
.toBe("╔═ [facet2] ═╗\n" + "facetValue2\n" + "╔═ [facet3] ═╗\n" + "facetValue3")
// TODO: order of lenses should matter
assertThrows<Throwable> { expectSelfie(multiple).facets("facet3", "facet2").toBe("WRONG") }
expectSelfie(multiple)
.facets("facet1", "")
.toBe("subject\n" + "╔═ [facet1] ═╗\n" + "facetValue1")
assertThrows<Throwable> { expectSelfie(multiple).facets("facet1", "").toBe("WRONG") }
}
}

0 comments on commit 972d8f9

Please sign in to comment.