From 55e96c46d2d8699887cea09d5cf8d4626cdc62f9 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Fri, 1 Sep 2023 18:40:07 +0200 Subject: [PATCH] Add errors for multiple writes to the same file See #1000 --- lib/Hakyll/Core/Runtime.hs | 25 +++++++++++++++++++++ tests/Hakyll/Core/Runtime/Tests.hs | 35 +++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/Hakyll/Core/Runtime.hs b/lib/Hakyll/Core/Runtime.hs index 71b2381d..429560b5 100644 --- a/lib/Hakyll/Core/Runtime.hs +++ b/lib/Hakyll/Core/Runtime.hs @@ -137,6 +137,9 @@ data Scheduler = Scheduler schedulerDone :: !(Set Identifier) , -- | Any snapshots stored. schedulerSnapshots :: !(Set (Identifier, Snapshot)) + , -- | Any 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 @@ -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 @@ -344,6 +348,25 @@ 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 identifier path scheduler0@Scheduler {..} = + case Map.lookup path schedulerRoutes of + Nothing -> + let routes = Map.insert path identifier schedulerRoutes in + (scheduler0 {schedulerRoutes = routes}, ()) + Just otherIdentifier -> + let msg = "multiple writes for route " ++ path ++ ": " ++ + show identifier ++ " and " ++ show otherIdentifier in + schedulerError (Just identifier) msg scheduler0 + + -------------------------------------------------------------------------------- build :: RunMode -> ReaderT RuntimeRead IO () build mode = do @@ -479,6 +502,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 diff --git a/tests/Hakyll/Core/Runtime/Tests.hs b/tests/Hakyll/Core/Runtime/Tests.hs index 8a5dde3e..bc2cf76d 100644 --- a/tests/Hakyll/Core/Runtime/Tests.hs +++ b/tests/Hakyll/Core/Runtime/Tests.hs @@ -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, (@?=)) -------------------------------------------------------------------------------- @@ -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 + ] -------------------------------------------------------------------------------- @@ -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