From 0ea9e415c351ca7289a4aacfd4ec79585e452d29 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Jan 2024 22:20:26 -0800 Subject: [PATCH 01/11] First cut at a test. --- .../kotlin/undertest/junit5/UT_BinaryTest.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 undertest-junit5/src/test/kotlin/undertest/junit5/UT_BinaryTest.kt diff --git a/undertest-junit5/src/test/kotlin/undertest/junit5/UT_BinaryTest.kt b/undertest-junit5/src/test/kotlin/undertest/junit5/UT_BinaryTest.kt new file mode 100644 index 00000000..d93c7045 --- /dev/null +++ b/undertest-junit5/src/test/kotlin/undertest/junit5/UT_BinaryTest.kt @@ -0,0 +1,22 @@ +package undertest.junit5 + +import com.diffplug.selfie.Selfie.expectSelfie +import kotlin.test.Test + +class UT_BinaryTest { + @Test fun emptyOnDisk() { + expectSelfie(byteArrayOf()).toMatchDisk_TODO() + } + + @Test fun emptyInline() { + expectSelfie(byteArrayOf()).toBe_TODO() + } + + @Test fun bigishOnDisk() { + expectSelfie(ByteArray(256) { it.toByte() }).toMatchDisk_TODO() + } + + @Test fun bigishInline() { + expectSelfie(ByteArray(256) { it.toByte() }).toBe_TODO() + } +} From 2367fc93e2f4c6f66f389d29bb2260a50aa5b3b0 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Jan 2024 23:08:05 -0800 Subject: [PATCH 02/11] Replace our made-up `StringWriter` with `Appendable` which is part of KMP it turns out! --- .../kotlin/com/diffplug/selfie/Selfie.kt | 7 ++- .../com/diffplug/selfie/SnapshotFile.kt | 46 ++++++++++++------- .../com/diffplug/selfie/SnapshotFileTest.kt | 6 +-- .../junit5/SelfieTestExecutionListener.kt | 2 +- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt index 94807560..170612cf 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt @@ -203,8 +203,7 @@ object Selfie { * missing facets. */ private fun serializeOnlyFacets(snapshot: Snapshot, keys: Collection): String { - val buf = StringBuilder() - val writer = StringWriter { buf.append(it) } + val writer = StringBuilder() for (key in keys) { if (key.isEmpty()) { SnapshotFile.writeValue(writer, snapshot.subjectOrFacet(key)) @@ -215,8 +214,8 @@ object Selfie { } } } - buf.setLength(buf.length - 1) - return buf.toString() + writer.setLength(writer.length - 1) + return writer.toString() } } diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt index c519708b..53318fbf 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt @@ -136,10 +136,8 @@ class SnapshotFile { // this will probably become `` we'll cross that bridge when we get to it var metadata: Map.Entry? = null var snapshots = ArrayMap.empty() - fun serialize(valueWriterRaw: StringWriter) { - val valueWriter = - if (unixNewlines) valueWriterRaw - else StringWriter { valueWriterRaw.write(it.efficientReplace("\n", "\r\n")) } + fun serialize(valueWriterRaw: Appendable) { + val valueWriter = if (unixNewlines) valueWriterRaw else ConvertToWindowsNewlines(valueWriterRaw) metadata?.let { writeKey(valueWriter, "šŸ“· ${it.key}", null) writeValue(valueWriter, SnapshotValue.of(it.value)) @@ -198,17 +196,17 @@ class SnapshotFile { result.unixNewlines = unixNewlines return result } - internal fun writeKey(valueWriter: StringWriter, key: String, facet: String?) { - valueWriter.write("ā•”ā• ") - valueWriter.write(SnapshotValueReader.nameEsc.escape(key)) + internal fun writeKey(valueWriter: Appendable, key: String, facet: String?) { + valueWriter.append("ā•”ā• ") + valueWriter.append(SnapshotValueReader.nameEsc.escape(key)) if (facet != null) { - valueWriter.write("[") - valueWriter.write(SnapshotValueReader.nameEsc.escape(facet)) - valueWriter.write("]") + valueWriter.append("[") + valueWriter.append(SnapshotValueReader.nameEsc.escape(facet)) + valueWriter.append("]") } - valueWriter.write(" ā•ā•—\n") + valueWriter.append(" ā•ā•—\n") } - internal fun writeValue(valueWriter: StringWriter, value: SnapshotValue) { + internal fun writeValue(valueWriter: Appendable, value: SnapshotValue) { if (value.isBinary) { TODO("BASE64") } else { @@ -216,8 +214,8 @@ class SnapshotFile { SnapshotValueReader.bodyEsc .escape(value.valueString()) .efficientReplace("\nā•”", "\n\uD801\uDF41") - valueWriter.write(escaped) - valueWriter.write("\n") + valueWriter.append(escaped) + valueWriter.append("\n") } } } @@ -377,6 +375,22 @@ expect class LineReader { fun forBinary(content: ByteArray): LineReader } } -fun interface StringWriter { - fun write(string: String) + +internal class ConvertToWindowsNewlines(val sink: Appendable) : Appendable { + override fun append(value: Char): Appendable { + if (value != '\n') sink.append(value) + else { + sink.append('\r') + sink.append('\n') + } + return this + } + override fun append(value: CharSequence?): Appendable { + value?.let { sink.append(it.toString().efficientReplace("\n", "\r\n")) } + return this + } + override fun append(value: CharSequence?, startIndex: Int, endIndex: Int): Appendable { + value?.let { sink.append(it.substring(startIndex, endIndex).efficientReplace("\n", "\r\n")) } + return this + } } diff --git a/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotFileTest.kt b/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotFileTest.kt index 721766be..0a0a82c7 100644 --- a/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotFileTest.kt +++ b/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotFileTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 DiffPlug + * Copyright (C) 2023-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,8 +72,8 @@ class SnapshotFileTest { "Apple", Snapshot.of("Granny Smith").plusFacet("color", "green").plusFacet("crisp", "yes")) underTest.snapshots = underTest.snapshots.plus("Orange", Snapshot.of("Orange")) - val buffer = StringBuffer() - underTest.serialize { line -> buffer.append(line) } + val buffer = StringBuilder() + underTest.serialize(buffer) buffer.toString() shouldBe """ ā•”ā• šŸ“· com.acme.AcmeTest ā•ā•— diff --git a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/SelfieTestExecutionListener.kt b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/SelfieTestExecutionListener.kt index 0b1a6bf8..6c6e6722 100644 --- a/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/SelfieTestExecutionListener.kt +++ b/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/SelfieTestExecutionListener.kt @@ -177,7 +177,7 @@ internal class ClassProgress(val parent: Progress, val className: String) { parent.markPathAsWritten(parent.layout.snapshotPathForClass(className)) Files.createDirectories(snapshotPath.toPath().parent) Files.newBufferedWriter(snapshotPath.toPath(), StandardCharsets.UTF_8).use { writer -> - file!!.serialize(writer::write) + file!!.serialize(writer) } } } From 7e584447b80a3658e9446d9ab671293f936432aa Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Jan 2024 23:15:54 -0800 Subject: [PATCH 03/11] Write binary to disk. --- .../commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt index 53318fbf..0f339cff 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt @@ -15,6 +15,8 @@ */ package com.diffplug.selfie +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.jvm.JvmStatic class ParseException private constructor(val line: Int, message: String?, cause: Throwable?) : @@ -206,17 +208,19 @@ class SnapshotFile { } valueWriter.append(" ā•ā•—\n") } + + @OptIn(ExperimentalEncodingApi::class) internal fun writeValue(valueWriter: Appendable, value: SnapshotValue) { if (value.isBinary) { - TODO("BASE64") + Base64.Mime.encodeToAppendable(value.valueBinary(), valueWriter) } else { val escaped = SnapshotValueReader.bodyEsc .escape(value.valueString()) .efficientReplace("\nā•”", "\n\uD801\uDF41") valueWriter.append(escaped) - valueWriter.append("\n") } + valueWriter.append("\n") } } } From e27a6e6ace7cfa96a76749ae0819c57efd691e90 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Jan 2024 23:16:10 -0800 Subject: [PATCH 04/11] Write binary to inline snapshot. --- .../src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt index 170612cf..51fe94a2 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt @@ -25,6 +25,8 @@ import com.diffplug.selfie.guts.LiteralValue import com.diffplug.selfie.guts.SnapshotStorage import com.diffplug.selfie.guts.initStorage import com.diffplug.selfie.guts.recordCall +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic @@ -93,12 +95,14 @@ object Selfie { 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()) + + @OptIn(ExperimentalEncodingApi::class) private fun actualString(): String { 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) { - TODO("BASE64") + Base64.Mime.encode(onlyValue.valueBinary()) } else onlyValue.valueString() } else { return serializeOnlyFacets( From 5b449488998d9efcff80960060faf1f9151252a4 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Jan 2024 23:22:56 -0800 Subject: [PATCH 05/11] Fix carriage returns in inline snapshots. --- selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt index 51fe94a2..05eb7748 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt @@ -102,7 +102,7 @@ object Selfie { // single value doesn't have to worry about escaping at all val onlyValue = actual.subjectOrFacet(onlyFacets?.first() ?: "") return if (onlyValue.isBinary) { - Base64.Mime.encode(onlyValue.valueBinary()) + Base64.Mime.encode(onlyValue.valueBinary()).replace("\r", "" ) } else onlyValue.valueString() } else { return serializeOnlyFacets( From 518b589dadcccd50c3c364bdf4233e553067bea0 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 Jan 2024 23:23:02 -0800 Subject: [PATCH 06/11] Fix carriage returns on disk. --- selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt | 2 +- .../src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt index 05eb7748..e94749ab 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt @@ -102,7 +102,7 @@ object Selfie { // single value doesn't have to worry about escaping at all val onlyValue = actual.subjectOrFacet(onlyFacets?.first() ?: "") return if (onlyValue.isBinary) { - Base64.Mime.encode(onlyValue.valueBinary()).replace("\r", "" ) + Base64.Mime.encode(onlyValue.valueBinary()).replace("\r", "") } else onlyValue.valueString() } else { return serializeOnlyFacets( diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt index 0f339cff..81442a4c 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt @@ -212,7 +212,8 @@ class SnapshotFile { @OptIn(ExperimentalEncodingApi::class) internal fun writeValue(valueWriter: Appendable, value: SnapshotValue) { if (value.isBinary) { - Base64.Mime.encodeToAppendable(value.valueBinary(), valueWriter) + val escaped = Base64.Mime.encode(value.valueBinary()) + valueWriter.append(escaped.efficientReplace("\r", "")) } else { val escaped = SnapshotValueReader.bodyEsc From 102c266f6b92668019853c4b7f8070a66828f0fb Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 18 Jan 2024 00:19:08 -0800 Subject: [PATCH 07/11] Add support for parsing binary values in snapshots. --- .../com/diffplug/selfie/SnapshotFile.kt | 11 ++++++-- .../com/diffplug/selfie/SnapshotReaderTest.kt | 28 ++++++++++++++++++- .../selfie/SnapshotValueReaderTest.kt | 11 +++++++- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt index 81442a4c..d4eed8bf 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt @@ -253,7 +253,7 @@ class SnapshotReader(val valueReader: SnapshotValueReader) { val facetEndIdx = nextKey.indexOf(']', facetIdx + 1) require(facetEndIdx != -1) { "Missing ] in $nextKey" } val facetName = nextKey.substring(facetIdx + 1, facetEndIdx) - snapshot = snapshot.plusFacet(facetName, valueReader.nextValue().valueString()) + snapshot = snapshot.plusFacet(facetName, valueReader.nextValue()) } } fun skipSnapshot() { @@ -276,9 +276,11 @@ class SnapshotValueReader(val lineReader: LineReader) { } /** Reads the next value. */ + @OptIn(ExperimentalEncodingApi::class) fun nextValue(): SnapshotValue { // validate key nextKey() + val isBase64 = nextLine()!!.contains(FLAG_BASE64) resetLine() // read value @@ -292,12 +294,14 @@ class SnapshotValueReader(val lineReader: LineReader) { } buffer.append('\n') } - return SnapshotValue.of( + val stringValue = if (buffer.isEmpty()) "" else { buffer.setLength(buffer.length - 1) bodyEsc.unescape(buffer.toString()) - }) + } + return if (isBase64) SnapshotValue.of(Base64.Mime.decode(stringValue)) + else SnapshotValue.of(stringValue) } /** Same as nextValue, but faster. */ @@ -359,6 +363,7 @@ class SnapshotValueReader(val lineReader: LineReader) { private const val KEY_FIRST_CHAR = 'ā•”' private const val KEY_START = "ā•”ā• " private const val KEY_END = " ā•ā•—" + private const val FLAG_BASE64 = " ā•ā•— base64" /** * https://github.com/diffplug/selfie/blob/main/selfie-lib/src/commonTest/resources/com/diffplug/selfie/scenarios_and_lenses.ss diff --git a/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotReaderTest.kt b/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotReaderTest.kt index e9f52a33..d10921ba 100644 --- a/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotReaderTest.kt +++ b/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotReaderTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 DiffPlug + * Copyright (C) 2023-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,4 +44,30 @@ class SnapshotReaderTest { reader.nextSnapshot() shouldBe Snapshot.of("Orange") reader.peekKey() shouldBe null } + + @Test + fun binary() { + val reader = + SnapshotReader( + SnapshotValueReader.of( + """ + ā•”ā• Apple ā•ā•— + Apple + ā•”ā• Apple[color] ā•ā•— base64 length 3 bytes + c2Fk + ā•”ā• Apple[crisp] ā•ā•— + yes + ā•”ā• Orange ā•ā•— base64 length 3 bytes + c2Fk + """ + .trimIndent())) + reader.peekKey() shouldBe "Apple" + reader.peekKey() shouldBe "Apple" + reader.nextSnapshot() shouldBe + Snapshot.of("Apple").plusFacet("color", "sad".toByteArray()).plusFacet("crisp", "yes") + reader.peekKey() shouldBe "Orange" + reader.peekKey() shouldBe "Orange" + reader.nextSnapshot() shouldBe Snapshot.of("sad".toByteArray()) + reader.peekKey() shouldBe null + } } diff --git a/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotValueReaderTest.kt b/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotValueReaderTest.kt index dcd10298..785accd8 100644 --- a/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotValueReaderTest.kt +++ b/selfie-lib/src/jvmTest/kotlin/com/diffplug/selfie/SnapshotValueReaderTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 DiffPlug + * Copyright (C) 2023-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -163,4 +163,13 @@ class SnapshotValueReaderTest { reader.skipValue() } } + + @Test + fun binary() { + val reader = SnapshotValueReader.of("""ā•”ā• Apple ā•ā•— base64 length 3 bytes +c2Fk +""") + reader.peekKey() shouldBe "Apple" + reader.nextValue().valueBinary() shouldBe "sad".toByteArray() + } } From 04e59ce9bc58ab34fc579ac6caab33dea03c2186 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 18 Jan 2024 00:49:18 -0800 Subject: [PATCH 08/11] Write binary snapshots. --- .../kotlin/com/diffplug/selfie/Selfie.kt | 17 +++++---- .../com/diffplug/selfie/SnapshotFile.kt | 37 ++++++++++++------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt index e94749ab..2b904037 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Selfie.kt @@ -210,16 +210,19 @@ object Selfie { val writer = StringBuilder() for (key in keys) { if (key.isEmpty()) { - SnapshotFile.writeValue(writer, snapshot.subjectOrFacet(key)) + SnapshotFile.writeEntry(writer, "", null, snapshot.subjectOrFacet(key)) } else { - snapshot.subjectOrFacetMaybe(key)?.let { - SnapshotFile.writeKey(writer, "", key) - SnapshotFile.writeValue(writer, it) - } + snapshot.subjectOrFacetMaybe(key)?.let { SnapshotFile.writeEntry(writer, "", key, it) } } } - writer.setLength(writer.length - 1) - return writer.toString() + val EMPTY_KEY_AND_FACET = "ā•”ā• ā•ā•—\n" + return if (writer.startsWith(EMPTY_KEY_AND_FACET)) { + // this codepath is triggered by the `key.isEmpty()` line above + writer.subSequence(EMPTY_KEY_AND_FACET.length, writer.length - 1).toString() + } else { + writer.setLength(writer.length - 1) + writer.toString() + } } } diff --git a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt index d4eed8bf..5f1f53f3 100644 --- a/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt +++ b/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SnapshotFile.kt @@ -140,19 +140,14 @@ class SnapshotFile { var snapshots = ArrayMap.empty() fun serialize(valueWriterRaw: Appendable) { val valueWriter = if (unixNewlines) valueWriterRaw else ConvertToWindowsNewlines(valueWriterRaw) - metadata?.let { - writeKey(valueWriter, "šŸ“· ${it.key}", null) - writeValue(valueWriter, SnapshotValue.of(it.value)) - } + metadata?.let { writeEntry(valueWriter, "šŸ“· ${it.key}", null, SnapshotValue.of(it.value)) } snapshots.entries.forEach { entry -> - writeKey(valueWriter, entry.key, null) - writeValue(valueWriter, entry.value.subject) + writeEntry(valueWriter, entry.key, null, entry.value.subject) for (facet in entry.value.facets.entries) { - writeKey(valueWriter, entry.key, facet.key) - writeValue(valueWriter, facet.value) + writeEntry(valueWriter, entry.key, facet.key, facet.value) } } - writeKey(valueWriter, "", "end of file") + writeEntry(valueWriter, "", "end of file", SnapshotValue.of("")) } var wasSetAtTestTime: Boolean = false @@ -198,7 +193,14 @@ class SnapshotFile { result.unixNewlines = unixNewlines return result } - internal fun writeKey(valueWriter: Appendable, key: String, facet: String?) { + + @OptIn(ExperimentalEncodingApi::class) + internal fun writeEntry( + valueWriter: Appendable, + key: String, + facet: String?, + value: SnapshotValue + ) { valueWriter.append("ā•”ā• ") valueWriter.append(SnapshotValueReader.nameEsc.escape(key)) if (facet != null) { @@ -206,11 +208,18 @@ class SnapshotFile { valueWriter.append(SnapshotValueReader.nameEsc.escape(facet)) valueWriter.append("]") } - valueWriter.append(" ā•ā•—\n") - } + valueWriter.append(" ā•ā•—") + if (value.isBinary) { + valueWriter.append(" base64 length ") + valueWriter.append(value.valueBinary().size.toString()) + valueWriter.append(" bytes") + } + valueWriter.append("\n") + + if (key.isEmpty() && facet == "end of file") { + return + } - @OptIn(ExperimentalEncodingApi::class) - internal fun writeValue(valueWriter: Appendable, value: SnapshotValue) { if (value.isBinary) { val escaped = Base64.Mime.encode(value.valueBinary()) valueWriter.append(escaped.efficientReplace("\r", "")) From 523ff8006386b39301ef3e7b4065f759110ac1aa Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 18 Jan 2024 00:52:45 -0800 Subject: [PATCH 09/11] Add an end-to-end test of the base64. --- .../com/diffplug/selfie/junit5/BinaryTest.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 selfie-runner-junit5/src/test/kotlin/com/diffplug/selfie/junit5/BinaryTest.kt diff --git a/selfie-runner-junit5/src/test/kotlin/com/diffplug/selfie/junit5/BinaryTest.kt b/selfie-runner-junit5/src/test/kotlin/com/diffplug/selfie/junit5/BinaryTest.kt new file mode 100644 index 00000000..82eb0eab --- /dev/null +++ b/selfie-runner-junit5/src/test/kotlin/com/diffplug/selfie/junit5/BinaryTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 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 +import org.junit.jupiter.api.MethodOrderer +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.TestMethodOrder +import org.junitpioneer.jupiter.DisableIfTestFails + +@TestMethodOrder(MethodOrderer.OrderAnnotation::class) +@DisableIfTestFails +class BinaryTest : Harness("undertest-junit5") { + @Test @Order(1) + fun readFailsBecauseTodo() { + gradleReadSSFail() + } + + @Test @Order(2) + fun writeSucceeds() { + gradleWriteSS() + } + + @Test @Order(3) + fun nowReadSucceeds() { + gradleReadSS() + } + + @Test @Order(4) + fun cleanup() { + ut_mirror().restoreFromGit() + ut_snapshot().deleteIfExists() + } +} From cdaa255268ea5304298bc422c3e51e0fdcffcc4d Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 18 Jan 2024 00:57:51 -0800 Subject: [PATCH 10/11] Update changelog and readme now that we're 1.0-complete. --- CHANGELOG.md | 4 ++++ README.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d6fd3d..e4a3abe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Full support for binary snapshots. ([#108](https://github.com/diffplug/selfie/pull/108)) +### Fixed +- Groovy multiline string values just go into `"` strings instead of `"""` until we have a chance to implement them properly. ([#107](https://github.com/diffplug/selfie/pull/107)) ## [0.3.0] - 2024-01-17 ### Added diff --git a/README.md b/README.md index 0822ab48..153a2616 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Snapshot testing is the [fastest and most precise mechanism to record and specif Robots are writing their own code. Are you still writing assertions by hand? -- *Almost* production ready for [JVM](https://selfie.diffplug.com/jvm). +- Production ready for [JVM](https://selfie.diffplug.com/jvm). - see [the changelog](CHANGELOG.md) to see the remaining "known broken" items. - Help wanted using Kotlin Multiplatform for js and wasm (node and browser) ([#84](https://github.com/diffplug/selfie/issues/84)). - Help also wanted for non-Kotlin platforms such as python and go ([#85](https://github.com/diffplug/selfie/issues/85)). From b8ada400ac3835e09e36835612b9f4d055f4e560 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 18 Jan 2024 01:02:28 -0800 Subject: [PATCH 11/11] Missed on save. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 153a2616..b9a21688 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ Snapshot testing is the [fastest and most precise mechanism to record and specif Robots are writing their own code. Are you still writing assertions by hand? - Production ready for [JVM](https://selfie.diffplug.com/jvm). - - see [the changelog](CHANGELOG.md) to see the remaining "known broken" items. - Help wanted using Kotlin Multiplatform for js and wasm (node and browser) ([#84](https://github.com/diffplug/selfie/issues/84)). - Help also wanted for non-Kotlin platforms such as python and go ([#85](https://github.com/diffplug/selfie/issues/85)).