diff --git a/test/test.py b/test/test.py index e6d7e165..59aaec39 100755 --- a/test/test.py +++ b/test/test.py @@ -470,8 +470,8 @@ class CmdGroupMgr(object): 'ref3_addr': ('TestSuiteRef3Addr',{'is3seed':True,'modname':'ref_3seed'}), 'ref_altcoin': ('TestSuiteRefAltcoin',{}), 'seedsplit': ('TestSuiteSeedSplit',{}), - 'tool': ('TestSuiteTool',{'modname':'misc','full_data':True}), - 'input': ('TestSuiteInput',{'modname':'misc','full_data':True}), + 'tool': ('TestSuiteTool',{'full_data':True}), + 'input': ('TestSuiteInput',{'full_data':True}), 'output': ('TestSuiteOutput',{'modname':'misc','full_data':True}), 'autosign': ('TestSuiteAutosign',{}), 'regtest': ('TestSuiteRegtest',{}), diff --git a/test/test_py_d/ts_input.py b/test/test_py_d/ts_input.py new file mode 100755 index 00000000..1e9f7120 --- /dev/null +++ b/test/test_py_d/ts_input.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2020 The MMGen Project +# +# Project source code repository: https://github.com/mmgen/mmgen +# Licensed according to the terms of GPL Version 3. See LICENSE for details. + +""" +ts_input.py: user input tests for the MMGen test.py test suite +""" + +from test.common import * +from test.test_py_d.ts_base import * +from mmgen.seed import SeedSource + +class TestSuiteInput(TestSuiteBase): + 'user input' + networks = ('btc',) + tmpdir_nums = [] + cmd_group = ( + ('password_entry_noecho', (1,"utf8 password entry", [])), + ('password_entry_echo', (1,"utf8 password entry (echoed)", [])), + ('mnemonic_entry_mmgen', (1,"stealth mnemonic entry (mmgen)", [])), + ('mnemonic_entry_bip39', (1,"stealth mnemonic entry (bip39)", [])), + ('dieroll_entry', (1,"dieroll entry (base6d)", [])), + ('dieroll_entry_usrrand', (1,"dieroll entry (base6d) with added user entropy", [])), + ) + + def password_entry(self,prompt,cmd_args): + t = self.spawn('test/misc/password_entry.py',cmd_args,cmd_dir='.') + pw = 'abc-α' + t.expect(prompt,pw) + ret = t.expect_getend('Entered: ') + assert ret == pw,'Password mismatch! {} != {}'.format(ret,pw) + return t + + def password_entry_noecho(self): + if self.skip_for_win(): + m = "getpass() doesn't work with pexpect.popen_spawn!\n" + m += 'Perform the following test by hand with non-ASCII password abc-α:\n' + m += ' test/misc/password_entry.py' + return ('skip_warn',m) + return self.password_entry('Enter passphrase: ',[]) + + def password_entry_echo(self): + if self.skip_for_win(): + m = "getpass() doesn't work with pexpect.popen_spawn!\n" + m += 'Perform the following test by hand with non-ASCII password abc-α:\n' + m += ' test/misc/password_entry.py --echo-passphrase' + return ('skip_warn',m) + return self.password_entry('Enter passphrase (echoed): ',['--echo-passphrase']) + + def _user_seed_entry(self,fmt,usr_rand=False,out_fmt=None,mn=None): + wcls = SeedSource.fmt_code_to_type(fmt) + wf = os.path.join(ref_dir,'FE3C6545.{}'.format(wcls.ext)) + if wcls.wclass == 'mnemonic': + mn = mn or read_from_file(wf).strip().split() + elif wcls.wclass == 'dieroll': + mn = mn or list(read_from_file(wf).strip().translate(dict((ord(ws),None) for ws in '\t\n '))) + for idx,val in ((5,'x'),(18,'0'),(30,'7'),(44,'9')): + mn.insert(idx,val) + t = self.spawn('mmgen-walletconv',['-r10','-S','-i',fmt,'-o',out_fmt or fmt]) + t.expect('{} type: {}'.format(capfirst(wcls.wclass),wcls.mn_type)) + t.expect(wcls.choose_seedlen_prompt,'1') + t.expect('(Y/n): ','y') + if wcls.wclass == 'mnemonic': + stealth_mnemonic_entry(t,mn,fmt=fmt) + elif wcls.wclass == 'dieroll': + user_dieroll_entry(t,mn) + if usr_rand: + t.expect(wcls.user_entropy_prompt,'y') + t.usr_rand(10) + else: + t.expect(wcls.user_entropy_prompt,'n') + if not usr_rand: + sid_chk = 'FE3C6545' + sid = t.expect_getend('Valid {} for Seed ID '.format(wcls.desc))[:8] + assert sid == sid_chk,'Seed ID mismatch! {} != {}'.format(sid,sid_chk) + t.expect('to confirm: ','YES\n') + t.read() + return t + + def mnemonic_entry_mmgen(self): return self._user_seed_entry('words',entry_mode='full') + def mnemonic_entry_bip39(self): return self._user_seed_entry('bip39',entry_mode='full') + + def dieroll_entry(self): return self._user_seed_entry('dieroll') + def dieroll_entry_usrrand(self): return self._user_seed_entry('dieroll',usr_rand=True,out_fmt='bip39') diff --git a/test/test_py_d/ts_misc.py b/test/test_py_d/ts_misc.py index 4012261d..ee5e1b8b 100755 --- a/test/test_py_d/ts_misc.py +++ b/test/test_py_d/ts_misc.py @@ -25,7 +25,6 @@ from test.common import * from test.test_py_d.common import * from test.test_py_d.ts_base import * from test.test_py_d.ts_main import TestSuiteMain -from mmgen.seed import SeedSource class TestSuiteHelp(TestSuiteBase): 'help, info and usage screens' @@ -112,149 +111,6 @@ class TestSuiteOutput(TestSuiteBase): def output_zh(self): return self.screen_output('zh') def output_jp(self): return self.screen_output('jp') -class TestSuiteInput(TestSuiteBase): - 'user input tests' - networks = ('btc',) - tmpdir_nums = [] - cmd_group = ( - ('password_entry_noecho', (1,"utf8 password entry", [])), - ('password_entry_echo', (1,"utf8 password entry (echoed)", [])), - ('mnemonic_entry_mmgen', (1,"stealth mnemonic entry (mmgen)", [])), - ('mnemonic_entry_bip39', (1,"stealth mnemonic entry (bip39)", [])), - ('dieroll_entry', (1,"dieroll entry (base6d)", [])), - ('dieroll_entry_usrrand', (1,"dieroll entry (base6d) with added user entropy", [])), - ) - - def password_entry(self,prompt,cmd_args): - t = self.spawn('test/misc/password_entry.py',cmd_args,cmd_dir='.') - pw = 'abc-α' - t.expect(prompt,pw) - ret = t.expect_getend('Entered: ') - assert ret == pw,'Password mismatch! {} != {}'.format(ret,pw) - return t - - def password_entry_noecho(self): - if self.skip_for_win(): - m = "getpass() doesn't work with pexpect.popen_spawn!\n" - m += 'Perform the following test by hand with non-ASCII password abc-α:\n' - m += ' test/misc/password_entry.py' - return ('skip_warn',m) - return self.password_entry('Enter passphrase: ',[]) - - def password_entry_echo(self): - if self.skip_for_win(): - m = "getpass() doesn't work with pexpect.popen_spawn!\n" - m += 'Perform the following test by hand with non-ASCII password abc-α:\n' - m += ' test/misc/password_entry.py --echo-passphrase' - return ('skip_warn',m) - return self.password_entry('Enter passphrase (echoed): ',['--echo-passphrase']) - - def _user_seed_entry(self,fmt,usr_rand=False,out_fmt=None,mn=None): - wcls = SeedSource.fmt_code_to_type(fmt) - wf = os.path.join(ref_dir,'FE3C6545.{}'.format(wcls.ext)) - if wcls.wclass == 'mnemonic': - mn = mn or read_from_file(wf).strip().split() - elif wcls.wclass == 'dieroll': - mn = mn or list(read_from_file(wf).strip().translate(dict((ord(ws),None) for ws in '\t\n '))) - for idx,val in ((5,'x'),(18,'0'),(30,'7'),(44,'9')): - mn.insert(idx,val) - t = self.spawn('mmgen-walletconv',['-r10','-S','-i',fmt,'-o',out_fmt or fmt]) - t.expect('{} type: {}'.format(capfirst(wcls.wclass),wcls.mn_type)) - t.expect(wcls.choose_seedlen_prompt,'1') - t.expect('(Y/n): ','y') - if wcls.wclass == 'mnemonic': - stealth_mnemonic_entry(t,mn,fmt=fmt) - elif wcls.wclass == 'dieroll': - user_dieroll_entry(t,mn) - if usr_rand: - t.expect(wcls.user_entropy_prompt,'y') - t.usr_rand(10) - else: - t.expect(wcls.user_entropy_prompt,'n') - if not usr_rand: - sid_chk = 'FE3C6545' - sid = t.expect_getend('Valid {} for Seed ID '.format(wcls.desc))[:8] - assert sid == sid_chk,'Seed ID mismatch! {} != {}'.format(sid,sid_chk) - t.expect('to confirm: ','YES\n') - t.read() - return t - - def mnemonic_entry_mmgen(self): return self._user_seed_entry('words') - def mnemonic_entry_bip39(self): return self._user_seed_entry('bip39') - def dieroll_entry(self): return self._user_seed_entry('dieroll') - def dieroll_entry_usrrand(self):return self._user_seed_entry('dieroll',usr_rand=True,out_fmt='bip39') - -class TestSuiteTool(TestSuiteMain,TestSuiteBase): - "tests for interactive 'mmgen-tool' commands" - networks = ('btc',) - segwit_opts_ok = False - tmpdir_nums = [9] - enc_infn = 'tool_encrypt.in' - cmd_group = ( - ('tool_find_incog_data', (9,"'mmgen-tool find_incog_data'", [[[hincog_fn],1],[[incog_id_fn],1]])), - ('tool_rand2file', (9,"'mmgen-tool rand2file'", [])), - ('tool_encrypt', (9,"'mmgen-tool encrypt' (random data)", [])), - ('tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", [[[enc_infn+'.mmenc'],9]])), - ('tool_twview_bad_comment',(9,"'mmgen-tool twview' (with bad comment)", [])), - # ('tool_encrypt_ref', (9,"'mmgen-tool encrypt' (reference text)", [])), - ) - - def tool_rand2file(self): - outfile = os.path.join(self.tmpdir,'rand2file.out') - from mmgen.tool import MMGenToolCmd - tu = MMGenToolCmd() - for nbytes in ('1','1023','1K','1048575','1M','1048577','123M'): - t = self.spawn( 'mmgen-tool', - ['-d',self.tmpdir,'-r0','rand2file','rand2file.out',nbytes], - extra_desc='({} byte{})'.format(nbytes,suf(tu.bytespec(nbytes))) - ) - t.expect('random data written to file') - t.read() - t.p.wait() - t.ok() - t.skip_ok = True - return t - - def tool_encrypt(self): - infile = joinpath(self.tmpdir,self.enc_infn) - write_to_file(infile,os.urandom(1033),binary=True) - t = self.spawn('mmgen-tool',['-d',self.tmpdir,self.usr_rand_arg,'encrypt',infile]) - t.usr_rand(self.usr_rand_chars) - t.hash_preset('user data','1') - t.passphrase_new('user data',tool_enc_passwd) - t.written_to_file('Encrypted data') - return t - - def tool_decrypt(self,f1): - out_fn = 'tool_encrypt.out' - t = self.spawn('mmgen-tool',['-d',self.tmpdir,'decrypt',f1,'outfile='+out_fn,'hash_preset=1']) - t.passphrase('user data',tool_enc_passwd) - t.written_to_file('Decrypted data') - d1 = self.read_from_tmpfile(self.enc_infn,binary=True) - d2 = self.read_from_tmpfile(out_fn,binary=True) - cmp_or_die(d1,d2) - return t - - def tool_find_incog_data(self,f1,f2): - i_id = read_from_file(f2).rstrip() - vmsg('Incog ID: {}'.format(cyan(i_id))) - t = self.spawn('mmgen-tool',['-d',self.tmpdir,'find_incog_data',f1,i_id]) - o = t.expect_getend('Incog data for ID {} found at offset '.format(i_id)) - if not g.platform == 'win': - os.unlink(f1) # causes problems with MSYS2 - cmp_or_die(hincog_offset,int(o)) - return t - - def tool_twview_bad_comment(self): # test correct operation of get_tw_label() - bw_save = os.getenv('MMGEN_BOGUS_WALLET_DATA') - os.environ['MMGEN_BOGUS_WALLET_DATA'] = joinpath(ref_dir,'bad-comment-unspent.json') - t = self.spawn('mmgen-tool',['twview']) - if bw_save: - os.environ['MMGEN_BOGUS_WALLET_DATA'] = bw_save - t.read() - t.req_exit_val = 2 - return t - class TestSuiteRefTX(TestSuiteMain,TestSuiteBase): 'create a reference transaction file (administrative command)' segwit_opts_ok = False diff --git a/test/test_py_d/ts_tool.py b/test/test_py_d/ts_tool.py new file mode 100755 index 00000000..e5f63b10 --- /dev/null +++ b/test/test_py_d/ts_tool.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2020 The MMGen Project +# +# Project source code repository: https://github.com/mmgen/mmgen +# Licensed according to the terms of GPL Version 3. See LICENSE for details. + +""" +ts_tool.py: tool tests for the MMGen test.py test suite +""" + +from test.common import * +from test.test_py_d.ts_base import * +from test.test_py_d.ts_main import TestSuiteMain + +class TestSuiteTool(TestSuiteMain,TestSuiteBase): + "interactive 'mmgen-tool' commands" + networks = ('btc',) + segwit_opts_ok = False + tmpdir_nums = [9] + enc_infn = 'tool_encrypt.in' + cmd_group = ( + ('tool_find_incog_data', (9,"'mmgen-tool find_incog_data'", [[[hincog_fn],1],[[incog_id_fn],1]])), + ('tool_rand2file', (9,"'mmgen-tool rand2file'", [])), + ('tool_encrypt', (9,"'mmgen-tool encrypt' (random data)", [])), + ('tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", [[[enc_infn+'.mmenc'],9]])), + ('tool_twview_bad_comment',(9,"'mmgen-tool twview' (with bad comment)", [])), + # ('tool_encrypt_ref', (9,"'mmgen-tool encrypt' (reference text)", [])), + ) + + def tool_rand2file(self): + outfile = os.path.join(self.tmpdir,'rand2file.out') + from mmgen.tool import MMGenToolCmd + tu = MMGenToolCmd() + for nbytes in ('1','1023','1K','1048575','1M','1048577','123M'): + t = self.spawn( 'mmgen-tool', + ['-d',self.tmpdir,'-r0','rand2file','rand2file.out',nbytes], + extra_desc='({} byte{})'.format(nbytes,suf(tu.bytespec(nbytes))) + ) + t.expect('random data written to file') + t.read() + t.p.wait() + t.ok() + t.skip_ok = True + return t + + def tool_encrypt(self): + infile = joinpath(self.tmpdir,self.enc_infn) + write_to_file(infile,os.urandom(1033),binary=True) + t = self.spawn('mmgen-tool',['-d',self.tmpdir,self.usr_rand_arg,'encrypt',infile]) + t.usr_rand(self.usr_rand_chars) + t.hash_preset('user data','1') + t.passphrase_new('user data',tool_enc_passwd) + t.written_to_file('Encrypted data') + return t + + def tool_decrypt(self,f1): + out_fn = 'tool_encrypt.out' + t = self.spawn('mmgen-tool',['-d',self.tmpdir,'decrypt',f1,'outfile='+out_fn,'hash_preset=1']) + t.passphrase('user data',tool_enc_passwd) + t.written_to_file('Decrypted data') + d1 = self.read_from_tmpfile(self.enc_infn,binary=True) + d2 = self.read_from_tmpfile(out_fn,binary=True) + cmp_or_die(d1,d2) + return t + + def tool_find_incog_data(self,f1,f2): + i_id = read_from_file(f2).rstrip() + vmsg('Incog ID: {}'.format(cyan(i_id))) + t = self.spawn('mmgen-tool',['-d',self.tmpdir,'find_incog_data',f1,i_id]) + o = t.expect_getend('Incog data for ID {} found at offset '.format(i_id)) + if not g.platform == 'win': + os.unlink(f1) # causes problems with MSYS2 + cmp_or_die(hincog_offset,int(o)) + return t + + def tool_twview_bad_comment(self): # test correct operation of get_tw_label() + bw_save = os.getenv('MMGEN_BOGUS_WALLET_DATA') + os.environ['MMGEN_BOGUS_WALLET_DATA'] = joinpath(ref_dir,'bad-comment-unspent.json') + t = self.spawn('mmgen-tool',['twview']) + if bw_save: + os.environ['MMGEN_BOGUS_WALLET_DATA'] = bw_save + t.read() + t.req_exit_val = 2 + return t