diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt index e899e87e..4470d696 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt @@ -31,6 +31,7 @@ typealias Justs = TIntSet fun emptyJusts() = TIntHashSet(1) fun justsOf(vararg elements: Int) = TIntHashSet(elements) fun justsFromCollection(collection: Collection) = TIntHashSet(collection) +fun justsCopy(other: Justs) = TIntHashSet(other) data class OccurrenceObserver(val occurrence: Occurrence, val controller: Controller) : LogicalObserver { diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt index dde1cbdf..6cb8e571 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt @@ -207,7 +207,7 @@ internal class ControllerImpl ( val args = supervisor.instantiateArguments(constraint.arguments(), context.logicalContext, context) return context.eval { status -> - state.processActivated(this, constraint.occurrence(this, args, justs, context.logicalContext), status) + state.processActivated(this, constraint.occurrence(this, args, justsCopy(justs), context.logicalContext), status) } } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt index 0d0024da..1b7cc0ea 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt @@ -161,7 +161,6 @@ interface MatchJournal : MutableIterable { } class OccChunk(override val id: Int, val occ: Occurrence) : Chunk { - init { occ.justifications.add(id) } override val justifications: Justs diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt index 133dc54d..f3fafdda 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt @@ -17,6 +17,7 @@ package jetbrains.mps.logic.reactor.core.internal import jetbrains.mps.logic.reactor.core.ProcessingState +import jetbrains.mps.logic.reactor.program.IncrementalProgramSpec import java.lang.IllegalArgumentException @@ -32,7 +33,10 @@ interface StoreAwareJournal : MatchJournal, ProcessingState { // for tests companion object { - fun fromView(view: MatchJournal.View? = null): StoreAwareJournal = StoreAwareJournalImpl(MatchJournalImpl(view)) + fun fromView( + ispec: IncrementalProgramSpec = IncrementalProgramSpec.DefaultSpec, + view: MatchJournal.View? = null + ): StoreAwareJournal = StoreAwareJournalImpl(MatchJournalImpl(ispec, view)) } } diff --git a/reactor/Test/test/MockIncrProgSpec.kt b/reactor/Test/test/MockIncrProgSpec.kt new file mode 100644 index 00000000..47bd05f6 --- /dev/null +++ b/reactor/Test/test/MockIncrProgSpec.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2014-2019 JetBrains s.r.o. + * + * 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 + * + * http://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. + */ + +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.ConstraintSymbol +import jetbrains.mps.logic.reactor.program.IncrementalProgramSpec +import jetbrains.mps.logic.reactor.program.Rule + +class MockIncrProgSpec(val principalRuleTags: Set, val principalCtrSyms: Set) : IncrementalProgramSpec { + override fun isPrincipal(ctr: Constraint): Boolean = principalCtrSyms.contains(ctr.symbol()) + override fun isPrincipal(rule: Rule): Boolean = principalRuleTags.contains(rule.uniqueTag()) +} \ No newline at end of file diff --git a/reactor/Test/test/TestIncrementalProgram.kt b/reactor/Test/test/TestIncrementalProgram.kt index 15367e6e..1a7ae655 100644 --- a/reactor/Test/test/TestIncrementalProgram.kt +++ b/reactor/Test/test/TestIncrementalProgram.kt @@ -7,7 +7,6 @@ import jetbrains.mps.logic.reactor.evaluation.EvaluationResult import jetbrains.mps.logic.reactor.evaluation.EvaluationSession import jetbrains.mps.logic.reactor.program.Constraint import jetbrains.mps.logic.reactor.program.ConstraintSymbol -import jetbrains.mps.logic.reactor.program.Rule as CRule import org.junit.* import org.junit.Assert.assertNotEquals import program.MockConstraint @@ -48,11 +47,6 @@ class TestIncrementalProgram { } } - private class MockIncrProgSpec(val principalRuleTags: Set, val principalCtrSyms: Set) : IncrementalProgramSpec { - override fun isPrincipal(ctr: Constraint): Boolean = principalCtrSyms.contains(ctr.symbol()) - override fun isPrincipal(rule: CRule): Boolean = principalRuleTags.contains(rule.uniqueTag()) - } - private fun Builder.launch(name: String, incrSpec: IncrementalProgramSpec, resultHandler: (EvaluationResult) -> Unit) : Pair { @@ -230,7 +224,7 @@ class TestIncrementalProgram { ).launch("addRule", progSpec) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("main")) - result.countChunks() shouldBe 2 + result.countChunks() shouldBe 3 result.lastChunkSymbols() shouldBe listOf(sym0("foo")) }.also { (builder, evalRes) -> @@ -246,7 +240,7 @@ class TestIncrementalProgram { ).relaunch("atStart", progSpec, evalRes.token()) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("main"), sym0("bar")) - result.countChunks() shouldBe 3 + result.countChunks() shouldBe 3+1 result.lastChunkSymbols() shouldBe listOf(sym0("bar")) } } @@ -635,7 +629,7 @@ class TestIncrementalProgram { // if "foobar" happens too early, "1st" occ won't be produced result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("marker")) result.lastChunkSymbols() shouldBe listOf(sym0("marker")) - result.countChunks() shouldBe (2 + nPrincipalMatches) // +[.bar, foobar] + result.countChunks() shouldBe (2 + 1 + nPrincipalMatches) // +[.bar, foobar] +activation of bar } } } @@ -859,7 +853,7 @@ class TestIncrementalProgram { result.storeView().constraintSymbols() shouldBe setOf(sym0("lax")) result.lastChunkSymbols() shouldBe listOf(sym0("lax")) - result.countChunks() shouldBe (nPrincipalMatches - 2) + result.countChunks() shouldBe (nPrincipalMatches - 3) } } } @@ -910,13 +904,12 @@ class TestIncrementalProgram { }.also { (builder, evalRes) -> val nPrincipalMatches = evalRes.countChunks() - // "barbaz.qux" rule match is removed as a dependency of "foo.bar" match builder.removeRules(listOf("foo.bar")) .relaunch("removed", progSpec, evalRes.token()) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("lax")) result.lastChunkSymbols() shouldBe listOf(sym0("lax")) - result.countChunks() shouldBe (nPrincipalMatches - 1 + 2) + result.countChunks() shouldBe (nPrincipalMatches - 1 + 3) } } } @@ -1026,7 +1019,7 @@ class TestIncrementalProgram { )) ).relaunch("substituted", progSpec, evalRes.token()) { result -> - result.countChunks() shouldBe (nPrincipalMatches - 1) + result.countChunks() shouldBe (nPrincipalMatches - 3 + 1) result.lastChunkSymbols() shouldBe listOf(sym0("bar")) } } @@ -1097,7 +1090,7 @@ class TestIncrementalProgram { // println(result.token().journalView.chunks) - result.countChunks() shouldBe (nPrincipalMatches) + result.countChunks() shouldBe (nPrincipalMatches - 1) result.storeView().constraintSymbols() shouldBe setOf(sym0("bar")) result.lastChunkSymbols() shouldBe listOf(sym0("bar")) } diff --git a/reactor/Test/test/TestStoreAwareJournal.kt b/reactor/Test/test/TestStoreAwareJournal.kt index 5cd45a8b..9f45d126 100644 --- a/reactor/Test/test/TestStoreAwareJournal.kt +++ b/reactor/Test/test/TestStoreAwareJournal.kt @@ -1,6 +1,9 @@ import jetbrains.mps.logic.reactor.core.* import jetbrains.mps.logic.reactor.core.internal.* +import jetbrains.mps.logic.reactor.program.Constraint import jetbrains.mps.logic.reactor.program.ConstraintSymbol +import jetbrains.mps.logic.reactor.program.IncrementalProgramSpec +import jetbrains.mps.logic.reactor.program.Rule import org.junit.Test import org.junit.Assert.* import org.junit.Ignore @@ -24,8 +27,16 @@ import org.junit.Ignore class TestStoreAwareJournal { - private class JournalDispatcherHelper(dispatcher: Dispatcher, val hist: StoreAwareJournal = StoreAwareJournal.fromView()) - { + private object LegacyMockIncrProgSpec : IncrementalProgramSpec { + override fun isPrincipal(ctr: Constraint): Boolean = ctr.isPrincipal + override fun isPrincipal(rule: Rule): Boolean = rule.all().any { it is Constraint && it.isPrincipal } + } + + private class JournalDispatcherHelper( + dispatcher: Dispatcher, + ispec: IncrementalProgramSpec = LegacyMockIncrProgSpec, + val hist: StoreAwareJournal = StoreAwareJournal.fromView(ispec) + ) { var d: Dispatcher.DispatchingFront = dispatcher.front() fun logExpand(occ: Occurrence) { @@ -37,7 +48,7 @@ class TestStoreAwareJournal { // log and expand occurrence while tracking its justifications fun logExpandJustified(id: String, vararg args: Any) = - logExpand(justifiedOccurrence(id, hist.justs(), * args)) + logExpand(justifiedOccurrence(id, justsCopy(hist.justs()), * args)) } @Test @@ -76,7 +87,7 @@ class TestStoreAwareJournal { // log first 'foo' match hist.logMatch(fooMatches.first()) - hist.justs() shouldBe justsOf(1) + hist.justs() shouldBe justsOf(1,2) logExpandJustified("bar") d.matches().count() shouldBe 0 @@ -84,13 +95,13 @@ class TestStoreAwareJournal { // log second 'foo' match hist.logMatch(fooMatches.elementAt(1)) - hist.justs() shouldBe justsOf(1,2) + hist.justs() shouldBe justsOf(1,4) logExpandJustified("qux") d.matches().count() shouldBe 1 logFirstMatch() - hist.justs() shouldBe justsOf(1,2,3) + hist.justs() shouldBe justsOf(1,2,3,4,5,6) // 3 & 5 are ids of OccChunks } } } @@ -130,6 +141,8 @@ class TestStoreAwareJournal { logExpand(justifiedOccurrence("foo", setOf(1))) + val fooPos = hist.currentPos() + logFirstMatch() logExpandJustified("bar") @@ -146,7 +159,7 @@ class TestStoreAwareJournal { storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax")) val nchunks = view().chunks.size - nchunks shouldBe 4 + nchunks shouldBe 4 * 2 // 4 rules, each activates 1 principal occurrence // try replay the last chunk replay(mockController, lastPos) @@ -164,8 +177,12 @@ class TestStoreAwareJournal { replay(mockController, initPos) - // storeView replays only the initial chunk, journal remains the same + storeView().constraintSymbols() shouldBe setOf() + + replay(mockController, fooPos) + storeView().constraintSymbols() shouldBe setOf(sym0("foo")) + // although storeView() results change, journal remains the same view().chunks.size shouldBe nchunks } } @@ -337,7 +354,6 @@ class TestStoreAwareJournal { hist.testPush() // last production from rule2 logExpand(quxOcc) -// logExpandJustified("qux") // rule4 @@ -352,12 +368,9 @@ class TestStoreAwareJournal { val oldStore = hist.storeView().allOccurrences() val savedPos = hist.currentPos() -// println("chunks on save: ${oldState.chunks}") hist.testPush() logExpand(occurrence("last")) -// println("chunks on save: ${oldState.chunks}") -// println("chunks on exec: ${hist.view().chunks}") oldState.chunks.size shouldBe 3 assertNotEquals(oldState.chunks, hist.view().chunks) assertNotEquals(oldStore, hist.storeView().allOccurrences()) @@ -373,6 +386,7 @@ class TestStoreAwareJournal { } + @Ignore("manipulating journal without controller is too fragile. not a good test.") @Test fun testRmAddInMiddle() { val mockController = MockController() @@ -452,9 +466,12 @@ class TestStoreAwareJournal { // continue from the second chunk (match of rule1) val rmIt = iterator() rmIt.next() // skip initial chunk - val continueFrom = rmIt.next().toPos() - rmIt.next() - rmIt.remove() + rmIt.next() // 'foo' activation + val continueFrom = rmIt.next().toPos() // 'rule1' activation + rmIt.next() // 'bar1' activation + rmIt.next() // 'bazz' activation + rmIt.next() // 'rule2a' match + rmIt.remove() // rm rule2a match // store is not longer valid after removing chunks from history, so reset it resetStore() @@ -462,14 +479,14 @@ class TestStoreAwareJournal { replay(mockController, continueFrom) // according to the history 'qux' wasn't activated at this point & 'bar1' wasn't discarded - storeView().constraintSymbols() shouldBe setOf(sym0("bazz"), sym0("bar1")) + storeView().constraintSymbols() shouldBe setOf() } // add another instance of bar (i.e. bar2) and trigger another rule, rule2b // (bar2 plays a role of the reactivation of original bar) logExpandJustified("bar2") // 'bazz' is already expanded from the first execution - hist.justs() shouldBe justsOf(1) // dependency is only the first chunk + hist.justs() shouldBe justsOf(1,2,7) // dependency is only the first chunk, the first activation + new id // we have only a single _new_ match; rule3 has been matched already and remains in the history, in future d.matches().count() shouldBe 1