Skip to content
This repository was archived by the owner on Apr 4, 2024. It is now read-only.

Commit 60a48c8

Browse files
committed
Merge branch 'main' into SnapshotValueReader-Edwin
2 parents b1532bd + e6c5905 commit 60a48c8

File tree

31 files changed

+850
-151
lines changed

31 files changed

+850
-151
lines changed

jvm/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
## [Unreleased]
1414

15+
## [2.0.2] - 2024-03-20
16+
### Fixed
17+
- `toBeFile` now checks for duplicate writes and throws a more helpful error message if the file doesn't exist. ([#277](https://github.com/diffplug/selfie/pull/277))
18+
1519
## [2.0.1] - 2024-02-24
1620
### Fixed
1721
- The `coroutines` methods used to eagerly throw an exception if they were ever called from anywhere besides a Kotest method. Now they wait until `toMatchDisk()` is called, because they can work just fine anywhere if you use `toBe`. ([#247](https://github.com/diffplug/selfie/pull/247))

jvm/example-junit5/build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ repositories {
55
mavenCentral()
66
}
77
dependencies {
8-
implementation 'io.jooby:jooby:3.0.7'
9-
implementation 'io.jooby:jooby-netty:3.0.7'
10-
implementation 'jakarta.mail:jakarta.mail-api:2.1.2'
11-
testImplementation 'io.jooby:jooby-test:3.0.7'
8+
implementation 'io.jooby:jooby:3.0.9'
9+
implementation 'io.jooby:jooby-netty:3.0.9'
10+
implementation 'jakarta.mail:jakarta.mail-api:2.1.3'
11+
testImplementation 'io.jooby:jooby-test:3.0.9'
1212
testImplementation "org.junit.jupiter:junit-jupiter:$ver_JUNIT_USE"
1313
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
1414
testImplementation 'io.rest-assured:rest-assured:5.4.0'

jvm/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ org.gradle.configuration-cache=true
77
ver_JUNIT_USE=5.10.2
88
ver_JUNIT_PIONEER=2.2.0
99
ver_OKIO=3.8.0
10-
ver_KOTLIN_TEST=1.9.22
10+
ver_KOTLIN_TEST=1.9.23
1111
ver_KOTLIN_SERIALIZATION=1.6.3
1212
# Kotest 5.4.0 is the oldest that we support
1313
ver_KOTEST=5.4.0

jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/CacheSelfieBinary.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,18 @@ class CacheSelfieBinary<T>(
7474
if (isTodo) {
7575
Selfie.system.writeInline(TodoStub.toBeFile.createLiteral(), call)
7676
}
77-
Selfie.system.fs.fileWriteBinary(resolvePath(subpath), roundtrip.serialize(actual))
77+
Selfie.system.writeToBeFile(resolvePath(subpath), roundtrip.serialize(actual), call)
7878
return actual
7979
} else {
8080
if (isTodo) {
8181
throw Selfie.system.fs.assertFailed("Can't call `toBeFile_TODO` in ${Mode.readonly} mode!")
8282
} else {
83-
return roundtrip.parse(Selfie.system.fs.fileReadBinary(resolvePath(subpath)))
83+
val path = resolvePath(subpath)
84+
if (!Selfie.system.fs.fileExists(path)) {
85+
throw Selfie.system.fs.assertFailed(
86+
Selfie.system.mode.msgSnapshotNotFoundNoSuchFile(path))
87+
}
88+
return roundtrip.parse(Selfie.system.fs.fileReadBinary(path))
8489
}
8590
}
8691
}

jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/Mode.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.diffplug.selfie
1818
import com.diffplug.selfie.guts.CallStack
1919
import com.diffplug.selfie.guts.CommentTracker
2020
import com.diffplug.selfie.guts.SnapshotSystem
21+
import com.diffplug.selfie.guts.TypedPath
2122

2223
enum class Mode {
2324
interactive,
@@ -39,6 +40,8 @@ enum class Mode {
3940
overwrite -> true
4041
}
4142
internal fun msgSnapshotNotFound() = msg("Snapshot not found")
43+
internal fun msgSnapshotNotFoundNoSuchFile(file: TypedPath) =
44+
msg("Snapshot not found: no such file $file")
4245
internal fun msgSnapshotMismatch() = msg("Snapshot mismatch")
4346
private fun msg(headline: String) =
4447
when (this) {

jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/SelfieImplementations.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,18 @@ class BinarySelfie(actual: Snapshot, disk: DiskStorage, private val onlyFacet: S
132132
if (isTodo) {
133133
Selfie.system.writeInline(TodoStub.toBeFile.createLiteral(), call)
134134
}
135-
Selfie.system.fs.fileWriteBinary(resolvePath(subpath), actualBytes)
135+
Selfie.system.writeToBeFile(resolvePath(subpath), actualBytes, call)
136136
return actualBytes
137137
} else {
138138
if (isTodo) {
139139
throw Selfie.system.fs.assertFailed("Can't call `toBeFile_TODO` in ${Mode.readonly} mode!")
140140
} else {
141-
val expected = Selfie.system.fs.fileReadBinary(resolvePath(subpath))
141+
val path = resolvePath(subpath)
142+
if (!Selfie.system.fs.fileExists(path)) {
143+
throw Selfie.system.fs.assertFailed(
144+
Selfie.system.mode.msgSnapshotNotFoundNoSuchFile(path))
145+
}
146+
val expected = Selfie.system.fs.fileReadBinary(path)
142147
if (expected.contentEquals(actualBytes)) {
143148
return actualBytes
144149
} else {

jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/coroutines/CacheSelfieBinarySuspend.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,18 @@ class CacheSelfieBinarySuspend<T>(
7878
if (isTodo) {
7979
Selfie.system.writeInline(TodoStub.toBeFile.createLiteral(), call)
8080
}
81-
Selfie.system.fs.fileWriteBinary(resolvePath(subpath), roundtrip.serialize(actual))
81+
Selfie.system.writeToBeFile(resolvePath(subpath), roundtrip.serialize(actual), call)
8282
return actual
8383
} else {
8484
if (isTodo) {
8585
throw Selfie.system.fs.assertFailed("Can't call `toBeFile_TODO` in ${Mode.readonly} mode!")
8686
} else {
87-
return roundtrip.parse(Selfie.system.fs.fileReadBinary(resolvePath(subpath)))
87+
val path = resolvePath(subpath)
88+
if (!Selfie.system.fs.fileExists(path)) {
89+
throw Selfie.system.fs.assertFailed(
90+
Selfie.system.mode.msgSnapshotNotFoundNoSuchFile(path))
91+
}
92+
return roundtrip.parse(Selfie.system.fs.fileReadBinary(path))
8893
}
8994
}
9095
}

jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/SnapshotSystem.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ data class TypedPath(val absolutePath: String) : Comparable<TypedPath> {
8080
}
8181

8282
interface FS {
83+
/**
84+
* Returns true if the given path exists *and is a file*, false if it doesn't or if it is a
85+
* folder.
86+
*/
87+
fun fileExists(typedPath: TypedPath): Boolean
8388
/** Walks the files (not directories) which are children and grandchildren of the given path. */
8489
fun <T> fileWalk(typedPath: TypedPath, walk: (Sequence<TypedPath>) -> T): T
8590
fun fileRead(typedPath: TypedPath) = fileReadBinary(typedPath).decodeToString()
@@ -100,6 +105,8 @@ interface SnapshotSystem {
100105
fun sourceFileHasWritableComment(call: CallStack): Boolean
101106
/** Indicates that the following value should be written into test sourcecode. */
102107
fun writeInline(literalValue: LiteralValue<*>, call: CallStack)
108+
/** Writes the given bytes to the given file, checking for duplicate writes. */
109+
fun writeToBeFile(path: TypedPath, data: ByteArray, call: CallStack)
103110
/** Returns the DiskStorage for the test associated with this thread, else error. */
104111
fun diskThreadLocal(): DiskStorage
105112
}

jvm/selfie-lib/src/commonMain/kotlin/com/diffplug/selfie/guts/WriteTracker.kt

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ sealed class WriteTracker<K : Comparable<K>, V> {
8080
expectSelfie(underTest).toBe("bash$")
8181
}
8282
"""
83-
.trimIndent()
83+
is ToBeFileWriteTracker ->
84+
"You can fix this with `.toBeFile(String filename)` and pass a unique filename for each code path."
8485
}
8586
if (existing.snapshot != snapshot) {
8687
throw layout.fs.assertFailed(
@@ -102,6 +103,46 @@ class DiskWriteTracker : WriteTracker<String, Snapshot>() {
102103
}
103104
}
104105

106+
class ToBeFileWriteTracker : WriteTracker<TypedPath, ToBeFileLazyBytes>() {
107+
fun writeToDisk(
108+
key: TypedPath,
109+
snapshot: ByteArray,
110+
call: CallStack,
111+
layout: SnapshotFileLayout
112+
) {
113+
val lazyBytes = ToBeFileLazyBytes(key, layout, snapshot)
114+
recordInternal(key, lazyBytes, call, layout)
115+
// recordInternal will throw an exception on a duplicate write, so we can safely write to disk
116+
lazyBytes.writeToDisk()
117+
// and because we are doing duplicate checks, `ToBeFileLazyBytes` can allow its in-memory
118+
// data to be garbage collected, because it can safely read from disk in the future
119+
}
120+
}
121+
122+
class ToBeFileLazyBytes(val location: TypedPath, val layout: SnapshotFileLayout, data: ByteArray) {
123+
/** When constructed, we always have the data. */
124+
var data: ByteArray? = data
125+
/**
126+
* Shortly after being construted, this data is written to disk, and we can stop holding it in
127+
* memory.
128+
*/
129+
internal fun writeToDisk() {
130+
data?.let { layout.fs.fileWriteBinary(location, it) }
131+
?: throw IllegalStateException("Data has already been written to disk!")
132+
data = null
133+
}
134+
/**
135+
* If we need to read our data, we do it from memory if it's still there, or from disk if it
136+
* isn't.
137+
*/
138+
private fun readData(): ByteArray = data ?: layout.fs.fileReadBinary(location)
139+
/** We calculate equality based on this data. */
140+
override fun equals(other: Any?): Boolean =
141+
if (this === other) true
142+
else if (other is ToBeFileLazyBytes) readData().contentEquals(other.readData()) else false
143+
override fun hashCode(): Int = readData().contentHashCode()
144+
}
145+
105146
class InlineWriteTracker : WriteTracker<CallLocation, LiteralValue<*>>() {
106147
fun record(call: CallStack, literalValue: LiteralValue<*>, layout: SnapshotFileLayout) {
107148
recordInternal(call.location, literalValue, call, layout)

jvm/selfie-runner-junit5/src/main/kotlin/com/diffplug/selfie/junit5/SnapshotSystemJUnit5.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.diffplug.selfie.guts.LiteralValue
2626
import com.diffplug.selfie.guts.SnapshotFileLayout
2727
import com.diffplug.selfie.guts.SnapshotSystem
2828
import com.diffplug.selfie.guts.SourceFile
29+
import com.diffplug.selfie.guts.ToBeFileWriteTracker
2930
import com.diffplug.selfie.guts.TypedPath
3031
import com.diffplug.selfie.guts.WithinTestGC
3132
import com.diffplug.selfie.guts.atomic
@@ -41,6 +42,7 @@ import org.opentest4j.AssertionFailedError
4142
internal fun TypedPath.toPath(): java.nio.file.Path = java.nio.file.Path.of(absolutePath)
4243

4344
internal object FSJava : FS {
45+
override fun fileExists(typedPath: TypedPath): Boolean = Files.isRegularFile(typedPath.toPath())
4446
override fun fileWriteBinary(typedPath: TypedPath, content: ByteArray) =
4547
typedPath.toPath().writeBytes(content)
4648
override fun fileReadBinary(typedPath: TypedPath) = typedPath.toPath().readBytes()
@@ -65,6 +67,7 @@ internal object SnapshotSystemJUnit5 : SnapshotSystem {
6567
override val layout = SnapshotFileLayoutJUnit5(SelfieSettingsAPI.initialize(), fs)
6668
private val commentTracker = CommentTracker()
6769
private val inlineWriteTracker = InlineWriteTracker()
70+
private val toBeFileWriteTracker = ToBeFileWriteTracker()
6871
private val progressPerClass = atomic(ArrayMap.empty<String, SnapshotFileProgress>())
6972
fun forClass(className: String): SnapshotFileProgress {
7073
// optimize for reads
@@ -90,6 +93,9 @@ internal object SnapshotSystemJUnit5 : SnapshotSystem {
9093
override fun writeInline(literalValue: LiteralValue<*>, call: CallStack) {
9194
inlineWriteTracker.record(call, literalValue, layout)
9295
}
96+
override fun writeToBeFile(path: TypedPath, data: ByteArray, call: CallStack) {
97+
toBeFileWriteTracker.writeToDisk(path, data, call, layout)
98+
}
9399
internal val testListenerRunning = AtomicBoolean(false)
94100
fun finishedAllTests() {
95101
val snapshotsFilesWrittenToDisk =

0 commit comments

Comments
 (0)