Skip to content

Commit d9db931

Browse files
committed
Make workspace cleanup retry on failure
1 parent 2a7d307 commit d9db931

File tree

3 files changed

+30
-9
lines changed

3 files changed

+30
-9
lines changed

.github/workflows/haskell.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
119119
tar zcvf artifacts-${{ runner.OS }}-${{ matrix.ghc }}.tar.gz artifacts
120120
121-
- uses: actions/upload-artifact@v2
121+
- uses: actions/upload-artifact@v4
122122
with:
123123
name: artifacts-${{ runner.OS }}-${{ matrix.ghc }}.tar.gz
124124
path: artifacts-${{ runner.OS }}-${{ matrix.ghc }}.tar.gz
@@ -226,7 +226,7 @@ jobs:
226226
os: [ubuntu-latest, macos-latest]
227227

228228
steps:
229-
- uses: actions/download-artifact@v2
229+
- uses: actions/download-artifact@v4
230230
id: download_artifact
231231
with:
232232
name: artifacts-${{ runner.OS }}-${{ matrix.ghc }}.tar.gz

hedgehog-extras.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ common mtl { build-depends: mtl
3737
common network { build-depends: network }
3838
common process { build-depends: process }
3939
common resourcet { build-depends: resourcet }
40+
common retry { build-depends: retry >= 0.9 }
4041
common stm { build-depends: stm }
4142
common tar { build-depends: tar ^>= 0.6 }
4243
common tasty { build-depends: tasty }
@@ -89,6 +90,7 @@ library
8990
network,
9091
process,
9192
resourcet,
93+
retry,
9294
stm,
9395
tar,
9496
temporary,

src/Hedgehog/Extras/Test/Base.hs

+26-7
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ import Control.Monad (Functor (fmap), Monad (return, (>>=)), mapM_, un
8080
import Control.Monad.Catch (MonadCatch)
8181
import Control.Monad.Morph (hoist)
8282
import Control.Monad.Reader (MonadIO (..), MonadReader (ask))
83-
import Control.Monad.Trans.Resource (ReleaseKey, runResourceT)
83+
import Control.Monad.Trans.Resource (MonadResource, ReleaseKey, register, runResourceT)
8484
import Data.Aeson (Result (..))
85-
import Data.Bool (Bool, (&&), otherwise)
85+
import Data.Bool (Bool (..), otherwise, (&&))
8686
import Data.Either (Either (..), either)
8787
import Data.Eq (Eq ((/=)))
8888
import Data.Foldable (for_)
@@ -96,7 +96,7 @@ import Data.String (String)
9696
import Data.Time.Clock (NominalDiffTime, UTCTime)
9797
import Data.Traversable (Traversable)
9898
import Data.Tuple (snd)
99-
import GHC.Stack (CallStack, HasCallStack)
99+
import GHC.Stack
100100
import Hedgehog (MonadTest)
101101
import Hedgehog.Extras.Internal.Test.Integration (Integration, IntegrationState (..))
102102
import Hedgehog.Extras.Stock.CallStack (callerModuleName)
@@ -111,7 +111,10 @@ import Text.Show (Show (show))
111111

112112
import qualified Control.Concurrent as IO
113113
import qualified Control.Concurrent.STM as STM
114+
import Control.Exception (IOException)
115+
import Control.Monad.Catch (Handler (..))
114116
import qualified Control.Monad.Trans.Resource as IO
117+
import qualified Control.Retry as R
115118
import qualified Data.List as L
116119
import qualified Data.Time.Clock as DTC
117120
import qualified GHC.Stack as GHC
@@ -148,16 +151,32 @@ failMessage cs = failWithCustom cs Nothing
148151
--
149152
-- The directory will be deleted if the block succeeds, but left behind if
150153
-- the block fails.
151-
workspace :: (MonadTest m, MonadIO m, HasCallStack) => FilePath -> (FilePath -> m ()) -> m ()
152-
workspace prefixPath f = GHC.withFrozenCallStack $ do
154+
workspace
155+
:: MonadTest m
156+
=> HasCallStack
157+
=> MonadResource m
158+
=> FilePath
159+
-> (FilePath -> m ())
160+
-> m ()
161+
workspace prefixPath f = withFrozenCallStack $ do
153162
systemTemp <- H.evalIO IO.getCanonicalTemporaryDirectory
154163
maybeKeepWorkspace <- H.evalIO $ IO.lookupEnv "KEEP_WORKSPACE"
155164
ws <- H.evalIO $ IO.createTempDirectory systemTemp $ prefixPath <> "-test"
156165
H.annotate $ "Workspace: " <> ws
157166
H.evalIO $ IO.writeFile (ws </> "module") callerModuleName
158167
f ws
159168
when (IO.os /= "mingw32" && maybeKeepWorkspace /= Just "1") $ do
160-
H.evalIO $ IO.removePathForcibly ws
169+
-- try to delete the directory 20 times, 100ms apart
170+
let retryPolicy = R.constantDelay 100000 <> R.limitRetries 20
171+
-- retry only on IOExceptions
172+
ioExH _ = Handler $ \(_ :: IOException) -> pure True
173+
-- For some reason, the temporary directory removal sometimes fails.
174+
-- Lets wrap this in MonadResource to try multiple times, during the cleanup, before we fail.
175+
void
176+
. register
177+
. R.recovering retryPolicy [ioExH]
178+
. const
179+
$ IO.removePathForcibly ws
161180

162181
-- | Create a workspace directory which will exist for at least the duration of
163182
-- the supplied block.
@@ -169,7 +188,7 @@ workspace prefixPath f = GHC.withFrozenCallStack $ do
169188
-- the block fails.
170189
--
171190
-- The 'prefix' argument should not contain directory delimeters.
172-
moduleWorkspace :: (MonadTest m, MonadIO m, HasCallStack) => String -> (FilePath -> m ()) -> m ()
191+
moduleWorkspace :: (MonadTest m, MonadResource m, HasCallStack) => String -> (FilePath -> m ()) -> m ()
173192
moduleWorkspace prefix f = GHC.withFrozenCallStack $ do
174193
let srcModule = maybe "UnknownModule" (GHC.srcLocModule . snd) (listToMaybe (GHC.getCallStack GHC.callStack))
175194
workspace (prefix <> "-" <> srcModule) f

0 commit comments

Comments
 (0)