keiro-dsl-0.22.0.0: test/conformance-mapped-structural-enum-equality/Main.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
module Main (main) where
import Conformance.MappedStructuralEnumEquality.Domain
import Control.Monad (forM, forM_, unless)
import Data.Aeson (Value (..), object, (.=))
import Data.Aeson qualified as Aeson
import Data.Maybe (isJust)
import Data.Proxy (Proxy (..))
import Data.Text (Text)
import Data.Text qualified as T
import Generated.MappedStructuralEnumEquality.ScopeGate.Codec
import Generated.MappedStructuralEnumEquality.ScopeGate.EventStream (scopeGateEventStreamDef)
import Keiro.EventStream qualified as EventStream
import Keiro.Snapshot.Codec (withFoldFingerprint)
import Generated.MappedStructuralEnumEquality.ScopeGate.Domain
import Generated.MappedStructuralEnumEquality.ScopeGate.Harness (harnessAssertions)
import Generated.MappedStructuralEnumEquality.ScopeGate.Transducer (scopeGateTransducer)
import Generated.MappedStructuralEnumEquality.StructuralConformance (structuralConformanceAssertions)
import Generated.MappedStructuralEnumEquality.StructuralProjections qualified as Projections
import Keiki.Core qualified as K
import Keiki.Symbolic qualified as S
import Keiro.Codec (eventType)
import System.Exit (exitFailure)
main :: IO ()
main = do
symbolic <- symbolicChecks
let checks = structuralConformanceAssertions <> harnessAssertions <> symbolic <>
[("concrete equality and inequality truth tables", concreteMatrix),
("expected enum wire meanings", expectedWire),
("nested and direct event JSON stays canonical", eventWire),
("event JSON bytes preserve declared spellings", exactEventBytes),
("generated events decode and replay to forward state", replayAgreement),
("compatible snapshot agrees with full replay and changed folds have a different discriminator", snapshotAgreement)]
forM_ checks $ \(label, passed) -> putStrLn ((if passed then "PASS " else "FAIL ") <> label)
unless (all snd checks) exitFailure
scopes :: [(RequiredScope, Text)]
scopes = [(PipelineScope, "pipeline"), (DiagnosticScope, "diagnostic")]
regs :: RequiredScope -> K.RegFile ScopeGateRegs
regs scope = K.RCons (Proxy @"requiredScope") scope K.RNil
command :: Int -> RequiredScope -> ScopeGateCommand
command 0 scope = Match (MatchData scope (Request scope))
command 1 scope = Differ (DifferData scope (Request scope))
command 2 scope = EqRight (EqRightData scope (Request scope))
command 3 scope = EqLeft (EqLeftData scope (Request scope))
command 4 scope = NeRight (NeRightData scope (Request scope))
command 5 scope = NeLeft (NeLeftData scope (Request scope))
command 6 scope = Self (SelfData scope (Request scope))
command _ _ = error "unknown fixture command"
accepts :: Int -> RequiredScope -> RequiredScope -> Bool
accepts 0 owner input = owner == input
accepts 1 owner input = owner /= input
accepts 2 _ input = input == PipelineScope
accepts 3 _ input = input == PipelineScope
accepts 4 _ input = input /= DiagnosticScope
accepts 5 _ input = input /= DiagnosticScope
accepts 6 _ _ = True
accepts _ _ _ = error "unknown fixture command"
concreteMatrix :: Bool
concreteMatrix = and
[ isJust (K.step scopeGateTransducer (ScopeGateReady, regs owner) (command index input)) == accepts index owner input
| index <- [0..6], (owner, _) <- scopes, (input, _) <- scopes ]
expectedWire :: Bool
expectedWire = and [encodeRequiredScopeMapped owner == String key | (owner, key) <- scopes]
eventWire :: Bool
eventWire = and
[ encodeScopeGateEvent (Matched (MatchedData owner (Request owner))) ==
object ["kind" .= ("Matched" :: Text), "requiredScope" .= key, "request" .= object ["requiredScope" .= key]]
| (owner, key) <- scopes ]
exactEventBytes :: Bool
exactEventBytes =
Aeson.encode (encodeScopeGateEvent (Matched (MatchedData PipelineScope (Request PipelineScope)))) == "{\"kind\":\"Matched\",\"request\":{\"requiredScope\":\"pipeline\"},\"requiredScope\":\"pipeline\"}"
&& Aeson.encode (encodeScopeGateEvent (Matched (MatchedData DiagnosticScope (Request DiagnosticScope)))) == "{\"kind\":\"Matched\",\"request\":{\"requiredScope\":\"diagnostic\"},\"requiredScope\":\"diagnostic\"}"
replayAgreement :: Bool
replayAgreement = and
[ case K.step scopeGateTransducer initial (command index input) of
Nothing -> True
Just (vertex, registers, events) ->
case traverse (\event -> parseScopeGateEvent (eventType scopeGateCodec event) (encodeScopeGateEvent event)) events of
Left _ -> False
Right decoded -> case K.applyEventsEither scopeGateTransducer initial decoded of
Left _ -> False
Right (replayedVertex, replayedRegisters) -> vertex == replayedVertex && registers K.! #requiredScope == replayedRegisters K.! #requiredScope
| index <- [0..6], (owner, _) <- scopes, (input, _) <- scopes, let initial = (ScopeGateReady, regs owner) ]
scopeTerm :: K.Term ScopeGateRegs ScopeGateCommand '[] Text
scopeTerm = K.regProj Projections.requiredScopeWitness (#requiredScope :: K.Index ScopeGateRegs RequiredScope)
symbolicChecks :: IO [(String, Bool)]
symbolicChecks = do
matrix <- forM [(index, owner, ownerKey, input, inputKey) | index <- [0..6], (owner, ownerKey) <- scopes, (input, inputKey) <- scopes] $ \(index, owner, ownerKey, input, inputKey) -> do
let generated = case drop index (K.edgesOut scopeGateTransducer ScopeGateReady) of
K.Edge edgePredicate _ _ _ _ : _ -> edgePredicate
_ -> error "generated edge missing"
predicate = K.PAnd generated (K.PAnd (K.PEq scopeTerm (K.lit ownerKey)) (K.PEq (inputTerm index) (K.lit inputKey)))
result <- S.verifyPredicateDetailed predicate
pure ("generated concrete/symbolic matrix: " <> show (index, owner, input), case result of
S.PredicateSatisfiable S.ExactTranslation values -> accepts index owner input && not (null values) && all modelRoundTrip values
S.PredicateUnsatisfiable S.ExactTranslation -> not (accepts index owner input)
_ -> False)
models <- forM scopes $ \(owner, key) -> do
result <- S.verifyPredicateDetailed (K.PEq scopeTerm (K.lit key))
pure ("exact finite reconstruction: " <> show owner, case result of
S.PredicateSatisfiable strength values -> S.predicateTranslationExact (K.PEq scopeTerm (K.lit key)) && not (null values) && all (\value -> S.projectionModelKeyAs @Text value == Just key && S.projectionModelOwnerAs @RequiredScope value == Just owner) values && strength == S.ExactTranslation
_ -> False)
outside <- S.verifyPredicateDetailed (K.PAnd (K.PNot (K.PEq scopeTerm (K.lit "pipeline"))) (K.PNot (K.PEq scopeTerm (K.lit "diagnostic"))))
pure (matrix <> models <> [("finite domain excludes every undeclared key", case outside of S.PredicateUnsatisfiable _ -> True; _ -> False)])
inputTerm :: Int -> K.Term ScopeGateRegs ScopeGateCommand '[ '("requiredScope", RequiredScope), '("request", Request)] Text
inputTerm index = K.inpProj Projections.requiredScopeWitness constructor #requiredScope
where
constructor = case index of
0 -> inCtorMatch
1 -> inCtorDiffer
2 -> inCtorEqRight
3 -> inCtorEqLeft
4 -> inCtorNeRight
5 -> inCtorNeLeft
6 -> inCtorSelf
_ -> error "unknown fixture command"
modelRoundTrip :: S.ProjectionModel -> Bool
modelRoundTrip value = case (S.projectionModelKeyAs @Text value, S.projectionModelOwnerAs @RequiredScope value) of
(Just key, Just owner) -> (owner, key) `elem` scopes && Projections.requiredScopeGet owner == key
_ -> False
snapshotAgreement :: Bool
snapshotAgreement = case EventStream.stateCodec scopeGateEventStreamDef of
Nothing -> False
Just codec ->
let initial = (ScopeGateReady, regs PipelineScope)
events = [Matched (MatchedData PipelineScope (Request PipelineScope))]
full = K.applyEventsEither scopeGateTransducer initial events
fromSnapshot = do
decoded <- EventStream.decode codec (EventStream.encode codec initial)
either (Left . T.pack . show) Right (K.applyEventsEither scopeGateTransducer decoded events)
changed = withFoldFingerprint "changed-scope-fold" codec
in EventStream.stateCodecVersion changed == EventStream.stateCodecVersion codec
&& EventStream.shapeHash changed == EventStream.shapeHash codec
&& EventStream.stateShapeHash changed /= EventStream.stateShapeHash codec
&& case (full, fromSnapshot) of
(Right (leftVertex, leftRegs), Right (rightVertex, rightRegs)) -> leftVertex == rightVertex && leftRegs K.! #requiredScope == rightRegs K.! #requiredScope
_ -> False