Merge #1101

1101: tests: Use TH for getting data directory r=rvl a=rvl

Relates to a previous PR #976 and is needed for running tests on Windows/Wine.

Overview

We need a method of locating the data directories of the test suites that works in multiple situations:

  • stack build (working directory at .cabal file)
  • stack ghci (working directory is project root)
  • ghci (not using cabal)
  • in the Nix build (working directory is at project root)
  • when running windows tests (the install prefix baked in to Paths_ will always be wrong)

With Paths_cardano_wallet_jormungandr.getDataDir, only the first two work.

Using makeRelativeToProject in a TH splice seems to work in all situations.

Example error with ghci and Paths_

:l lib/jormungandr/test/unit/Cardano/Wallet/Jormungandr/BinarySpec.hs
Ok, 24 modules loaded.
λ> hspec spec
*** Exception: /home/rodney/.cabal/share/x86_64-linux-ghc-8.6.5/cardano-wallet-jormungandr-2019.11.18/block0s: getDirectoryContents:openDirStream: does not exist (No such file or directory)

Example error with nix-build:

Linking dist/build/unit/unit ...
running tests
unit: /nix/store/8kgw4dkyx6yl68if3vdk83vx64v7zkw7-cardano-wallet-jormungandr-2019.11.18-test-unit/share/x86_64-osx-ghc-8.6.5/cardano-wallet-jormungandr-2019.11.18/block0s: getDirectoryContents:openDirStream: does not exist (No such file or directory)
builder for '/nix/store/z5dphvdqafx9y2nvzch4clhg5j6iyvh6-cardano-wallet-jormungandr-2019.11.18-test-unit.drv' failed with exit code 1

This nix-build error is arguably a bug in Haskell.nix (or maybe it’s even a limitation of Cabal). The tests install prefix is different from the library install prefix. Either the tests should be referring to paths in the library install prefix, or the data files should be copied into the tests install prefix. Either way, something needs to be fixed.

Example error on Windows:

C:\Users\win\cw>cardano-wallet-jormungandr-2019.11.18-test-unit.exe
cardano-wallet-jormungandr-2019.11.18-test-unit.exe: /nix/store/rhdan17ab6f4310ibfcjiyhgjx48b7yh-cardano-wallet-jormungandr-2019.11.18-test-unit-x86_64-pc-mingw32/share/x86_64-windows-ghc-8.6.5/cardano-wallet-jormungandr-2019.11.18\block0s: getDirectoryContents:findFirstFile: does not exist (The system cannot find the path specified.)

Under Windows, installation prefixes such as /usr/local do not make sense. So it should just be searching for data in the current directory.

Co-authored-by: Rodney Lorrimar [email protected] Co-authored-by: KtorZ [email protected]

View on GitHub
File Changes
    ( encode )
import Data.ByteString
    ( ByteString )
-
import Data.FileEmbed
-
    ( makeRelativeToProject )
import Data.Maybe
    ( mapMaybe )
import Network.Wai.Application.Static
    )
import Test.QuickCheck.Monadic
    ( assert, monadicIO, monitor, run )
+
import Test.Utils.Paths
+
    ( getTestData )

                      
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T
-
import qualified Language.Haskell.TH.Syntax as TH

                      
spec :: Spec
spec = do
    where app = staticApp $ defaultWebAppSettings root

                      
dataDir :: FilePath
-
dataDir = $( TH.LitE . TH.StringL <$>
-
    makeRelativeToProject "test/data/stake-pool-registry" )
+
dataDir = $(getTestData) </> "stake-pool-registry"

                      
-- | Make a file server URL for the test data file.
-- This file was downloaded from <https://github.com/input-output-hk/testnet-stake-pool-registry/archive/master.zip>
    ( genericArbitrary, genericShrink )
import Test.Text.Roundtrip
    ( textRoundtrip )
+
import Test.Utils.Paths
+
    ( getTestData )
import Test.Utils.Time
    ( genUniformTime )
import Web.HttpApiData
    settings :: Settings
    settings = defaultSettings
        { goldenDirectoryOption =
-
            CustomDirectoryName $ foldr (</>) mempty
-
                [ "test", "data", "Cardano", "Wallet", "Api" ]
+
            CustomDirectoryName ($(getTestData) </> "Cardano" </> "Wallet" </> "Api")
        , useModuleNameAsSubDirectory =
            False
        , sampleSize = 10
extra-source-files:  README.md
cabal-version:       >=1.10

                      
-
data-dir: test/data
-
data-files:
-
  block0s/*.bin
-
  jormungandr/block0.bin
-
  jormungandr/config.yaml
-
  jormungandr/secret.yaml
-

                      
flag development
    description: Disable `-Werror`
    default: False
      Cardano.Wallet.Jormungandr.CompatibilitySpec
      Cardano.Wallet.Jormungandr.NetworkSpec
      Cardano.Wallet.Jormungandr.TransactionSpec
-
      Paths_cardano_wallet_jormungandr

                      
test-suite integration
  default-language:
      Test.Integration.Jormungandr.Scenario.CLI.Server
      Test.Integration.Jormungandr.Scenario.CLI.StakePools
      Test.Integration.Jormungandr.Scenario.CLI.Transactions
-
      Paths_cardano_wallet_jormungandr
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
+
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}

                      
module Cardano.Faucet
    ( ByteString )
import Data.Text
    ( Text )
-
import Paths_cardano_wallet_jormungandr
-
    ( getDataFileName )
import System.Command
    ( CmdResult, Stdout (..), command )
import System.FilePath
    ( withSystemTempDirectory )
import Test.Integration.Faucet
    ( Faucet (..) )
+
import Test.Utils.Paths
+
    ( getTestData )

                      
import qualified Codec.Binary.Bech32 as Bech32
import qualified Data.ByteString as BS
    <*> newMVar (mkTxBuilder <$> externalAddresses)

                      
getBlock0H :: IO (Hash "Genesis")
-
getBlock0H = do
-
    block0 <- getDataFileName "jormungandr/block0.bin"
-
    extractId <$> BL.readFile block0
+
getBlock0H = extractId <$> BL.readFile block0
  where
+
    block0 = $(getTestData) </> "jormungandr" </> "block0.bin"
    extractId = Hash . getHash . runGet getBlockId

                      
getBlock0HText :: IO Text
{-# LANGUAGE DataKinds #-}
+
{-# LANGUAGE TemplateHaskell #-}

                      
-- |
-- Copyright: © 2018-2019 IOHK
    ( JormungandrConfig (..), JormungandrConnParams, withJormungandr )
import Control.Exception
    ( bracket, throwIO )
-
import Paths_cardano_wallet_jormungandr
-
    ( getDataFileName )
import System.Directory
    ( doesDirectoryExist, removeDirectoryRecursive )
import System.Environment
    ( IOMode (..), hClose, openFile )
import System.IO.Temp
    ( createTempDirectory, getCanonicalTemporaryDirectory )
+
import Test.Utils.Paths
+
    ( getTestData )

                      
-- | Starts jormungandr on a random port using the integration tests config.
-- The data directory will be stored in a unique location under the system

                      
setupConfig :: IO JormungandrConfig
setupConfig = do
-
    block0 <- getDataFileName "jormungandr/block0.bin"
-
    secret <- getDataFileName "jormungandr/secret.yaml"
-
    config <- getDataFileName "jormungandr/config.yaml"
+
    let testData = $(getTestData) </> "jormungandr"
+
    let block0 = testData </> "block0.bin"
+
    let secret = testData </> "secret.yaml"
+
    let config = testData </> "config.yaml"
    tmp <- getCanonicalTemporaryDirectory
    stateDir <- createTempDirectory tmp "cardano-wallet-jormungandr"
    logFile <- openFile (stateDir </> "jormungandr.log") WriteMode
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
+
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}

                      
    ( toText )
import Network.HTTP.Client
    ( defaultManagerSettings, newManager )
-
import Paths_cardano_wallet_jormungandr
-
    ( getDataFileName )
import System.Command
    ( Exit (..), Stderr (..), Stdout (..) )
import System.Directory
import System.Process
    ( terminateProcess, withCreateProcess )
import Test.Hspec
-
    ( Spec, describe, it, pendingWith, runIO )
+
    ( Spec, describe, it, pendingWith )
import Test.Hspec.Expectations.Lifted
    ( shouldBe, shouldContain )
import Test.Integration.Framework.DSL
    , state
    , waitForServer
    )
+
import Test.Utils.Paths
+
    ( getTestData )
import Test.Utils.Ports
    ( findPort )

                      
import qualified Data.Text.IO as TIO

                      
spec :: forall t. (KnownCommand t) => Spec
spec = do
-
    block0 <- runIO $ getDataFileName "jormungandr/block0.bin"
-
    secret <- runIO $ getDataFileName "jormungandr/secret.yaml"
+
    let testData = $(getTestData) </> "jormungandr"
+
    let block0 = testData </> "block0.bin"
+
    let secret = testData </> "secret.yaml"
    describe "LAUNCH - cardano-wallet launch [SERIAL]" $ do
        it "LAUNCH - Stop when --state-dir is an existing file" $ withTempFile $ \f _ -> do
            let args =
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
+
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
    ( Word8 )
import GHC.Generics
    ( Generic )
-
import Paths_cardano_wallet_jormungandr
-
    ( getDataDir, getDataFileName )
import System.Directory
    ( getDirectoryContents )
import System.FilePath
    ( genericArbitrary, genericShrink )
import Test.QuickCheck.Monadic
    ( assert, monadicIO, monitor, run )
+
import Test.Utils.Paths
+
    ( getTestData )

                      
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as B8
spec = do
    describe "Decoding" $ do
        it "should decode the block0.bin used for integration tests" $ do
-
            bs <- BL.readFile =<< getDataFileName "jormungandr/block0.bin"
+
            bs <- BL.readFile ($(getTestData) </> "jormungandr/block0.bin")
            res <- try' (runGet getBlock bs)
            res `shouldSatisfy` isRight

                      
        describe "golden block0s generated in jormungandr-lib" $ do
-
            dir <- (</> "block0s") <$> runIO getDataDir
+
            let dir = $(getTestData) </> "block0s"
            files <- runIO $ filter (".bin" `isSuffixOf`)
                <$> getDirectoryContents dir
            forM_ files $ \filename -> do
  build-depends:
      base
    , async
+
    , filepath
+
    , file-embed
    , hspec
    , hspec-core
    , hspec-expectations
    , network
    , QuickCheck
    , random-shuffle
+
    , template-haskell
    , time
    , unliftio
  hs-source-dirs:
      src
  exposed-modules:
      Test.Hspec.Extra
+
      Test.Utils.Paths
      Test.Utils.Ports
      Test.Utils.Time
      Test.Utils.Windows
+
-- |
+
-- Copyright: © 2018-2019 IOHK
+
-- License: Apache-2.0
+
--
+
-- Utility function for finding the package test data directory.
+

                      
+
module Test.Utils.Paths
+
    ( getTestData
+
    ) where
+

                      
+
import Prelude
+

                      
+
import Data.FileEmbed
+
    ( makeRelativeToProject )
+
import Language.Haskell.TH.Syntax
+
    ( Exp, Q, liftData )
+
import System.FilePath
+
    ( (</>) )
+

                      
+
-- | A TH function to get the test data directory. It combines the current
+
-- source file location and cabal file to locate the package directory in such a
+
-- way that works in both the package build and ghci.
+
getTestData :: Q Exp
+
getTestData = makeRelativeToProject ("test" </> "data") >>= liftData
        depends = [
          (hsPkgs."base" or (buildDepError "base"))
          (hsPkgs."async" or (buildDepError "async"))
+
          (hsPkgs."filepath" or (buildDepError "filepath"))
+
          (hsPkgs."file-embed" or (buildDepError "file-embed"))
          (hsPkgs."hspec" or (buildDepError "hspec"))
          (hsPkgs."hspec-core" or (buildDepError "hspec-core"))
          (hsPkgs."hspec-expectations" or (buildDepError "hspec-expectations"))
          (hsPkgs."network" or (buildDepError "network"))
          (hsPkgs."QuickCheck" or (buildDepError "QuickCheck"))
          (hsPkgs."random-shuffle" or (buildDepError "random-shuffle"))
+
          (hsPkgs."template-haskell" or (buildDepError "template-haskell"))
          (hsPkgs."time" or (buildDepError "time"))
          (hsPkgs."unliftio" or (buildDepError "unliftio"))
          ];