Skip to content

Commit

Permalink
Spawn cardano-node in a test for 10 seconds.
Browse files Browse the repository at this point in the history
  • Loading branch information
newhoggy committed Aug 3, 2020
1 parent 1936eae commit a5e4505
Show file tree
Hide file tree
Showing 32 changed files with 1,475 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -45,3 +45,5 @@ hie.yaml

# Ignore files generated by tests
tmp
/cardano-node/logs

5 changes: 3 additions & 2 deletions cardano-cli/cardano-cli.cabal
@@ -1,3 +1,5 @@
cabal-version: 2.4

name: cardano-cli
version: 1.18.0
description: The Cardano command-line interface.
Expand All @@ -8,7 +10,6 @@ license-files:
LICENSE
NOTICE
build-type: Simple
cabal-version: >= 1.10
extra-source-files: README.md

Flag unexpected_thunks
Expand Down Expand Up @@ -236,4 +237,4 @@ test-suite cardano-cli-test
-Wcompat
-threaded -rtsopts -with-rtsopts=-N -with-rtsopts=-T

build-tools: cardano-cli
build-tool-depends: cardano-cli:cardano-cli
3 changes: 2 additions & 1 deletion cardano-config/cardano-config.cabal
@@ -1,3 +1,5 @@
cabal-version: 2.4

name: cardano-config
version: 0.1.0.0
author: IOHK
Expand All @@ -7,7 +9,6 @@ license-files:
LICENSE
NOTICE
build-type: Simple
cabal-version: >= 1.10
extra-source-files: README.md

flag systemd
Expand Down
23 changes: 14 additions & 9 deletions cardano-node/cardano-node.cabal
@@ -1,3 +1,5 @@
cabal-version: 2.4

name: cardano-node
version: 1.18.0
description: The cardano full node
Expand All @@ -8,7 +10,6 @@ license-files:
LICENSE
NOTICE
build-type: Simple
cabal-version: >= 1.10
extra-source-files: ChangeLog.md

Flag unexpected_thunks
Expand All @@ -20,9 +21,7 @@ flag systemd
default: True
manual: False


library

if flag(unexpected_thunks)
cpp-options: -DUNEXPECTED_THUNKS

Expand Down Expand Up @@ -238,8 +237,7 @@ executable chairman
, contra-tracer
, cardano-prelude
, io-sim-classes
, network-mux
, cardano-node-config
, network-mux , cardano-node-config
, optparse-applicative
, ouroboros-consensus
, ouroboros-consensus-cardano
Expand All @@ -264,29 +262,36 @@ test-suite cardano-node-test
build-depends:
base >= 4.12 && < 5
, aeson
, async
, bytestring
, cardano-node
, cardano-config
, cardano-crypto-class
, cardano-crypto-test
, cardano-crypto-wrapper
, cardano-node
, cardano-prelude
, cardano-prelude-test
, cardano-slotting
, containers
, cryptonite
, directory
, hedgehog
, hedgehog-corpus
, iproute
, ouroboros-consensus
, ouroboros-consensus-shelley
, ouroboros-network
, process
, shelley-spec-ledger
, shelley-spec-ledger-test
, temporary
, time
, hedgehog
, hedgehog-corpus

other-modules: Test.Cardano.Node.Gen
other-modules: Test.Cardano.Node.Chairman
Test.Cardano.Node.Gen
Test.Cardano.Node.Json
Test.Common.Base
Test.Common.Process

default-language: Haskell2010
default-extensions: NoImplicitPrelude
Expand Down
4 changes: 2 additions & 2 deletions cardano-node/src/Cardano/Node/Types.hs
Expand Up @@ -45,7 +45,7 @@ import Data.IP (IP)
import qualified Data.Text as Text
import Data.Yaml (decodeFileThrow)
import Network.Socket (PortNumber)
import System.FilePath (takeDirectory, (</>))
import System.FilePath (takeDirectory)
import System.Posix.Types (Fd)

import Cardano.Api.Typed (EpochNo)
Expand Down Expand Up @@ -545,7 +545,7 @@ parseNodeConfigurationFP :: ConfigYamlFilePath -> IO NodeConfiguration
parseNodeConfigurationFP (ConfigYamlFilePath fp) = do
nc <- decodeFileThrow fp
-- Make all the files be relative to the location of the config file.
pure $ adjustFilePaths (takeDirectory fp </>) nc
pure $ adjustFilePaths (\p -> takeDirectory fp <> "/" <> p) nc

-- | A human readable name for the protocol
--
Expand Down
48 changes: 48 additions & 0 deletions cardano-node/test/Test/Cardano/Node/Chairman.hs
@@ -0,0 +1,48 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

module Test.Cardano.Node.Chairman
( tests
) where

import Cardano.Prelude
import Hedgehog (Property, discover)

import qualified Hedgehog as H
import qualified System.Directory as IO
import qualified Test.Common.Base as H
import qualified Test.Common.Process as H

prop_spawnOneNode :: Property
prop_spawnOneNode = H.propertyOnce . H.workspace "temp/chairman" $ \tempDir -> do
let dbDir = tempDir <> "/db/node-2"
let socketDir = tempDir <> "/socket"

H.createDirectoryIfMissing dbDir
H.createDirectoryIfMissing socketDir

base <- H.getProjectBase

dirContents <- liftIO $ IO.listDirectory base

H.annotateShow $ dirContents

(_mIn, _mOut, _mErr, hProcess) <- H.createProcess =<< H.procNode
[ "run"
, "--database-path", dbDir
, "--socket-path", socketDir <> "/node-2-socket"
, "--port", "3002"
, "--topology", base <> "/chairman/configuration/defaults/simpleview/topology-node-2.json"
, "--config", base <> "/chairman/configuration/defaults/simpleview/config-2.yaml"
, "--signing-key", base <> "/chairman/configuration/defaults/simpleview/genesis/delegate-keys.002.key"
, "--delegation-certificate", base <> "/chairman/configuration/defaults/simpleview/genesis/delegation-cert.002.json"
]

H.threadDelay 10000000

H.interruptProcessGroupOf hProcess

void $ H.waitForProcess hProcess

tests :: IO Bool
tests = H.checkParallel $$discover
79 changes: 79 additions & 0 deletions cardano-node/test/Test/Common/Base.hs
@@ -0,0 +1,79 @@
module Test.Common.Base
( propertyOnce
, failWithCustom
, threadDelay
, workspace
, moduleWorkspace
, createDirectoryIfMissing
) where

import Control.Monad.IO.Class (liftIO)
import Data.Bool
import Data.Either (Either (..))
import Data.Function (($), (.))
import Data.Functor
import Data.Int
import Data.Maybe (Maybe (..), fromMaybe, listToMaybe)
import Data.Monoid (Monoid (..))
import Data.Semigroup (Semigroup (..))
import Data.String (String)
import Data.Tuple
import GHC.Stack (CallStack, HasCallStack, callStack, getCallStack)
import Hedgehog (MonadTest)
import Hedgehog.Internal.Property (Diff, liftTest, mkTest)
import Hedgehog.Internal.Source (getCaller)
import System.IO (FilePath, IO)

import qualified Control.Concurrent as IO
import qualified GHC.Stack as GHC
import qualified Hedgehog as H
import qualified Hedgehog.Internal.Property as H
import qualified System.Directory as IO
import qualified System.IO.Temp as IO

cardanoCliPath :: FilePath
cardanoCliPath = "cardano-cli"

propertyOnce :: H.PropertyT IO () -> H.Property
propertyOnce = H.withTests 1 . H.property

threadDelay :: Int -> H.PropertyT IO ()
threadDelay = liftIO . IO.threadDelay

-- | Takes a 'CallStack' so the error can be rendered at the appropriate call site.
failWithCustom :: MonadTest m => CallStack -> Maybe Diff -> String -> m a
failWithCustom cs mdiff msg = liftTest $ mkTest (Left $ H.Failure (getCaller cs) msg mdiff, mempty)

-- | Create a workspace directory which will exist for at least the duration of
-- the supplied block.
--
-- The directory will have the supplied prefix but contain a generated random
-- suffix to prevent interference between tests
--
-- The directory will be deleted if the block succeeds, but left behind if
-- the block fails.
workspace :: HasCallStack => FilePath -> (FilePath -> H.PropertyT IO ()) -> H.PropertyT IO ()
workspace prefixPath f = GHC.withFrozenCallStack $ do
systemTemp <- liftIO $ IO.getCanonicalTemporaryDirectory
let systemPrefixPath = systemTemp <> "/" <> prefixPath
liftIO $ IO.createDirectoryIfMissing True systemPrefixPath
ws <- liftIO $ IO.createTempDirectory systemPrefixPath "test"
H.annotate $ "Workspace: " <> cardanoCliPath <> "/" <> ws
f ws
liftIO $ IO.removeDirectoryRecursive ws

-- | Create a workspace directory which will exist for at least the duration of
-- the supplied block.
--
-- The directory will have the prefix as "$prefixPath/$moduleName" but contain a generated random
-- suffix to prevent interference between tests
--
-- The directory will be deleted if the block succeeds, but left behind if
-- the block fails.
moduleWorkspace :: HasCallStack => FilePath -> (FilePath -> H.PropertyT IO ()) -> H.PropertyT IO ()
moduleWorkspace prefixPath f = GHC.withFrozenCallStack $ do
let srcModule = fromMaybe "UnknownModule" (fmap (GHC.srcLocModule . snd) (listToMaybe (getCallStack callStack)))
workspace (prefixPath <> "/" <> srcModule) f

createDirectoryIfMissing :: HasCallStack => FilePath -> H.PropertyT IO ()
createDirectoryIfMissing filePath = H.evalM . liftIO $ IO.createDirectoryIfMissing True filePath
70 changes: 70 additions & 0 deletions cardano-node/test/Test/Common/Process.hs
@@ -0,0 +1,70 @@
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Test.Common.Process
( createProcess
, getProjectBase
, procNode
, interruptProcessGroupOf
, waitForProcess
) where

import Control.Concurrent.Async
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Data.Bool
import Data.Function
import Data.Maybe (Maybe (..))
import Data.Semigroup ((<>))
import Data.String (String)
import GHC.Stack (HasCallStack)
import System.Exit (ExitCode)
import System.IO (Handle, IO)
import System.Process (CmdSpec (..), CreateProcess (..), ProcessHandle)

import qualified Data.List as L
import qualified GHC.Stack as GHC
import qualified Hedgehog as H
import qualified System.Environment as IO
import qualified System.Process as IO

createProcess :: HasCallStack
=> CreateProcess
-> H.PropertyT IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
createProcess cp = GHC.withFrozenCallStack $ do
case IO.cmdspec cp of
RawCommand cmd args -> H.annotate $ "Command line: " <> cmd <> " " <> L.intercalate " " args
ShellCommand cmd -> H.annotate $ "Command line: " <> cmd
H.evalM . liftIO $ IO.createProcess cp

interruptProcessGroupOf :: HasCallStack
=> ProcessHandle
-> H.PropertyT IO ()
interruptProcessGroupOf hProcess = GHC.withFrozenCallStack $ do
H.evalM . liftIO $ IO.interruptProcessGroupOf hProcess

waitForProcess :: HasCallStack
=> ProcessHandle
-> H.PropertyT IO (Maybe ExitCode)
waitForProcess hProcess = GHC.withFrozenCallStack $ do
H.evalM . liftIO $ catch (fmap Just (IO.waitForProcess hProcess)) $ \(_ :: AsyncCancelled) -> return Nothing

procNode
:: [String]
-- ^ Arguments to the CLI command
-> H.PropertyT IO CreateProcess
-- ^ Captured stdout
procNode arguments = do
maybeCardanoCli <- liftIO $ IO.lookupEnv "CARDANO_NODE"
cp <- case maybeCardanoCli of
Just cardanoCli -> return $ IO.proc cardanoCli arguments
Nothing -> return $ IO.proc "cabal" ("exec":"--":"cardano-node":arguments)
return $ cp { IO.create_group = True }

getProjectBase :: H.PropertyT IO String
getProjectBase = do
maybeCardanoCli <- liftIO $ IO.lookupEnv "CARDANO_NODE_SRC"
case maybeCardanoCli of
Just path -> return path
Nothing -> return ".."
4 changes: 3 additions & 1 deletion cardano-node/test/cardano-node-test.hs
Expand Up @@ -3,10 +3,12 @@ import Cardano.Prelude

import Hedgehog.Main (defaultMain)

import qualified Test.Cardano.Node.Chairman
import qualified Test.Cardano.Node.Json

main :: IO ()
main =
defaultMain
[ Test.Cardano.Node.Json.tests
[ Test.Cardano.Node.Chairman.tests
, Test.Cardano.Node.Json.tests
]

0 comments on commit a5e4505

Please sign in to comment.