packages feed

effectful-zoo-0.0.4.0: components/hedgehog/Effectful/Zoo/Hedgehog/Api/Workspace.hs

module Effectful.Zoo.Hedgehog.Api.Workspace
  ( PackagePath(..),
    ProjectRoot(..),
    Workspace(..),
    workspace,
    moduleWorkspace,
    findCabalProjectDir,
  ) where

import Data.Text qualified as T
import Effectful
import Effectful.Concurrent
import Effectful.Environment
import Effectful.Reader.Static
import Effectful.Zoo.Core
import Effectful.Zoo.Error.Static
import Effectful.Zoo.FileSystem
import Effectful.Zoo.Hedgehog.Api.Assert
import Effectful.Zoo.Hedgehog.Api.Hedgehog
import Effectful.Zoo.Hedgehog.Api.Journal
import Effectful.Zoo.Hedgehog.Api.Stack
import Effectful.Zoo.Hedgehog.Data.PackagePath
import Effectful.Zoo.Hedgehog.Data.ProjectRoot
import Effectful.Zoo.Hedgehog.Data.Workspace
import Effectful.Zoo.Hedgehog.Effect.Hedgehog
import Effectful.Zoo.Log.Static
import HaskellWorks.Prelude
import Hedgehog.Internal.Property qualified as H
import System.FilePath ((</>))
import System.Info

-- | Create a workspace directory which will exist for at least the duration of
-- the supplied block.
--
-- The directory will have the supplied prefix but contain a generated random
-- suffix to prevent interference between tests
--
-- The directory will be deleted if the block succeeds, but left behind if
-- the block fails.
workspace :: ()
  => HasCallStack
  => r <: Concurrent
  => r <: Environment
  => r <: Error H.Failure
  => r <: FileSystem
  => r <: Hedgehog
  => r <: Log Text
  => FilePath
  -> Eff (Reader Workspace : r) ()
  -> Eff r ()
workspace prefixPath f = withFrozenCallStack $ do
  systemTemp <- getCanonicalTemporaryDirectory
    & trapFail @IOException
  maybeKeepWorkspace <- lookupEnv "KEEP_WORKSPACE"
  ws <- createTempDirectory systemTemp (prefixPath <> "-test")
    & trapFail @IOException
  jot_ $ "Workspace: " <> T.pack ws
  writeStringFile (ws </> "module") callerModuleName
    & trapFail @IOException
  runReader (Workspace ws) f
  when (os /= "mingw32" && maybeKeepWorkspace /= Just "1") $ do
    removePathForcibly ws
      & trapFail @IOException

-- | Create a workspace directory which will exist for at least the duration of
-- the supplied block.
--
-- The directory will have the prefix as "$prefixPath/$moduleName" but contain a generated random
-- suffix to prevent interference between tests
--
-- The directory will be deleted if the block succeeds, but left behind if
-- the block fails.
--
-- The 'prefix' argument should not contain directory delimeters.
moduleWorkspace ::  ()
  => HasCallStack
  => r <: Concurrent
  => r <: Environment
  => r <: Error H.Failure
  => r <: FileSystem
  => r <: Hedgehog
  => r <: Log Text
  => String
  -> Eff (Reader Workspace : r) ()
  -> Eff r ()
moduleWorkspace prefix f = withFrozenCallStack $
  workspace (prefix <> "-" <> callerModuleName) f

-- | Compute the project base.  This will be the first parent directory that contains
-- the `cabal.project` file.
-- This should should point to the root directory of the Github project checkout.
findCabalProjectDir :: ()
  => r <: Concurrent
  => r <: Environment
  => r <: Error H.Failure
  => r <: FileSystem
  => r <: Hedgehog
  => r <: Log Text
  => FilePath
  -> Eff r FilePath
findCabalProjectDir dir = do
  atBase <- doesFileExist (dir </> "cabal.project")
    & trap_ @IOException (pure False)
  if atBase
    then return dir
    else do
      let up = dir </> ".."
      upExist <- doesDirectoryExist up
        & trap_ @IOException (pure False)
      if upExist
        then findCabalProjectDir up
        else failWith Nothing "Could not detect project base directory (containing cabal.project)"