-- | For the diff functions in this module: If CREATE_GOLDEN_FILES environment is
-- set to "1", then should the golden file not exist it would be created.  If
-- RECREATE_GOLDEN_FILES is set to "1", then should the golden file exist it would
-- be recreated. If GOLDEN_FILE_LOG_FILE is set to a filename, then the golden file
-- path will be logged to the specified file.
--
-- Set the environment variable when you intend to generate or re-generate the golden
-- file for example when running the test for the first time or if the golden file
-- genuinely needs to change.
--
-- To re-generate a golden file you must also delete the golden file because golden
-- files are never overwritten

module HaskellWorks.Polysemy.Hedgehog.Golden
  ( diffVsGoldenFile,
    diffFileVsGoldenFile,
    diffJsonVsGoldenFile,
    diffYamlVsGoldenFile,
  ) where

import           Control.Applicative
import           Control.Monad
import qualified Data.Aeson                                    as J
import           Data.Algorithm.Diff                           (PolyDiff (Both),
                                                                getGroupedDiff)
import           Data.Algorithm.DiffOutput                     (ppDiff)
import           Data.Bool
import           Data.Eq
import           Data.Function
import           Data.Maybe
import           Data.Monoid
import           Data.String
import qualified Data.Text                                     as T
import qualified Data.Text.Encoding                            as T
import           GHC.Stack                                     (callStack)
import           HaskellWorks.Polysemy.Hedgehog.Assert
import           HaskellWorks.Polysemy.Hedgehog.Jot
import           System.FilePath                               (takeDirectory)

import qualified Control.Concurrent.QSem                       as IO
import qualified Data.ByteString.Lazy                          as LBS
import qualified Data.List                                     as List
import           Data.Yaml                                     as Y
import qualified HaskellWorks.Polysemy.Control.Concurrent.QSem as PIO
import           HaskellWorks.Polysemy.Data.ByteString         as PBS
import           HaskellWorks.Polysemy.Prelude
import           HaskellWorks.Polysemy.System.Directory        as PIO
import           HaskellWorks.Polysemy.System.IO               as PIO
import           Polysemy
import           Polysemy.Error
import           Polysemy.Log
import           Polysemy.Resource
import qualified System.Environment                            as IO
import qualified System.IO.Unsafe                              as IO

sem :: IO.QSem
sem :: QSem
sem = IO QSem -> QSem
forall a. IO a -> a
IO.unsafePerformIO (IO QSem -> QSem) -> IO QSem -> QSem
forall a b. (a -> b) -> a -> b
$ Int -> IO QSem
IO.newQSem Int
1
{-# NOINLINE sem #-}

-- | The file to log whenever a golden file is referenced.
mGoldenFileLogFile :: Maybe FilePath
mGoldenFileLogFile :: Maybe String
mGoldenFileLogFile = IO (Maybe String) -> Maybe String
forall a. IO a -> a
IO.unsafePerformIO (IO (Maybe String) -> Maybe String)
-> IO (Maybe String) -> Maybe String
forall a b. (a -> b) -> a -> b
$
  String -> IO (Maybe String)
IO.lookupEnv String
"GOLDEN_FILE_LOG_FILE"

-- | Whether the test should create the golden files if the files do not exist.
createGoldenFiles :: Bool
createGoldenFiles :: Bool
createGoldenFiles = IO Bool -> Bool
forall a. IO a -> a
IO.unsafePerformIO (IO Bool -> Bool) -> IO Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
  Maybe String
value <- String -> IO (Maybe String)
IO.lookupEnv String
"CREATE_GOLDEN_FILES"
  Bool -> IO Bool
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> IO Bool) -> Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ Maybe String
value Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
"1"

-- | Whether the test should recreate the golden files if the files already exist.
recreateGoldenFiles :: Bool
recreateGoldenFiles :: Bool
recreateGoldenFiles = IO Bool -> Bool
forall a. IO a -> a
IO.unsafePerformIO (IO Bool -> Bool) -> IO Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
  Maybe String
value <- String -> IO (Maybe String)
IO.lookupEnv String
"RECREATE_GOLDEN_FILES"
  Bool -> IO Bool
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> IO Bool) -> Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ Maybe String
value Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
"1"

writeGoldenFile :: ()
  => HasCallStack
  => Member Hedgehog r
  => Member (Embed IO) r
  => Member (Error IOException) r
  => Member Log r
  => FilePath
  -> String
  -> Sem r ()
writeGoldenFile :: forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> String -> Sem r ()
writeGoldenFile String
goldenFile String
actualContent = (HasCallStack => Sem r ()) -> Sem r ()
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => Sem r ()) -> Sem r ())
-> (HasCallStack => Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$ do
  String -> Sem r ()
forall a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, ToString a) =>
a -> Sem r ()
jot_ (String -> Sem r ()) -> String -> Sem r ()
forall a b. (a -> b) -> a -> b
$ String
"Creating golden file " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
goldenFile
  Bool -> String -> Sem r ()
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
Bool -> String -> Sem r ()
PIO.createDirectoryIfMissing Bool
True (String -> String
takeDirectory String
goldenFile)
  String -> String -> Sem r ()
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
String -> String -> Sem r ()
PIO.writeFile String
goldenFile String
actualContent

writeByteStringGoldenFile :: ()
  => HasCallStack
  => Member Hedgehog r
  => Member (Embed IO) r
  => Member (Error IOException) r
  => Member Log r
  => FilePath
  -> ByteString
  -> Sem r ()
writeByteStringGoldenFile :: forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> ByteString -> Sem r ()
writeByteStringGoldenFile String
goldenFile ByteString
bs = (HasCallStack => Sem r ()) -> Sem r ()
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => Sem r ()) -> Sem r ())
-> (HasCallStack => Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$ do
  String -> Sem r ()
forall a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, ToString a) =>
a -> Sem r ()
jot_ (String -> Sem r ()) -> String -> Sem r ()
forall a b. (a -> b) -> a -> b
$ String
"Creating golden file " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
goldenFile
  Bool -> String -> Sem r ()
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
Bool -> String -> Sem r ()
PIO.createDirectoryIfMissing Bool
True (String -> String
takeDirectory String
goldenFile)
  String -> ByteString -> Sem r ()
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
String -> ByteString -> Sem r ()
PBS.writeFile String
goldenFile ByteString
bs

reportGoldenFileMissing :: ()
  => HasCallStack
  => Member Hedgehog r
  => Member (Embed IO) r
  => Member (Error IOException) r
  => Member Log r
  => FilePath
  -> Sem r ()
reportGoldenFileMissing :: forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> Sem r ()
reportGoldenFileMissing String
goldenFile = (HasCallStack => Sem r ()) -> Sem r ()
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => Sem r ()) -> Sem r ())
-> (HasCallStack => Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$ do
  String -> Sem r ()
forall a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, ToString a) =>
a -> Sem r ()
jot_ (String -> Sem r ()) -> String -> Sem r ()
forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines
    [ String
"Golden file " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
goldenFile String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" does not exist."
    , String
"To create it, run with CREATE_GOLDEN_FILES=1."
    , String
"To recreate it, run with RECREATE_GOLDEN_FILES=1."
    ]
  Sem r ()
forall a (r :: EffectRow).
(Member Hedgehog r, HasCallStack) =>
Sem r a
failure

checkAgainstGoldenFile :: ()
  => HasCallStack
  => Member Hedgehog r
  => Member (Embed IO) r
  => Member (Error IOException) r
  => Member Log r
  => FilePath
  -> [String]
  -> Sem r ()
checkAgainstGoldenFile :: forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> [String] -> Sem r ()
checkAgainstGoldenFile String
goldenFile [String]
actualLines = (HasCallStack => Sem r ()) -> Sem r ()
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => Sem r ()) -> Sem r ())
-> (HasCallStack => Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$ do
  [String]
referenceLines <- String -> [String]
List.lines (String -> [String]) -> Sem r String -> Sem r [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Sem r String
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
String -> Sem r String
PIO.readFile String
goldenFile
  let difference :: [Diff [String]]
difference = [String] -> [String] -> [Diff [String]]
forall a. Eq a => [a] -> [a] -> [Diff [a]]
getGroupedDiff [String]
actualLines [String]
referenceLines
  case [Diff [String]]
difference of
    []       -> () -> Sem r ()
forall a. a -> Sem r a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
    [Both{}] -> () -> Sem r ()
forall a. a -> Sem r a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
    [Diff [String]]
_        -> do
      String -> Sem r ()
forall a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, ToString a) =>
a -> Sem r ()
jot_ (String -> Sem r ()) -> String -> Sem r ()
forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines
        [ String
"Golden test failed against golden file: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
goldenFile
        , String
"To recreate golden file, run with RECREATE_GOLDEN_FILES=1."
        ]
      CallStack -> String -> Sem r ()
forall a (r :: EffectRow).
(Member Hedgehog r, HasCallStack) =>
CallStack -> String -> Sem r a
failMessage CallStack
HasCallStack => CallStack
callStack (String -> Sem r ()) -> String -> Sem r ()
forall a b. (a -> b) -> a -> b
$ [Diff [String]] -> String
ppDiff [Diff [String]]
difference

-- | Diff contents against the golden file.
--
-- TODO: Improve the help output by saying the difference of
-- each input.
diffVsGoldenFile :: ()
  => HasCallStack
  => Member Hedgehog r
  => Member (Embed IO) r
  => Member Resource r
  => Member Log r
  => String   -- ^ Actual content
  -> FilePath -- ^ Reference file
  -> Sem r ()
diffVsGoldenFile :: forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member Resource r, Member Log r) =>
String -> String -> Sem r ()
diffVsGoldenFile String
actualContent String
goldenFile = (HasCallStack => Sem r ()) -> Sem r ()
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => Sem r ()) -> Sem r ())
-> (HasCallStack => Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$ do
  Maybe String -> (String -> Sem r ()) -> Sem r ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ Maybe String
mGoldenFileLogFile ((String -> Sem r ()) -> Sem r ())
-> (String -> Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$ \String
logFile ->
    QSem -> Sem r () -> Sem r ()
forall a (r :: EffectRow) (m :: * -> *).
(MonadIO m, Member (Embed m) r, Member Resource r) =>
QSem -> Sem r a -> Sem r a
PIO.bracketQSem QSem
sem (Sem r () -> Sem r ()) -> Sem r () -> Sem r ()
forall a b. (a -> b) -> a -> b
$
      String -> String -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
String -> String -> Sem r ()
PIO.appendFile String
logFile (String
goldenFile String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\n")
        Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException

  Bool
fileExists <- String -> Sem (Error IOException : r) Bool
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
String -> Sem r Bool
PIO.doesFileExist String
goldenFile
    Sem (Error IOException : r) Bool
-> (Sem (Error IOException : r) Bool -> Sem r Bool) -> Sem r Bool
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException

  if
    | Bool
recreateGoldenFiles -> String -> String -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> String -> Sem r ()
writeGoldenFile String
goldenFile String
actualContent       Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException
    | Bool
fileExists          -> String -> [String] -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> [String] -> Sem r ()
checkAgainstGoldenFile String
goldenFile [String]
actualLines  Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException
    | Bool
createGoldenFiles   -> String -> String -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> String -> Sem r ()
writeGoldenFile String
goldenFile String
actualContent       Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException
    | Bool
otherwise           -> String -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> Sem r ()
reportGoldenFileMissing String
goldenFile             Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException

  where
    actualLines :: [String]
actualLines = String -> [String]
List.lines String
actualContent

-- | Diff utf8 bytestring contents against the golden file.
diffByteStringVsGoldenFile :: ()
  => HasCallStack
  => Member Hedgehog r
  => Member (Embed IO) r
  => Member Resource r
  => Member Log r
  => ByteString -- ^ Actual content
  -> FilePath -- ^ Reference file
  -> Sem r ()
diffByteStringVsGoldenFile :: forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member Resource r, Member Log r) =>
ByteString -> String -> Sem r ()
diffByteStringVsGoldenFile ByteString
bs String
goldenFile = (HasCallStack => Sem r ()) -> Sem r ()
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => Sem r ()) -> Sem r ())
-> (HasCallStack => Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$ do
  Maybe String -> (String -> Sem r ()) -> Sem r ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ Maybe String
mGoldenFileLogFile ((String -> Sem r ()) -> Sem r ())
-> (String -> Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$ \String
logFile ->
    QSem -> Sem r () -> Sem r ()
forall a (r :: EffectRow) (m :: * -> *).
(MonadIO m, Member (Embed m) r, Member Resource r) =>
QSem -> Sem r a -> Sem r a
PIO.bracketQSem QSem
sem (Sem r () -> Sem r ()) -> Sem r () -> Sem r ()
forall a b. (a -> b) -> a -> b
$
      String -> String -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
String -> String -> Sem r ()
PIO.appendFile String
logFile (String
goldenFile String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\n")
        Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException

  Bool
fileExists <- String -> Sem (Error IOException : r) Bool
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
String -> Sem r Bool
PIO.doesFileExist String
goldenFile
    Sem (Error IOException : r) Bool
-> (Sem (Error IOException : r) Bool -> Sem r Bool) -> Sem r Bool
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException

  if
    | Bool
recreateGoldenFiles -> String -> ByteString -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> ByteString -> Sem r ()
writeByteStringGoldenFile String
goldenFile ByteString
bs        Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException
    | Bool
fileExists          -> String -> [String] -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> [String] -> Sem r ()
checkAgainstGoldenFile String
goldenFile [String]
actualLines  Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException
    | Bool
createGoldenFiles   -> String -> ByteString -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> ByteString -> Sem r ()
writeByteStringGoldenFile String
goldenFile ByteString
bs        Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException
    | Bool
otherwise           -> String -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member (Error IOException) r, Member Log r) =>
String -> Sem r ()
reportGoldenFileMissing String
goldenFile             Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException

  where
    actualLines :: [String]
actualLines = String -> [String]
List.lines (String -> [String]) -> String -> [String]
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ ByteString -> Text
T.decodeUtf8 ByteString
bs

-- | Diff JSON against the golden file.
diffJsonVsGoldenFile :: ()
  => HasCallStack
  => Member Hedgehog r
  => Member (Embed IO) r
  => Member Resource r
  => Member Log r
  => ToJSON a
  => a -- ^ Actual content
  -> FilePath -- ^ Reference file
  -> Sem r ()
diffJsonVsGoldenFile :: forall (r :: EffectRow) a.
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member Resource r, Member Log r, ToJSON a) =>
a -> String -> Sem r ()
diffJsonVsGoldenFile a
a String
goldenFile = (HasCallStack => Sem r ()) -> Sem r ()
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => Sem r ()) -> Sem r ())
-> (HasCallStack => Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$
  ByteString -> String -> Sem r ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member Resource r, Member Log r) =>
ByteString -> String -> Sem r ()
diffByteStringVsGoldenFile (ByteString -> ByteString
LBS.toStrict (a -> ByteString
forall a. ToJSON a => a -> ByteString
J.encode a
a)) String
goldenFile

-- | Diff YAML against the golden file.
diffYamlVsGoldenFile :: ()
  => HasCallStack
  => Member Hedgehog r
  => Member (Embed IO) r
  => Member Resource r
  => Member Log r
  => ToJSON a
  => a -- ^ Actual content
  -> FilePath -- ^ Reference file
  -> Sem r ()
diffYamlVsGoldenFile :: forall (r :: EffectRow) a.
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member Resource r, Member Log r, ToJSON a) =>
a -> String -> Sem r ()
diffYamlVsGoldenFile a
a String
goldenFile = (HasCallStack => Sem r ()) -> Sem r ()
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => Sem r ()) -> Sem r ())
-> (HasCallStack => Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$
  ByteString -> String -> Sem r ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member Resource r, Member Log r) =>
ByteString -> String -> Sem r ()
diffByteStringVsGoldenFile (a -> ByteString
forall a. ToJSON a => a -> ByteString
Y.encode a
a) String
goldenFile

-- | Diff file against the golden file.
diffFileVsGoldenFile :: ()
  => HasCallStack
  => Member (Embed IO) r
  => Member Hedgehog r
  => Member Log r
  => Member Resource r
  => FilePath -- ^ Actual file
  -> FilePath -- ^ Reference file
  -> Sem r ()
diffFileVsGoldenFile :: forall (r :: EffectRow).
(HasCallStack, Member (Embed IO) r, Member Hedgehog r,
 Member Log r, Member Resource r) =>
String -> String -> Sem r ()
diffFileVsGoldenFile String
actualFile String
referenceFile = (HasCallStack => Sem r ()) -> Sem r ()
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => Sem r ()) -> Sem r ())
-> (HasCallStack => Sem r ()) -> Sem r ()
forall a b. (a -> b) -> a -> b
$ do
  String
contents <- String -> Sem (Error IOException : r) String
forall (r :: EffectRow).
(HasCallStack, Member (Error IOException) r, Member (Embed IO) r,
 Member Log r) =>
String -> Sem r String
PIO.readFile String
actualFile
    Sem (Error IOException : r) String
-> (Sem (Error IOException : r) String -> Sem r String)
-> Sem r String
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException

  String -> String -> Sem (Error IOException : r) ()
forall (r :: EffectRow).
(HasCallStack, Member Hedgehog r, Member (Embed IO) r,
 Member Resource r, Member Log r) =>
String -> String -> Sem r ()
diffVsGoldenFile String
contents String
referenceFile
    Sem (Error IOException : r) ()
-> (Sem (Error IOException : r) () -> Sem r ()) -> Sem r ()
forall a b. a -> (a -> b) -> b
& forall e a (r :: EffectRow).
(Member Hedgehog r, HasCallStack, Show e) =>
Sem (Error e : r) a -> Sem r a
trapFail @IOException