Closed gusbicalho closed 2 years ago
I just adapted my example to use the current version from main (commit 21337e45f710a88de7760e3d4b51458969d6eb63), and everything seem to be working now! New code below:
module Specs.SimpleCounterSpec where
import Data.Either qualified as Either
import Data.Hashable (Hashable)
import GHC.Generics (Generic)
import Language.Spectacle (
ActionType (ActionWF),
Fairness (WeakFair),
Modality (Eventually, Stays),
Specification (..),
Temporal,
TemporalType (PropF, PropFG),
modelcheck,
plain,
(.=),
pattern ConF,
pattern NilF,
type (#),
)
import Test.Hspec (Spec, describe, hspec, it, shouldSatisfy)
main :: IO ()
main = hspec spec
spec :: Spec
spec =
describe "SimpleCounter spec" $ do
describe "eventually is maxCounter" $ do
specification <- pure $ specification @Eventually PropF
it "passes when initialCounter < maxCounter" $ do
result <- modelcheck (specification (1, 4))
result `shouldSatisfy` Either.isRight
it "passes when initialCounter = maxCounter" $ do
result <- modelcheck (specification (4, 4))
result `shouldSatisfy` Either.isRight
it "fails when initialCounter > maxCounter" $ do
result <- modelcheck (specification (4, 2))
result `shouldSatisfy` Either.isLeft
describe "eventually always is maxCounter" $ do
specification <- pure $ specification @Stays PropFG
it "passes when initialCounter < maxCounter" $ do
result <- modelcheck (specification (1, 4))
result `shouldSatisfy` Either.isRight
it "passes when initialCounter = maxCounter" $ do
result <- modelcheck (specification (4, 4))
result `shouldSatisfy` Either.isRight
it "fails when initialCounter > maxCounter" $ do
result <- modelcheck (specification (4, 2))
result `shouldSatisfy` Either.isLeft
data Counter = Counting Word | Done Word
deriving stock (Eq, Show, Generic)
deriving anyclass (Hashable)
type CounterSpec modality =
Specification CounterState CounterActions (CounterProperty modality)
type CounterState =
'[ "goal" # Word
, "counter" # Counter
]
type CounterActions =
'[ "counterInc" # 'WeakFair
]
type CounterProperty modality =
'["counterEqualsMax" # modality]
specification ::
(forall s. Temporal s Bool -> TemporalType s modality) ->
(Word, Word) ->
CounterSpec modality
specification toProp (initialCounter, maxCounter) =
Specification
{ specInit =
ConF #goal (pure maxCounter)
. ConF #counter (pure (Counting initialCounter))
$ NilF
, specNext =
ConF #counterInc (ActionWF counterInc) NilF
, specProp =
ConF #counterEqualsMax (toProp counterEqualsMax) NilF
}
where
counterInc =
plain #counter >>= \case
Done _ -> pure False
Counting counter -> do
goal <- plain #goal
let counter' =
if counter < goal
then Counting $ counter + 1
else Done counter
#counter .= pure counter'
pure True
counterEqualsMax = do
goal <- plain #goal
counter <- plain #counter
pure
case counter of
Done c -> c == goal
_ -> False
Hello! First of all, impressive work with his library. Thanks for releasing it.
I was trying to port some stuff from TLA+ to Spectacle and run into some weird behaviour with
eventually
andeventually . always
properties.The test file below reproduces the issues.
The expected behaviour of the model is that a
Right counter
value will be incremented until it gets to the maxCounter constant, and then it shifts to the final stateLeft counter
. Thus, it should hold that#counter
is eventually equal toLeft maxCounter
, and it is also the case that it eventually is always equal toLeft counter
. However, this property should be violated if the initialCounter value is larger than maxCounter.What I see, though, is that
eventually
seems to reject cases where the model gets stuck forever in a state, even though that state matches the formula. On the other hand, it looks likeeventually . always
accepts possibilities that actually never achieve the desired state.Of course it may be that I'm making a mistake using the library. If that's the case I would be very thankful if you could point me in the right direction.
Versions:
Test module
Output of test run: