Skip to content

Explore state machine testing #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .hlint.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
- 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
- Test.Network.Gossip.Membership.StateMachine
2 changes: 2 additions & 0 deletions .stylish-haskell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ language_extensions:
- DeriveFunctor
- DeriveGeneric
- DeriveTraversable
- DerivingStrategies
- DerivingVia
- FlexibleContexts
- FlexibleInstances
- FunctionalDependencies
Expand Down
2 changes: 2 additions & 0 deletions gossip.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ test-suite tests

build-depends:
algebraic-graphs == 0.2.*
, generic-lens
, gossip
, hashable
, hedgehog
Expand All @@ -118,6 +119,7 @@ test-suite tests
Test.Network.Gossip.Gen
Test.Network.Gossip.Helpers
Test.Network.Gossip.Membership
Test.Network.Gossip.Membership.StateMachine

ghc-options:
-threaded
Expand Down
12 changes: 11 additions & 1 deletion src/Network/Gossip/HyParView.hs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,17 @@ data Connection n = Connection
data Peers n = Peers
{ active :: HashSet n
, passive :: HashSet n
}
} deriving (Eq, Show, Generic)

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
Expand Down
2 changes: 2 additions & 0 deletions test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -12,6 +13,7 @@ main = do
and <$> sequence
[ Broadcast.tests
, Membership.tests
, Membership.State.tests
]

unless success exitFailure
14 changes: 6 additions & 8 deletions test/Test/Network/Gossip/Broadcast.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

module Test.Network.Gossip.Broadcast (tests) where

Expand Down Expand Up @@ -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 <-
Expand All @@ -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 <-
Expand Down
37 changes: 33 additions & 4 deletions test/Test/Network/Gossip/Gen.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}

module Test.Network.Gossip.Gen
( MockNodeId
( MockPeer (..)
, MockNodeId
, Contacts
, SplitMixSeed
, LinkState (..)
Expand All @@ -9,6 +13,9 @@ module Test.Network.Gossip.Gen
, NetworkBounds
, defaultNetworkBounds

, mockPeer
, nodeId

, connectedContacts
, disconnectedContacts
, circularContacts
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
45 changes: 13 additions & 32 deletions test/Test/Network/Gossip/Membership.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}

module Test.Network.Gossip.Membership (tests) where
Expand All @@ -11,6 +12,7 @@ import Test.Network.Gossip.Gen
, InfiniteListOf(..)
, LinkState(..)
, MockNodeId
, MockPeer(..)
, SplitMixSeed
, renderInf
)
Expand All @@ -21,36 +23,20 @@ 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')

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
Expand All @@ -61,42 +47,37 @@ 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 <-
forAllWith (renderInf 10) $
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 <-
forAllWith (renderInf 10) $
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 <-
forAllWith (renderInf 10) $
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 <-
Expand All @@ -116,7 +97,7 @@ activeConnected seed boot links = do
annotateShow $ passiveNetwork peers
assert $ isConnected (activeNetwork peers)

-- | Like 'propActiveConnected', 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'
Expand Down
Loading