-
Notifications
You must be signed in to change notification settings - Fork 221
Fix mainInParentDirectory failing when initdb is not on PATH #2398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
4ca03e4
6149b66
9c9ddd2
095d896
5908473
557852a
b432aa0
20c65d7
c67dea2
2e982b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| module Test.IDE.PostgresSpec where | ||
|
|
||
| import IHP.Prelude | ||
| import Test.Hspec | ||
| import IHP.IDE.Postgres (withBuiltinOrDevenvPostgres) | ||
| import IHP.IDE.Types (Context (..)) | ||
| import IHP.IDE.PortConfig (PortConfig (..)) | ||
| import qualified IHP.Log as Log | ||
| import Control.Concurrent.MVar (takeMVar) | ||
| import qualified Control.Concurrent.Chan.Unagi as Queue | ||
| import qualified Control.Exception.Safe as Exception | ||
| import qualified Data.Map as Map | ||
| import Data.Default (def) | ||
| import qualified Data.Text as Text | ||
| import qualified Data.Text.IO as TextIO | ||
| import qualified Network.Socket as Socket | ||
| import qualified System.Directory as Directory | ||
| import qualified System.Environment as Env | ||
| import qualified System.Process as Process | ||
| import qualified System.Timeout as Timeout | ||
|
|
||
| tests :: Spec | ||
| tests = describe "IHP.IDE.Postgres" do | ||
| it "runs postgres setup through direnv in wrapped mode" do | ||
| withTemporaryTestDirectory \testDir -> do | ||
| let scriptsDir = testDir <> "/bin" | ||
| let commandLog = testDir <> "/commands.log" | ||
| path <- fromMaybe "" <$> Env.lookupEnv "PATH" | ||
|
|
||
| writeExecutable (scriptsDir <> "/direnv") direnvScript | ||
| writeExecutable (scriptsDir <> "/initdb") initdbScript | ||
| writeExecutable (scriptsDir <> "/createdb") createdbScript | ||
| writeExecutable (scriptsDir <> "/psql") psqlScript | ||
| writeExecutable (scriptsDir <> "/postgres") postgresScript | ||
|
|
||
| withEnvVar "PATH" (scriptsDir <> ":" <> path) do | ||
| withEnvVar "IHP_DEVENV" "0" do | ||
| withEnvVar "IHP_TEST_COMMAND_LOG" commandLog do | ||
| withEnvVar "IHP_LIB" (testDir <> "/IHP/ihp-ide/lib/IHP") do | ||
| withTestContext True do | ||
| withCurrentDirectory testDir do | ||
| withBuiltinOrDevenvPostgres \databaseIsReady _ _ -> do | ||
| ready <- Timeout.timeout (2 * 1000 * 1000) (takeMVar databaseIsReady) | ||
| ready `shouldBe` Just () | ||
|
|
||
| commandLogContent <- TextIO.readFile commandLog | ||
| let commandLines = Text.lines commandLogContent | ||
| commandLines `shouldSatisfy` any ("exec . initdb " `Text.isPrefixOf`) | ||
| commandLines `shouldSatisfy` any ("exec . createdb " `Text.isPrefixOf`) | ||
| commandLines `shouldSatisfy` any ("exec . psql " `Text.isPrefixOf`) | ||
| commandLines `shouldSatisfy` any (\line -> "-f " `Text.isInfixOf` line && "IHPSchema.sql" `Text.isInfixOf` line) | ||
| commandLines `shouldSatisfy` any ("-f Application/Schema.sql" `Text.isInfixOf`) | ||
| commandLines `shouldSatisfy` any ("-f Application/Fixtures.sql" `Text.isInfixOf`) | ||
|
Comment on lines
+49
to
+57
|
||
| length (filter ("exec . postgres " `Text.isPrefixOf`) commandLines) `shouldSatisfy` (>= 2) | ||
|
|
||
| withEnvVar :: String -> String -> IO a -> IO a | ||
| withEnvVar key value action = do | ||
| oldValue <- Env.lookupEnv key | ||
| Exception.bracket_ | ||
| (Env.setEnv key value) | ||
| (restoreEnvVar oldValue) | ||
| action | ||
| where | ||
| restoreEnvVar (Just oldValue) = Env.setEnv key oldValue | ||
| restoreEnvVar Nothing = Env.unsetEnv key | ||
|
|
||
| withTemporaryTestDirectory :: (FilePath -> IO a) -> IO a | ||
| withTemporaryTestDirectory callback = do | ||
| currentDirectory <- Directory.getCurrentDirectory | ||
| let testDir = currentDirectory <> "/build/test-postgres-direnv" | ||
| ignoreIOError (Directory.removePathForcibly testDir) | ||
|
||
| Directory.createDirectoryIfMissing True (testDir <> "/Application") | ||
| Directory.createDirectoryIfMissing True (testDir <> "/bin") | ||
| Directory.createDirectoryIfMissing True (testDir <> "/IHP/ihp-ide") | ||
| TextIO.writeFile (testDir <> "/Application/Schema.sql") "CREATE TABLE test_table (id UUID PRIMARY KEY);\n" | ||
| TextIO.writeFile (testDir <> "/Application/Fixtures.sql") "" | ||
| TextIO.writeFile (testDir <> "/IHP/ihp-ide/IHPSchema.sql") "CREATE EXTENSION IF NOT EXISTS pgcrypto;\n" | ||
| Exception.finally | ||
| (callback testDir) | ||
| (ignoreIOError (Directory.removePathForcibly testDir)) | ||
|
|
||
| withCurrentDirectory :: FilePath -> IO a -> IO a | ||
| withCurrentDirectory workingDirectory callback = do | ||
| oldDirectory <- Directory.getCurrentDirectory | ||
| Exception.bracket_ | ||
| (Directory.setCurrentDirectory workingDirectory) | ||
| (Directory.setCurrentDirectory oldDirectory) | ||
| callback | ||
|
|
||
| ignoreIOError :: IO () -> IO () | ||
| ignoreIOError io = io `Exception.catchAny` \_ -> pure () | ||
|
|
||
| withTestContext :: Bool -> ((?context :: Context) => IO a) -> IO a | ||
| withTestContext wrapWithDirenv callback = Exception.bracket createContext cleanupContext runCallback | ||
| where | ||
| createContext = do | ||
| logger <- Log.newLogger def | ||
| (ghciInChan, ghciOutChan) <- Queue.newChan | ||
| liveReloadClients <- newIORef Map.empty | ||
| lastSchemaCompilerError <- newIORef Nothing | ||
| (appSocket, helperSocket) <- Socket.socketPair Socket.AF_UNIX Socket.Stream Socket.defaultProtocol | ||
| Socket.close helperSocket | ||
| let portConfig = PortConfig { appPort = 8000, toolServerPort = 8001 } | ||
| let isDebugMode = False | ||
| let context = Context { portConfig, isDebugMode, logger, ghciInChan, ghciOutChan, wrapWithDirenv, liveReloadClients, lastSchemaCompilerError, appSocket } | ||
| pure context | ||
|
|
||
| cleanupContext context = do | ||
| Socket.close context.appSocket | ||
| context.logger.cleanup | ||
|
|
||
| runCallback context = do | ||
| let ?context = context | ||
| callback | ||
|
|
||
| writeExecutable :: FilePath -> Text -> IO () | ||
| writeExecutable filePath content = do | ||
| TextIO.writeFile filePath content | ||
| Process.callProcess "chmod" ["+x", filePath] | ||
|
|
||
| direnvScript :: Text | ||
| direnvScript = Text.unlines | ||
| [ "#!/bin/sh" | ||
| , "set -eu" | ||
| , "if [ \"${1:-}\" != \"exec\" ] || [ \"${2:-}\" != \".\" ]; then" | ||
| , " echo \"unexpected direnv invocation: $*\" >&2" | ||
| , " exit 1" | ||
| , "fi" | ||
| , "echo \"$*\" >> \"$IHP_TEST_COMMAND_LOG\"" | ||
| , "shift 2" | ||
| , "export IHP_TEST_CALLED_VIA_DIRENV=1" | ||
| , "exec \"$@\"" | ||
| ] | ||
|
|
||
| initdbScript :: Text | ||
| initdbScript = Text.unlines | ||
| [ "#!/bin/sh" | ||
| , "set -eu" | ||
| , "if [ \"${IHP_TEST_CALLED_VIA_DIRENV:-0}\" != \"1\" ]; then" | ||
| , " echo \"initdb called without direnv\" >&2" | ||
| , " exit 1" | ||
| , "fi" | ||
| , "mkdir -p \"$1\"" | ||
| ] | ||
|
|
||
| createdbScript :: Text | ||
| createdbScript = Text.unlines | ||
| [ "#!/bin/sh" | ||
| , "set -eu" | ||
| , "if [ \"${IHP_TEST_CALLED_VIA_DIRENV:-0}\" != \"1\" ]; then" | ||
| , " echo \"createdb called without direnv\" >&2" | ||
| , " exit 1" | ||
| , "fi" | ||
| ] | ||
|
|
||
| psqlScript :: Text | ||
| psqlScript = Text.unlines | ||
| [ "#!/bin/sh" | ||
| , "set -eu" | ||
| , "if [ \"${IHP_TEST_CALLED_VIA_DIRENV:-0}\" != \"1\" ]; then" | ||
| , " echo \"psql called without direnv\" >&2" | ||
| , " exit 1" | ||
| , "fi" | ||
| ] | ||
|
|
||
| postgresScript :: Text | ||
| postgresScript = Text.unlines | ||
| [ "#!/bin/sh" | ||
| , "set -eu" | ||
| , "if [ \"${IHP_TEST_CALLED_VIA_DIRENV:-0}\" != \"1\" ]; then" | ||
| , " echo \"postgres called without direnv\" >&2" | ||
| , " exit 1" | ||
| , "fi" | ||
| , "echo \"database system is ready to accept connections\" >&2" | ||
| , "trap 'exit 0' INT TERM" | ||
| , "while true; do" | ||
| , " sleep 0.1" | ||
| , "done" | ||
| ] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
callProcessDirenvAwareduplicates the direnv-wrapping logic already implemented inprocDirenvAware(inIHP.IDE.Types). Keeping two implementations increases the chance they diverge (e.g. flags/arguments/order). Consider implementingcallProcessDirenvAwarein terms ofprocDirenvAware(or moving a shared helper intoIHP.IDE.Types) so all wrapped command execution follows the same code path.