@@ -1,14 +1,15 @@
1{-# LANGUAGE LambdaCase #-}
1{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE PatternGuards #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE TypeApplications #-}
5{-# LANGUAGE TypeApplications #-}
4module Obelisk.SelfTest where
6module Obelisk.SelfTest where
6−import Control.Exception (Exception, throw, bracket)
8+import Control.Exception (bracket, throw)
7import Control.Monad
9import Control.Monad
8import Control.Monad.IO.Class
10import Control.Monad.IO.Class
9import Data.Bool (bool)
11import Data.Bool (bool)
10import Data.Function (fix)
12import Data.Function (fix)
11−import Data.Maybe (isJust)
12import Data.Semigroup (Semigroup, (<>))
13import Data.Semigroup (Semigroup, (<>))
13import qualified Data.Set as Set
14import qualified Data.Set as Set
14import Data.String
15import Data.String
@@ -21,15 +22,17 @@ import qualified Network.Socket as Socket
21import Shelly
22import Shelly
22import System.Directory (withCurrentDirectory)
23import System.Directory (withCurrentDirectory)
23import System.Environment
24import System.Environment
24−import System.Exit (ExitCode (..), exitWith)
25+import System.Exit (ExitCode (..))
25import System.Info
26import System.Info
26import System.IO (Handle, hClose)
27import System.IO (Handle, hClose)
27import System.IO.Temp
28import System.IO.Temp
29+import System.Process (readProcessWithExitCode)
28import System.Timeout
30import System.Timeout
29import Test.Hspec
31import Test.Hspec
30import Test.HUnit.Base
32import Test.HUnit.Base
31−import System.Process (readProcessWithExitCode)
34+import Obelisk.CliApp hiding (runCli)
35+import qualified Obelisk.CliApp as CliApp
33import Obelisk.Run (getConfigRoute)
36import Obelisk.Run (getConfigRoute)
35data ObRunState
38data ObRunState
@@ -50,18 +53,44 @@ commit msg = void $ run "git"
50 , doubleQuotes msg
53 , doubleQuotes msg
56+runCli :: MonadIO m => CliT IO a -> m a
57+runCli f = liftIO $ do
58+ c <- newCliConfig Notice False False
61+tshow :: Show a => a -> Text
64+-- | Like `shelly` but used when running `ob` commands
66+-- This tests the NIX_REMOTE workaround, whether that gets printed
67+-- on failure, and whether retrying with the suggested workaround
68+-- leads to the test succeeding.
69+shellyOb :: MonadIO m => (Sh a -> Sh a) -> Sh a -> m a
70+shellyOb f obTest = shelly $ f $ do
71+ obTest `catch_sh` \(e :: RunFailed) -> do
72+ obStderr <- lastStderr
73+ if T.isInfixOf "export NIX_REMOTE" obStderr
76+ putLog Notice "Detected NIX_REMOTE suggestion in stderr of failed ob"
77+ putLog Warning "Retrying this test with NIX_REMOTE=daemon"
78+ setenv "NIX_REMOTE" "daemon"
53main :: IO ()
82main :: IO ()
84+ -- Note: you can pass hspec arguments as well, eg: `-m <pattern>`
55− isVerbose <- getArgs >>= \case
85+ isVerbose <- (elem "-v") <$> getArgs
56− ["-v"] -> pure True
86+ unless isVerbose $
57− [] -> putStrLn "Tests may take longer to run if there are unbuilt derivations: use -v for verbose output" *> pure False
87+ putStrLn "Tests may take longer to run if there are unbuilt derivations: use -v for verbose output"
58− _ -> putStrLn "Usage: selftest [-v]" *> exitWith (ExitFailure 2)
59 let verbosity = bool silently verbosely isVerbose
88 let verbosity = bool silently verbosely isVerbose
60 obeliskImpl <- fromString <$> getEnv "OBELISK_IMPL"
89 obeliskImpl <- fromString <$> getEnv "OBELISK_IMPL"
61 httpManager <- HTTP.newManager HTTP.defaultManagerSettings
90 httpManager <- HTTP.newManager HTTP.defaultManagerSettings
62 withSystemTempDirectory "blank-project" $ \blankProject ->
91 withSystemTempDirectory "blank-project" $ \blankProject ->
63 hspec $ do
92 hspec $ do
64− let shelly_ = void . shelly . verbosity
93+ let shelly_ = void . shellyOb verbosity
66 inProj :: Sh a -> IO ()
95 inProj :: Sh a -> IO ()
67 inProj = shelly_ . chdir (fromString blankProject)
96 inProj = shelly_ . chdir (fromString blankProject)
@@ -75,7 +104,7 @@ main = do
75 revParseHead = T.strip <$> run "git" ["rev-parse", "HEAD"]
104 revParseHead = T.strip <$> run "git" ["rev-parse", "HEAD"]
77 commitAll = do
106 commitAll = do
78− run "git" ["add", "."]
107+ run_ "git" ["add", "."]
79 commit "checkpoint"
108 commit "checkpoint"
80 revParseHead
109 revParseHead
@@ -92,31 +121,32 @@ main = do
92 it "works with symlink" $ inTmp $ \_ -> run "ob" ["init", "--symlink", obeliskImpl]
121 it "works with symlink" $ inTmp $ \_ -> run "ob" ["init", "--symlink", obeliskImpl]
94 it "doesn't create anything when given an invalid impl" $ inTmp $ \tmp -> do
123 it "doesn't create anything when given an invalid impl" $ inTmp $ \tmp -> do
95− errExit False $ run "ob" ["init", "--symlink", "/dev/null"]
124+ void $ errExit False $ run "ob" ["init", "--symlink", "/dev/null"]
96 ls tmp >>= liftIO . assertEqual "" []
125 ls tmp >>= liftIO . assertEqual "" []
98 it "produces a valid route config" $ inTmp $ \tmp -> do
127 it "produces a valid route config" $ inTmp $ \tmp -> do
99− run "ob" ["init"]
128+ run_ "ob" ["init"]
100 liftIO $ withCurrentDirectory (T.unpack $ toTextIgnore tmp) $ getConfigRoute `shouldNotReturn` Nothing
129 liftIO $ withCurrentDirectory (T.unpack $ toTextIgnore tmp) $ getConfigRoute `shouldNotReturn` Nothing
102 describe "ob run" $ do
131 describe "ob run" $ do
103 it "works in root directory" $ inTmp $ \_ -> do
132 it "works in root directory" $ inTmp $ \_ -> do
104− run "ob" ["init"]
133+ run_ "ob" ["init"]
105 testObRunInDir Nothing httpManager
134 testObRunInDir Nothing httpManager
106 it "works in sub directory" $ inTmp $ \_ -> do
135 it "works in sub directory" $ inTmp $ \_ -> do
107− run "ob" ["init"]
136+ run_ "ob" ["init"]
108 testObRunInDir (Just "frontend") httpManager
137 testObRunInDir (Just "frontend") httpManager
110 describe "obelisk project" $ parallel $ do
139 describe "obelisk project" $ parallel $ do
111 it "can build obelisk command" $ shelly_ $ run "nix-build" ["-A", "command" , obeliskImpl]
140 it "can build obelisk command" $ shelly_ $ run "nix-build" ["-A", "command" , obeliskImpl]
112 it "can build obelisk skeleton" $ shelly_ $ run "nix-build" ["-A", "skeleton", obeliskImpl]
141 it "can build obelisk skeleton" $ shelly_ $ run "nix-build" ["-A", "skeleton", obeliskImpl]
113 it "can build obelisk shell" $ shelly_ $ run "nix-build" ["-A", "shell", obeliskImpl]
142 it "can build obelisk shell" $ shelly_ $ run "nix-build" ["-A", "shell", obeliskImpl]
143+ -- See https://github.com/obsidiansystems/obelisk/issues/101
114− it "can build everything" $ shelly_ $ run "nix-build" [obeliskImpl]
144+ -- it "can build everything" $ shelly_ $ run "nix-build" [obeliskImpl]
116 describe "blank initialized project" $ do
146 describe "blank initialized project" $ do
117 it "can be created" $ inProj $ do
147 it "can be created" $ inProj $ do
118− run "ob" ["init"]
148+ run_ "ob" ["init"]
119− run "git" ["init"]
149+ run_ "git" ["init"]
120 commitAll
150 commitAll
122 it "can build ghc.backend" $ inProj $ do
152 it "can build ghc.backend" $ inProj $ do
@@ -131,7 +161,7 @@ main = do
131 forM_ ["ghc", "ghcjs"] $ \compiler -> do
161 forM_ ["ghc", "ghcjs"] $ \compiler -> do
133 shell = "shells." <> compiler
163 shell = "shells." <> compiler
134− inShell cmd = run "nix-shell" ["-A", fromString shell, "--run", cmd]
164+ inShell cmd' = run "nix-shell" ["-A", fromString shell, "--run", cmd']
135 it ("can enter " <> shell) $ inProj $ inShell "exit"
165 it ("can enter " <> shell) $ inProj $ inShell "exit"
136 it ("can build in " <> shell) $ inProj $ inShell $ "cabal new-build --" <> fromString compiler <> " all"
166 it ("can build in " <> shell) $ inProj $ inShell $ "cabal new-build --" <> fromString compiler <> " all"
@@ -156,15 +186,15 @@ main = do
156 assertRevNE e eu
186 assertRevNE e eu
158 it "can pack and unpack plain git repos" $ do
188 it "can pack and unpack plain git repos" $ do
159− withSystemTempDirectory "git-repo" $ \dir -> shelly_ $ do
189+ shelly_ $ withSystemTempDirectory "git-repo" $ \dir -> do
160 let repo = toTextIgnore $ dir </> ("repo" :: String)
190 let repo = toTextIgnore $ dir </> ("repo" :: String)
161− run_ "git" ["clone", "https://git.haskell.org/packages/primitive.git", repo]
191+ run_ "git" ["clone", "https://github.com/haskell/process.git", repo]
162 origHash <- chdir (fromText repo) revParseHead
192 origHash <- chdir (fromText repo) revParseHead
164 run_ "ob" ["thunk", "pack", repo]
194 run_ "ob" ["thunk", "pack", repo]
165 packedFiles <- Set.fromList <$> ls (fromText repo)
195 packedFiles <- Set.fromList <$> ls (fromText repo)
166− liftIO $ assertEqual "" packedFiles $
196+ liftIO $ assertEqual "" packedFiles $ Set.fromList $ (repo </>) <$>
167− Set.fromList $ (repo </>) <$> ["default.nix", "git.json", ".attr-cache" :: String]
197+ ["default.nix", "github.json", ".attr-cache" :: String]
169 run_ "ob" ["thunk", "unpack", repo]
199 run_ "ob" ["thunk", "unpack", repo]
170 chdir (fromText repo) $ do
200 chdir (fromText repo) $ do
@@ -174,7 +204,7 @@ main = do
174 testThunkPack $ fromText repo
204 testThunkPack $ fromText repo
176 it "aborts thunk pack when there are uncommitted files" $ inProj $ do
206 it "aborts thunk pack when there are uncommitted files" $ inProj $ do
177− unpack
207+ void $ unpack
178 testThunkPack (blankProject </> thunk)
208 testThunkPack (blankProject </> thunk)
180-- | Run `ob run` in the given directory (maximum of one level deep)
210-- | Run `ob run` in the given directory (maximum of one level deep)
@@ -191,33 +221,31 @@ testObRunInDir mdir httpManager = handle_sh (\case ExitSuccess -> pure (); e ->
191 maybe id (\_ -> chdir "..") mdir $ alterRouteTo newUri stdout
221 maybe id (\_ -> chdir "..") mdir $ alterRouteTo newUri stdout
192 (_, runningUri) <- handleObRunStdout httpManager stdout
222 (_, runningUri) <- handleObRunStdout httpManager stdout
193 if runningUri /= newUri
223 if runningUri /= newUri
194− then errorExit $ "Reloading failed: expected " <> newUri <> " but got " <> runningUri
224+ then errorExit $ "Reloading failed: expected " <> newUri <> " but got " <> runningUri
195− else exit 0
225+ else exit 0
197testThunkPack :: Shelly.FilePath -> Sh ()
227testThunkPack :: Shelly.FilePath -> Sh ()
198−testThunkPack path = withTempFile (T.unpack $ toTextIgnore path) "test-file" $ \file handle -> do
228+testThunkPack path' = withTempFile (T.unpack $ toTextIgnore path') "test-file" $ \file handle -> do
199− let try_sh :: Exception e => Sh a -> Sh (Either e a)
200− try_sh a = catch_sh (a >>= pure . Right) (pure . Left)
201− pack' = readProcessWithExitCode "ob" ["thunk", "pack", T.unpack $ toTextIgnore path] ""
229+ let pack' = readProcessWithExitCode "ob" ["thunk", "pack", T.unpack $ toTextIgnore path'] ""
202− ensureThunkPackFails err = liftIO $ pack' >>= \case
230+ ensureThunkPackFails q = liftIO $ pack' >>= \case
203− (code, out, _err)
231+ (code, out, err)
204 | code == ExitSuccess -> fail "ob thunk pack succeeded when it should have failed"
232 | code == ExitSuccess -> fail "ob thunk pack succeeded when it should have failed"
205− | err `T.isInfixOf` T.pack out -> pure ()
233+ | q `T.isInfixOf` T.pack (out <> err) -> pure ()
206− | otherwise -> fail $ "ob thunk pack failed for an unexpected reason: " <> show out
234+ | otherwise -> fail $ "ob thunk pack failed for an unexpected reason: " <> show out <> "\nstderr: " <> err
207− git = chdir path . run "git"
235+ git = chdir path' . run "git"
208 -- Untracked files
236 -- Untracked files
209− ensureThunkPackFails "unsaved modifications"
237+ ensureThunkPackFails "Untracked files"
210− git ["add", T.pack file]
238+ void $ git ["add", T.pack file]
211 -- Uncommitted files (staged)
239 -- Uncommitted files (staged)
212− ensureThunkPackFails "unsaved modifications"
240+ ensureThunkPackFails "unsaved"
213− chdir path $ commit "test commit"
241+ chdir path' $ commit "test commit"
214 -- Non-pushed commits in any branch
242 -- Non-pushed commits in any branch
215 ensureThunkPackFails "not been pushed"
243 ensureThunkPackFails "not been pushed"
216 -- Uncommitted files (unstaged)
244 -- Uncommitted files (unstaged)
217 liftIO $ T.hPutStrLn handle "test file" >> hClose handle
245 liftIO $ T.hPutStrLn handle "test file" >> hClose handle
218− ensureThunkPackFails "unsaved modifications"
246+ ensureThunkPackFails "modified"
219 -- Existing stashes
247 -- Existing stashes
220− git ["stash"]
248+ void $ git ["stash"]
221 ensureThunkPackFails "has stashes"
249 ensureThunkPackFails "has stashes"
223-- | Blocks until a non-empty line is available
251-- | Blocks until a non-empty line is available