ts_misc.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2023 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. test.test_py_d.ts_misc: Miscellaneous test groups for the test.py test suite
  20. """
  21. import sys,os,re,time
  22. from mmgen.cfg import gc
  23. from mmgen.util import ymsg
  24. from ..include.common import cfg,start_test_daemons,stop_test_daemons,imsg
  25. from .common import get_file_with_ext
  26. from .ts_base import TestSuiteBase
  27. from .ts_main import TestSuiteMain
  28. class TestSuiteMisc(TestSuiteBase):
  29. 'miscellaneous tests (RPC backends, xmrwallet_txview, term)'
  30. networks = ('btc',)
  31. tmpdir_nums = [99]
  32. passthru_opts = ('daemon_data_dir','rpc_port')
  33. cmd_group = (
  34. ('rpc_backends', 'RPC backends'),
  35. ('xmrwallet_txview', "'mmgen-xmrwallet' txview"),
  36. ('xmrwallet_txlist', "'mmgen-xmrwallet' txlist"),
  37. ('coin_daemon_info', "'examples/coin-daemon-info.py'"),
  38. ('term_echo', "term.set('echo')"),
  39. ('term_cleanup', 'term.register_cleanup()'),
  40. )
  41. need_daemon = True
  42. color = True
  43. def rpc_backends(self):
  44. backends = cfg._autoset_opts['rpc_backend'][1]
  45. for b in backends:
  46. t = self.spawn_chk('mmgen-tool',[f'--rpc-backend={b}','daemon_version'],extra_desc=f'({b})')
  47. return t
  48. def xmrwallet_txview(self,op='txview'):
  49. files = get_file_with_ext('test/ref/monero','tx',no_dot=True,delete=False,return_list=True)
  50. t = self.spawn( 'mmgen-xmrwallet', [op] + files )
  51. res = t.read(strip_color=True)
  52. if op == 'txview':
  53. for s in (
  54. 'Amount: 0.74 XMR',
  55. 'Dest: 56VQ9M6k',
  56. ):
  57. assert s in res, f'{s} not in {res}'
  58. elif op == 'txlist':
  59. assert re.search( '3EBD06-.*D94583-.*8BFA29-', res, re.DOTALL )
  60. return t
  61. def xmrwallet_txlist(self):
  62. return self.xmrwallet_txview(op='txlist')
  63. def coin_daemon_info(self):
  64. if cfg.no_altcoin:
  65. coins = ['btc']
  66. else:
  67. coins = ['btc','ltc','eth']
  68. start_test_daemons('ltc','eth')
  69. t = self.spawn('examples/coin-daemon-info.py',coins,cmd_dir='.')
  70. for coin in coins:
  71. t.expect(coin.upper() + r'\s+mainnet\s+Up',regex=True)
  72. if cfg.pexpect_spawn:
  73. t.send('q')
  74. if not cfg.no_altcoin:
  75. stop_test_daemons('ltc','eth')
  76. return t
  77. def term_echo(self):
  78. def test_echo():
  79. t.expect('echo> ','foo\n')
  80. t.expect('foo')
  81. def test_noecho():
  82. t.expect('noecho> ','foo\n')
  83. import pexpect
  84. try:
  85. t.expect('foo')
  86. except pexpect.TIMEOUT:
  87. imsg('[input not echoed - OK]')
  88. t.send('x')
  89. if self.skip_for_win():
  90. return 'skip'
  91. t = self.spawn('test/misc/term_ni.py',['echo'],cmd_dir='.',pexpect_spawn=True,timeout=1)
  92. t.p.logfile = None
  93. t.p.logfile_read = sys.stdout if cfg.verbose or cfg.exact_output else None
  94. t.p.logfile_send = None
  95. test_noecho()
  96. test_echo()
  97. test_noecho()
  98. return t
  99. def term_cleanup(self):
  100. if self.skip_for_win():
  101. return 'skip'
  102. return self.spawn('test/misc/term_ni.py',['cleanup'],cmd_dir='.',pexpect_spawn=True)
  103. class TestSuiteHelp(TestSuiteBase):
  104. 'help, info and usage screens'
  105. networks = ('btc','ltc','bch','eth','xmr')
  106. tmpdir_nums = []
  107. passthru_opts = ('daemon_data_dir','rpc_port','coin','testnet')
  108. cmd_group = (
  109. ('usage', (1,'usage message',[])),
  110. ('version', (1,'version message',[])),
  111. ('license', (1,'license message',[])),
  112. ('helpscreens', (1,'help screens', [])),
  113. ('longhelpscreens', (1,'help screens (--longhelp)',[])),
  114. ('show_hash_presets', (1,'info screen (--show-hash-presets)',[])),
  115. ('tool_help', (1,"'mmgen-tool' usage screen",[])),
  116. ('tool_cmd_usage', (1,"'mmgen-tool' usage screen",[])),
  117. ('test_help', (1,"'test.py' help screens",[])),
  118. ('tooltest_help', (1,"'tooltest.py' help screens",[])),
  119. )
  120. def usage(self):
  121. t = self.spawn('mmgen-walletgen',['foo'])
  122. t.expect('MMGenSystemExit(1)')
  123. t.expect('USAGE: mmgen-walletgen')
  124. t.req_exit_val = 1
  125. return t
  126. def version(self):
  127. t = self.spawn('mmgen-tool',['--version'])
  128. t.expect('MMGEN-TOOL version')
  129. return t
  130. def license(self):
  131. t = self.spawn(
  132. 'mmgen-walletconv',
  133. ['--stdout','--in-fmt=hex','--out-fmt=hex'],
  134. env = {'MMGEN_NO_LICENSE':''} )
  135. t.expect('to continue: ', 'w')
  136. t.expect('TERMS AND CONDITIONS') # start of GPL text
  137. if cfg.pexpect_spawn:
  138. t.send('G')
  139. t.expect('return for a fee.') # end of GPL text
  140. if cfg.pexpect_spawn:
  141. t.send('q')
  142. t.expect('to continue: ', 'c')
  143. t.expect('data: ','beadcafe'*4 + '\n')
  144. t.expect('to confirm: ', 'YES\n')
  145. return t
  146. def spawn_chk_expect(self,*args,**kwargs):
  147. expect = kwargs.pop('expect')
  148. t = self.spawn(*args,**kwargs)
  149. t.expect(expect)
  150. if t.pexpect_spawn:
  151. time.sleep(0.4)
  152. t.send('q')
  153. t.read()
  154. t.ok()
  155. t.skip_ok = True
  156. return t
  157. def helpscreens(self,arg='--help',scripts=(),expect='USAGE:.*OPTIONS:',pager=True):
  158. scripts = list(scripts) or [s.replace('mmgen-','') for s in os.listdir('cmds')]
  159. if 'tx' not in self.proto.mmcaps:
  160. scripts = [s for s in scripts if not (s == 'regtest' or s.startswith('tx'))]
  161. if self.proto.coin not in ('BTC','XMR') and 'xmrwallet' in scripts:
  162. scripts.remove('xmrwallet')
  163. if gc.platform == 'win' and 'autosign' in scripts:
  164. scripts.remove('autosign')
  165. for s in sorted(scripts):
  166. t = self.spawn(f'mmgen-{s}',[arg],extra_desc=f'(mmgen-{s})')
  167. t.expect(expect,regex=True)
  168. if pager and t.pexpect_spawn:
  169. time.sleep(0.2)
  170. t.send('q')
  171. t.read()
  172. t.ok()
  173. t.skip_ok = True
  174. return t
  175. def longhelpscreens(self):
  176. return self.helpscreens(arg='--longhelp',expect='USAGE:.*LONG OPTIONS:')
  177. def show_hash_presets(self):
  178. return self.helpscreens(
  179. arg = '--show-hash-presets',
  180. scripts = (
  181. 'walletgen','walletconv','walletchk','passchg','subwalletgen',
  182. 'addrgen','keygen','passgen',
  183. 'txsign','txdo','txbump'),
  184. expect = 'Available parameters.*Preset',
  185. pager = False )
  186. def tool_help(self):
  187. if os.getenv('PYTHONOPTIMIZE') == '2':
  188. ymsg('Skipping tool help with PYTHONOPTIMIZE=2 (no docstrings)')
  189. return 'skip'
  190. for arg in (
  191. 'help',
  192. 'usage',
  193. ):
  194. t = self.spawn_chk_expect(
  195. 'mmgen-tool',
  196. [arg],
  197. extra_desc = f'(mmgen-tool {arg})',
  198. expect = 'GENERAL USAGE' )
  199. return t
  200. def tool_cmd_usage(self):
  201. if os.getenv('PYTHONOPTIMIZE') == '2':
  202. ymsg('Skipping tool cmd usage with PYTHONOPTIMIZE=2 (no docstrings)')
  203. return 'skip'
  204. from mmgen.main_tool import mods
  205. for cmdlist in mods.values():
  206. for cmd in cmdlist:
  207. t = self.spawn_chk( 'mmgen-tool', ['help',cmd], extra_desc=f'({cmd})' )
  208. return t
  209. def test_help(self):
  210. for arg,expect in (
  211. ('--help','USAGE'),
  212. ('--list-cmds','AVAILABLE COMMANDS'),
  213. ('--list-cmd-groups','AVAILABLE COMMAND GROUPS')
  214. ):
  215. t = self.spawn_chk_expect(
  216. 'test.py',
  217. [arg],
  218. cmd_dir = 'test',
  219. extra_desc = f'(test.py {arg})',
  220. expect = expect )
  221. return t
  222. def tooltest_help(self):
  223. for arg,expect in (
  224. ('--list-cmds','Available commands'),
  225. ('--testing-status','Testing status')
  226. ):
  227. t = self.spawn_chk_expect(
  228. 'tooltest.py',
  229. [arg],
  230. cmd_dir = 'test',
  231. extra_desc = f'(tooltest.py {arg})',
  232. expect = expect )
  233. return t
  234. class TestSuiteOutput(TestSuiteBase):
  235. 'screen output'
  236. networks = ('btc',)
  237. tmpdir_nums = []
  238. cmd_group = (
  239. ('output_gr', (1,"Greek text", [])),
  240. ('output_ru', (1,"Russian text", [])),
  241. ('output_zh', (1,"Chinese text", [])),
  242. ('output_jp', (1,"Japanese text", [])),
  243. ('oneshot_warning', (1,"Oneshot warnings", [])),
  244. ('oneshot_warning_term', (1,"Oneshot warnings (pexpect_spawn)", []))
  245. )
  246. color = True
  247. def screen_output(self,lang):
  248. return self.spawn('test/misc/utf8_output.py',[lang],cmd_dir='.')
  249. def output_gr(self):
  250. return self.screen_output('gr')
  251. def output_ru(self):
  252. return self.screen_output('ru')
  253. def output_zh(self):
  254. return self.screen_output('zh')
  255. def output_jp(self):
  256. return self.screen_output('jp')
  257. def oneshot_warning(self,pexpect_spawn=None):
  258. t = self.spawn('test/misc/oneshot_warning.py',cmd_dir='.',pexpect_spawn=pexpect_spawn)
  259. nl = '\r\n' if gc.platform == 'win' or t.pexpect_spawn else '\n'
  260. for s in (
  261. f'pw{nl}wg1',
  262. 'foo is experimental',
  263. 'wg2', 'The bar command is dangerous',
  264. 'wg3', 'baz variant alpha',
  265. 'wg4', 'baz variant beta',
  266. 'w1', 'foo variant alpha',
  267. 'w2', 'foo variant beta',
  268. 'w3', 'bar is experimental',
  269. 'pw',
  270. "passphrase from file 'A'",
  271. "passphrase from file 'B'",
  272. f'wg1{nl}wg2{nl}wg3{nl}wg4{nl}w1{nl}w2{nl}w3',
  273. 'pw',
  274. "passphrase from file 'A'",
  275. "passphrase from file 'B'",
  276. f'wg1{nl}wg2{nl}wg3{nl}wg4{nl}w1{nl}w2{nl}w3',
  277. 'bottom',
  278. ):
  279. t.expect(s)
  280. return t
  281. def oneshot_warning_term(self):
  282. if self.skip_for_win():
  283. return 'skip'
  284. return self.oneshot_warning(pexpect_spawn=True)
  285. class TestSuiteRefTX(TestSuiteMain,TestSuiteBase):
  286. 'create a reference transaction file (administrative command)'
  287. segwit_opts_ok = False
  288. passthru_opts = ('daemon_data_dir','rpc_port','coin','testnet')
  289. tmpdir_nums = [31,32,33,34]
  290. need_daemon = True
  291. cmd_group = (
  292. ('ref_tx_addrgen1', (31,'address generation (legacy)', [[[],1]])),
  293. ('ref_tx_addrgen2', (32,'address generation (compressed)', [[[],1]])),
  294. ('ref_tx_addrgen3', (33,'address generation (segwit)', [[[],1]])),
  295. ('ref_tx_addrgen4', (34,'address generation (bech32)', [[[],1]])),
  296. ('ref_tx_txcreate', (31,'transaction creation',
  297. ([['addrs'],31],[['addrs'],32],[['addrs'],33],[['addrs'],34]))),
  298. )
  299. def __init__(self,trunner,cfgs,spawn):
  300. if cfgs:
  301. for n in self.tmpdir_nums:
  302. cfgs[str(n)].update({ 'addr_idx_list': '1-2',
  303. 'segwit': n in (33,34),
  304. 'dep_generators': { 'addrs':'ref_tx_addrgen'+str(n)[-1] }})
  305. return TestSuiteMain.__init__(self,trunner,cfgs,spawn)
  306. def ref_tx_addrgen(self,atype):
  307. if atype not in self.proto.mmtypes:
  308. return
  309. return self.spawn('mmgen-addrgen',['--outdir='+self.tmpdir,'--type='+atype,dfl_words_file,'1-2'])
  310. def ref_tx_addrgen1(self):
  311. return self.ref_tx_addrgen(atype='L')
  312. def ref_tx_addrgen2(self):
  313. return self.ref_tx_addrgen(atype='C')
  314. def ref_tx_addrgen3(self):
  315. return self.ref_tx_addrgen(atype='S')
  316. def ref_tx_addrgen4(self):
  317. return self.ref_tx_addrgen(atype='B')
  318. def ref_tx_txcreate(self,f1,f2,f3,f4):
  319. sources = ['31','32']
  320. if 'S' in self.proto.mmtypes:
  321. sources += ['33']
  322. if 'B' in self.proto.mmtypes:
  323. sources += ['34']
  324. return self.txcreate_common(
  325. addrs_per_wallet = 2,
  326. sources = sources,
  327. add_args = ['--locktime=1320969600'],
  328. do_label = True)