From 54a3b4c593279c1b74755ee61aad32bd4e93a493 Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Mon, 8 Apr 2019 17:30:45 +0200 Subject: [PATCH 1/5] Use TH disco already --- .hlint.yaml | 6 ++++++ test/Test/Network/Gossip/Broadcast.hs | 14 ++++++------- test/Test/Network/Gossip/Membership.hs | 28 +++++++++++--------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.hlint.yaml b/.hlint.yaml index 7564743..508546d 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -1,3 +1,9 @@ - arguments: [ -XTypeApplications ] - ignore: { name: Eta reduce } +# https://github.com/ndmitchell/hlint/issues/186 +- ignore: + name: Unused LANGUAGE pragma + within: + - Test.Network.Gossip.Broadcast + - Test.Network.Gossip.Membership diff --git a/test/Test/Network/Gossip/Broadcast.hs b/test/Test/Network/Gossip/Broadcast.hs index f8ebaa2..090688b 100644 --- a/test/Test/Network/Gossip/Broadcast.hs +++ b/test/Test/Network/Gossip/Broadcast.hs @@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} module Test.Network.Gossip.Broadcast (tests) where @@ -37,13 +38,10 @@ import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range tests :: IO Bool -tests = checkParallel $ Group "Gossip.Broadcast" - [ ("prop_atomic_connected", propAtomicConnected) - , ("prop_atomic_network_delays", propAtomicNetworkDelays) - ] +tests = checkParallel $$discover -propAtomicConnected :: Property -propAtomicConnected = property $ do +prop_atomicConnected :: Property +prop_atomicConnected = property $ do seed <- forAll $ Gen.prune Gen.splitMixSeed boot <- forAll $ Gen.connectedContacts Gen.defaultNetworkBounds links <- @@ -52,8 +50,8 @@ propAtomicConnected = property $ do bcasts <- forAll $ genBroadcasts boot atomicBroadcast (seedSMGen' seed) boot links bcasts -propAtomicNetworkDelays :: Property -propAtomicNetworkDelays = property $ do +prop_atomicNetworkDelays :: Property +prop_atomicNetworkDelays = property $ do seed <- forAll $ Gen.prune Gen.splitMixSeed boot <- forAll $ Gen.connectedContacts Gen.defaultNetworkBounds links <- diff --git a/test/Test/Network/Gossip/Membership.hs b/test/Test/Network/Gossip/Membership.hs index 342b087..3f94816 100644 --- a/test/Test/Network/Gossip/Membership.hs +++ b/test/Test/Network/Gossip/Membership.hs @@ -1,5 +1,6 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} module Test.Network.Gossip.Membership (tests) where @@ -61,15 +62,10 @@ data Network = Network newtype Node = Node { nodeEnv :: Env MockPeer } tests :: IO Bool -tests = checkParallel $ Group "Gossip.Membership" - [ ("prop_disconnected", propDisconnected) - , ("prop_circular_connected", propCircularConnected) - , ("prop_connected", propConnected) - , ("prop_network_delays", propNetworkDelays) - ] - -propDisconnected :: Property -propDisconnected = property $ do +tests = checkParallel $$discover + +prop_disconnected :: Property +prop_disconnected = property $ do seed <- forAll Gen.splitMixSeed boot <- forAll $ Gen.disconnectedContacts Gen.defaultNetworkBounds links <- @@ -77,8 +73,8 @@ propDisconnected = property $ do Gen.prune $ Gen.infiniteListOf (pure Fast) activeDisconnected seed boot links -propCircularConnected :: Property -propCircularConnected = property $ do +prop_circularConnected :: Property +prop_circularConnected = property $ do seed <- forAll Gen.splitMixSeed boot <- forAll $ Gen.circularContacts Gen.defaultNetworkBounds links <- @@ -86,8 +82,8 @@ propCircularConnected = property $ do Gen.prune $ Gen.infiniteListOf (pure Fast) activeConnected seed boot links -propConnected :: Property -propConnected = property $ do +prop_connected :: Property +prop_connected = property $ do seed <- forAll Gen.splitMixSeed boot <- forAll $ Gen.connectedContacts Gen.defaultNetworkBounds links <- @@ -95,8 +91,8 @@ propConnected = property $ do Gen.prune $ Gen.infiniteListOf (pure Fast) activeConnected seed boot links -propNetworkDelays :: Property -propNetworkDelays = property $ do +prop_networkDelays :: Property +prop_networkDelays = property $ do seed <- forAll Gen.splitMixSeed boot <- forAll $ Gen.connectedContacts Gen.defaultNetworkBounds links <- @@ -116,7 +112,7 @@ activeConnected seed boot links = do annotateShow $ passiveNetwork peers assert $ isConnected (activeNetwork peers) --- | Like 'propActiveConnected', but assert that the network converges to a +-- | Like 'prop_activeConnected', but assert that the network converges to a -- disconnected state. -- -- This exists to suppress output which 'Test.Tasty.ExpectedFailure.expectFail' From 319565d3d247fefbabed71e148fb967a4f35d7e9 Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Tue, 9 Apr 2019 16:12:16 +0200 Subject: [PATCH 2/5] Make a start on state machine testing --- .hlint.yaml | 1 + .stylish-haskell.yaml | 2 + gossip.cabal | 1 + src/Network/Gossip/HyParView.hs | 12 +- test/Main.hs | 2 + test/Test/Network/Gossip/Gen.hs | 37 +++- test/Test/Network/Gossip/Membership.hs | 19 +-- .../Network/Gossip/Membership/StateMachine.hs | 160 ++++++++++++++++++ 8 files changed, 212 insertions(+), 22 deletions(-) create mode 100644 test/Test/Network/Gossip/Membership/StateMachine.hs diff --git a/.hlint.yaml b/.hlint.yaml index 508546d..4ec5cbb 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -7,3 +7,4 @@ within: - Test.Network.Gossip.Broadcast - Test.Network.Gossip.Membership + - Test.Network.Gossip.Membership.StateMachine diff --git a/.stylish-haskell.yaml b/.stylish-haskell.yaml index da36aa8..9b68a78 100644 --- a/.stylish-haskell.yaml +++ b/.stylish-haskell.yaml @@ -184,6 +184,8 @@ language_extensions: - DeriveFunctor - DeriveGeneric - DeriveTraversable + - DerivingStrategies + - DerivingVia - FlexibleContexts - FlexibleInstances - FunctionalDependencies diff --git a/gossip.cabal b/gossip.cabal index ff75f88..2ea806f 100644 --- a/gossip.cabal +++ b/gossip.cabal @@ -118,6 +118,7 @@ test-suite tests Test.Network.Gossip.Gen Test.Network.Gossip.Helpers Test.Network.Gossip.Membership + Test.Network.Gossip.Membership.StateMachine ghc-options: -threaded diff --git a/src/Network/Gossip/HyParView.hs b/src/Network/Gossip/HyParView.hs index 0090799..42fdf67 100644 --- a/src/Network/Gossip/HyParView.hs +++ b/src/Network/Gossip/HyParView.hs @@ -109,7 +109,17 @@ data Connection n = Connection data Peers n = Peers { active :: HashSet n , passive :: HashSet n - } + } deriving (Eq, Show) + +instance (Eq n, Hashable n) => Semigroup (Peers n) where + a <> b = Peers + { active = active a <> active b + , passive = passive a <> passive b + } + +instance (Eq n, Hashable n) => Monoid (Peers n) where + mempty = Peers mempty mempty + mappend = (<>) -- TODO: stm containers data Env n = Env diff --git a/test/Main.hs b/test/Main.hs index ff6602e..420e232 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -2,6 +2,7 @@ module Main (main) where import qualified Test.Network.Gossip.Broadcast as Broadcast import qualified Test.Network.Gossip.Membership as Membership +import qualified Test.Network.Gossip.Membership.StateMachine as Membership.State import Control.Monad (unless) import System.Exit (exitFailure) @@ -12,6 +13,7 @@ main = do and <$> sequence [ Broadcast.tests , Membership.tests + , Membership.State.tests ] unless success exitFailure diff --git a/test/Test/Network/Gossip/Gen.hs b/test/Test/Network/Gossip/Gen.hs index 79d00ee..fde3e59 100644 --- a/test/Test/Network/Gossip/Gen.hs +++ b/test/Test/Network/Gossip/Gen.hs @@ -1,5 +1,9 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE TypeFamilies #-} + module Test.Network.Gossip.Gen - ( MockNodeId + ( MockPeer (..) + , MockNodeId , Contacts , SplitMixSeed , LinkState (..) @@ -9,6 +13,9 @@ module Test.Network.Gossip.Gen , NetworkBounds , defaultNetworkBounds + , mockPeer + , nodeId + , connectedContacts , disconnectedContacts , circularContacts @@ -21,6 +28,8 @@ module Test.Network.Gossip.Gen ) where +import Network.Gossip.HyParView (HasPeerAddr(..), HasPeerNodeId(..)) + import qualified Algebra.Graph.Class as Alga import Algebra.Graph.Relation.Symmetric ( SymmetricRelation @@ -30,11 +39,15 @@ import Algebra.Graph.Relation.Symmetric import qualified Algebra.Graph.ToGraph as Alga import Control.Applicative (liftA2) import Data.Bifunctor (second) +import Data.Coerce (coerce) import qualified Data.Graph as Graph +import Data.Hashable (Hashable) import Data.List (uncons, unfoldr) +import Data.Maybe (fromMaybe) import Data.Set (Set) import qualified Data.Set as Set import Data.Word (Word16, Word64) +import Lens.Micro (lens) import Hedgehog import qualified Hedgehog.Gen as Gen @@ -45,6 +58,19 @@ import qualified Test.QuickCheck.Hedgehog as Gen -- Types ----------------------------------------------------------------------- +newtype MockPeer = MockPeer MockNodeId + deriving (Eq, Show, Hashable) + +instance HasPeerNodeId MockPeer where + type NodeId MockPeer = MockNodeId + peerNodeId = lens coerce (const coerce) + {-# INLINE peerNodeId #-} + +instance HasPeerAddr MockPeer where + type Addr MockPeer = MockNodeId + peerAddr = lens coerce (const coerce) + {-# INLINE peerAddr #-} + type MockNodeId = Word16 type Contacts = [(MockNodeId, [MockNodeId])] type SplitMixSeed = (Word64, Word64) @@ -79,6 +105,12 @@ defaultNetworkBounds = NetworkBounds -- Generators ------------------------------------------------------------------ +mockPeer :: MonadGen m => Maybe Int -> m MockPeer +mockPeer maxPeers = MockPeer <$> nodeId (fromMaybe maxBound maxPeers) + +nodeId :: MonadGen m => Int -> m MockNodeId +nodeId maxNodes = Gen.word16 (Range.constant 0 (fromIntegral $ maxNodes - 1)) + ---- Contacts ------------------------------------------------------------------ connectedContacts :: MonadGen m => NetworkBounds -> m Contacts @@ -170,6 +202,3 @@ nodeIds :: MonadGen m => NetworkBounds -> m (Set MockNodeId) nodeIds NetworkBounds{..} = Gen.set (Range.constantFrom netMinNodes netMinNodes netMaxContacts) (nodeId netMaxNodes) - -nodeId :: MonadGen m => Int -> m MockNodeId -nodeId maxNodes = Gen.word16 (Range.constant 0 (fromIntegral $ maxNodes - 1)) diff --git a/test/Test/Network/Gossip/Membership.hs b/test/Test/Network/Gossip/Membership.hs index 3f94816..9cbf6d6 100644 --- a/test/Test/Network/Gossip/Membership.hs +++ b/test/Test/Network/Gossip/Membership.hs @@ -12,6 +12,7 @@ import Test.Network.Gossip.Gen , InfiniteListOf(..) , LinkState(..) , MockNodeId + , MockPeer(..) , SplitMixSeed , renderInf ) @@ -22,16 +23,13 @@ import qualified Algebra.Graph.AdjacencyMap as Alga import Control.Concurrent (threadDelay) import Control.Monad.Trans.Class (lift) import Data.Bifunctor (second) -import Data.Coerce (coerce) import Data.Foldable (for_) -import Data.Hashable (Hashable) import qualified Data.HashSet as Set import Data.IORef (IORef, atomicModifyIORef', newIORef) import Data.List (uncons) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Traversable (for) -import Lens.Micro (lens) import Lens.Micro.Extras (view) import System.Random (randomR, split) import System.Random.SplitMix (SMGen, seedSMGen') @@ -39,19 +37,6 @@ import System.Random.SplitMix (SMGen, seedSMGen') import Hedgehog hiding (eval) import qualified Hedgehog.Gen as Gen -newtype MockPeer = MockPeer MockNodeId - deriving (Eq, Hashable) - -instance HasPeerNodeId MockPeer where - type NodeId MockPeer = MockNodeId - peerNodeId = lens coerce (const coerce) - {-# INLINE peerNodeId #-} - -instance HasPeerAddr MockPeer where - type Addr MockPeer = MockNodeId - peerAddr = lens coerce (const coerce) - {-# INLINE peerAddr #-} - data Network = Network { netNodes :: Map MockNodeId Node , netTaskQueue :: TaskQueue @@ -112,7 +97,7 @@ activeConnected seed boot links = do annotateShow $ passiveNetwork peers assert $ isConnected (activeNetwork peers) --- | Like 'prop_activeConnected', but assert that the network converges to a +-- | Like 'activeConnected', but assert that the network converges to a -- disconnected state. -- -- This exists to suppress output which 'Test.Tasty.ExpectedFailure.expectFail' diff --git a/test/Test/Network/Gossip/Membership/StateMachine.hs b/test/Test/Network/Gossip/Membership/StateMachine.hs new file mode 100644 index 0000000..0cf7532 --- /dev/null +++ b/test/Test/Network/Gossip/Membership/StateMachine.hs @@ -0,0 +1,160 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE TemplateHaskell #-} + +module Test.Network.Gossip.Membership.StateMachine (tests) where + +import qualified Network.Gossip.HyParView as Impl + +import Control.Monad.IO.Class +import qualified Data.HashSet as Set +import Lens.Micro.Extras (view) +import System.Random.SplitMix (seedSMGen') + +import Hedgehog hiding (eval) +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +import Test.Network.Gossip.Gen (MockNodeId, MockPeer(..)) +import qualified Test.Network.Gossip.Gen as Gen + + +tests :: IO Bool +tests = checkParallel $$discover + +data Model (v :: * -> *) = Model + { modelSelf :: MockPeer + -- it's not so clear yet what we gain by maintaining the state separately. + -- should this be 'HashSet (Var MockPeer v)'? + , modelPeers :: Impl.Peers MockPeer + } + +initialState :: MockPeer -> Model v +initialState self = Model self mempty + +-- Join ------------------------------------------------------------------------ + +newtype Join (v :: * -> *) = Join MockNodeId + deriving (Eq, Show) + +instance HTraversable Join where + htraverse _ (Join nid) = pure $ Join nid + +cmdJoin + :: MonadGen n + => (MockNodeId -> m (Impl.Peers MockPeer)) + -> Command n m Model +cmdJoin run = + let + gen Model { modelSelf } = + Just . fmap Join + . Gen.filter (/= view Impl.peerNodeId modelSelf) + $ Gen.nodeId maxBound + + exe (Join nid) = run nid + in + Command gen exe + [ Update $ \s (Join nid) _ -> + let + peers = modelPeers s + peers' = peers + { Impl.active = + Set.insert (MockPeer nid) (Impl.active peers) } + in + s { modelPeers = peers' } + + , Ensure $ \_ after (Join nid) out -> do + assert $ Set.member (MockPeer nid) (Impl.active (modelPeers after)) + assert $ Set.member (MockPeer nid) (Impl.active out) + ] + +-- Disconnect ------------------------------------------------------------------ + +newtype Disconnect (v :: * -> *) = Disconnect MockNodeId + deriving (Eq, Show) + +instance HTraversable Disconnect where + htraverse _ (Disconnect nid) = pure $ Disconnect nid + +cmdDisconnect + :: MonadGen n + => (MockNodeId -> m (Impl.Peers MockPeer)) + -> Command n m Model +cmdDisconnect run = + let + gen Model { modelSelf } = + Just . fmap Disconnect + . Gen.filter (/= view Impl.peerNodeId modelSelf) + $ Gen.nodeId maxBound + + exe (Disconnect nid) = run nid + in + Command gen exe + [ Update $ \s (Disconnect nid) _ -> + let + peers = modelPeers s + peers' = peers + { Impl.active = Set.delete (MockPeer nid) (Impl.active peers) + , Impl.passive = Set.insert (MockPeer nid) (Impl.passive peers) + } + in + s { modelPeers = peers' } + + , Ensure $ \_ after (Disconnect nid) out -> do + let + active = Impl.active (modelPeers after) + passive = Impl.passive (modelPeers after) + in do + assert $ not $ Set.member (MockPeer nid) active + assert $ Set.member (MockPeer nid) passive + + assert $ not $ Set.member (MockPeer nid) (Impl.active out) + assert $ Set.member (MockPeer nid) (Impl.passive out) + ] + +-------------------------------------------------------------------------------- + +prop_singleNode :: Property +prop_singleNode = property $ do + rng <- seedSMGen' <$> forAll Gen.splitMixSeed + self <- forAll $ Gen.mockPeer Nothing + nenv <- liftIO $ Impl.new self Impl.defaultConfig rng + actions <- forAll $ + Gen.sequential (Range.linear 1 100) (initialState self) + [ cmdJoin $ runJoin nenv + , cmdDisconnect $ runDisconnect nenv + ] + executeSequential (initialState self) actions + where + runJoin nenv nid = liftIO . runSingleNode nenv $ do + Impl.receive Impl.RPC + { Impl.rpcSender = MockPeer nid + , Impl.rpcRecipient = Impl.envSelf nenv + , Impl.rpcPayload = Impl.Join + } + Impl.getPeers' + + runDisconnect nenv nid = liftIO . runSingleNode nenv $ do + Impl.receive Impl.RPC + { Impl.rpcSender = MockPeer nid + , Impl.rpcRecipient = Impl.envSelf nenv + , Impl.rpcPayload = Impl.Disconnect + } + Impl.getPeers' + +runSingleNode :: Impl.Env MockPeer -> Impl.HyParView MockPeer a -> IO a +runSingleNode env ma = Impl.runHyParView env ma >>= eval + where + eval = \case + Impl.ConnectionOpen addr _ k -> + k (Right (mkConn (MockPeer addr))) >>= eval + + Impl.SendAdHoc _ k -> k >>= eval + Impl.NeighborUp _ k -> k >>= eval + Impl.NeighborDown _ k -> k >>= eval + Impl.Done a -> pure a + + mkConn to = Impl.Connection + { Impl.connPeer = to + , Impl.connSend = const $ pure () + , Impl.connClose = pure () + } From 97acc3ca1c6e22b996aec48c330ed0036668541a Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Tue, 9 Apr 2019 17:28:39 +0200 Subject: [PATCH 3/5] generic-lens ftw --- gossip.cabal | 1 + src/Network/Gossip/HyParView.hs | 2 +- .../Network/Gossip/Membership/StateMachine.hs | 60 +++++++++---------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/gossip.cabal b/gossip.cabal index 2ea806f..4076277 100644 --- a/gossip.cabal +++ b/gossip.cabal @@ -104,6 +104,7 @@ test-suite tests build-depends: algebraic-graphs == 0.2.* + , generic-lens , gossip , hashable , hedgehog diff --git a/src/Network/Gossip/HyParView.hs b/src/Network/Gossip/HyParView.hs index 42fdf67..8d5d391 100644 --- a/src/Network/Gossip/HyParView.hs +++ b/src/Network/Gossip/HyParView.hs @@ -109,7 +109,7 @@ data Connection n = Connection data Peers n = Peers { active :: HashSet n , passive :: HashSet n - } deriving (Eq, Show) + } deriving (Eq, Show, Generic) instance (Eq n, Hashable n) => Semigroup (Peers n) where a <> b = Peers diff --git a/test/Test/Network/Gossip/Membership/StateMachine.hs b/test/Test/Network/Gossip/Membership/StateMachine.hs index 0cf7532..13a95bd 100644 --- a/test/Test/Network/Gossip/Membership/StateMachine.hs +++ b/test/Test/Network/Gossip/Membership/StateMachine.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE TemplateHaskell #-} @@ -7,7 +8,10 @@ module Test.Network.Gossip.Membership.StateMachine (tests) where import qualified Network.Gossip.HyParView as Impl import Control.Monad.IO.Class +import Data.Generics.Product import qualified Data.HashSet as Set +import GHC.Generics (Generic) +import Lens.Micro (over, to, toListOf) import Lens.Micro.Extras (view) import System.Random.SplitMix (seedSMGen') @@ -26,7 +30,7 @@ data Model (v :: * -> *) = Model -- it's not so clear yet what we gain by maintaining the state separately. -- should this be 'HashSet (Var MockPeer v)'? , modelPeers :: Impl.Peers MockPeer - } + } deriving Generic initialState :: MockPeer -> Model v initialState self = Model self mempty @@ -54,17 +58,16 @@ cmdJoin run = in Command gen exe [ Update $ \s (Join nid) _ -> + over (field @"modelPeers" . field @"active") + (Set.insert (MockPeer nid)) + s + + , Ensure $ \_ after (Join nid) out -> let - peers = modelPeers s - peers' = peers - { Impl.active = - Set.insert (MockPeer nid) (Impl.active peers) } - in - s { modelPeers = peers' } - - , Ensure $ \_ after (Join nid) out -> do - assert $ Set.member (MockPeer nid) (Impl.active (modelPeers after)) - assert $ Set.member (MockPeer nid) (Impl.active out) + peer = MockPeer nid + in do + assert $ Set.member peer (view (field @"modelPeers" . field @"active") after) + assert $ Set.member peer (view (field @"active") out) ] -- Disconnect ------------------------------------------------------------------ @@ -90,25 +93,22 @@ cmdDisconnect run = in Command gen exe [ Update $ \s (Disconnect nid) _ -> + over (field @"modelPeers") + ( over (field @"active") (Set.delete (MockPeer nid)) + . over (field @"passive") (Set.insert (MockPeer nid)) + ) + s + + , Ensure $ \_ after (Disconnect nid) out -> let - peers = modelPeers s - peers' = peers - { Impl.active = Set.delete (MockPeer nid) (Impl.active peers) - , Impl.passive = Set.insert (MockPeer nid) (Impl.passive peers) - } - in - s { modelPeers = peers' } - - , Ensure $ \_ after (Disconnect nid) out -> do - let - active = Impl.active (modelPeers after) - passive = Impl.passive (modelPeers after) + peer = MockPeer nid + chk = and . toListOf + ( field @"active" . to (not . Set.member peer) + <> field @"passive" . to (Set.member peer) + ) in do - assert $ not $ Set.member (MockPeer nid) active - assert $ Set.member (MockPeer nid) passive - - assert $ not $ Set.member (MockPeer nid) (Impl.active out) - assert $ Set.member (MockPeer nid) (Impl.passive out) + assert $ chk (modelPeers after) + assert $ chk out ] -------------------------------------------------------------------------------- @@ -153,8 +153,8 @@ runSingleNode env ma = Impl.runHyParView env ma >>= eval Impl.NeighborDown _ k -> k >>= eval Impl.Done a -> pure a - mkConn to = Impl.Connection - { Impl.connPeer = to + mkConn to' = Impl.Connection + { Impl.connPeer = to' , Impl.connSend = const $ pure () , Impl.connClose = pure () } From 6ea5d10a4ecd466c256f8155f5c7e04822cdffd0 Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Tue, 9 Apr 2019 17:41:00 +0200 Subject: [PATCH 4/5] fixup! generic-lens ftw --- .../Network/Gossip/Membership/StateMachine.hs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/test/Test/Network/Gossip/Membership/StateMachine.hs b/test/Test/Network/Gossip/Membership/StateMachine.hs index 13a95bd..6bd9a7d 100644 --- a/test/Test/Network/Gossip/Membership/StateMachine.hs +++ b/test/Test/Network/Gossip/Membership/StateMachine.hs @@ -65,9 +65,11 @@ cmdJoin run = , Ensure $ \_ after (Join nid) out -> let peer = MockPeer nid + chk = and . toListOf + (field @"active" . to (Set.member peer)) in do - assert $ Set.member peer (view (field @"modelPeers" . field @"active") after) - assert $ Set.member peer (view (field @"active") out) + assert $ chk (modelPeers after) + assert $ chk out ] -- Disconnect ------------------------------------------------------------------ @@ -93,19 +95,22 @@ cmdDisconnect run = in Command gen exe [ Update $ \s (Disconnect nid) _ -> - over (field @"modelPeers") - ( over (field @"active") (Set.delete (MockPeer nid)) - . over (field @"passive") (Set.insert (MockPeer nid)) - ) - s + let + peer = MockPeer nid + in + over (field @"modelPeers") + ( over (field @"active") (Set.delete peer) + . over (field @"passive") (Set.insert peer) + ) + s , Ensure $ \_ after (Disconnect nid) out -> let peer = MockPeer nid chk = and . toListOf - ( field @"active" . to (not . Set.member peer) - <> field @"passive" . to (Set.member peer) - ) + ( field @"active" . to (not . Set.member peer) + <> field @"passive" . to (Set.member peer) + ) in do assert $ chk (modelPeers after) assert $ chk out From 49d07f07690a148a6bb1d15b149e3e1d1669e6ce Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Wed, 10 Apr 2019 11:33:06 +0200 Subject: [PATCH 5/5] Maintain state from output --- .../Network/Gossip/Membership/StateMachine.hs | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/test/Test/Network/Gossip/Membership/StateMachine.hs b/test/Test/Network/Gossip/Membership/StateMachine.hs index 6bd9a7d..8f1bff3 100644 --- a/test/Test/Network/Gossip/Membership/StateMachine.hs +++ b/test/Test/Network/Gossip/Membership/StateMachine.hs @@ -9,9 +9,10 @@ import qualified Network.Gossip.HyParView as Impl import Control.Monad.IO.Class import Data.Generics.Product +import Data.HashSet (HashSet) import qualified Data.HashSet as Set import GHC.Generics (Generic) -import Lens.Micro (over, to, toListOf) +import Lens.Micro (Lens', set) import Lens.Micro.Extras (view) import System.Random.SplitMix (seedSMGen') @@ -26,14 +27,16 @@ tests :: IO Bool tests = checkParallel $$discover data Model (v :: * -> *) = Model - { modelSelf :: MockPeer - -- it's not so clear yet what we gain by maintaining the state separately. - -- should this be 'HashSet (Var MockPeer v)'? - , modelPeers :: Impl.Peers MockPeer + { self :: MockPeer + , peers :: Maybe (Var (Impl.Peers MockPeer) v) } deriving Generic +active, passive :: Lens' (Impl.Peers MockPeer) (HashSet MockPeer) +active = field @"active" +passive = field @"passive" + initialState :: MockPeer -> Model v -initialState self = Model self mempty +initialState self = Model self Nothing -- Join ------------------------------------------------------------------------ @@ -49,27 +52,27 @@ cmdJoin -> Command n m Model cmdJoin run = let - gen Model { modelSelf } = + gen Model { self } = Just . fmap Join - . Gen.filter (/= view Impl.peerNodeId modelSelf) + . Gen.filter (/= view Impl.peerNodeId self) $ Gen.nodeId maxBound exe (Join nid) = run nid in Command gen exe - [ Update $ \s (Join nid) _ -> - over (field @"modelPeers" . field @"active") - (Set.insert (MockPeer nid)) - s + [ Update $ \s _ out -> + set (field @"peers") (Just out) s , Ensure $ \_ after (Join nid) out -> let - peer = MockPeer nid - chk = and . toListOf - (field @"active" . to (Set.member peer)) - in do - assert $ chk (modelPeers after) - assert $ chk out + peer = MockPeer nid + peers' = concrete <$> peers after + in + case peers' of + Nothing -> failure + Just ps -> do + ps === out + assert $ Set.member peer $ view active ps ] -- Disconnect ------------------------------------------------------------------ @@ -86,34 +89,28 @@ cmdDisconnect -> Command n m Model cmdDisconnect run = let - gen Model { modelSelf } = + gen Model { self } = Just . fmap Disconnect - . Gen.filter (/= view Impl.peerNodeId modelSelf) + . Gen.filter (/= view Impl.peerNodeId self) $ Gen.nodeId maxBound exe (Disconnect nid) = run nid in Command gen exe - [ Update $ \s (Disconnect nid) _ -> - let - peer = MockPeer nid - in - over (field @"modelPeers") - ( over (field @"active") (Set.delete peer) - . over (field @"passive") (Set.insert peer) - ) - s + [ Update $ \s _ out -> + set (field @"peers") (Just out) s , Ensure $ \_ after (Disconnect nid) out -> let peer = MockPeer nid - chk = and . toListOf - ( field @"active" . to (not . Set.member peer) - <> field @"passive" . to (Set.member peer) - ) - in do - assert $ chk (modelPeers after) - assert $ chk out + peers' = concrete <$> peers after + in + case peers' of + Nothing -> failure + Just ps -> do + ps === out + assert $ Set.member peer $ view passive ps + assert $ not $ Set.member peer $ view active ps ] --------------------------------------------------------------------------------