@@ -80,9 +80,9 @@ import Control.Monad (Functor (fmap), Monad (return, (>>=)), mapM_, un
80
80
import Control.Monad.Catch (MonadCatch )
81
81
import Control.Monad.Morph (hoist )
82
82
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 )
84
84
import Data.Aeson (Result (.. ))
85
- import Data.Bool (Bool , (&& ) , otherwise )
85
+ import Data.Bool (Bool ( .. ), otherwise , (&&) )
86
86
import Data.Either (Either (.. ), either )
87
87
import Data.Eq (Eq ((/=) ))
88
88
import Data.Foldable (for_ )
@@ -96,7 +96,7 @@ import Data.String (String)
96
96
import Data.Time.Clock (NominalDiffTime , UTCTime )
97
97
import Data.Traversable (Traversable )
98
98
import Data.Tuple (snd )
99
- import GHC.Stack ( CallStack , HasCallStack )
99
+ import GHC.Stack
100
100
import Hedgehog (MonadTest )
101
101
import Hedgehog.Extras.Internal.Test.Integration (Integration , IntegrationState (.. ))
102
102
import Hedgehog.Extras.Stock.CallStack (callerModuleName )
@@ -111,7 +111,10 @@ import Text.Show (Show (show))
111
111
112
112
import qualified Control.Concurrent as IO
113
113
import qualified Control.Concurrent.STM as STM
114
+ import Control.Exception (IOException )
115
+ import Control.Monad.Catch (Handler (.. ))
114
116
import qualified Control.Monad.Trans.Resource as IO
117
+ import qualified Control.Retry as R
115
118
import qualified Data.List as L
116
119
import qualified Data.Time.Clock as DTC
117
120
import qualified GHC.Stack as GHC
@@ -148,16 +151,32 @@ failMessage cs = failWithCustom cs Nothing
148
151
--
149
152
-- The directory will be deleted if the block succeeds, but left behind if
150
153
-- 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
153
162
systemTemp <- H. evalIO IO. getCanonicalTemporaryDirectory
154
163
maybeKeepWorkspace <- H. evalIO $ IO. lookupEnv " KEEP_WORKSPACE"
155
164
ws <- H. evalIO $ IO. createTempDirectory systemTemp $ prefixPath <> " -test"
156
165
H. annotate $ " Workspace: " <> ws
157
166
H. evalIO $ IO. writeFile (ws </> " module" ) callerModuleName
158
167
f ws
159
168
when (IO. os /= " mingw32" && maybeKeepWorkspace /= Just " 1" ) $ do
160
- H. evalIO $ IO. removePathForcibly ws
169
+ -- try to delete the directory 5 times, 100ms apart
170
+ let retryPolicy = R. constantDelay 100000 <> R. limitRetries 10
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 try multiple times before we fail.
175
+ void
176
+ . register
177
+ . R. recovering retryPolicy [ioExH]
178
+ . const
179
+ $ IO. removePathForcibly ws
161
180
162
181
-- | Create a workspace directory which will exist for at least the duration of
163
182
-- the supplied block.
@@ -169,7 +188,7 @@ workspace prefixPath f = GHC.withFrozenCallStack $ do
169
188
-- the block fails.
170
189
--
171
190
-- 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 ()
173
192
moduleWorkspace prefix f = GHC. withFrozenCallStack $ do
174
193
let srcModule = maybe " UnknownModule" (GHC. srcLocModule . snd ) (listToMaybe (GHC. getCallStack GHC. callStack))
175
194
workspace (prefix <> " -" <> srcModule) f
0 commit comments