module Test.Syd.Def.Golden
  ( module Test.Syd.Def.Golden,
    GoldenTest (..),
  )
where

import Data.ByteString (ByteString)
import qualified Data.ByteString as SB
import qualified Data.ByteString.Builder as SBB
import qualified Data.ByteString.Lazy as LB
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Path
import Path.IO
import Test.Syd.Expectation
import Test.Syd.Run
import Text.Show.Pretty

-- | Test that the given bytestring is the same as what we find in the given golden file.
pureGoldenByteStringFile :: FilePath -> ByteString -> GoldenTest ByteString
pureGoldenByteStringFile :: String -> ByteString -> GoldenTest ByteString
pureGoldenByteStringFile String
fp ByteString
bs = String -> IO ByteString -> GoldenTest ByteString
goldenByteStringFile String
fp (ByteString -> IO ByteString
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ByteString
bs)

-- | Test that the produced bytestring is the same as what we find in the given golden file.
goldenByteStringFile :: FilePath -> IO ByteString -> GoldenTest ByteString
goldenByteStringFile :: String -> IO ByteString -> GoldenTest ByteString
goldenByteStringFile String
fp IO ByteString
produceBS =
  GoldenTest
    { goldenTestRead :: IO (Maybe ByteString)
goldenTestRead = do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        IO ByteString -> IO (Maybe ByteString)
forall (m :: * -> *) a.
(MonadIO m, MonadCatch m) =>
m a -> m (Maybe a)
forgivingAbsence (IO ByteString -> IO (Maybe ByteString))
-> IO ByteString -> IO (Maybe ByteString)
forall a b. (a -> b) -> a -> b
$ String -> IO ByteString
SB.readFile (String -> IO ByteString) -> String -> IO ByteString
forall a b. (a -> b) -> a -> b
$ Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile,
      goldenTestProduce :: IO ByteString
goldenTestProduce = IO ByteString
produceBS,
      goldenTestWrite :: ByteString -> IO ()
goldenTestWrite = \ByteString
actual -> do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        Path Abs Dir -> IO ()
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m ()
ensureDir (Path Abs Dir -> IO ()) -> Path Abs Dir -> IO ()
forall a b. (a -> b) -> a -> b
$ Path Abs File -> Path Abs Dir
forall b t. Path b t -> Path b Dir
parent Path Abs File
resolvedFile
        String -> ByteString -> IO ()
SB.writeFile (Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile) ByteString
actual,
      goldenTestCompare :: ByteString -> ByteString -> IO (Maybe Assertion)
goldenTestCompare = \ByteString
actual ByteString
expected ->
        Maybe Assertion -> IO (Maybe Assertion)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Assertion -> IO (Maybe Assertion))
-> Maybe Assertion -> IO (Maybe Assertion)
forall a b. (a -> b) -> a -> b
$
          if ByteString
actual ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
expected
            then Maybe Assertion
forall a. Maybe a
Nothing
            else Assertion -> Maybe Assertion
forall a. a -> Maybe a
Just (Assertion -> Maybe Assertion) -> Assertion -> Maybe Assertion
forall a b. (a -> b) -> a -> b
$ Assertion -> String -> Assertion
Context (ByteString -> ByteString -> Assertion
bytestringsNotEqualButShouldHaveBeenEqual ByteString
actual ByteString
expected) (String -> String
goldenContext String
fp)
    }

-- | Test that the given lazy bytestring is the same as what we find in the given golden file.
--
-- Note: This converts the lazy bytestring to a strict bytestring first.
pureGoldenLazyByteStringFile :: FilePath -> LB.ByteString -> GoldenTest LB.ByteString
pureGoldenLazyByteStringFile :: String -> ByteString -> GoldenTest ByteString
pureGoldenLazyByteStringFile String
fp ByteString
bs = String -> IO ByteString -> GoldenTest ByteString
goldenLazyByteStringFile String
fp (ByteString -> IO ByteString
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ByteString
bs)

-- | Test that the produced bytestring is the same as what we find in the given golden file.
--
-- Note: This converts the lazy bytestring to a strict bytestring first.
goldenLazyByteStringFile :: FilePath -> IO LB.ByteString -> GoldenTest LB.ByteString
goldenLazyByteStringFile :: String -> IO ByteString -> GoldenTest ByteString
goldenLazyByteStringFile String
fp IO ByteString
produceBS =
  GoldenTest
    { goldenTestRead :: IO (Maybe ByteString)
goldenTestRead = do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        IO ByteString -> IO (Maybe ByteString)
forall (m :: * -> *) a.
(MonadIO m, MonadCatch m) =>
m a -> m (Maybe a)
forgivingAbsence (IO ByteString -> IO (Maybe ByteString))
-> IO ByteString -> IO (Maybe ByteString)
forall a b. (a -> b) -> a -> b
$ (ByteString -> ByteString) -> IO ByteString -> IO ByteString
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ByteString -> ByteString
LB.fromStrict (IO ByteString -> IO ByteString) -> IO ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$ String -> IO ByteString
SB.readFile (String -> IO ByteString) -> String -> IO ByteString
forall a b. (a -> b) -> a -> b
$ Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile,
      goldenTestProduce :: IO ByteString
goldenTestProduce = IO ByteString
produceBS,
      goldenTestWrite :: ByteString -> IO ()
goldenTestWrite = \ByteString
actual -> do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        Path Abs Dir -> IO ()
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m ()
ensureDir (Path Abs Dir -> IO ()) -> Path Abs Dir -> IO ()
forall a b. (a -> b) -> a -> b
$ Path Abs File -> Path Abs Dir
forall b t. Path b t -> Path b Dir
parent Path Abs File
resolvedFile
        String -> ByteString -> IO ()
SB.writeFile (Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile) (ByteString -> ByteString
LB.toStrict ByteString
actual),
      goldenTestCompare :: ByteString -> ByteString -> IO (Maybe Assertion)
goldenTestCompare = \ByteString
actual ByteString
expected ->
        Maybe Assertion -> IO (Maybe Assertion)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Assertion -> IO (Maybe Assertion))
-> Maybe Assertion -> IO (Maybe Assertion)
forall a b. (a -> b) -> a -> b
$
          let actualBS :: ByteString
actualBS = ByteString -> ByteString
LB.toStrict ByteString
actual
              expectedBS :: ByteString
expectedBS = ByteString -> ByteString
LB.toStrict ByteString
expected
           in if ByteString
actualBS ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
expectedBS
                then Maybe Assertion
forall a. Maybe a
Nothing
                else Assertion -> Maybe Assertion
forall a. a -> Maybe a
Just (Assertion -> Maybe Assertion) -> Assertion -> Maybe Assertion
forall a b. (a -> b) -> a -> b
$ Assertion -> String -> Assertion
Context (ByteString -> ByteString -> Assertion
bytestringsNotEqualButShouldHaveBeenEqual ByteString
actualBS ByteString
expectedBS) (String -> String
goldenContext String
fp)
    }

-- | Test that the given lazy bytestring is the same as what we find in the given golden file.
--
-- Note: This converts the builder to a strict bytestring first.
pureGoldenByteStringBuilderFile :: FilePath -> SBB.Builder -> GoldenTest SBB.Builder
pureGoldenByteStringBuilderFile :: String -> Builder -> GoldenTest Builder
pureGoldenByteStringBuilderFile String
fp Builder
bs = String -> IO Builder -> GoldenTest Builder
goldenByteStringBuilderFile String
fp (Builder -> IO Builder
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Builder
bs)

-- | Test that the produced bytestring is the same as what we find in the given golden file.
--
-- Note: This converts the builder to a strict bytestring first.
goldenByteStringBuilderFile :: FilePath -> IO SBB.Builder -> GoldenTest SBB.Builder
goldenByteStringBuilderFile :: String -> IO Builder -> GoldenTest Builder
goldenByteStringBuilderFile String
fp IO Builder
produceBS =
  GoldenTest
    { goldenTestRead :: IO (Maybe Builder)
goldenTestRead = do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        IO Builder -> IO (Maybe Builder)
forall (m :: * -> *) a.
(MonadIO m, MonadCatch m) =>
m a -> m (Maybe a)
forgivingAbsence (IO Builder -> IO (Maybe Builder))
-> IO Builder -> IO (Maybe Builder)
forall a b. (a -> b) -> a -> b
$ (ByteString -> Builder) -> IO ByteString -> IO Builder
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ByteString -> Builder
SBB.byteString (IO ByteString -> IO Builder) -> IO ByteString -> IO Builder
forall a b. (a -> b) -> a -> b
$ String -> IO ByteString
SB.readFile (String -> IO ByteString) -> String -> IO ByteString
forall a b. (a -> b) -> a -> b
$ Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile,
      goldenTestProduce :: IO Builder
goldenTestProduce = IO Builder
produceBS,
      goldenTestWrite :: Builder -> IO ()
goldenTestWrite = \Builder
actual -> do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        Path Abs Dir -> IO ()
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m ()
ensureDir (Path Abs Dir -> IO ()) -> Path Abs Dir -> IO ()
forall a b. (a -> b) -> a -> b
$ Path Abs File -> Path Abs Dir
forall b t. Path b t -> Path b Dir
parent Path Abs File
resolvedFile
        String -> ByteString -> IO ()
SB.writeFile (Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile) (ByteString -> ByteString
LB.toStrict (Builder -> ByteString
SBB.toLazyByteString Builder
actual)),
      goldenTestCompare :: Builder -> Builder -> IO (Maybe Assertion)
goldenTestCompare = \Builder
actual Builder
expected ->
        Maybe Assertion -> IO (Maybe Assertion)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Assertion -> IO (Maybe Assertion))
-> Maybe Assertion -> IO (Maybe Assertion)
forall a b. (a -> b) -> a -> b
$
          let actualBS :: ByteString
actualBS = ByteString -> ByteString
LB.toStrict (Builder -> ByteString
SBB.toLazyByteString Builder
actual)
              expectedBS :: ByteString
expectedBS = ByteString -> ByteString
LB.toStrict (Builder -> ByteString
SBB.toLazyByteString Builder
expected)
           in if ByteString
actualBS ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
expectedBS
                then Maybe Assertion
forall a. Maybe a
Nothing
                else Assertion -> Maybe Assertion
forall a. a -> Maybe a
Just (Assertion -> Maybe Assertion) -> Assertion -> Maybe Assertion
forall a b. (a -> b) -> a -> b
$ Assertion -> String -> Assertion
Context (ByteString -> ByteString -> Assertion
bytestringsNotEqualButShouldHaveBeenEqual ByteString
actualBS ByteString
expectedBS) (String -> String
goldenContext String
fp)
    }

-- | Test that the given text is the same as what we find in the given golden file.
pureGoldenTextFile :: FilePath -> Text -> GoldenTest Text
pureGoldenTextFile :: String -> Text -> GoldenTest Text
pureGoldenTextFile String
fp Text
bs = String -> IO Text -> GoldenTest Text
goldenTextFile String
fp (Text -> IO Text
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Text
bs)

-- | Test that the produced text is the same as what we find in the given golden file.
goldenTextFile :: FilePath -> IO Text -> GoldenTest Text
goldenTextFile :: String -> IO Text -> GoldenTest Text
goldenTextFile String
fp IO Text
produceBS =
  GoldenTest
    { goldenTestRead :: IO (Maybe Text)
goldenTestRead = do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        IO Text -> IO (Maybe Text)
forall (m :: * -> *) a.
(MonadIO m, MonadCatch m) =>
m a -> m (Maybe a)
forgivingAbsence (IO Text -> IO (Maybe Text)) -> IO Text -> IO (Maybe Text)
forall a b. (a -> b) -> a -> b
$ ByteString -> Text
TE.decodeUtf8 (ByteString -> Text) -> IO ByteString -> IO Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO ByteString
SB.readFile (Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile),
      goldenTestProduce :: IO Text
goldenTestProduce = IO Text
produceBS,
      goldenTestWrite :: Text -> IO ()
goldenTestWrite = \Text
actual -> do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        Path Abs Dir -> IO ()
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m ()
ensureDir (Path Abs Dir -> IO ()) -> Path Abs Dir -> IO ()
forall a b. (a -> b) -> a -> b
$ Path Abs File -> Path Abs Dir
forall b t. Path b t -> Path b Dir
parent Path Abs File
resolvedFile
        String -> ByteString -> IO ()
SB.writeFile (Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile) (Text -> ByteString
TE.encodeUtf8 Text
actual),
      goldenTestCompare :: Text -> Text -> IO (Maybe Assertion)
goldenTestCompare = \Text
actual Text
expected ->
        Maybe Assertion -> IO (Maybe Assertion)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Assertion -> IO (Maybe Assertion))
-> Maybe Assertion -> IO (Maybe Assertion)
forall a b. (a -> b) -> a -> b
$
          if Text
actual Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
expected
            then Maybe Assertion
forall a. Maybe a
Nothing
            else Assertion -> Maybe Assertion
forall a. a -> Maybe a
Just (Assertion -> Maybe Assertion) -> Assertion -> Maybe Assertion
forall a b. (a -> b) -> a -> b
$ Assertion -> String -> Assertion
Context (Text -> Text -> Assertion
textsNotEqualButShouldHaveBeenEqual Text
actual Text
expected) (String -> String
goldenContext String
fp)
    }

-- | Test that the given string is the same as what we find in the given golden file.
pureGoldenStringFile :: FilePath -> String -> GoldenTest String
pureGoldenStringFile :: String -> String -> GoldenTest String
pureGoldenStringFile String
fp String
bs = String -> IO String -> GoldenTest String
goldenStringFile String
fp (String -> IO String
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure String
bs)

-- | Test that the produced string is the same as what we find in the given golden file.
goldenStringFile :: FilePath -> IO String -> GoldenTest String
goldenStringFile :: String -> IO String -> GoldenTest String
goldenStringFile String
fp IO String
produceBS =
  GoldenTest
    { goldenTestRead :: IO (Maybe String)
goldenTestRead = do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        IO String -> IO (Maybe String)
forall (m :: * -> *) a.
(MonadIO m, MonadCatch m) =>
m a -> m (Maybe a)
forgivingAbsence (IO String -> IO (Maybe String)) -> IO String -> IO (Maybe String)
forall a b. (a -> b) -> a -> b
$ (Text -> String) -> IO Text -> IO String
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> String
T.unpack (IO Text -> IO String) -> IO Text -> IO String
forall a b. (a -> b) -> a -> b
$ ByteString -> Text
TE.decodeUtf8 (ByteString -> Text) -> IO ByteString -> IO Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO ByteString
SB.readFile (Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile),
      goldenTestProduce :: IO String
goldenTestProduce = IO String
produceBS,
      goldenTestWrite :: String -> IO ()
goldenTestWrite = \String
actual -> do
        Path Abs File
resolvedFile <- String -> IO (Path Abs File)
forall (m :: * -> *). MonadIO m => String -> m (Path Abs File)
resolveFile' String
fp
        Path Abs Dir -> IO ()
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m ()
ensureDir (Path Abs Dir -> IO ()) -> Path Abs Dir -> IO ()
forall a b. (a -> b) -> a -> b
$ Path Abs File -> Path Abs Dir
forall b t. Path b t -> Path b Dir
parent Path Abs File
resolvedFile
        String -> ByteString -> IO ()
SB.writeFile (Path Abs File -> String
fromAbsFile Path Abs File
resolvedFile) (Text -> ByteString
TE.encodeUtf8 (String -> Text
T.pack String
actual)),
      goldenTestCompare :: String -> String -> IO (Maybe Assertion)
goldenTestCompare = \String
actual String
expected ->
        Maybe Assertion -> IO (Maybe Assertion)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Assertion -> IO (Maybe Assertion))
-> Maybe Assertion -> IO (Maybe Assertion)
forall a b. (a -> b) -> a -> b
$
          if String
actual String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
expected
            then Maybe Assertion
forall a. Maybe a
Nothing
            else Assertion -> Maybe Assertion
forall a. a -> Maybe a
Just (Assertion -> Maybe Assertion) -> Assertion -> Maybe Assertion
forall a b. (a -> b) -> a -> b
$ Assertion -> String -> Assertion
Context (String -> String -> Assertion
stringsNotEqualButShouldHaveBeenEqual String
actual String
expected) (String -> String
goldenContext String
fp)
    }

-- | Test that the show instance has not changed for the given value.
goldenShowInstance :: (Show a) => FilePath -> a -> GoldenTest String
goldenShowInstance :: forall a. Show a => String -> a -> GoldenTest String
goldenShowInstance String
fp a
a = String -> String -> GoldenTest String
pureGoldenStringFile String
fp (a -> String
forall a. Show a => a -> String
show a
a)

-- | Test that the show instance has not changed for the given value, via `ppShow`.
goldenPrettyShowInstance :: (Show a) => FilePath -> a -> GoldenTest String
goldenPrettyShowInstance :: forall a. Show a => String -> a -> GoldenTest String
goldenPrettyShowInstance String
fp a
a = String -> String -> GoldenTest String
pureGoldenStringFile String
fp (a -> String
forall a. Show a => a -> String
ppShow a
a)

-- | The golden test context for adding context to a golden test assertion:
--
-- > goldenTestCompare = \actual expected ->
-- >   if actual == expected
-- >     then Nothing
-- >     else Just $ Context (stringsNotEqualButShouldHaveBeenEqual actual expected) (goldenContext fp)
goldenContext :: FilePath -> String
goldenContext :: String -> String
goldenContext String
fp = String
"The golden results are in: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
fp