-
Notifications
You must be signed in to change notification settings - Fork 81
/
Copy pathIntegrationTesting.hs
179 lines (156 loc) · 6.64 KB
/
IntegrationTesting.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
module IntegrationTesting
( bazelCmd
, setupWorkspace
, setupTestBazel
, assertSuccess
, assertFailure
, outputSatisfy
, failedOutputSatisfy
, formatOutput
) where
import qualified System.Process as Process
import System.Environment (getEnv, unsetEnv, lookupEnv)
import System.Info (os)
import System.FilePath (pathSeparators, (</>))
import System.Directory (copyFile, doesDirectoryExist, doesFileExist, removePathForcibly, createDirectory, listDirectory, doesPathExist)
import Data.Text (pack, unpack, replace, breakOn)
import Data.Text.IO (readFile, writeFile)
import System.Exit (ExitCode(..))
import Control.Monad (when, unless, forM_)
import Test.Hspec (shouldSatisfy, expectationFailure)
import qualified Bazel.Runfiles as Runfiles
bazelCmd :: String -> String -> IO ([String] -> Process.CreateProcess)
bazelCmd workspaceDir outputUserRoot = do
bazelPath <- getEnv "BIT_BAZEL_BINARY"
config <- (fmap bazelConfig isNixpkgs)
let bazelConfigurableSubcommands =
["aquery", "build", "canonicalize-flags", "clean", "coverage", "cquery", "info", "mobile-install", "print_action", "run", "test"]
return (\args -> case args of
subcommand:xs | elem subcommand bazelConfigurableSubcommands -> (Process.proc bazelPath (["--output_user_root", outputUserRoot, subcommand, "--config", config] ++ xs)) { Process.cwd = Just workspaceDir }
xs -> (Process.proc bazelPath (["--output_user_root", outputUserRoot] ++ xs)) { Process.cwd = Just workspaceDir })
isNixpkgs :: IO Bool
isNixpkgs = lookupEnv "NIXPKGS" >>= \value ->
case value of
Just "1" -> pure True
_ -> pure False
bazelConfig :: Bool -> String
bazelConfig isnix
| isnix = case os of
"darwin" -> "macos-nixpkgs"
_ -> "linux-nixpkgs"
| otherwise = case os of
"darwin" -> "macos-bindist"
"mingw32" -> "windows-bindist"
_ -> "linux-bindist"
outputBaseDir :: IO String
outputBaseDir = do
tmpDir <- getEnv "TEST_TMPDIR"
return (unpack . fst $ breakOn (pack (pathSeparators ++ "execroot" ++ pathSeparators)) (pack tmpDir))
replaceInFile :: FilePath -> String -> String -> IO ()
replaceInFile path from to = do
content <- Data.Text.IO.readFile path
Data.Text.IO.writeFile path (replace (pack from) (pack to) content)
removeDirIfExist :: FilePath -> IO ()
removeDirIfExist path = do
dirExist <- doesDirectoryExist path
when dirExist (removePathForcibly path)
createDirIfNotExist :: FilePath -> IO ()
createDirIfNotExist path = do
dirExist <- doesDirectoryExist path
unless dirExist (createDirectory path)
generateBazelRc :: FilePath -> IO ()
generateBazelRc dir = do
alreadyExist <- doesFileExist (dir </> ".bazelrc")
unless alreadyExist $ Data.Text.IO.writeFile (dir </> ".bazelrc") (pack " \n\
\ build:linux-nixpkgs --host_platform=@io_tweag_rules_nixpkgs//nixpkgs/platforms:host \n\
\ build:linux-nixpkgs --incompatible_enable_cc_toolchain_resolution \n\
\ build:macos-nixpkgs --host_platform=@io_tweag_rules_nixpkgs//nixpkgs/platforms:host \n\
\ build:macos-nixpkgs --incompatible_enable_cc_toolchain_resolution \n\
\ build:linux-bindist --incompatible_enable_cc_toolchain_resolution \n\
\ build:macos-bindist --incompatible_enable_cc_toolchain_resolution \n\
\ build:windows-bindist --incompatible_enable_cc_toolchain_resolution \n\
\ common --enable_platform_specific_config \n\
\ common:macos --repo_env=BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 \n\
\ common:windows --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 \n\
\ ")
setupWorkspace :: IO (String, String)
setupWorkspace = do
runfiles <- Runfiles.create
workspaceDir <- getEnv "BIT_WORKSPACE_DIR"
bazelBinId <- getEnv "BIT_BAZEL_BIN_ID"
outputBase <- outputBaseDir
let execDir = outputBase </> "bazel_testing"
createDirIfNotExist execDir
let newWorkspaceDir = execDir </> bazelBinId
let outputUserRoot = outputBase
removeDirIfExist newWorkspaceDir
copyDirectoryRecursive workspaceDir newWorkspaceDir
generateBazelRc newWorkspaceDir
removeDirIfExist (execDir </> "rules_haskell")
copyDirectoryRecursive (Runfiles.rlocation runfiles "rules_haskell") (execDir </> "rules_haskell")
replaceInFile (newWorkspaceDir </> "WORKSPACE") "%RULES_HASKELL_PATH%" "../rules_haskell"
return (newWorkspaceDir, outputUserRoot)
setupTestBazel :: IO ([String] -> Process.CreateProcess)
setupTestBazel = setupWorkspace >>= uncurry bazelCmd
-- * Action helpers
-- | Ensure that @(stdout, stderr)@ of the command satisfies a predicate
outputSatisfy
:: ((String, String) -> Bool)
-> Process.CreateProcess
-> IO ()
outputSatisfy predicate cmd = do
(exitCode, stdout, stderr) <- Process.readCreateProcessWithExitCode cmd ""
case exitCode of
ExitSuccess -> (stdout, stderr) `shouldSatisfy` predicate
ExitFailure _ -> expectationFailure (formatOutput exitCode stdout stderr)
-- | Ensure that command is failed and @(stdout, stderr)@ of the command satisfies a predicate
failedOutputSatisfy
:: ((String, String) -> Bool)
-> Process.CreateProcess
-> IO ()
failedOutputSatisfy predicate cmd = do
(exitCode, stdout, stderr) <- Process.readCreateProcessWithExitCode cmd ""
case exitCode of
ExitFailure _ -> (stdout, stderr) `shouldSatisfy` predicate
ExitSuccess -> expectationFailure (formatOutput exitCode stdout stderr)
-- | The command must succeed
assertSuccess :: Process.CreateProcess -> IO ()
assertSuccess = outputSatisfy (const True)
-- | The command must fail
assertFailure :: Process.CreateProcess -> IO ()
assertFailure cmd = do
(exitCode, stdout, stderr) <- Process.readCreateProcessWithExitCode cmd ""
case exitCode of
ExitFailure _ -> pure ()
ExitSuccess -> expectationFailure ("Unexpected success of a failure test with output:\n" ++ formatOutput exitCode stdout stderr)
-- * Formatting helpers
formatOutput :: ExitCode -> String -> String -> String
formatOutput exitcode stdout stderr =
let
header = replicate 20 '-'
headerLarge = replicate 20 '='
in unlines [
headerLarge
, "Exit Code: " <> show exitcode
, headerLarge
, "Standard Output"
, header
, stdout
, headerLarge
, "Error Output"
, header
, stderr
, header]
copyDirectoryRecursive :: FilePath -> FilePath -> IO ()
copyDirectoryRecursive srcDir dstDir = do
unlessM (doesPathExist dstDir) (createDirectory dstDir)
entries <- listDirectory srcDir
forM_ entries $ \name -> do
let srcPath = srcDir </> name
let dstPath = dstDir </> name
isDir <- doesDirectoryExist srcPath
if isDir
then copyDirectoryRecursive srcPath dstPath
else copyFile srcPath dstPath
where
unlessM b f = do b <- b; if b then pure() else f