From 6a4faf0b2eb54449c7cb29ef979d34fad88055b8 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 12 Jul 2019 17:11:32 +0200 Subject: [PATCH 1/4] bootstrap command-line tests to verify CLI usage --- lib/cli/cardano-wallet-cli.cabal | 4 ++ lib/cli/test/unit/Cardano/CLISpec.hs | 71 +++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/lib/cli/cardano-wallet-cli.cabal b/lib/cli/cardano-wallet-cli.cabal index da824058c1e..5cb0fbf4b39 100644 --- a/lib/cli/cardano-wallet-cli.cabal +++ b/lib/cli/cardano-wallet-cli.cabal @@ -68,8 +68,12 @@ test-suite unit -Werror build-depends: base + , cardano-crypto , cardano-wallet-cli + , cardano-wallet-core , hspec + , memory + , optparse-applicative , QuickCheck , quickcheck-instances , text diff --git a/lib/cli/test/unit/Cardano/CLISpec.hs b/lib/cli/test/unit/Cardano/CLISpec.hs index 961d86b685c..e59514d767b 100644 --- a/lib/cli/test/unit/Cardano/CLISpec.hs +++ b/lib/cli/test/unit/Cardano/CLISpec.hs @@ -14,15 +14,31 @@ import Cardano.CLI ( Iso8601Time (..) , MnemonicSize (..) , Port (..) + , cli + , cmdAddress + , cmdMnemonic + , cmdTransaction + , cmdVersion + , cmdWallet , hGetLine , hGetSensitiveLine ) +import Cardano.Crypto.Wallet + ( unXPub ) +import Cardano.Wallet.Primitive.AddressDerivation + ( KeyToAddress (..), getKey ) +import Cardano.Wallet.Primitive.Types + ( Address (..), DecodeAddress (..), EncodeAddress (..) ) import Control.Concurrent ( forkFinally ) import Control.Concurrent.MVar ( newEmptyMVar, putMVar, takeMVar ) import Control.Monad ( mapM_ ) +import Data.Bifunctor + ( bimap ) +import Data.ByteArray.Encoding + ( Base (Base16), convertFromBase, convertToBase ) import Data.Either ( isLeft, isRight ) import Data.Proxy @@ -31,10 +47,19 @@ import Data.Text ( Text ) import Data.Text.Class ( FromText (..), TextDecodingError (..), toText ) +import Options.Applicative + ( ParserFailure (..) + , ParserHelp (..) + , ParserResult (..) + , execParserPure + , prefs + ) +import Options.Applicative.Help.Chunk + ( unChunk ) import System.IO ( Handle, IOMode (..), hClose, openFile ) import Test.Hspec - ( Spec, describe, it, shouldBe, shouldSatisfy ) + ( Spec, describe, expectationFailure, it, shouldBe, shouldSatisfy ) import Test.QuickCheck ( Arbitrary (..) , Large (..) @@ -52,10 +77,34 @@ import Test.Text.Roundtrip ( textRoundtrip ) import qualified Data.Text as T +import qualified Data.Text.Encoding as T import qualified Data.Text.IO as TIO spec :: Spec spec = do + describe "Specification / Usage Overview" $ do + let parser = cli $ mempty + <> cmdMnemonic + <> cmdWallet @DummyTarget + <> cmdTransaction @DummyTarget + <> cmdAddress @DummyTarget + <> cmdVersion + + let defaultPrefs = prefs mempty + + let shouldShowUsage args expected = + case execParserPure defaultPrefs parser args of + Success _ -> expectationFailure + "expected parser to show usage but it has succeeded" + CompletionInvoked _ -> expectationFailure + "expected parser to show usage but it offered completion" + Failure (ParserFailure help) -> do + let (ParserHelp _ _ _ usage _ _, _, _) = help mempty + maybe mempty show (unChunk usage) `shouldBe` expected + + it "--help" $ ["--help"] `shouldShowUsage` mconcat + [] + describe "Can perform roundtrip textual encoding & decoding" $ do textRoundtrip $ Proxy @Iso8601Time textRoundtrip $ Proxy @(Port "test") @@ -367,3 +416,23 @@ ensureIso8601TimesEquivalent t1 t2 = it title $ property $ title = mempty <> "Equivalent ISO 8601 times are decoded equivalently: " <> show (t1, t2) + +{------------------------------------------------------------------------------- + Dummy Target +-------------------------------------------------------------------------------} + +data DummyTarget + +instance KeyToAddress DummyTarget where + keyToAddress = Address . unXPub . getKey + +instance EncodeAddress DummyTarget where + encodeAddress _ = T.decodeUtf8 . convertToBase Base16 . unAddress + +instance DecodeAddress DummyTarget where + decodeAddress _ = bimap decodingError Address + . convertFromBase Base16 + . T.encodeUtf8 + where + decodingError _ = TextDecodingError + "Unable to decode Address: expected Base16 encoding" From 98a2abf54a10b07a841f90363ba5d81f49e731c6 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 12 Jul 2019 17:43:22 +0200 Subject: [PATCH 2/4] Move CLI usage comment as golden tests specification in the CLI unit tests --- lib/cli/src/Cardano/CLI.hs | 18 --- lib/cli/test/unit/Cardano/CLISpec.hs | 200 ++++++++++++++++++++++++--- 2 files changed, 182 insertions(+), 36 deletions(-) diff --git a/lib/cli/src/Cardano/CLI.hs b/lib/cli/src/Cardano/CLI.hs index 418c921ed13..856f2c99397 100644 --- a/lib/cli/src/Cardano/CLI.hs +++ b/lib/cli/src/Cardano/CLI.hs @@ -269,8 +269,6 @@ runCli = join . customExecParser preferences {------------------------------------------------------------------------------- Commands - 'mnemonic' - - cardano-wallet mnemonic generate [--size=INT] -------------------------------------------------------------------------------} -- | cardano-wallet mnemonic @@ -303,13 +301,6 @@ cmdMnemonicGenerate = command "generate" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Commands - 'wallet' - - cardano-wallet wallet list [--port=INT] - cardano-wallet wallet create [--port=INT] [--address-pool-gap=INT] - cardano-wallet wallet get [--port=INT] - cardano-wallet wallet update name [--port=INT] STRING - cardano-wallet wallet update passphrase [--port=INT] - cardano-wallet wallet delete [--port=INT] -------------------------------------------------------------------------------} -- | cardano-wallet wallet @@ -490,9 +481,6 @@ cmdWalletDelete = command "delete" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Commands - 'transaction' - - cardano-wallet transaction create [--port=INT] --payment=PAYMENT... - cardano-wallet transaction fees [--port=INT] --payment=PAYMENT... -------------------------------------------------------------------------------} -- | cardano-wallet transaction @@ -558,8 +546,6 @@ cmdTransactionFees = command "fees" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Commands - 'address' - - cardano-wallet address list [--port=INT] [--state=STRING] -------------------------------------------------------------------------------} -- | cardano-wallet address @@ -596,8 +582,6 @@ cmdAddressList = command "list" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Commands - 'version' - - cardano-wallet version -------------------------------------------------------------------------------} -- | cardano-wallet version @@ -612,8 +596,6 @@ cmdVersion = command "version" $ info cmd $ mempty {------------------------------------------------------------------------------- Commands - 'launch' - - cardano-wallet launch ... -------------------------------------------------------------------------------} -- | Execute 'launch' commands. This differs from the 'serve' command as it diff --git a/lib/cli/test/unit/Cardano/CLISpec.hs b/lib/cli/test/unit/Cardano/CLISpec.hs index e59514d767b..358d426c7cc 100644 --- a/lib/cli/test/unit/Cardano/CLISpec.hs +++ b/lib/cli/test/unit/Cardano/CLISpec.hs @@ -18,7 +18,6 @@ import Cardano.CLI , cmdAddress , cmdMnemonic , cmdTransaction - , cmdVersion , cmdWallet , hGetLine , hGetSensitiveLine @@ -48,23 +47,17 @@ import Data.Text import Data.Text.Class ( FromText (..), TextDecodingError (..), toText ) import Options.Applicative - ( ParserFailure (..) - , ParserHelp (..) - , ParserResult (..) - , execParserPure - , prefs - ) -import Options.Applicative.Help.Chunk - ( unChunk ) + ( ParserResult (..), columns, execParserPure, prefs, renderFailure ) import System.IO ( Handle, IOMode (..), hClose, openFile ) import Test.Hspec - ( Spec, describe, expectationFailure, it, shouldBe, shouldSatisfy ) + ( Spec, describe, it, shouldBe, shouldSatisfy ) import Test.QuickCheck ( Arbitrary (..) , Large (..) , arbitraryBoundedEnum , checkCoverage + , counterexample , cover , genericShrink , property @@ -88,22 +81,193 @@ spec = do <> cmdWallet @DummyTarget <> cmdTransaction @DummyTarget <> cmdAddress @DummyTarget - <> cmdVersion - let defaultPrefs = prefs mempty + let defaultPrefs = prefs (mempty <> columns 65) + + let expectationFailure = flip counterexample False - let shouldShowUsage args expected = + let shouldShowUsage args expected = it (unwords args) $ case execParserPure defaultPrefs parser args of Success _ -> expectationFailure "expected parser to show usage but it has succeeded" CompletionInvoked _ -> expectationFailure "expected parser to show usage but it offered completion" - Failure (ParserFailure help) -> do - let (ParserHelp _ _ _ usage _ _, _, _) = help mempty - maybe mempty show (unChunk usage) `shouldBe` expected + Failure failure -> property $ + let (usage, _) = renderFailure failure mempty + in counterexample usage $ expected === lines usage + + ["--help"] `shouldShowUsage` + [ "The CLI is a proxy to the wallet server, which is required for" + , "most commands. Commands are turned into corresponding API calls," + , "and submitted to an up-and-running server. Some commands do not" + , "require an active server and can be run offline (e.g. 'mnemonic" + , "generate')." + , "" + , "Usage: COMMAND" + , " Cardano Wallet Command-Line Interface (CLI)" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " mnemonic " + , " wallet " + , " transaction " + , " address " + ] + + ["mnemonic", "--help"] `shouldShowUsage` + [ "Usage: mnemonic COMMAND" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " generate Generate English BIP-0039 compatible" + , " mnemonic words." + ] + + ["mnemonic", "generate", "--help"] `shouldShowUsage` + [ "Usage: mnemonic generate [--size INT]" + , " Generate English BIP-0039 compatible mnemonic words." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --size INT number of mnemonic words to" + , " generate. (default: 15)" + ] - it "--help" $ ["--help"] `shouldShowUsage` mconcat - [] + ["wallet", "--help"] `shouldShowUsage` + [ "Usage: wallet COMMAND" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " list List all known wallets." + , " create Create a new wallet using a sequential" + , " address scheme." + , " get Fetch the wallet with specified id." + , " update Update a wallet." + , " delete Deletes wallet with specified wallet" + , " id." + ] + + ["wallet", "list", "--help"] `shouldShowUsage` + [ "Usage: wallet list [--port INT]" + , " List all known wallets." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + ] + + ["wallet", "create", "--help"] `shouldShowUsage` + [ "Usage: wallet create [--port INT] STRING" + , " [--address-pool-gap INT]" + , " Create a new wallet using a sequential address scheme." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + , " --address-pool-gap INT number of unused consecutive addresses" + , " to keep track of. (default: 20)" + ] + + ["wallet", "get", "--help"] `shouldShowUsage` + [ "Usage: wallet get [--port INT] WALLET_ID" + , " Fetch the wallet with specified id." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + ] + + ["wallet", "update", "--help"] `shouldShowUsage` + [ "Usage: wallet update COMMAND" + , " Update a wallet." + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " name Update a wallet's name." + , " passphrase Update a wallet's passphrase." + ] + + ["wallet", "delete", "--help"] `shouldShowUsage` + [ "Usage: wallet delete [--port INT] WALLET_ID" + , " Deletes wallet with specified wallet id." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + ] + + ["transaction", "--help"] `shouldShowUsage` + [ "Usage: transaction COMMAND" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " create Create and submit a new transaction." + , " fees Estimate fees for a transaction." + ] + + ["transaction", "create", "--help"] `shouldShowUsage` + [ "Usage: transaction create [--port INT] WALLET_ID" + , " --payment PAYMENT" + , " Create and submit a new transaction." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + , " --payment PAYMENT address to send to and amount to send" + , " separated by @, e.g." + , " '@
'" + ] + + ["transaction", "fees", "--help"] `shouldShowUsage` + [ "Usage: transaction fees [--port INT] WALLET_ID --payment PAYMENT" + , " Estimate fees for a transaction." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + , " --payment PAYMENT address to send to and amount to send" + , " separated by @, e.g." + , " '@
'" + ] + + ["address", "--help"] `shouldShowUsage` + [ "Usage: address COMMAND" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " list List all known addresses of a given" + , " wallet." + ] + + ["address", "list", "--help"] `shouldShowUsage` + [ "Usage: address list [--port INT] [--state STRING] WALLET_ID" + , " List all known addresses of a given wallet." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + , " --state STRING only addresses with the given state:" + , " either 'used' or 'unused'." + ] describe "Can perform roundtrip textual encoding & decoding" $ do textRoundtrip $ Proxy @Iso8601Time From a6c96bb6ca65a2a10f445e87668bd82671cec193 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 17 Jul 2019 11:02:53 +0200 Subject: [PATCH 3/4] re-generate nix machinery --- nix/.stack.nix/cardano-wallet-cli.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nix/.stack.nix/cardano-wallet-cli.nix b/nix/.stack.nix/cardano-wallet-cli.nix index d8b8d978f5c..306e43facc9 100644 --- a/nix/.stack.nix/cardano-wallet-cli.nix +++ b/nix/.stack.nix/cardano-wallet-cli.nix @@ -42,8 +42,12 @@ "unit" = { depends = [ (hsPkgs.base) + (hsPkgs.cardano-crypto) (hsPkgs.cardano-wallet-cli) + (hsPkgs.cardano-wallet-core) (hsPkgs.hspec) + (hsPkgs.memory) + (hsPkgs.optparse-applicative) (hsPkgs.QuickCheck) (hsPkgs.quickcheck-instances) (hsPkgs.text) From c783a42ec792e7e4202dfa89f9535b82882233b6 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 17 Jul 2019 12:26:29 +0200 Subject: [PATCH 4/4] remove unecessary extra CLI comments --- exe/wallet/http-bridge/Main.hs | 17 +---------------- exe/wallet/jormungandr/Main.hs | 19 +------------------ lib/cli/src/Cardano/CLI.hs | 13 ------------- 3 files changed, 2 insertions(+), 47 deletions(-) diff --git a/exe/wallet/http-bridge/Main.hs b/exe/wallet/http-bridge/Main.hs index bc4f9369b8a..6c74699a591 100644 --- a/exe/wallet/http-bridge/Main.hs +++ b/exe/wallet/http-bridge/Main.hs @@ -140,15 +140,9 @@ main = runCli $ cli $ mempty {------------------------------------------------------------------------------- Command - 'launch' - - cardano-wallet launch - [--network=STRING] - [(--port=INT | --random-port)] - [--node-port=INT] - [--state-dir=DIR] - [(--quiet | --verbose )] -------------------------------------------------------------------------------} +-- | Arguments for the 'launch' command data LaunchArgs = LaunchArgs { _network :: Either Local Network , _listen :: Listen @@ -157,7 +151,6 @@ data LaunchArgs = LaunchArgs , _verbosity :: Verbosity } --- | cardano-wallet launch cmdLaunch :: Mod CommandFields (IO ()) cmdLaunch = command "launch" $ info (helper <*> cmd) $ mempty @@ -210,13 +203,6 @@ cmdLaunch = command "launch" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Command - 'serve' - - cardano-wallet serve - [--network=STRING] - [(--port=INT | --random-port)] - [--node-port=INT] - [--database=FILE] - [(--quiet | --verbose )] -------------------------------------------------------------------------------} -- | Arguments for the 'serve' command @@ -228,7 +214,6 @@ data ServeArgs = ServeArgs , _verbosity :: Verbosity } --- | cardano-wallet serve cmdServe :: Mod CommandFields (IO ()) cmdServe = command "serve" $ info (helper <*> cmd) $ mempty diff --git a/exe/wallet/jormungandr/Main.hs b/exe/wallet/jormungandr/Main.hs index d5a162496cc..b8db8000f51 100644 --- a/exe/wallet/jormungandr/Main.hs +++ b/exe/wallet/jormungandr/Main.hs @@ -166,16 +166,9 @@ main = runCli $ cli $ mempty {------------------------------------------------------------------------------- Command - 'launch' - - cardano-wallet launch - [(--port=INT | --random-port)] - [--node-port=INT] - [--state-dir=DIR] - [(--quiet | --verbose )] - --genesis-block=FILE - --bft-leaders=FILE -------------------------------------------------------------------------------} +-- | Arguments for the 'launch' command data LaunchArgs = LaunchArgs { _listen :: Listen , _nodePort :: Port "Node" @@ -189,7 +182,6 @@ data JormungandrArgs = JormungandrArgs , _bftLeaders :: FilePath } --- | cardano-wallet launch cmdLaunch :: Mod CommandFields (IO ()) cmdLaunch = command "launch" $ info (helper <*> cmd) $ mempty @@ -267,14 +259,6 @@ parseBlock0H file = do {------------------------------------------------------------------------------- Command - 'serve' - - cardano-wallet serve - [--network=STRING] - [(--port=INT | --random-port)] - [--node-port=INT] - [--database=FILE] - [(--quiet | --verbose )] - --genesis-hash=STRING -------------------------------------------------------------------------------} -- | Arguments for the 'serve' command @@ -286,7 +270,6 @@ data ServeArgs = ServeArgs , _block0H :: Hash "Genesis" } --- | cardano-wallet serve cmdServe :: Mod CommandFields (IO ()) cmdServe = command "serve" $ info (helper <*> cmd) $ mempty diff --git a/lib/cli/src/Cardano/CLI.hs b/lib/cli/src/Cardano/CLI.hs index 856f2c99397..98a338477a7 100644 --- a/lib/cli/src/Cardano/CLI.hs +++ b/lib/cli/src/Cardano/CLI.hs @@ -271,7 +271,6 @@ runCli = join . customExecParser preferences Commands - 'mnemonic' -------------------------------------------------------------------------------} --- | cardano-wallet mnemonic cmdMnemonic :: Mod CommandFields (IO ()) cmdMnemonic = command "mnemonic" $ info (helper <*> cmds) mempty where @@ -283,7 +282,6 @@ newtype MnemonicGenerateArgs = MnemonicGenerateArgs { _size :: MnemonicSize } --- | cardano-wallet mnemonic generate [--size=INT] cmdMnemonicGenerate :: Mod CommandFields (IO ()) cmdMnemonicGenerate = command "generate" $ info (helper <*> cmd) $ mempty <> progDesc "Generate English BIP-0039 compatible mnemonic words." @@ -303,7 +301,6 @@ cmdMnemonicGenerate = command "generate" $ info (helper <*> cmd) $ mempty Commands - 'wallet' -------------------------------------------------------------------------------} --- | cardano-wallet wallet cmdWallet :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -321,7 +318,6 @@ newtype WalletListArgs = WalletListArgs { _port :: Port "Wallet" } --- | cardano-wallet wallet list [--port=INT] cmdWalletList :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -339,7 +335,6 @@ data WalletCreateArgs = WalletCreateArgs , _gap :: AddressPoolGap } --- | cardano-wallet wallet create [--port=INT] [--address-pool-gap=INT] cmdWalletCreate :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -380,7 +375,6 @@ data WalletGetArgs = WalletGetArgs , _id :: WalletId } --- | cardano-wallet wallet get [--port=INT] cmdWalletGet :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -411,7 +405,6 @@ data WalletUpdateNameArgs = WalletUpdateNameArgs , _name :: WalletName } --- | cardano-wallet wallet update name [--port=INT] STRING cmdWalletUpdateName :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -433,7 +426,6 @@ data WalletUpdatePassphraseArgs = WalletUpdatePassphraseArgs , _id :: WalletId } --- | cardano-wallet wallet update passphrase [--port=INT] cmdWalletUpdatePassphrase :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -465,7 +457,6 @@ data WalletDeleteArgs = WalletDeleteArgs , _id :: WalletId } --- | cardano-wallet wallet delete [--port=INT] cmdWalletDelete :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -500,7 +491,6 @@ data TransactionCreateArgs t = TransactionCreateArgs , _payments :: NonEmpty (AddressAmount t) } --- | cardano-wallet wallet list [--port=INT] cmdTransactionCreate :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -548,7 +538,6 @@ cmdTransactionFees = command "fees" $ info (helper <*> cmd) $ mempty Commands - 'address' -------------------------------------------------------------------------------} --- | cardano-wallet address cmdAddress :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -564,7 +553,6 @@ data AddressListArgs = AddressListArgs , _id :: WalletId } --- | cardano-wallet address list [--port=INT] [--state=STRING] cmdAddressList :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -584,7 +572,6 @@ cmdAddressList = command "list" $ info (helper <*> cmd) $ mempty Commands - 'version' -------------------------------------------------------------------------------} --- | cardano-wallet version cmdVersion :: Mod CommandFields (IO ()) cmdVersion = command "version" $ info cmd $ mempty <> progDesc "Show the program's version."