sandwich-0.3.0.0: src/Test/Sandwich/Interpreters/RunTree.hs
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
module Test.Sandwich.Interpreters.RunTree (
specToRunTree
, specToRunTreeVariable
, specToRunTreeM
, isEmptySpec
) where
import Control.Concurrent.STM
import Control.Monad.Free
import Control.Monad.Logger
import Control.Monad.Trans.RWS
import Data.Foldable (toList)
import Data.Functor.Identity
import qualified Data.List as L
import qualified Data.Map as M
import Data.Sequence as Seq
import GHC.Stack
import Lens.Micro
import Lens.Micro.TH
import Safe (headMay)
import System.FilePath
import Test.Sandwich.Interpreters.RunTree.Util
import Test.Sandwich.RunTree (unFixRunTree)
import Test.Sandwich.Types.RunTree
import Test.Sandwich.Types.Spec
data CreatedNode = CreatedNode {
_createdNodeFolder :: Maybe (FilePath, Int, Int)
} deriving (Show)
makeLenses ''CreatedNode
data ConvertState = ConvertState {
_convertStateIdCounter :: Int
, _convertStateCreatedNodes :: M.Map Int CreatedNode
, _convertStateRootNextChildIndex :: Int
}
emptyConvertState :: ConvertState
emptyConvertState = ConvertState 0 mempty 0
makeLenses ''ConvertState
specToRunTree :: BaseContext -> Free (SpecCommand BaseContext IO) () -> [RunNodeFixed BaseContext]
specToRunTree baseContext spec = runIdentity $ runNoLoggingT $ specToRunTreeM baseContext spec
specToRunTreeVariable :: BaseContext -> Free (SpecCommand BaseContext IO) () -> STM [RunNode BaseContext]
specToRunTreeVariable bc spec = mapM unFixRunTree $ specToRunTree bc spec
isEmptySpec :: forall context. Free (SpecCommand context IO) () -> Bool
isEmptySpec spec = L.null ret
where context = RunTreeContext {
runTreeCurrentAncestors = mempty
, runTreeRootFolderAndNumChildren = Nothing
}
(ret, _, _) = runIdentity $ runNoLoggingT $ runRWST (specToRunTree' spec) context emptyConvertState
specToRunTreeM :: (MonadLogger m) => BaseContext -> Free (SpecCommand BaseContext IO) () -> m [RunNodeFixed BaseContext]
specToRunTreeM baseContext spec = do
let context = RunTreeContext {
runTreeCurrentAncestors = mempty
, runTreeRootFolderAndNumChildren = case baseContextRunRoot baseContext of
Nothing -> Nothing
Just root -> Just (root </> "results", countImmediateFolderChildren spec)
}
(ret, _, _) <- runRWST (specToRunTree' spec) context emptyConvertState
return ret
-- | Convert a spec to a run tree
specToRunTree' :: (MonadLogger m) => Free (SpecCommand context IO) r -> ConvertM m [RunNodeFixed context]
specToRunTree' node@(Free (Before'' loc no l f subspec next)) = do
common <- getCommon l loc node no
continueWith next =<< RunNodeBefore common <$> recurse l no common subspec <*> pure f
specToRunTree' node@(Free (After'' loc no l f subspec next)) = do
common <- getCommon l loc node no
continueWith next =<< RunNodeAfter common <$> recurse l no common subspec <*> pure f
specToRunTree' node@(Free (Introduce'' loc no l _cl alloc cleanup subspec next)) = do
common <- getCommon l loc node no
continueWith next =<< RunNodeIntroduce common <$> recurse l no common subspec <*> pure alloc <*> pure cleanup
specToRunTree' node@(Free (IntroduceWith'' loc no l _cl action subspec next)) = do
common <- getCommon l loc node no
continueWith next =<< RunNodeIntroduceWith common <$> recurse l no common subspec <*> pure action
specToRunTree' node@(Free (Around'' loc no l actionWith subspec next)) = do
common <- getCommon l loc node no
continueWith next =<< RunNodeAround common <$> recurse l no common subspec <*> pure actionWith
specToRunTree' node@(Free (Describe'' loc no l subspec next)) = do
common <- getCommon l loc node no
continueWith next =<< RunNodeDescribe common <$> recurse l no common subspec
specToRunTree' node@(Free (Parallel'' loc no subspec next)) = do
common <- getCommon "Parallel" loc node no
continueWith next =<< RunNodeParallel common <$> recurse "Parallel" no common subspec
specToRunTree' node@(Free (It'' loc no l example next)) = do
common <- getCommon l loc node no
continueWith next =<< RunNodeIt common <$> pure example
specToRunTree' (Pure _) = return []
-- * Util
type ConvertM m = RWST RunTreeContext () ConvertState m
getCommon :: (MonadLogger m) => String -> Maybe SrcLoc -> Free (SpecCommand context n) a -> NodeOptions -> ConvertM m RunNodeCommonFixed
getCommon l srcLoc node (NodeOptions {..}) = do
RunTreeContext {..} <- ask
-- Get a unique ID for this node
ident <- _convertStateIdCounter <$> get
modify (over convertStateIdCounter (+1))
-- Determine the folder for the node
folder <- case nodeOptionsCreateFolder of
False -> pure Nothing
True -> do
createdNodes <- _convertStateCreatedNodes <$> get
-- Look up the first ancestor that has a folder
case headMay [(ancestor, folder) | ancestor@(flip M.lookup createdNodes -> Just (CreatedNode (Just folder)))
<- (toList $ Seq.reverse runTreeCurrentAncestors)] of
Nothing -> do
-- No ancestor has a folder, so we have to put this folder under the root
case runTreeRootFolderAndNumChildren of
Just (rootFolder, numRootChildren) -> do
nextRootChild <- _convertStateRootNextChildIndex <$> get
modify (over convertStateRootNextChildIndex (+1))
pure $ Just (rootFolder </> nodeToFolderName l numRootChildren nextRootChild)
Nothing -> pure Nothing
Just (ancestor, (folder, totalChildren, nextChildIndex)) -> do
let bumpAncestorNextChildIndex :: M.Map Int CreatedNode -> M.Map Int CreatedNode
bumpAncestorNextChildIndex = M.adjust (over (createdNodeFolder . _Just . _3) (+1)) ancestor
modify (over convertStateCreatedNodes bumpAncestorNextChildIndex)
pure $ Just (folder </> nodeToFolderName l totalChildren nextChildIndex)
-- Insert this node into the ConvertState
modify $ over convertStateCreatedNodes $ M.insert ident $ CreatedNode $ case folder of
Nothing -> Nothing
Just f -> Just (f, countSubspecFolderChildren node, 0)
return $ RunNodeCommonWithStatus {
runTreeLabel = l
, runTreeId = ident
, runTreeAncestors = runTreeCurrentAncestors |> ident
, runTreeToggled = False
, runTreeOpen = True
, runTreeStatus = NotStarted
, runTreeVisible = True
, runTreeFolder = folder
, runTreeVisibilityLevel = nodeOptionsVisibilityThreshold
, runTreeRecordTime = nodeOptionsRecordTime
, runTreeLogs = mempty
, runTreeLoc = srcLoc
}
continueWith :: (MonadLogger m) => Free (SpecCommand context IO) r -> RunNodeFixed context -> ConvertM m [RunNodeFixed context]
continueWith next node = do
rest <- specToRunTree' next
return (node : rest)
recurse :: (MonadLogger m) => String -> NodeOptions -> RunNodeCommonFixed -> Free (SpecCommand context IO) r -> ConvertM m [RunNodeFixed context]
recurse _ (NodeOptions {}) (RunNodeCommonWithStatus {..}) subspec = local
(\rtc -> rtc { runTreeCurrentAncestors = runTreeAncestors })
(specToRunTree' subspec)