-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Address review remarks, add retry for
PortSpec
test
- Loading branch information
1 parent
f0f6199
commit fe3d4ee
Showing
5 changed files
with
175 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,128 @@ | ||
{-# LANGUAGE NamedFieldPuns #-} | ||
{-# LANGUAGE RankNTypes #-} | ||
|
||
-- | This modules provides a tripwire abstraction. You can use tripwire as a detection mechanism if the code | ||
-- path was executed. Trip a tripwire with 'trip' in the place where you'd like to detect if it was | ||
-- reached. The tripwire can then be checked in the other place in the code using for example 'isTripped' or | ||
-- 'assertNotTripped'. | ||
module Hedgehog.Extras.Test.Tripwire | ||
( Tripwire | ||
( | ||
-- * Create a tripwire | ||
Tripwire | ||
, makeTripwire | ||
, triggerTripwire | ||
, makeTripwireWithLabel | ||
-- * Tripwire operations | ||
, trip | ||
, trip_ | ||
, isTripped | ||
, getTripSite | ||
, resetTripwire | ||
, checkTripwire | ||
, isTriggered | ||
-- * Assertions | ||
, assertNotTripped | ||
, assertTripped | ||
) where | ||
|
||
import Control.Monad.IO.Class | ||
import GHC.Stack | ||
|
||
import Control.Concurrent.MVar | ||
import Control.Monad | ||
import Data.IORef | ||
import Data.Maybe | ||
import Hedgehog (MonadTest) | ||
import qualified Hedgehog.Extras.Test.Base as H | ||
import qualified Hedgehog.Internal.Property as H | ||
import Prelude | ||
import System.IO.Unsafe (unsafePerformIO) | ||
|
||
-- | Counter used to allocate consecutive IDs to tripwires | ||
tripwireCounter :: IORef Int | ||
tripwireCounter = unsafePerformIO $ newIORef 0 | ||
{-# NOINLINE tripwireCounter #-} | ||
|
||
-- | Represents a tripwire which can be triggered only once | ||
newtype Tripwire = Tripwire (MVar CallStack) | ||
-- | Represents a tripwire which can be tripped only once. It can be used to detect if a particular code path | ||
-- was reached. | ||
data Tripwire = Tripwire | ||
{ tripwireId :: !String -- ^ a label for identifying the tripwire | ||
, tripSite :: MVar CallStack -- ^ call stack of the trip site | ||
} | ||
|
||
instance Show Tripwire where | ||
show Tripwire{tripwireId} = "Tripwire " <> tripwireId | ||
|
||
-- | Creates a new tripwire | ||
makeTripwire :: MonadIO m => m Tripwire | ||
makeTripwire = Tripwire <$> liftIO newEmptyMVar | ||
makeTripwire = liftIO $ do | ||
id' <- atomicModifyIORef' tripwireCounter (join (,) . (+1)) | ||
Tripwire (show id') <$> newEmptyMVar | ||
-- | ||
-- | Creates a new tripwire with a label, which is visible when 'show'ed: @Tripwire mylabel@ | ||
makeTripwireWithLabel :: MonadIO m | ||
=> String | ||
-> m Tripwire | ||
makeTripwireWithLabel label = liftIO $ do | ||
Tripwire label <$> newEmptyMVar | ||
|
||
-- | Triggers the tripwire and registers the place of the first trigger. Idempotent. | ||
-- Does not do do anything besides just registering the place where this function is called. | ||
triggerTripwire :: HasCallStack | ||
=> MonadIO m | ||
=> Tripwire | ||
-> m () | ||
triggerTripwire (Tripwire mv) = withFrozenCallStack $ | ||
void . liftIO $ tryPutMVar mv callStack | ||
-- Prints the information in the test log about tripping the tripwire. | ||
trip :: HasCallStack | ||
=> MonadIO m | ||
=> MonadTest m | ||
=> Tripwire | ||
-> m () | ||
trip t@Tripwire{tripSite} = withFrozenCallStack $ do | ||
H.note_ $ show t <> " has been tripped" | ||
void . liftIO $ tryPutMVar tripSite callStack | ||
|
||
-- | Triggers the tripwire and registers the place of the first trigger. Idempotent. A silent variant of | ||
-- 'trip' which does not require 'MonadTest', but also does not log the information about tripping. | ||
trip_ :: HasCallStack | ||
=> MonadIO m | ||
=> Tripwire | ||
-> m () | ||
trip_ Tripwire{tripSite} = withFrozenCallStack $ do | ||
void . liftIO $ tryPutMVar tripSite callStack | ||
|
||
-- | Restore tripwire to initial non triggered state | ||
resetTripwire :: MonadIO m | ||
=> Tripwire | ||
-> m () | ||
resetTripwire (Tripwire mv) = liftIO $ void $ tryTakeMVar mv | ||
resetTripwire Tripwire{tripSite} = liftIO $ void $ tryTakeMVar tripSite | ||
|
||
-- | Check if the tripwire is triggered. Return the first trigger location. | ||
isTriggered :: MonadIO m | ||
-- | Return the call stack, where the tripwire was tripped - if it was tripped. | ||
getTripSite :: MonadIO m | ||
=> Tripwire | ||
-> m (Maybe CallStack) | ||
isTriggered (Tripwire mv) = liftIO $ tryReadMVar mv | ||
getTripSite Tripwire{tripSite} = liftIO $ tryReadMVar tripSite | ||
|
||
-- | Fails the test if the tripwire was triggered. Prints the callstack where the tripwire was triggered. | ||
checkTripwire :: HasCallStack | ||
-- | Check if the tripwire was tripped. | ||
isTripped :: MonadIO m | ||
=> Tripwire | ||
-> m Bool | ||
isTripped Tripwire{tripSite} = liftIO $ not <$> isEmptyMVar tripSite | ||
|
||
-- | Fails the test if the tripwire was triggered. Prints the call stack where the tripwire was triggered. | ||
assertNotTripped :: HasCallStack | ||
=> MonadTest m | ||
=> MonadIO m | ||
=> Tripwire | ||
-> m () | ||
assertNotTripped tripwire = withFrozenCallStack $ do | ||
mTripSite <- getTripSite tripwire | ||
forM_ mTripSite $ \cs -> do | ||
H.note_ $ show tripwire <> " has been tripped at: " <> prettyCallStack cs | ||
H.failure | ||
|
||
-- | Fails the test if the tripwire was not triggered yet. | ||
assertTripped :: HasCallStack | ||
=> MonadTest m | ||
=> MonadIO m | ||
=> Tripwire | ||
-> m () | ||
checkTripwire = withFrozenCallStack $ do | ||
isTriggered >=> void . mapM | ||
(\cs -> do | ||
H.note_ $ "Tripwire has been tripped at: " <> prettyCallStack cs | ||
H.failure | ||
) | ||
assertTripped tripwire = withFrozenCallStack $ do | ||
mTripSite <- getTripSite tripwire | ||
when (isNothing mTripSite) $ do | ||
H.note_ $ show tripwire <> " was not tripped" | ||
H.failure | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters