Ensure MatchJournalImpl.replay is idempotent operation, add test for it
Add another test for replaying inside Chunk. Doesn't pass for now for the lack of machinery for tracking precise Pos.
This commit is contained in:
parent
5c4b1f8263
commit
81721a0067
|
|
@ -88,13 +88,6 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk>, EvidenceSource {
|
|||
*/
|
||||
fun replay(observable: LogicalStateObservable, futurePos: Pos)
|
||||
|
||||
/**
|
||||
* Same as [replay] but stops at the last descendant of [ancestor] if [until] isn't found before that.
|
||||
* Suitable for "restricted" replay to [Pos] that can be already invalid (i.e. not in [MatchJournal]).
|
||||
*/
|
||||
fun replayDescendants(observable: LogicalStateObservable, ancestor: Chunk, until: Pos)
|
||||
|
||||
|
||||
/**
|
||||
* Returns snapshot of the journal.
|
||||
*/
|
||||
|
|
@ -191,7 +184,7 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk>, EvidenceSource {
|
|||
|
||||
override var entries: MutableList<Chunk.Entry> = mutableListOf()
|
||||
|
||||
override fun toString() = "(id=$evidence, ${justifications()}, activation of $occ)"
|
||||
override fun toString() = "(id=$evidence, ${justifications()}, activation of $occ, $entries)"
|
||||
}
|
||||
|
||||
open class Pos(val chunk: Chunk, val entriesCount: Int) {
|
||||
|
|
|
|||
|
|
@ -120,6 +120,10 @@ internal open class MatchJournalImpl(
|
|||
|
||||
override fun isFront(): Boolean = current == hist.last
|
||||
|
||||
|
||||
override fun reset(pastPos: MatchJournal.Pos) = resetPos(pastPos, true)
|
||||
|
||||
// override fun resetPos() = resetPos(Pos(initialChunk(), 0), false)
|
||||
override fun resetPos() {
|
||||
// walk backwards and reset all occurrences
|
||||
posPtr = hist.listIterator(hist.size)
|
||||
|
|
@ -129,56 +133,38 @@ internal open class MatchJournalImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun reset(pastPos: MatchJournal.Pos) {
|
||||
// walk backwards, reset occurrences and remove history from posPtr down to pastPos
|
||||
private fun resetPos(pastPos: MatchJournal.Pos, drop: Boolean) {
|
||||
while (posPtr.hasPrevious()) {
|
||||
current = posPtr.previous()
|
||||
if (current === pastPos.chunk) {
|
||||
// todo: need reversed?
|
||||
resetOccurrences(current.entries.drop(pastPos.entriesCount))
|
||||
current.entries = current.entries.subList(0, pastPos.entriesCount)
|
||||
if (drop) current.entries = current.entries.subList(0, pastPos.entriesCount)
|
||||
posPtr.next() // make 'posPtr' to always point right after 'current'
|
||||
return
|
||||
}
|
||||
// todo: need reversed?
|
||||
resetOccurrences(current.entries)
|
||||
posPtr.remove()
|
||||
if (drop) posPtr.remove()
|
||||
}
|
||||
if (currentPos() != pastPos) throw IllegalStateException()
|
||||
}
|
||||
|
||||
override fun replay(observable: LogicalStateObservable, futurePos: MatchJournal.Pos) {
|
||||
while (posPtr.hasNext()) {
|
||||
current = posPtr.next()
|
||||
override fun replay(observable: LogicalStateObservable, futurePos: MatchJournal.Pos) = replayWith(futurePos, {})
|
||||
|
||||
private fun replayWith(futurePos: MatchJournal.Pos, action: (Chunk) -> Unit) {
|
||||
do {
|
||||
if (futurePos.chunk === current) {
|
||||
replayOccurrences(observable, current.entries.take(futurePos.entriesCount))
|
||||
replayOccurrences(current.entries.take(futurePos.entriesCount))
|
||||
action(current)
|
||||
return
|
||||
}
|
||||
replayOccurrences(observable, current.entries)
|
||||
}
|
||||
if (currentPos() != futurePos) throw IllegalStateException()
|
||||
}
|
||||
replayOccurrences(current.entries)
|
||||
action(current)
|
||||
|
||||
override fun replayDescendants(observable: LogicalStateObservable, ancestor: Chunk, until: MatchJournal.Pos) {
|
||||
while (posPtr.hasNext()) {
|
||||
val previous = current
|
||||
if (!posPtr.hasNext()) break
|
||||
current = posPtr.next()
|
||||
} while (true)
|
||||
|
||||
if (until.chunk === current) {
|
||||
replayOccurrences(observable, current.entries.take(until.entriesCount))
|
||||
return
|
||||
}
|
||||
|
||||
if (previous.isDescendantOf(ancestor) && !current.isDescendantOf(ancestor)) {
|
||||
// reset ptr so that it points after previous chunk
|
||||
current = previous
|
||||
posPtr.previous()
|
||||
return
|
||||
}
|
||||
|
||||
replayOccurrences(observable, current.entries)
|
||||
}
|
||||
if (currentPos() != until) throw IllegalStateException()
|
||||
if (currentPos() != futurePos) throw IllegalStateException()
|
||||
}
|
||||
|
||||
override fun dropDescendantsWhile(ancestor: Chunk, dropIf: (Chunk) -> Boolean) {
|
||||
|
|
@ -211,7 +197,8 @@ internal open class MatchJournalImpl(
|
|||
if (posPtr.hasNext()) posPtr.next()
|
||||
}
|
||||
|
||||
private fun resetOccurrences(occSpecs: Iterable<MatchJournal.Chunk.Entry>) =
|
||||
private fun resetOccurrences(occSpecs: Iterable<Chunk.Entry>) =
|
||||
// todo: need iterating over reversed list?
|
||||
occSpecs.forEach {
|
||||
if (it.discarded) {
|
||||
it.occ.alive = true
|
||||
|
|
@ -222,7 +209,7 @@ internal open class MatchJournalImpl(
|
|||
}
|
||||
}
|
||||
|
||||
private fun replayOccurrences(observable: LogicalStateObservable, occSpecs: Iterable<MatchJournal.Chunk.Entry>) =
|
||||
private fun replayOccurrences(occSpecs: Iterable<Chunk.Entry>) =
|
||||
occSpecs.forEach {
|
||||
if (it.discarded) {
|
||||
// it.occ.terminate(observable)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ class TestStoreAwareJournal {
|
|||
d = d.expand(occ)
|
||||
}
|
||||
|
||||
fun logExpand(id: String, vararg args: Any) =
|
||||
logExpand(occurrence(id, * args))
|
||||
|
||||
fun logFirstMatch() = hist.logMatch(d.matches().first())
|
||||
|
||||
// log and expand occurrence while tracking its justifications
|
||||
|
|
@ -189,6 +192,182 @@ class TestStoreAwareJournal {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReplayToSamePosTwice() {
|
||||
val mockController = MockController()
|
||||
with(programWithRules(
|
||||
rule("rule1",
|
||||
headKept(
|
||||
pconstraint("foo")
|
||||
),
|
||||
body(
|
||||
pconstraint("bar")
|
||||
)),
|
||||
rule("rule2",
|
||||
headReplaced(
|
||||
pconstraint("bar"),
|
||||
pconstraint("foo")
|
||||
),
|
||||
body(
|
||||
pconstraint("qux")
|
||||
)),
|
||||
rule("rule3",
|
||||
headKept(
|
||||
pconstraint("qux")
|
||||
),
|
||||
body(
|
||||
pconstraint("lax")
|
||||
))
|
||||
))
|
||||
{
|
||||
with(JournalDispatcherHelper(Dispatcher(RuleIndex(rulesLists)))) {
|
||||
|
||||
val initPos = hist.currentPos()
|
||||
|
||||
logExpand(justifiedOccurrenceInit("foo"))
|
||||
|
||||
val fooPos = hist.currentPos()
|
||||
|
||||
logFirstMatch()
|
||||
logExpandJustified("bar")
|
||||
|
||||
logFirstMatch()
|
||||
logExpandJustified("qux")
|
||||
|
||||
val quxPos = hist.currentPos()
|
||||
|
||||
logFirstMatch()
|
||||
logExpandJustified("lax")
|
||||
|
||||
val lastPos = hist.currentPos()
|
||||
|
||||
// 'replay' to the saved pos after full 'resetStore' must restore the store
|
||||
with(hist) {
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax"))
|
||||
|
||||
resetStore()
|
||||
|
||||
|
||||
replay(mockController.logicalStateObservable(), fooPos)
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("foo"))
|
||||
|
||||
// second replay to the same position, nothing should change
|
||||
replay(mockController.logicalStateObservable(), fooPos)
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("foo"))
|
||||
|
||||
|
||||
replay(mockController.logicalStateObservable(), quxPos)
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("qux"))
|
||||
|
||||
// second replay to the same position, nothing should change
|
||||
replay(mockController.logicalStateObservable(), quxPos)
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("qux"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Journal in current state doesn't track precise position inside Chunk")
|
||||
fun testReplayInsideChunk() {
|
||||
val mockController = MockController()
|
||||
with(programWithRules(
|
||||
rule("rule1",
|
||||
headKept(
|
||||
pconstraint("foo")
|
||||
),
|
||||
body(
|
||||
pconstraint("bar")
|
||||
)),
|
||||
rule("rule2",
|
||||
headReplaced(
|
||||
pconstraint("bar"),
|
||||
pconstraint("foo")
|
||||
),
|
||||
body(
|
||||
constraint("lax"),
|
||||
constraint("qux"),
|
||||
constraint("shwux"),
|
||||
constraint("kex"),
|
||||
pconstraint("buzz")
|
||||
|
||||
)),
|
||||
rule("rule3",
|
||||
headKept(
|
||||
constraint("qux")
|
||||
),
|
||||
headReplaced(
|
||||
constraint("lax"),
|
||||
constraint("shwux")
|
||||
),
|
||||
body(
|
||||
))
|
||||
))
|
||||
{
|
||||
with(JournalDispatcherHelper(Dispatcher(RuleIndex(rulesLists)))) {
|
||||
|
||||
val initPos = hist.currentPos()
|
||||
|
||||
logExpand(justifiedOccurrenceInit("foo"))
|
||||
val fooPos = hist.currentPos()
|
||||
|
||||
logFirstMatch()
|
||||
logExpandJustified("bar")
|
||||
|
||||
logFirstMatch()
|
||||
logExpand("lax")
|
||||
logExpand("qux")
|
||||
val quxPos = hist.currentPos()
|
||||
logExpand("shwux")
|
||||
val shwuxPos = hist.currentPos()
|
||||
|
||||
logFirstMatch()
|
||||
logExpand("kex")
|
||||
val kexPos = hist.currentPos()
|
||||
logExpandJustified("buzz")
|
||||
|
||||
val lastPos = hist.currentPos()
|
||||
|
||||
// 'replay' to the saved pos after full 'resetStore' must restore the store
|
||||
with(hist) {
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("kex"), sym0("buzz"))
|
||||
|
||||
resetStore()
|
||||
|
||||
|
||||
replay(mockController.logicalStateObservable(), quxPos)
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax"))
|
||||
|
||||
// second replay to the same position inside chunk, nothing should change
|
||||
replay(mockController.logicalStateObservable(), quxPos)
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax"))
|
||||
|
||||
|
||||
replay(mockController.logicalStateObservable(), shwuxPos)
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax"), sym0("shwux"))
|
||||
|
||||
// replay inside chunk when non principal-match happenned
|
||||
replay(mockController.logicalStateObservable(), kexPos)
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("kex"))
|
||||
|
||||
|
||||
replay(mockController.logicalStateObservable(), lastPos)
|
||||
|
||||
storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("kex"), sym0("buzz"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testResetStoreThenReplay() {
|
||||
|
|
@ -272,7 +451,7 @@ class TestStoreAwareJournal {
|
|||
// execute program
|
||||
|
||||
hist.testPush()
|
||||
logExpand(occurrence("foo"))
|
||||
logExpand("foo")
|
||||
|
||||
logFirstMatch()
|
||||
// provide initial justification
|
||||
|
|
@ -334,8 +513,8 @@ class TestStoreAwareJournal {
|
|||
|
||||
// rule2
|
||||
logFirstMatch()
|
||||
logExpand(occurrence("bar"))
|
||||
logExpand(occurrence("bazz"))
|
||||
logExpand("bar")
|
||||
logExpand("bazz")
|
||||
// use this production later, but create it now to get relevant justs
|
||||
val quxOcc = justifiedOccurrence("qux", hist.nextEvidence(), hist.justifications())
|
||||
|
||||
|
|
@ -343,7 +522,7 @@ class TestStoreAwareJournal {
|
|||
val curChunk = hist.currentPos().chunk
|
||||
// rule3
|
||||
logFirstMatch()
|
||||
logExpand(occurrence("bazz"))
|
||||
logExpand("bazz")
|
||||
// matched on rule with heads without justifications, should remain in the same chunk
|
||||
hist.currentPos().chunk shouldBeSame curChunk
|
||||
|
||||
|
|
@ -451,7 +630,7 @@ class TestStoreAwareJournal {
|
|||
|
||||
// rule3, this rule match will remain in history untouched
|
||||
hist.logMatch(rule1matches.last())
|
||||
logExpand(occurrence("qux"))
|
||||
logExpand("qux")
|
||||
|
||||
// execution ended
|
||||
|
||||
|
|
@ -489,7 +668,7 @@ class TestStoreAwareJournal {
|
|||
d.matches().count() shouldBe 1
|
||||
// rule2b, this rule match is added at the place of rule2a match
|
||||
logFirstMatch()
|
||||
logExpand(occurrence("marker"))
|
||||
logExpand("marker")
|
||||
|
||||
// reexecution ended
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue