Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add errors for multiple writes to the same file #1001

Merged
merged 2 commits into from
Sep 2, 2023
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
24 changes: 24 additions & 0 deletions lib/Hakyll/Core/Runtime.hs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ data Scheduler = Scheduler
schedulerDone :: !(Set Identifier)
, -- | Any snapshots stored.
schedulerSnapshots :: !(Set (Identifier, Snapshot))
, -- | Any routed files and who wrote them. This is used to detect multiple
-- writes to the same file, which can yield inconsistent results.
schedulerRoutes :: !(Map FilePath Identifier)
, -- | Currently blocked compilers.
schedulerBlocked :: !(Set Identifier)
, -- | Compilers that may resume on triggers
Expand All @@ -160,6 +163,7 @@ emptyScheduler = Scheduler {..}
schedulerQueue = Seq.empty
schedulerWorking = Set.empty
schedulerSnapshots = Set.empty
schedulerRoutes = Map.empty
schedulerBlocked = Set.empty
schedulerTriggers = Map.empty
schedulerStarved = 0
Expand Down Expand Up @@ -344,6 +348,24 @@ schedulerWrite identifier depFacts scheduler0@Scheduler {..} =
_ -> (scheduler2, step)


--------------------------------------------------------------------------------
-- | Record that a specific identifier was routed to a specific filepath.
-- This is used to detect multiple (inconsistent) writes to the same file.
schedulerRoute
:: Identifier
-> FilePath
-> Scheduler
-> (Scheduler, ())
schedulerRoute id0 path scheduler0@Scheduler {..}
| Just id1 <- Map.lookup path schedulerRoutes, id0 /= id1 =
let msg = "multiple writes for route " ++ path ++ ": " ++
show id0 ++ " and " ++ show id1 in
schedulerError (Just id0) msg scheduler0
| otherwise =
let routes = Map.insert path id0 schedulerRoutes in
(scheduler0 {schedulerRoutes = routes}, ())


--------------------------------------------------------------------------------
build :: RunMode -> ReaderT RuntimeRead IO ()
build mode = do
Expand Down Expand Up @@ -479,6 +501,8 @@ work id' compiler = do
case mroute of
Nothing -> return ()
Just route -> do
liftIO . IORef.atomicModifyIORef' scheduler $
schedulerRoute id' route
let path = destinationDirectory config </> route
liftIO $ makeDirectories path
liftIO $ write path item
Expand Down
35 changes: 32 additions & 3 deletions tests/Hakyll/Core/Runtime/Tests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Data.List (isInfixOf)
import System.Exit (ExitCode (..))
import System.FilePath ((</>))
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (Assertion, (@?=))
import Test.Tasty.HUnit (Assertion, assertBool, (@?=))


--------------------------------------------------------------------------------
Expand All @@ -24,8 +24,15 @@ import TestSuite.Util

--------------------------------------------------------------------------------
tests :: TestTree
tests = testGroup "Hakyll.Core.Runtime.Tests" $
fromAssertions "run" [case01, case02, case03, case04, case05, case06]
tests = testGroup "Hakyll.Core.Runtime.Tests" $ fromAssertions "run"
[ case01
, case02
, case03
, case04
, case05
, case06
, issue1000
]


--------------------------------------------------------------------------------
Expand Down Expand Up @@ -232,3 +239,25 @@ case06 = do
ec @?= ExitSuccess

cleanTestEnv


--------------------------------------------------------------------------------
issue1000 :: Assertion
issue1000 = do
(logger, inMemLog) <- Logger.newInMem
(ec, _) <- run RunModeNormal testConfiguration logger $ do
match "*.md" $ do
route $ setExtension "html"
compile getResourceBody
match "*.md" $ version "nav" $ do
route $ setExtension "html"
compile $ getResourceBody >>= traverse (pure . reverse)

ec @?= ExitFailure 1
msgs <- inMemLog
assertBool "missing 'multiple writes' errors" $ not $ null $
[ msg
| (Logger.Error, msg) <- msgs, "multiple writes" `isInfixOf` msg
]

cleanTestEnv