packages feed

ki-0.1.0: test/dejafu-tests/DejaFuTestUtils.hs

{-# LANGUAGE TypeApplications #-}

module DejaFuTestUtils
  ( P,
    _failingTest,
    block,
    deadlocks,
    ignoring,
    nondeterministic,
    returns,
    test,
    throws,
    todo,
  )
where

import Control.Concurrent.Classy hiding (fork, forkWithUnmask, wait)
import Control.Exception (Exception (fromException))
import Control.Monad
import Data.Foldable
import Data.Function
import Data.List (intercalate)
import Data.Maybe
import qualified Ki.Implicit as Ki
import System.Exit
import qualified Test.DejaFu as DejaFu
import qualified Test.DejaFu.Types as DejaFu
import Text.Printf (printf)
import Prelude

type P =
  DejaFu.Program DejaFu.Basic IO

test :: (Eq a, Show a) => String -> DejaFu.Predicate a -> (Ki.Context => P a) -> IO ()
test name predicate t = do
  result <- DejaFu.runTestWithSettings settings (DejaFu.representative predicate) (Ki.withGlobalContext t)
  printf "[%s] %s\n" (if DejaFu._pass result then "x" else " ") name
  for_ (DejaFu._failures result) \(value, trace) -> prettyPrintTrace value trace
  unless (DejaFu._pass result) exitFailure
  where
    settings :: DejaFu.Settings IO a
    settings =
      DejaFu.fromWayAndMemType (DejaFu.systematically bounds) DejaFu.defaultMemType
      where
        bounds =
          DejaFu.Bounds
            { DejaFu.boundPreemp = Just 2,
              DejaFu.boundFair = Just 5
            }

_failingTest :: String -> IO (DejaFu.Result a) -> IO ()
_failingTest name action = do
  result <- action
  printf "[%s] %s\n" (if DejaFu._pass result then " " else "x") name
  when (DejaFu._pass result) exitFailure

todo :: String -> IO ()
todo =
  printf "[-] %s\n"

deadlocks :: DejaFu.Predicate a
deadlocks =
  DejaFu.alwaysTrue \case
    Left DejaFu.Deadlock -> True
    _ -> False

nondeterministic :: (Eq a, Show a) => [Either DejaFu.Condition a] -> DejaFu.Predicate a
nondeterministic =
  DejaFu.gives

returns :: Eq a => a -> DejaFu.Predicate a
returns expected =
  DejaFu.alwaysTrue \case
    Left _ -> False
    Right actual -> actual == expected

throws :: (Eq e, Exception e) => e -> DejaFu.Predicate a
throws expected =
  DejaFu.alwaysTrue \case
    Left (DejaFu.UncaughtException actual) -> fromException actual == Just expected
    _ -> False

--

block :: P ()
block =
  newEmptyMVar >>= takeMVar

ignoring :: forall e. Exception e => P () -> P ()
ignoring action =
  catch @_ @e action \_ -> pure ()

--

prettyPrintTrace :: Show a => Either DejaFu.Condition a -> DejaFu.Trace -> IO ()
prettyPrintTrace value trace = do
  print value
  flip fix trace \loop -> \case
    [] -> pure ()
    (decision, _, action) : xs -> do
      case decision of
        DejaFu.Start n -> putStrLn ("  [" ++ prettyThreadId n ++ "]")
        DejaFu.SwitchTo n -> putStrLn ("  [" ++ prettyThreadId n ++ "]")
        DejaFu.Continue -> pure ()
      putStrLn ("    " ++ prettyThreadAction action)
      loop xs

prettyThreadAction :: DejaFu.ThreadAction -> String
prettyThreadAction = \case
  DejaFu.BlockedSTM actions -> "atomically " ++ show actions ++ " (blocked)"
  DejaFu.BlockedTakeMVar n -> "takeMVar " ++ prettyMVarId n ++ " (blocked)"
  DejaFu.BlockedThrowTo n -> "throwTo " ++ prettyThreadId n ++ " (blocked)"
  DejaFu.Fork n -> "fork " ++ prettyThreadId n
  DejaFu.MyThreadId -> "myThreadId"
  DejaFu.NewIORef n -> prettyIORefId n ++ " <- newIORef"
  DejaFu.NewMVar n -> prettyMVarId n ++ " <- newMVar"
  DejaFu.PutMVar n [] -> "putMVar " ++ prettyMVarId n
  DejaFu.PutMVar n ts ->
    "putMVar " ++ prettyMVarId n ++ " (waking "
      ++ intercalate ", " (map prettyThreadId ts)
      ++ ")"
  DejaFu.ReadIORef n -> "readIORef " ++ prettyIORefId n
  DejaFu.ResetMasking _ state -> "setMaskingState " ++ show state
  DejaFu.Return -> "pure"
  DejaFu.STM actions _ -> "atomically " ++ show actions
  DejaFu.SetMasking _ state -> "setMaskingState " ++ show state
  DejaFu.Stop -> "stop"
  DejaFu.TakeMVar n [] -> "takeMVar " ++ prettyMVarId n
  DejaFu.TakeMVar n ts ->
    "takeMVar " ++ prettyMVarId n ++ " (waking "
      ++ intercalate ", " (map prettyThreadId ts)
      ++ ")"
  DejaFu.Throw Nothing -> "throw (thread died)"
  DejaFu.Throw (Just _) -> "throw (thread still alive)"
  DejaFu.ThrowTo n Nothing -> "throwTo " ++ prettyThreadId n ++ " (killed it)"
  DejaFu.ThrowTo n (Just _) -> "throwTo " ++ prettyThreadId n ++ " (didn't kill it)"
  action -> show action

prettyIORefId :: DejaFu.IORefId -> String
prettyIORefId n =
  "ioref#" ++ show n

prettyMVarId :: DejaFu.MVarId -> String
prettyMVarId n =
  "mvar#" ++ show n

prettyThreadId :: DejaFu.ThreadId -> String
prettyThreadId n =
  "thread#" ++ show n