darcs-2.18.2: harness/Darcs/Test/Shell.hs
{-# LANGUAGE CPP, OverloadedStrings, ExtendedDefaultRules, RecordWildCards #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
module Darcs.Test.Shell
( Format(..)
, DiffAlgorithm(..)
, UseIndex(..)
, UseCache(..)
, findShell
) where
import Darcs.Prelude
import Control.Exception ( SomeException )
import Data.Data ( Data, Typeable )
import Data.Text ( Text, pack, unpack )
import qualified Data.Text as T
import qualified Shelly ( FilePath, run )
import Shelly
( Sh
, catch_sh
, cd
, cp
, fromText
, get_env_text
, initOutputHandles
, lastExitCode
, lastStderr
, mkdir
, mkdir_p
, onCommandHandles
, pwd
, setenv
, shelly
, silently
, sub
, toTextIgnore
, withTmpDir
, writefile
, (</>)
)
import qualified System.FilePath as Native ( searchPathSeparator, splitSearchPath )
import System.FilePath ( makeRelative, takeBaseName, takeDirectory )
import qualified System.FilePath.Posix as Posix ( searchPathSeparator )
import System.IO ( hSetBinaryMode )
import Test.Framework.Providers.API
( Test(..)
, TestResultlike(..)
, Testlike(..)
, liftIO
, runImprovingIO
, yieldImprovement
)
data Format = Darcs1 | Darcs2 | Darcs3 deriving (Show, Eq, Typeable, Data)
data DiffAlgorithm = Myers | Patience deriving (Show, Eq, Typeable, Data)
data UseIndex = NoIndex | WithIndex deriving (Show, Eq, Typeable, Data)
data UseCache = NoCache | WithCache deriving (Show, Eq, Typeable, Data)
data ShellTest = ShellTest
{ format :: Format
, testfile :: FilePath
, testdir :: Maybe FilePath -- ^ only if you want to set it explicitly
, darcspath :: FilePath
, ghcflags :: String
, diffalgorithm :: DiffAlgorithm
, useindex :: UseIndex
, usecache :: UseCache
} deriving (Typeable)
data Running = Running deriving Show
data Result = Success | Skipped | Failed String
instance Show Result where
show Success = "Success"
show Skipped = "Skipped"
show (Failed f) = unlines (map ("| " ++) $ lines f)
instance TestResultlike Running Result where
testSucceeded Success = True
testSucceeded Skipped = True
testSucceeded _ = False
instance Testlike Running Result ShellTest where
testTypeName _ = "Shell"
runTest _ test =
runImprovingIO $ do
yieldImprovement Running
liftIO (shelly $ runtest test)
-- | Environment variable values may need translating depending on whether we
-- are setting them directly or writing out a shell script to set them, and
-- depending on the kind of value and the platform. This type captures the
-- different kinds of values.
data EnvItem
= EnvString String
-- ^ A normal string that won't need conversion
| EnvFilePath Shelly.FilePath
-- ^ A path on disk that may need conversion for the platform
| EnvSearchPath [Shelly.FilePath]
-- ^ A list of paths on disk, for the PATH variable
runtest' :: ShellTest -> Text -> Sh Result
runtest' ShellTest{..} srcdir =
do
wd <- pwd
p <- unpack <$> get_env_text "PATH"
let pathToUse =
map (fromText . pack) $ takeDirectory darcspath : Native.splitSearchPath p
let env =
[ ("HOME" , EnvFilePath wd)
-- in case someone has XDG_CACHE_HOME set:
, ("XDG_CACHE_HOME" , EnvFilePath (wd </> ".cache"))
, ("TESTDATA", EnvFilePath (srcdir </> "tests" </> "data"))
, ("TESTBIN", EnvFilePath (srcdir </> "tests" </> "bin"))
, ("DARCS_TESTING_PREFS_DIR" , EnvFilePath $ wd </> ".darcs")
, ("EMAIL" , EnvString "tester")
, ("GIT_AUTHOR_NAME" , EnvString "tester")
, ("GIT_AUTHOR_EMAIL" , EnvString "tester")
, ("GIT_COMMITTER_NAME" , EnvString "tester")
, ("GIT_COMMITTER_EMAIL" , EnvString "tester")
, ("DARCS_DONT_COLOR" , EnvString "1")
, ("DARCS_DONT_ESCAPE_ANYTHING", EnvString "1")
, ("PATH" , EnvSearchPath pathToUse)
-- the DARCS variable is passed to the tests purely so they can
-- double-check that the darcs on the path is the expected one,
-- so is passed as a string directly without any translation
, ("DARCS" , EnvString darcspath)
, ("GHC_FLAGS" , EnvString ghcflags)
, ("GHC_VERSION", EnvString $ show (__GLASGOW_HASKELL__ :: Int))
-- https://www.joshkel.com/2018/01/18/symlinks-in-windows/
, ("MSYS" , EnvString "winsymlinks:nativestrict")
]
-- we write the variables to a shell script and source them from there in
-- ./lib, so that it's easy to reproduce a test failure after running the
-- harness with -d.
writefile "env" $ T.unlines $ map
(\(k, v) -> T.concat ["export ", k, "=", envItemForScript v])
env
-- just in case the test script doesn't source ./lib:
mapM_ (\(k, v) -> setenv k (envItemForEnv v)) env
mkdir ".darcs"
writefile ".darcs/defaults" defaults
_ <-
onCommandHandles (initOutputHandles (\h -> hSetBinaryMode h True))
$ Shelly.run "bash" ["test"]
return Success
`catch_sh` \(_ :: SomeException) -> do
code <- lastExitCode
case code of
200 -> return Skipped
_ -> Failed <$> unpack <$> lastStderr
where
defaults =
pack
$ unlines
$ [ "ALL " ++ fmtstr
, "send no-edit-description"
, "ALL " ++ uif
, "ALL " ++ daf
]
++ ucf
fmtstr = case format of
Darcs3 -> "darcs-3"
Darcs2 -> "darcs-2"
Darcs1 -> "darcs-1"
daf = case diffalgorithm of
Patience -> "patience"
Myers -> "myers"
uif = case useindex of
WithIndex -> "no-ignore-times"
NoIndex -> "ignore-times"
ucf = case usecache of
WithCache -> []
NoCache -> ["ALL no-cache"]
-- convert an 'EnvItem' to a string you can put in the environment directly
envItemForEnv :: EnvItem -> Text
envItemForEnv (EnvString v) = pack v
envItemForEnv (EnvFilePath v) = toTextIgnore v
envItemForEnv (EnvSearchPath vs) =
T.intercalate (T.singleton Native.searchPathSeparator) $ map toTextIgnore vs
-- convert an 'EnvItem' to a string that will evaluate to the right value
-- when embedded in a bash script
envItemForScript :: EnvItem -> Text
envItemForScript (EnvString v) = quoteForShell (pack v)
envItemForScript (EnvFilePath v) = filePathForScript v
envItemForScript (EnvSearchPath vs) =
-- note the use of the Posix search path separator (':') regardless of platform
T.intercalate (T.singleton Posix.searchPathSeparator)
$ map filePathForScript vs
-- add quotes around a 'Shelly.FilePath'
quotedFilePath :: Shelly.FilePath -> Text
quotedFilePath = quoteForShell . toTextIgnore
quoteForShell :: Text -> Text
quoteForShell = surround '\'' . T.replace "'" "'\\''"
where surround c t = T.cons c $ T.snoc t c
-- convert a 'Shelly.FilePath' into a string that will evaluate to the right
-- value when put in a bash script
filePathForScript :: Shelly.FilePath -> Text
#ifdef WIN32
-- we have a native Windows path, but we are going to put it in an bash
-- script run in an environment like msys2 which works with an illusion
-- of a Unix style filesystem. Calling cygpath at runtime does the
-- necessary translation.
filePathForScript v = T.concat ["$(cygpath ", quotedFilePath v, ")"]
#else
filePathForScript v = quotedFilePath v
#endif
runtest :: ShellTest -> Sh Result
runtest test@ShellTest{..} =
withTmp $ \dir -> do
cp "tests/lib" dir
cp "tests/network/sshlib" dir
cp "tests/network/httplib" dir
cp (fromText $ pack $ testfile) (dir </> "test")
srcdir <- pwd
silently $ sub $ cd dir >> runtest' test (toTextIgnore srcdir)
where
withTmp =
case testdir of
Just dir ->
\job -> do
let d =
dir </> show format </> show diffalgorithm </>
show useindex </>
show usecache </>
takeTestName testfile
mkdir_p d
job d
Nothing -> withTmpDir
findShell
:: FilePath
-> [FilePath]
-> Maybe FilePath
-> String
-> [DiffAlgorithm]
-> [Format]
-> [UseIndex]
-> [UseCache]
-> IO [Test]
findShell dp files tdir ghcflags diffAlgorithms repoFormats useindexs usecaches =
do
return
[ shellTest
ShellTest
{ format = fmt
, testfile = file
, testdir = tdir
, darcspath = dp
, ghcflags = ghcflags
, diffalgorithm = da
, useindex = ui
, usecache = uc
}
| file <- files
, fmt <- repoFormats
, da <- diffAlgorithms
, ui <- useindexs
, uc <- usecaches
]
shellTest :: ShellTest -> Test
shellTest test@ShellTest{..} = Test name test
where
name =
concat
[ unpack (toTextIgnore (takeTestName testfile))
, " ("
, show format
, ","
, show diffalgorithm
, ","
, show useindex
, ","
, show usecache
, ")"
]
takeTestName :: FilePath -> Shelly.FilePath
takeTestName n =
let n' = makeRelative "tests" n
in takeBaseName (takeDirectory n') </> takeBaseName n'