@@ -17,7 +17,10 @@ package com.diffplug.selfie
1717
1818import com.diffplug.selfie.guts.CallStack
1919import com.diffplug.selfie.guts.DiskStorage
20+ import com.diffplug.selfie.guts.atomic
2021import com.diffplug.selfie.guts.recordCall
22+ import com.diffplug.selfie.guts.reentrantLock
23+ import com.diffplug.selfie.guts.withLock
2124
2225private const val OPEN = " «"
2326private const val CLOSE = " »"
@@ -28,73 +31,98 @@ internal constructor(
2831 private val call: CallStack ,
2932 private val disk: DiskStorage ,
3033) : AutoCloseable {
34+ /* * Creates VCRs who store their data based on where this TestLocator was created. */
3135 class TestLocator internal constructor(private val sub : String , private val disk : DiskStorage ) {
3236 private val call = recordCall(false )
3337 fun createVcr () = VcrSelfie (sub, call, disk)
3438 }
39+ private val state: State
40+
41+ internal sealed class State {
42+ class Read (val frames : List <Pair <String , SnapshotValue >>) : State() {
43+ fun currentFrameThenAdvance (): Int = cf.getAndUpdate { it + 1 }
44+ fun framesReadSoFar (): Int = cf.get()
45+ private val cf = atomic(0 )
46+ }
3547
36- private class State (val readMode : Boolean ) {
37- var currentFrame = 0
38- val frames = mutableListOf<Pair <String , SnapshotValue >>()
48+ class Write : State () {
49+ private val lock = reentrantLock()
50+ private var frames: MutableList <Map .Entry <String , SnapshotValue >>? =
51+ mutableListOf<Map .Entry <String , SnapshotValue >>()
52+ fun add (key : String , value : SnapshotValue ) {
53+ lock.withLock {
54+ frames?.apply {
55+ val idx = size + 1
56+ add(entry(" $OPEN$idx$CLOSE$key " , value))
57+ } ? : throw IllegalStateException (" This VCR was already closed." )
58+ }
59+ }
60+ fun closeAndGetSnapshot (): Snapshot =
61+ Snapshot .ofEntries(
62+ lock.withLock {
63+ val frozen = frames ? : throw IllegalStateException (" This VCR was already closed." )
64+ frames = null
65+ frozen
66+ })
67+ }
3968 }
40- private val state: State
4169
4270 init {
4371 val canWrite = Selfie .system.mode.canWrite(isTodo = false , call, Selfie .system)
44- state = State (readMode = ! canWrite)
45- if (state.readMode) {
72+ if (canWrite) {
73+ state = State .Write ()
74+ } else {
4675 val snapshot =
4776 disk.readDisk(sub, call)
48- ? : throw Selfie .system.fs.assertFailed(Selfie .system.mode.msgSnapshotNotFound())
77+ ? : throw Selfie .system.fs.assertFailed(
78+ Selfie .system.mode.msgVcrSnapshotNotFound(call))
4979 var idx = 1
80+ val frames = mutableListOf<Pair <String , SnapshotValue >>()
5081 for ((key, value) in snapshot.facets) {
5182 check(key.startsWith(OPEN ))
5283 val nextClose = key.indexOf(CLOSE )
5384 check(nextClose != - 1 )
5485 val num = key.substring(OPEN .length, nextClose).toInt()
55- check(num == idx)
86+ check(num == idx) { " expected $idx in $key " }
5687 ++ idx
5788 val keyAfterNum = key.substring(nextClose + 1 )
58- state. frames.add(keyAfterNum to value)
89+ frames.add(keyAfterNum to value)
5990 }
91+ state = State .Read (frames)
6092 }
6193 }
6294 override fun close () {
63- if (state.readMode ) {
64- if (state.frames.size != state.currentFrame ) {
95+ if (state is State . Read ) {
96+ if (state.frames.size != state.framesReadSoFar() ) {
6597 throw Selfie .system.fs.assertFailed(
66- Selfie .system.mode.msgVcrUnread(state.frames.size, state.currentFrame ))
98+ Selfie .system.mode.msgVcrUnread(state.frames.size, state.framesReadSoFar(), call ))
6799 }
68100 } else {
69- var snapshot = Snapshot .of(" " )
70- var idx = 1
71- for ((key, value) in state.frames) {
72- snapshot = snapshot.plusFacet(" $OPEN$idx$CLOSE$key " , value)
73- }
74- disk.writeDisk(snapshot, sub, call)
101+ disk.writeDisk((state as State .Write ).closeAndGetSnapshot(), sub, call)
75102 }
76103 }
77- private fun nextFrameValue (key : String ): SnapshotValue {
104+ private fun nextFrameValue (state : State . Read , key : String ): SnapshotValue {
78105 val mode = Selfie .system.mode
79106 val fs = Selfie .system.fs
80- if (state.frames.size <= state.currentFrame) {
81- throw fs.assertFailed(mode.msgVcrUnderflow(state.frames.size))
107+ val currentFrame = state.currentFrameThenAdvance()
108+ if (state.frames.size <= currentFrame) {
109+ throw fs.assertFailed(mode.msgVcrUnderflow(state.frames.size, call), call)
82110 }
83- val expected = state.frames[state. currentFrame++ ]
111+ val expected = state.frames[currentFrame]
84112 if (expected.first != key) {
85113 throw fs.assertFailed(
86- mode.msgVcrMismatch(" $sub [$OPEN${state. currentFrame}$CLOSE ]" , expected.first, key),
114+ mode.msgVcrMismatch(" $sub [$OPEN${currentFrame}$CLOSE ]" , expected.first, key, call ),
87115 expected.first,
88116 key)
89117 }
90118 return expected.second
91119 }
92120 fun <V > nextFrame (key : String , roundtripValue : Roundtrip <V , String >, value : Cacheable <V >): V {
93- if (state.readMode ) {
94- return roundtripValue.parse(nextFrameValue(key).valueString())
121+ if (state is State . Read ) {
122+ return roundtripValue.parse(nextFrameValue(state, key).valueString())
95123 } else {
96124 val value = value.get()
97- state.frames .add(key to SnapshotValue .of(roundtripValue.serialize(value)))
125+ ( state as State . Write ) .add(key, SnapshotValue .of(roundtripValue.serialize(value)))
98126 return value
99127 }
100128 }
@@ -107,11 +135,11 @@ internal constructor(
107135 roundtripValue : Roundtrip <V , ByteArray >,
108136 value : Cacheable <V >
109137 ): V {
110- if (state.readMode ) {
111- return roundtripValue.parse(nextFrameValue(key).valueBinary())
138+ if (state is State . Read ) {
139+ return roundtripValue.parse(nextFrameValue(state, key).valueBinary())
112140 } else {
113141 val value = value.get()
114- state.frames .add(key to SnapshotValue .of(roundtripValue.serialize(value)))
142+ ( state as State . Write ) .add(key, SnapshotValue .of(roundtripValue.serialize(value)))
115143 return value
116144 }
117145 }
0 commit comments