Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 34 additions & 24 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import System.Console.ANSI
import System.Directory (doesDirectoryExist, doesFileExist,
listDirectory, renameFile)
import qualified Test.Hspec.Golden as G
import System.FilePath((</>))
import Data.List (groupBy, isInfixOf)

defaultDirGoldenTest :: FilePath
defaultDirGoldenTest = ".golden"
Expand Down Expand Up @@ -49,35 +51,43 @@ failure = withColor Red
updateGolden :: FilePath -> IO ()
updateGolden dir = do
putStrLn "Replacing golden with actual:"
go dir
udpateFilesInDir dir
success $ putStrLn "Finished!"
where
go dir = do
getBaseFileName filename = takeWhile (/= '-') filename
udpateFilesInDir dir = do
entries <- listDirectory dir
forM_ entries $ \entry -> do
let entryInDir = dir ++ "/" ++ entry
isDir <- doesDirectoryExist entryInDir
when isDir $ do
mvActualToGolden entryInDir
go entryInDir

mvActualToGolden :: FilePath -> IO ()
mvActualToGolden testPath =
let actualFilePath = testPath ++ "/actual"
goldenFilePath = testPath ++ "/golden"
in do
actualFileExist <- doesFileExist actualFilePath
when actualFileExist $ do
putStr " Replacing file: "
warning $ putStr goldenFilePath
putStr " with: "
success $ putStrLn actualFilePath
renameFile actualFilePath goldenFilePath `catch` handleErr
where
handleErr :: IOError -> IO ()
handleErr e =
failure $ putStr $ "Warning: Could not replace file due to error: " ++ show e
let groupedEntries =
[ (file1, file2)
| [file1, file2] <-
groupBy
(\file1 file2 ->
getBaseFileName file1 == getBaseFileName file2
) entries,
"-actual" `isInfixOf` file1,
"-golden" `isInfixOf` file2
]

dirExists <- doesDirectoryExist dir
if not dirExists
then warning $ putStrLn (dir <> " does not exist")
else forM_ groupedEntries $ \(actual, golden) ->
mvActualToGolden (dir </> actual) (dir </> golden)

mvActualToGolden :: FilePath -> FilePath -> IO ()
mvActualToGolden actualFilePath goldenFilePath = do
actualFileExist <- doesFileExist actualFilePath
when actualFileExist $ do
putStr " Replacing file: "
warning $ putStr goldenFilePath
putStr " with: "
success $ putStrLn actualFilePath
renameFile actualFilePath goldenFilePath `catch` handleErr
where
handleErr :: IOError -> IO ()
handleErr e =
failure $ putStr $ "Warning: Could not replace file due to error: " ++ show e

-- Main

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["1","2","Fizz","4","Buzz","11","Fizz","13","14","FizzBuzz"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["1","2","Fizz","4","Buzz","11","Fizz","13","14","FizzBuzz"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["1","2","Fizz","4","Buzz","11","Fizz","13","14","FizzBuzz"]
File renamed without changes.
14 changes: 14 additions & 0 deletions examples/html/.golden/html_pretty-actual
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>
<title>
Stack Builders
</title>
</head>
<body>
<div>
<p>
Hello World!
</p>
</div>
</body>
</html>
14 changes: 14 additions & 0 deletions examples/html/.golden/html_pretty-golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>
<title>
Stack Builders
</title>
</head>
<body>
<div>
<p>
Hello World!
</p>
</div>
</body>
</html>
2 changes: 1 addition & 1 deletion examples/html/stack.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
resolver: lts-14
resolver: lts-21.7
extra-deps:
- ../../
12 changes: 12 additions & 0 deletions examples/html/stack.yaml.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# This file was autogenerated by Stack.
# You should not edit this file by hand.
# For more information, please see the documentation at:
# https://docs.haskellstack.org/en/stable/lock_files

packages: []
snapshots:
- completed:
sha256: 23bb9bb355bfdb1635252e120a29b712f0d5e8a6c6a65c5ab5bd6692f46c438e
size: 640457
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/7.yaml
original: lts-21.7
1 change: 0 additions & 1 deletion examples/json/.otherGolden/json

This file was deleted.

1 change: 0 additions & 1 deletion examples/json/.otherGolden/json-actual

This file was deleted.

1 change: 1 addition & 0 deletions examples/json/.otherGolden/json-golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"cname":"Ecuador","continent":"America","ctag":1},{"cname":"Germany","continent":"Europe","ctag":2},{"cname":"Japan","continent":"Asia","ctag":4},{"cname":"United States","continent":"America","ctag":3}]
5 changes: 3 additions & 2 deletions examples/json/src/Json.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ data Country = Country
instance ToJSON Country


ecuador = Country "Ecuador" "America" 1
ecuador = Country "Ecuador" "South America" 1
germany = Country "Germany" "Europe" 2
japan = Country "Japan" "Asia" 4
unitedStates = Country "United States" "North America" 3

countries :: [Country]
countries = [ecuador,germany,japan]
countries = [ecuador,germany,japan, unitedStates]

encodeCountries :: [Country] -> ByteString
encodeCountries = encode
2 changes: 1 addition & 1 deletion examples/json/stack.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
resolver: lts-14.12
resolver: lts-21.7
extra-deps:
- ../../
8 changes: 4 additions & 4 deletions examples/json/stack.yaml.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
packages: []
snapshots:
- completed:
size: 545658
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/12.yaml
sha256: 26b807457213126d26b595439d705dc824dbb7618b0de6b900adc2bf6a059406
original: lts-14.12
sha256: 23bb9bb355bfdb1635252e120a29b712f0d5e8a6c6a65c5ab5bd6692f46c438e
size: 640457
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/7.yaml
original: lts-21.7
9 changes: 5 additions & 4 deletions examples/json/test/JsonGoldenSpec.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{-# language NamedFieldPuns #-}
module JsonGoldenSpec where

import Test.Hspec
Expand All @@ -7,14 +8,14 @@ import qualified Data.ByteString.Lazy as B


goldenBytestring :: String -> B.ByteString -> Golden B.ByteString
goldenBytestring name actualOutput =
goldenBytestring name output =
Golden {
output = actualOutput,
name,
output,
outputDir = ".otherGolden/",
encodePretty = show,
writeToFile = B.writeFile,
readFromFile = B.readFile,
goldenFile = ".otherGolden/" <> name,
actualFile = Just (".otherGolden/" <> name <> "-actual"),
failFirstTime = False
}

Expand Down
2 changes: 2 additions & 0 deletions hspec-golden.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ executable hgold
ansi-terminal >=1.0 && <2.0
, base >=4.6 && <5
, directory >=1.2.5.0
, filepath
, hspec-golden
, optparse-applicative >=0.18.1 && <0.19
default-language: Haskell2010
Expand All @@ -84,6 +85,7 @@ test-suite hspec-golden-test
build-depends:
base >=4.6 && <5
, directory
, filepath
, hspec
, hspec-core
, hspec-golden
Expand Down
2 changes: 2 additions & 0 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ executables:
- directory >= 1.2.5.0
- optparse-applicative >= 0.18.1 && < 0.19
- ansi-terminal >= 1.0 && < 2.0
- filepath

tests:
hspec-golden-test:
Expand All @@ -70,5 +71,6 @@ tests:
- hspec
- hspec-golden
- hspec-core
- filepath
build-tools:
- hspec-discover:hspec-discover >= 2.0 && < 3.0
65 changes: 31 additions & 34 deletions src/Test/Hspec/Golden.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type @String@. If your SUT has a different output, you can use 'Golden'.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE NamedFieldPuns #-}

module Test.Hspec.Golden
( Golden(..)
Expand Down Expand Up @@ -61,12 +62,12 @@ import Test.Hspec.Core.Spec (Example (..), FailureReason (..),

data Golden str =
Golden {
output :: str, -- ^ Output
outputDir :: FilePath, -- ^ Output directory for golden test results
filename :: String, -- ^ filename of golden test result
output :: str, -- ^ Output content
encodePretty :: str -> String, -- ^ Makes the comparison pretty when the test fails
writeToFile :: FilePath -> str -> IO (), -- ^ How to write into the golden file the file
readFromFile :: FilePath -> IO str, -- ^ How to read the file,
goldenFile :: FilePath, -- ^ Where to read/write the golden file for this test.
actualFile :: Maybe FilePath, -- ^ Where to save the actual file for this test. If it is @Nothing@ then no file is written.
failFirstTime :: Bool -- ^ Whether to record a failure the first time this test is run
}

Expand Down Expand Up @@ -118,14 +119,14 @@ fromGoldenResult (MismatchOutput expected actual) =
-- @

defaultGolden :: String -> String -> Golden String
defaultGolden name output_ =
defaultGolden filename output =
Golden {
output = output_,
filename,
output,
outputDir = ".golden",
encodePretty = show,
writeToFile = writeFile,
readFromFile = readFile,
goldenFile = ".golden" </> name </> "golden",
actualFile = Just (".golden" </> name </> "actual"),
failFirstTime = False
}

Expand All @@ -140,34 +141,30 @@ data GoldenResult =
-- | Runs a Golden test.

runGolden :: Eq str => Golden str -> IO GoldenResult
runGolden Golden{..} =
let goldenTestDir = takeDirectory goldenFile
in do
createDirectoryIfMissing True goldenTestDir
goldenFileExist <- doesFileExist goldenFile

case actualFile of
Nothing -> return ()
Just actual -> do
-- It is recommended to always write the actual file, this way,
-- hgold will always upgrade based on the latest run
let actualDir = takeDirectory actual
createDirectoryIfMissing True actualDir
writeToFile actual output

if not goldenFileExist
then do
writeToFile goldenFile output
return $ if failFirstTime
then FirstExecutionFail
else FirstExecutionSucceed
else do
contentGolden <- readFromFile goldenFile

if contentGolden == output
then return SameOutput
else return $ MismatchOutput (encodePretty contentGolden) (encodePretty output)
runGolden Golden{..} = do
let goldenTestDir = outputDir

let goldenFile = filename <> "-golden"
let actualFile = filename <> "-actual"

createDirectoryIfMissing True goldenTestDir

writeToFile (goldenTestDir </> actualFile) output

goldenFileExist <- doesFileExist (goldenTestDir </> goldenFile)

if not goldenFileExist
then do
writeToFile (goldenTestDir </> goldenFile) output
return $ if failFirstTime
then FirstExecutionFail
else FirstExecutionSucceed
else do
contentGolden <- readFromFile (goldenTestDir </> goldenFile)

if contentGolden == output
then return SameOutput
else return $ MismatchOutput (encodePretty contentGolden) (encodePretty output)

-- | A helper function to create a golden test.
--
Expand Down
28 changes: 19 additions & 9 deletions test/Test/Hspec/GoldenSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,28 @@ import Test.Hspec.Golden

import System.Directory
import System.IO.Silently
import System.FilePath ((</>))

{-# ANN module "HLint: ignore Reduce duplication" #-}

fixtureContent, fixtureTestName, fixtureUpdatedContent :: String

fixtureUpdatedContent :: String
fixtureUpdatedContent = "different text"

fixtureContent :: String
fixtureContent = "simple text"

fixtureTestName :: String
fixtureTestName = "id"

goldenTestDir, goldenFilePath, actualFilePath :: FilePath
goldenTestDir = ".golden" ++ "/" ++ "id"
goldenFilePath = goldenTestDir ++ "/" ++ "golden"
actualFilePath = goldenTestDir ++ "/" ++ "actual"
goldenTestDir :: FilePath
goldenTestDir = ".golden"

goldenFilePath :: FilePath
goldenFilePath = goldenTestDir </> "id-golden"

actualFilePath :: FilePath
actualFilePath = goldenTestDir </> "id-actual"

fixtureTest :: String -> H.Spec
fixtureTest content =
Expand Down Expand Up @@ -66,7 +76,7 @@ spec =
void $ runSpec $ fixtureTest fixtureUpdatedContent
actualFileContent <- readFile actualFilePath
actualFileContent `shouldBe` fixtureUpdatedContent

it "shouldn't override the `golden` file" $ do
void $ runSpec $ fixtureTest fixtureContent
void $ runSpec $ fixtureTest fixtureUpdatedContent
Expand All @@ -78,18 +88,18 @@ spec =
void $ runSpec $ fixtureTest fixtureContent
result <- readFile goldenFilePath
pure $ defaultGolden "io-test" result

context "when the output is not updated" $
context "when the test is executed a second time" $
it "shouldn't change the `golden` file content" $ do
void $ runSpec $ fixtureTest fixtureContent
void $ runSpec $ fixtureTest fixtureContent
goldenFileContent <- readFile goldenFilePath
goldenFileContent `shouldBe` fixtureContent

describe "golden" $
context "given some input" $
it "creates file with separated dashes" $ do
void $ runSpec $ fixtureGoldenTest fixtureContent
goldenFile <- readFile ".golden/id-golden-sample-file/golden"
goldenFile <- readFile ".golden/id-golden-sample-file-golden"
goldenFile `shouldBe` fixtureContent