avoid setEnv in test framework when tasty is running

setEnv is not thread safe and could cause a getEnv by another thread to
segfault, or perhaps other had behavior. This is particularly a problem
when using tasty, because tasty runs the test in a thread, and a getEnv
in another thread.

The use of top-level TMVars is ugly, but ok because only 1 test actually
runs at a time per process. Because it has to chdir into the test repo.

The setEnv that remains happens before tasty is running.

Sponsored-by: Dartmouth College's Datalad project
This commit is contained in:
Joey Hess 2022-05-18 16:41:41 -04:00
parent ebb76f0486
commit 1cacfd1b19
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
2 changed files with 70 additions and 46 deletions

View file

@ -24,6 +24,7 @@ import System.Environment (getArgs)
import System.Console.Concurrent
import System.Console.ANSI
import GHC.Conc
import System.IO.Unsafe (unsafePerformIO)
import Common
import Types.Test
@ -260,14 +261,42 @@ ensuredir d = do
e <- doesDirectoryExist d
unless e $
createDirectory d
{- Prevent global git configs from affecting the test suite. -}
isolateGitConfig :: IO a -> IO a
isolateGitConfig a = Utility.Tmp.Dir.withTmpDir "testhome" $ \tmphome -> do
{- This is the only place in the test suite that can use setEnv.
- Using it elsewhere can conflict with tasty's use of getEnv, which can
- happen concurrently with a test case running, and would be a problem
- since setEnv is not thread safe. This is run before tasty. -}
setTestEnv :: IO a -> IO a
setTestEnv a = Utility.Tmp.Dir.withTmpDir "testhome" $ \tmphome -> do
tmphomeabs <- fromRawFilePath <$> absPath (toRawFilePath tmphome)
{- Prevent global git configs from affecting the test suite. -}
Utility.Env.Set.setEnv "HOME" tmphomeabs True
Utility.Env.Set.setEnv "XDG_CONFIG_HOME" tmphomeabs True
Utility.Env.Set.setEnv "GIT_CONFIG_NOSYSTEM" "1" True
-- Ensure that the same git-annex binary that is running
-- git-annex test is at the front of the PATH.
p <- Utility.Env.getEnvDefault "PATH" ""
pp <- Annex.Path.programPath
Utility.Env.Set.setEnv "PATH" (takeDirectory pp ++ [searchPathSeparator] ++ p) True
-- Avoid git complaining if it cannot determine the user's
-- email address, or exploding if it doesn't know the user's name.
Utility.Env.Set.setEnv "GIT_AUTHOR_EMAIL" "test@example.com" True
Utility.Env.Set.setEnv "GIT_AUTHOR_NAME" "git-annex test" True
Utility.Env.Set.setEnv "GIT_COMMITTER_EMAIL" "test@example.com" True
Utility.Env.Set.setEnv "GIT_COMMITTER_NAME" "git-annex test" True
-- force gpg into batch mode for the tests
Utility.Env.Set.setEnv "GPG_BATCH" "1" True
-- Make git and git-annex access ssh remotes on the local
-- filesystem, without using ssh at all.
Utility.Env.Set.setEnv "GIT_SSH_COMMAND" "git-annex test --fakessh --" True
Utility.Env.Set.setEnv "GIT_ANNEX_USE_GIT_SSH" "1" True
-- Record top directory.
currdir <- getCurrentDirectory
Utility.Env.Set.setEnv "TOPDIR" currdir True
a
removeDirectoryForCleanup :: FilePath -> IO ()
@ -455,7 +484,7 @@ data TestMode = TestMode
, adjustedUnlockedBranch :: Bool
, annexVersion :: Types.RepoVersion.RepoVersion
, keepFailures :: Bool
} deriving (Read, Show)
} deriving (Show)
testMode :: TestOptions -> Types.RepoVersion.RepoVersion -> TestMode
testMode opts v = TestMode
@ -471,47 +500,32 @@ hasUnlockedFiles m = unlockedFiles m || adjustedUnlockedBranch m
withTestMode :: TestMode -> TestTree -> TestTree
withTestMode testmode = withResource prepare release . const
where
prepare = do
setTestMode testmode
setmainrepodir =<< newmainrepodir
prepare = setTestMode testmode
release _ = noop
{- The current test mode is stored here while a test is running.
-
- Only one test can be running at a time by a process; running a
- test also involves chdir into a test repository.
-}
{-# NOINLINE currentTestMode #-}
currentTestMode :: TMVar TestMode
currentTestMode = unsafePerformIO newEmptyTMVarIO
currentMainRepoDir :: TMVar FilePath
currentMainRepoDir = unsafePerformIO newEmptyTMVarIO
setTestMode :: TestMode -> IO ()
setTestMode testmode = do
currdir <- getCurrentDirectory
p <- Utility.Env.getEnvDefault "PATH" ""
pp <- Annex.Path.programPath
mapM_ (\(var, val) -> Utility.Env.Set.setEnv var val True)
-- Ensure that the same git-annex binary that is running
-- git-annex test is at the front of the PATH.
[ ("PATH", takeDirectory pp ++ [searchPathSeparator] ++ p)
, ("TOPDIR", currdir)
-- Avoid git complaining if it cannot determine the user's
-- email address, or exploding if it doesn't know the user's
-- name.
, ("GIT_AUTHOR_EMAIL", "test@example.com")
, ("GIT_AUTHOR_NAME", "git-annex test")
, ("GIT_COMMITTER_EMAIL", "test@example.com")
, ("GIT_COMMITTER_NAME", "git-annex test")
-- force gpg into batch mode for the tests
, ("GPG_BATCH", "1")
-- Make git and git-annex access ssh remotes on the local
-- filesystem, without using ssh at all.
, ("GIT_SSH_COMMAND", "git-annex test --fakessh --")
, ("GIT_ANNEX_USE_GIT_SSH", "1")
, ("TESTMODE", show testmode)
]
runFakeSsh :: [String] -> IO ()
runFakeSsh ("-n":ps) = runFakeSsh ps
runFakeSsh (_host:cmd:[]) =
withCreateProcess (shell cmd) $
\_ _ _ pid -> exitWith =<< waitForProcess pid
runFakeSsh ps = error $ "fake ssh option parse error: " ++ show ps
atomically $ do
_ <- tryTakeTMVar currentTestMode
putTMVar currentTestMode testmode
setmainrepodir =<< newmainrepodir
getTestMode :: IO TestMode
getTestMode = Prelude.read <$> Utility.Env.getEnvDefault "TESTMODE" ""
getTestMode = atomically (tryReadTMVar currentTestMode) >>= \case
Just tm -> return tm
Nothing -> error "getTestMode without setTestMode"
setupTestMode :: IO ()
setupTestMode = do
@ -528,12 +542,15 @@ changeToTopDir t = do
tmpdir :: String
tmpdir = ".t"
mainrepodir :: IO FilePath
mainrepodir = Utility.Env.getEnvDefault "MAINREPODIR"
(giveup "MAINREPODIR not set")
setmainrepodir :: FilePath -> IO ()
setmainrepodir d = Utility.Env.Set.setEnv "MAINREPODIR" d True
setmainrepodir mrd = atomically $ do
_ <- tryTakeTMVar currentMainRepoDir
putTMVar currentMainRepoDir mrd
mainrepodir :: IO FilePath
mainrepodir = atomically (tryReadTMVar currentMainRepoDir) >>= \case
Just tm -> return tm
Nothing -> error "mainrepodir without setmainrepodir"
newmainrepodir :: IO FilePath
newmainrepodir = go (0 :: Int)
@ -676,6 +693,13 @@ make_writeable d = void $
Utility.Process.Transcript.processTranscript
"chmod" ["-R", "u+w", d] Nothing
runFakeSsh :: [String] -> IO ()
runFakeSsh ("-n":ps) = runFakeSsh ps
runFakeSsh (_host:cmd:[]) =
withCreateProcess (shell cmd) $
\_ _ _ pid -> exitWith =<< waitForProcess pid
runFakeSsh ps = error $ "fake ssh option parse error: " ++ show ps
{- Tests each TestTree in parallel, and exits with succcess/failure.
-
- Tasty supports parallel tests, but this does not use it, because

View file

@ -19,4 +19,4 @@ There is also Utility.Gpg.testHarness, which sets GNUPGHOME. It seems that
instead, every place that git-annex is run inside the gpg test harness
would need to add GNUPGHOME to the environment of the git-annex process.
> Fixed this part to not setEnv. --[[Joey]]
> [[fixed|done]] --[[Joey]]