opts.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # Project source code repository: https://github.com/mmgen/mmgen-wallet
  7. # Licensed according to the terms of GPL Version 3. See LICENSE for details.
  8. """
  9. test.cmdtest_d.opts: options processing tests for the MMGen cmdtest.py test suite
  10. """
  11. import os, time
  12. from .base import CmdTestBase
  13. class CmdTestOpts(CmdTestBase):
  14. 'command-line options parsing and processing'
  15. networks = ('btc',)
  16. tmpdir_nums = [41]
  17. cmd_group = (
  18. ('opt_helpscreen', (41, 'helpscreen output', [])),
  19. ('opt_noargs', (41, 'invocation with no user options or arguments', [])),
  20. ('opt_good1', (41, 'good opts (long opts only)', [])),
  21. ('opt_good2', (41, 'good opts (mixed short and long opts)', [])),
  22. ('opt_good3', (41, 'good opts (max arg count)', [])),
  23. ('opt_good4', (41, 'good opts (maxlen arg)', [])),
  24. ('opt_good5', (41, 'good opts (long opt substring)', [])),
  25. ('opt_good6', (41, 'good global opt (--coin=xmr)', [])),
  26. ('opt_good7', (41, 'good global opt (--coin xmr)', [])),
  27. ('opt_good8', (41, 'good global opt (--pager)', [])),
  28. ('opt_good9', (41, 'good cmdline arg ‘-’', [])),
  29. ('opt_good10', (41, 'good cmdline arg ‘-’ with arg', [])),
  30. ('opt_good11', (41, 'good cmdline arg ‘-’ with option', [])),
  31. ('opt_good12', (41, 'good cmdline opt (short opt with option)', [])),
  32. ('opt_good13', (41, 'good cmdline opt (short opt with option)', [])),
  33. ('opt_good14', (41, 'good cmdline opt (combined short opt with option)', [])),
  34. ('opt_good15', (41, 'good cmdline opt (combined short opt with option)', [])),
  35. ('opt_good16', (41, 'good cmdline opt (param with equals signs)', [])),
  36. ('opt_good17', (41, 'good cmdline opt (param with equals signs)', [])),
  37. ('opt_good18', (41, 'good cmdline opt (param with equals signs)', [])),
  38. ('opt_good19', (41, 'good cmdline opt (param with equals signs)', [])),
  39. ('opt_good20', (41, 'good cmdline opt (opt + negated opt)', [])),
  40. ('opt_good21', (41, 'good cmdline opt (negated negative opt)', [])),
  41. ('opt_good22', (41, 'good cmdline opt (opt + negated opt [substring])', [])),
  42. ('opt_good23', (41, 'good cmdline opt (negated negative opt [substring])', [])),
  43. ('opt_good24', (41, 'good cmdline opt (negated opt + opt [substring])', [])),
  44. ('opt_good25', (41, 'good cmdline opt (--btc-rpc-host)', [])),
  45. ('opt_good26', (41, 'good cmdline opt (--btc-rpc-port)', [])),
  46. ('opt_good27', (41, 'good cmdline opt (--btc-ignore-daemon-version)', [])),
  47. ('opt_good28', (41, 'good cmdline opt (--bch-cashaddr)', [])),
  48. ('opt_good29', (41, 'good cmdline opt (--etc-max-tx-fee=0.1)', [])),
  49. ('opt_good30', (41, 'good cmdline opt (--eth-chain-names=foo,bar)', [])),
  50. ('opt_good31', (41, 'good cmdline opt (--xmr-rpc-port=28081)', [])),
  51. ('opt_bad_param', (41, 'bad global opt (--pager=1)', [])),
  52. ('opt_bad_infile', (41, 'bad infile parameter', [])),
  53. ('opt_bad_outdir', (41, 'bad outdir parameter', [])),
  54. ('opt_bad_incompatible', (41, 'incompatible opts', [])),
  55. ('opt_bad_autoset', (41, 'invalid autoset value', [])),
  56. ('opt_invalid_1', (41, 'invalid cmdline opt ‘--x’', [])),
  57. ('opt_invalid_2', (41, 'invalid cmdline opt ‘---’', [])),
  58. ('opt_invalid_5', (41, 'invalid cmdline opt (missing parameter)', [])),
  59. ('opt_invalid_6', (41, 'invalid cmdline opt (missing parameter)', [])),
  60. ('opt_invalid_7', (41, 'invalid cmdline opt (parameter not required)', [])),
  61. ('opt_invalid_8', (41, 'invalid cmdline opt (non-existent option)', [])),
  62. ('opt_invalid_9', (41, 'invalid cmdline opt (non-existent option)', [])),
  63. ('opt_invalid_10', (41, 'invalid cmdline opt (missing parameter)', [])),
  64. ('opt_invalid_11', (41, 'invalid cmdline opt (missing parameter)', [])),
  65. ('opt_invalid_12', (41, 'invalid cmdline opt (non-existent option)', [])),
  66. ('opt_invalid_13', (41, 'invalid cmdline opt (ambiguous long opt substring)', [])),
  67. ('opt_invalid_14', (41, 'invalid cmdline opt (long opt substring too short)', [])),
  68. ('opt_invalid_15', (41, 'invalid cmdline (too many args)', [])),
  69. ('opt_invalid_16', (41, 'invalid cmdline (overlong arg)', [])),
  70. ('opt_invalid_17', (41, 'invalid cmdline opt (--btc-rpc-host without ‘need_proto’)', [])),
  71. ('opt_invalid_18', (41, 'invalid cmdline opt (--btc-rpc-port without ‘need_proto’)', [])),
  72. ('opt_invalid_19', (41, 'invalid cmdline opt (--btc-rpc-port with non-integer param)', [])),
  73. ('opt_invalid_21', (41, 'invalid cmdline opt (--btc-foo)', [])),
  74. ('opt_invalid_22', (41, 'invalid cmdline opt (--btc-rpc-host with missing param)', [])),
  75. ('opt_invalid_23', (41, 'invalid cmdline opt (--btc-ignore-daemon-version with param)', [])),
  76. ('opt_invalid_24', (41, 'invalid cmdline opt (--bch-cashaddr without ‘need_proto’)', [])),
  77. ('opt_invalid_25', (41, 'invalid cmdline opt (--bch-cashaddr without parameter)', [])),
  78. ('opt_invalid_26', (41, 'invalid cmdline opt (--bch-cashaddr with non-bool parameter)', [])),
  79. ('opt_invalid_27', (41, 'invalid cmdline opt (--ltc-cashaddr)', [])),
  80. ('opt_invalid_28', (41, 'invalid cmdline opt (--xmr-max-tx-fee)', [])),
  81. ('opt_invalid_29', (41, 'invalid cmdline opt (--eth-max-tx-fee without parameter)', [])),
  82. ('opt_invalid_30', (41, 'invalid cmdline opt (--eth-max-tx-fee with non-numeric parameter)', [])),
  83. ('opt_invalid_31', (41, 'invalid cmdline opt (--bch-cashaddr without --coin=bch)', [])),
  84. ('opt_invalid_32', (41, 'invalid cmdline opt (--eth-chain-names without --coin=eth)', [])),
  85. ('opt_invalid_33', (41, 'invalid cmdline opt (--xmr-rpc-host)', [])),
  86. ('opt_invalid_34', (41, 'invalid cmdline opt (--eth-rpc-user)', [])),
  87. )
  88. def spawn_prog(self, args, opts=[], exit_val=None, need_proto=False):
  89. return self.spawn(
  90. 'test/misc/opts.py',
  91. opts + args,
  92. cmd_dir = '.',
  93. exit_val = exit_val,
  94. env = {'TEST_MISC_OPTS_NEEDS_PROTO': '1' if need_proto else ''})
  95. def check_vals(self, args, vals, check=True, need_proto=False):
  96. show_opts = [a.removeprefix('cfg.') for a, b in vals if a.startswith('cfg.')]
  97. t = self.spawn_prog(
  98. args,
  99. opts = ['--show-opts=' + ','.join(show_opts)] if show_opts else [],
  100. need_proto = need_proto)
  101. if check:
  102. for k, v in vals:
  103. t.expect(rf'{k}:\s+{v}', regex=True)
  104. return t
  105. def do_run(self, args, expect, exit_val, regex=False):
  106. t = self.spawn_prog(args, exit_val=exit_val or None)
  107. t.expect(expect, regex=regex)
  108. return t
  109. def opt_helpscreen(self):
  110. expect = r'OPTS.PY: Opts test.*USAGE:\s+opts.py'
  111. if not self.cfg.pexpect_spawn:
  112. expect += r'.*--minconf.*NOTES FOR THIS.*a note'
  113. t = self.do_run(['--help'], expect, 0, regex=True)
  114. if t.pexpect_spawn:
  115. time.sleep(0.4)
  116. t.send('q')
  117. return t
  118. def opt_noargs(self):
  119. return self.check_vals(
  120. [],
  121. (
  122. ('cfg.foo', 'None'), # added opt
  123. ('cfg.print_checksum', 'None'), # sets 'quiet'
  124. ('cfg.quiet', 'False'), # _incompatible_opts
  125. ('cfg.verbose', 'False'), # _incompatible_opts
  126. ('cfg.passwd_file', ''), # _infile_opts - check_infile()
  127. ('cfg.outdir', ''), # check_outdir()
  128. ('cfg.cached_balances', 'False'),
  129. ('cfg.minconf', '1'),
  130. ('cfg.coin', 'BTC'),
  131. ('cfg.pager', 'False'),
  132. ('cfg.fee_estimate_mode', 'conservative'), # _autoset_opts
  133. ))
  134. def opt_good1(self):
  135. pf_base = 'testfile'
  136. pf = os.path.join(self.tmpdir, pf_base)
  137. self.write_to_tmpfile(pf_base, '')
  138. return self.check_vals(
  139. [
  140. '--print-checksum',
  141. '--fee-estimate-mode=E',
  142. '--passwd-file='+pf,
  143. '--outdir='+self.tmpdir,
  144. '--cached-balances',
  145. f'--hidden-incog-input-params={pf},123',
  146. ], (
  147. ('cfg.print_checksum', 'True'),
  148. ('cfg.quiet', 'True'), # set by print_checksum
  149. ('cfg.passwd_file', pf),
  150. ('cfg.outdir', self.tmpdir),
  151. ('cfg.cached_balances', 'True'),
  152. ('cfg.hidden_incog_input_params', pf+',123'),
  153. ('cfg.fee_estimate_mode', 'economical'),
  154. ))
  155. def opt_good2(self):
  156. return self.check_vals(
  157. [
  158. '--print-checksum',
  159. '-qX',
  160. f'--outdir={self.tmpdir}',
  161. '-p5',
  162. '-m', '0',
  163. '--seed-len=256',
  164. '-L--my-label',
  165. '--seed-len', '128',
  166. '--min-temp=-30',
  167. '-T-10',
  168. '--',
  169. 'x', 'y', '12345'
  170. ], (
  171. ('cfg.print_checksum', 'True'),
  172. ('cfg.quiet', 'True'),
  173. ('cfg.outdir', self.tmpdir),
  174. ('cfg.cached_balances', 'True'),
  175. ('cfg.minconf', '0'),
  176. ('cfg.keep_label', 'None'),
  177. ('cfg.seed_len', '128'),
  178. ('cfg.hash_preset', '5'),
  179. ('cfg.label', '--my-label'),
  180. ('cfg.min_temp', '-30'),
  181. ('cfg.max_temp', '-10'),
  182. ('arg1', 'x'),
  183. ('arg2', 'y'),
  184. ('arg3', '12345'),
  185. ))
  186. def opt_good3(self):
  187. return self.check_vals(['m'] * 256, (('arg256', 'm'),))
  188. def opt_good4(self):
  189. return self.check_vals(['e' * 4096], (('arg1', 'e' * 4096),))
  190. def opt_good5(self):
  191. return self.check_vals(['--minc=7'], (('cfg.minconf', '7'),))
  192. def opt_good6(self):
  193. if self.cfg.no_altcoin:
  194. return 'skip'
  195. return self.check_vals(['--coin=xmr'], (('cfg.coin', 'XMR'),))
  196. def opt_good7(self):
  197. if self.cfg.no_altcoin:
  198. return 'skip'
  199. return self.check_vals(['--coin', 'xmr'], (('cfg.coin', 'XMR'),))
  200. def opt_good8(self):
  201. return self.check_vals(['--pager'], (('cfg.pager', 'True'),))
  202. def opt_good9(self):
  203. return self.check_vals(['-'], (('arg1', '-'),))
  204. def opt_good10(self):
  205. return self.check_vals(['-', '-x'], (('arg1', '-'), ('arg2', '-x')))
  206. def opt_good11(self):
  207. return self.check_vals(['-q', '-', '-x'], (('arg1', '-'), ('arg2', '-x')))
  208. def opt_good12(self):
  209. return self.check_vals(['-l128'], (('cfg.seed_len', '128'),))
  210. def opt_good13(self):
  211. return self.check_vals(['-l', '128'], (('cfg.seed_len', '128'),))
  212. def opt_good14(self):
  213. return self.check_vals(['-kl128'], (('cfg.keep_label', 'True'), ('cfg.seed_len', '128')))
  214. def opt_good15(self):
  215. return self.check_vals(['-kl', '128'], (('cfg.keep_label', 'True'), ('cfg.seed_len', '128')))
  216. def opt_good16(self):
  217. return self.check_vals(['--point=x=1,y=2,z=3'], (('cfg.point', 'x=1,y=2,z=3'),))
  218. def opt_good17(self):
  219. return self.check_vals(['--point', 'x=1,y=2,z=3'], (('cfg.point', 'x=1,y=2,z=3'),))
  220. def opt_good18(self):
  221. return self.check_vals(['-xx=1,y=2,z=3'], (('cfg.point', 'x=1,y=2,z=3'),))
  222. def opt_good19(self):
  223. return self.check_vals(['-x', 'x=1,y=2,z=3'], (('cfg.point', 'x=1,y=2,z=3'),))
  224. def opt_good20(self):
  225. return self.check_vals(['--pager', '--no-pager'], (('cfg.pager', 'False'),))
  226. def opt_good21(self):
  227. return self.check_vals(['--foobleize'], (('cfg.no_foobleize', 'False'),))
  228. def opt_good22(self):
  229. return self.check_vals(['--quiet', '--no-q'], (('cfg.quiet', 'False'),))
  230. def opt_good23(self):
  231. return self.check_vals(['--foobl'], (('cfg.no_foobleize', 'False'),))
  232. def opt_good24(self):
  233. return self.check_vals(['--no-pag', '--pag'], (('cfg.pager', 'True'),))
  234. def opt_good25(self):
  235. return self.check_vals(
  236. ['--btc-rpc-host=pi5'],
  237. (('cfg.btc_rpc_host', 'pi5'), ('proto.rpc_host', 'pi5')),
  238. need_proto=True)
  239. def opt_good26(self):
  240. return self.check_vals(
  241. ['--btc-rpc-port=7272'],
  242. (('cfg.btc_rpc_port', '7272'), ('proto.rpc_port', '7272')),
  243. need_proto=True)
  244. def opt_good27(self):
  245. return self.check_vals(
  246. ['--btc-ignore-daemon-version'],
  247. (('cfg.btc_ignore_daemon_version', 'True'), ('proto.ignore_daemon_version', 'True'),),
  248. need_proto = True)
  249. def opt_good28(self):
  250. return self.check_vals(
  251. ['--coin=bch', '--bch-cashaddr=yes'],
  252. (('cfg.bch_cashaddr', 'True'), ('proto.cashaddr', 'True'),),
  253. need_proto = True)
  254. def opt_good29(self):
  255. return self.check_vals(['--etc-max-tx-fee=0.1'], (('cfg.etc_max_tx_fee', '0.1'),), need_proto=True)
  256. def opt_good30(self):
  257. return self.check_vals(
  258. ['--coin=eth', '--eth-mainnet-chain-names=foo,bar'],
  259. (('cfg.eth_mainnet_chain_names', r"\['foo', 'bar'\]"), ('proto.chain_names', r"\['foo', 'bar'\]")),
  260. need_proto = True)
  261. def opt_good31(self):
  262. return self.check_vals(
  263. ['--coin=xmr', '--xmr-rpc-port=28081'],
  264. (('cfg.xmr_rpc_port', '28081'),('proto.rpc_port', '28081'),),
  265. need_proto = True)
  266. def opt_bad_param(self):
  267. return self.do_run(['--pager=1'], 'no parameter', 1)
  268. def opt_bad_infile(self):
  269. pf = os.path.join(self.tmpdir, 'fubar')
  270. return self.do_run(['--passwd-file='+pf], 'not found', 1)
  271. def opt_bad_outdir(self):
  272. bo = self.tmpdir+'_fubar'
  273. return self.do_run(['--outdir='+bo], 'not found', 1)
  274. def opt_bad_incompatible(self):
  275. return self.do_run(['--label=Label', '--keep-label'], 'Conflicting options', 1)
  276. def opt_bad_autoset(self):
  277. return self.do_run(['--fee-estimate-mode=Fubar'], 'not unique substring', 1)
  278. def opt_invalid(self, args, expect, opts=[], need_proto=False, exit_val=1):
  279. t = self.spawn_prog(args, opts=opts, exit_val=exit_val, need_proto=need_proto)
  280. t.expect(expect)
  281. return t
  282. def opt_invalid_1(self):
  283. return self.opt_invalid(['--x'], 'must be at least')
  284. def opt_invalid_2(self):
  285. return self.opt_invalid(['---'], 'must be at least')
  286. def opt_invalid_5(self):
  287. return self.opt_invalid(['-l'], 'missing parameter')
  288. def opt_invalid_6(self):
  289. return self.opt_invalid(['-l', '-k'], 'missing parameter')
  290. def opt_invalid_7(self):
  291. return self.opt_invalid(['--quiet=1'], 'requires no parameter')
  292. def opt_invalid_8(self):
  293. return self.opt_invalid(['-w'], 'unrecognized option')
  294. def opt_invalid_9(self):
  295. return self.opt_invalid(['--frobnicate'], 'unrecognized option')
  296. def opt_invalid_10(self):
  297. return self.opt_invalid(['--label', '-q'], 'missing parameter')
  298. def opt_invalid_11(self):
  299. return self.opt_invalid(['-T', '-10'], 'missing parameter')
  300. def opt_invalid_12(self):
  301. return self.opt_invalid(['-q', '-10'], 'unrecognized option')
  302. def opt_invalid_13(self):
  303. return self.opt_invalid(['--mi=3'], 'ambiguous option')
  304. def opt_invalid_14(self):
  305. return self.opt_invalid(['--m=3'], 'must be at least')
  306. def opt_invalid_15(self):
  307. return self.opt_invalid(['m'] * 257, 'too many')
  308. def opt_invalid_16(self):
  309. return self.opt_invalid(['e' * 4097], 'too long')
  310. def opt_invalid_17(self):
  311. return self.opt_invalid(['--btc-rpc-host'], 'unrecognized option')
  312. def opt_invalid_18(self):
  313. return self.opt_invalid(['--btc-rpc-port'], 'unrecognized option')
  314. def opt_invalid_19(self):
  315. return self.opt_invalid(['--btc-rpc-port=foo'], "must be of type 'int'", need_proto=True)
  316. def opt_invalid_21(self):
  317. return self.opt_invalid(['--btc-foo'], 'unrecognized option')
  318. def opt_invalid_22(self):
  319. return self.opt_invalid(['--btc-rpc-host'], 'missing parameter', need_proto=True)
  320. def opt_invalid_23(self):
  321. return self.opt_invalid(['--btc-ignore-daemon-version=1'], 'requires no parameter', need_proto=True)
  322. def opt_invalid_24(self):
  323. return self.opt_invalid(['--bch-cashaddr'], 'unrecognized option')
  324. def opt_invalid_25(self):
  325. return self.opt_invalid(['--bch-cashaddr'], 'missing parameter', need_proto=True)
  326. def opt_invalid_26(self):
  327. return self.opt_invalid(['--bch-cashaddr=foo'], "must be of type 'bool'", need_proto=True)
  328. def opt_invalid_27(self):
  329. return self.opt_invalid(['--ltc-cashaddr'], 'unrecognized option', need_proto=True)
  330. def opt_invalid_28(self):
  331. return self.opt_invalid(['--xmr-max-tx-fee=0.1'], 'unrecognized option', need_proto=True)
  332. def opt_invalid_29(self):
  333. return self.opt_invalid(['--eth-max-tx-fee'], 'missing parameter', need_proto=True)
  334. def opt_invalid_30(self):
  335. return self.opt_invalid(['--eth-max-tx-fee=true'], 'must be of type', need_proto=True)
  336. def opt_invalid_31(self):
  337. return self.opt_invalid(['--bch-cashaddr=true'], 'has no attribute', opts=['--show-opts=bch_cashaddr'], need_proto=True)
  338. def opt_invalid_32(self):
  339. return self.opt_invalid(['--eth-chain-names=foo,bar'], 'unrecognized option', need_proto=True)
  340. def opt_invalid_33(self):
  341. return self.opt_invalid(['--xmr-rpc-host=solaris'], 'unrecognized option', need_proto=True)
  342. def opt_invalid_34(self):
  343. return self.opt_invalid(['--eth-rpc-user=bob'], 'unrecognized option', need_proto=True)