Skip to content

Commit 07598e7

Browse files
committed
First cut.
1 parent 91b890d commit 07598e7

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2023-2024 DiffPlug
2+
* Copyright (C) 2023-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -88,4 +88,5 @@ object Selfie {
8888
@JvmStatic
8989
fun <T> cacheSelfieBinary(roundtrip: Roundtrip<T, ByteArray>, toCache: Cacheable<T>) =
9090
CacheSelfieBinary<T>(deferredDiskStorage, roundtrip, toCache)
91+
@JvmStatic fun vcrTestLocator(sub: String = "") = VcrSelfie.TestLocator(sub, deferredDiskStorage)
9192
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (C) 2025 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.selfie
17+
18+
import com.diffplug.selfie.guts.CallStack
19+
import com.diffplug.selfie.guts.DiskStorage
20+
import com.diffplug.selfie.guts.recordCall
21+
22+
private const val OPEN = "«"
23+
private const val CLOSE = "»"
24+
25+
class VcrSelfie(
26+
private val sub: String,
27+
private val call: CallStack,
28+
private val disk: DiskStorage,
29+
) : AutoCloseable {
30+
class TestLocator internal constructor(private val sub: String, private val disk: DiskStorage) {
31+
private val call = recordCall(false)
32+
fun createVcr() = VcrSelfie(sub, call, disk)
33+
}
34+
35+
private class State(val readMode: Boolean) {
36+
var count = 0
37+
val sequence = mutableListOf<Pair<String, SnapshotValue>>()
38+
}
39+
private val state: State
40+
41+
init {
42+
val canWrite = Selfie.system.mode.canWrite(isTodo = false, call, Selfie.system)
43+
state = State(readMode = !canWrite)
44+
if (state.readMode) {
45+
val snapshot =
46+
disk.readDisk(sub, call)
47+
?: throw Selfie.system.fs.assertFailed(Selfie.system.mode.msgSnapshotNotFound())
48+
var idx = 1
49+
for ((key, value) in snapshot.facets) {
50+
check(key.startsWith(OPEN))
51+
val nextClose = key.indexOf(CLOSE)
52+
check(nextClose != -1)
53+
val num = key.substring(OPEN.length, nextClose).toInt()
54+
check(num == idx)
55+
++idx
56+
val keyAfterNum = key.substring(nextClose + 1)
57+
state.sequence.add(keyAfterNum to value)
58+
}
59+
}
60+
}
61+
override fun close() {
62+
if (state.readMode) {
63+
check(state.count == state.sequence.size)
64+
} else {
65+
var snapshot = Snapshot.of("")
66+
var idx = 1
67+
for ((key, value) in state.sequence) {
68+
snapshot = snapshot.plusFacet("$OPEN$idx$CLOSE$key", value)
69+
}
70+
disk.writeDisk(snapshot, sub, call)
71+
}
72+
}
73+
fun <K, V> next(
74+
roundtripKey: Roundtrip<K, String>,
75+
key: K,
76+
roundtripValue: Roundtrip<V, String>,
77+
value: Cacheable<V>
78+
): V {
79+
if (state.readMode) {
80+
val expected = state.sequence[state.count++]
81+
val keyString = roundtripKey.serialize(key)
82+
if (expected.first != keyString) {
83+
throw Selfie.system.fs.assertFailed(
84+
"vcr key mismatch at index ${state.count - 1}", expected, keyString)
85+
}
86+
return roundtripValue.parse(expected.second.valueString())
87+
} else {
88+
val value = value.get()
89+
state.sequence.add(
90+
roundtripKey.serialize(key) to SnapshotValue.of(roundtripValue.serialize(value)))
91+
return value
92+
}
93+
}
94+
fun next(key: String, value: Cacheable<String>): String =
95+
next(Roundtrip.identity(), key, Roundtrip.identity(), value)
96+
fun <K> next(roundtripKey: Roundtrip<K, String>, key: K, value: Cacheable<String>): String =
97+
next(roundtripKey, key, Roundtrip.identity(), value)
98+
fun <V> next(key: String, roundtripValue: Roundtrip<V, String>, value: Cacheable<V>): V =
99+
next(Roundtrip.identity(), key, roundtripValue, value)
100+
inline fun <reified K, reified V> nextJson(key: K, value: Cacheable<V>): V =
101+
next(RoundtripJson.of<K>(), key, RoundtripJson.of<V>(), value)
102+
}

0 commit comments

Comments
 (0)