mmgen-wallet/test/test.py

1056 lines
31 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2018-11-17 20:34:04 +00:00
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
2022-01-04 19:51:22 +00:00
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
test/test.py: Test suite for the MMGen wallet system
"""
def check_segwit_opts():
for k,m in (('segwit','S'),('segwit_random','S'),('bech32','B')):
if getattr(opt,k) and m not in proto.mmtypes:
die(1,f'--{k.replace("_","-")} option incompatible with {proto.cls_name}')
def create_shm_dir(data_dir,trash_dir):
# Laggy flash media can cause pexpect to fail, so create a temporary directory
# under '/dev/shm' and put datadir and tmpdirs here.
import shutil
from subprocess import run
if g.platform == 'win':
for tdir in (data_dir,trash_dir):
2018-05-08 10:44:12 +00:00
try: os.listdir(tdir)
except: pass
else:
try: shutil.rmtree(tdir)
except: # we couldn't remove data dir - perhaps regtest daemon is running
try:
run(['python3',os.path.join('cmds','mmgen-regtest'),'stop'],check=True)
except:
rdie(1,f'Unable to remove {tdir!r}!')
2018-05-08 10:44:12 +00:00
else:
time.sleep(2)
shutil.rmtree(tdir)
os.mkdir(tdir,0o755)
shm_dir = 'test'
else:
tdir,pfx = '/dev/shm','mmgen-test-'
try:
run(f'rm -rf {tdir}/{pfx}*',shell=True,check=True)
except Exception as e:
die(2,f'Unable to delete directory tree {tdir}/{pfx}* ({e.args[0]})')
try:
import tempfile
shm_dir = str(tempfile.mkdtemp('',pfx,tdir))
except Exception as e:
die(2,f'Unable to create temporary directory in {tdir} ({e.args[0]})')
dest = os.path.join(shm_dir,os.path.basename(trash_dir))
os.mkdir(dest,0o755)
2020-05-10 13:39:53 +00:00
run(f'rm -rf {trash_dir}',shell=True,check=True)
os.symlink(dest,trash_dir)
dest = os.path.join(shm_dir,os.path.basename(data_dir))
shutil.move(data_dir,dest) # data_dir was created by opts.init()
os.symlink(dest,data_dir)
return shm_dir
import sys,os,time
2021-10-03 17:40:02 +00:00
from include.tests_header import repo_root
from test.overlay import overlay_setup
overlay_dir = overlay_setup(repo_root)
sys.path.insert(0,overlay_dir)
try: os.unlink(os.path.join(repo_root,'my.err'))
except: pass
from mmgen.common import *
from test.include.common import *
from test.test_py_d.common import *
g.quiet = False # if 'quiet' was set in config file, disable here
os.environ['MMGEN_QUIET'] = '0' # for this script and spawned scripts
opts_data = {
'sets': [('list_current_cmd_groups',True,'list_cmd_groups',True)],
'text': {
'desc': 'Test suite for the MMGen suite',
'usage':'[options] [command(s) or metacommand(s)]',
2021-10-03 17:40:02 +00:00
'options': f"""
-h, --help Print this help message
--, --longhelp Print help message for long options (common options)
-A, --no-daemon-autostart Don't start and stop daemons automatically
-B, --bech32 Generate and use Bech32 addresses
-b, --buf-keypress Use buffered keypresses as with real human input
(often required on slow systems, or under emulation)
-c, --print-cmdline Print the command line of each spawned command
-C, --coverage Produce code coverage info using trace module
-x, --debug-pexpect Produce debugging output for pexpect calls
-D, --no-daemon-stop Don't stop auto-started daemons after running tests
-E, --direct-exec Bypass pexpect and execute a command directly (for
debugging only)
-e, --exact-output Show the exact output of the MMGen script(s) being run
-G, --exclude-groups=G Exclude the specified command groups (comma-separated)
-l, --list-cmds List and describe the commands in the test suite
-L, --list-cmd-groups Output a list of command groups with descriptions
-g, --list-current-cmd-groups List command groups for current configuration
-n, --names Display command names instead of descriptions
2021-09-29 21:17:56 +00:00
-N, --no-timings Suppress display of timing information
2021-10-03 17:40:02 +00:00
-o, --log Log commands to file {log_file!r}
-O, --pexpect-spawn Use pexpect.spawn instead of popen_spawn (much slower,
kut does real terminal emulation)
-p, --pause Pause between tests, resuming on keypress
-P, --profile Record the execution time of each script
-q, --quiet Produce minimal output. Suppress dependency info
-r, --resume=c Resume at command 'c' after interrupted run
2020-05-10 13:39:53 +00:00
-R, --resume-after=c Same, but resume at command following 'c'
-s, --system Test scripts and modules installed on system rather
than those in the repo root
-S, --skip-deps Skip dependency checking for command
-u, --usr-random Get random data interactively from user
2019-03-23 14:30:47 +00:00
-T, --pexpect-timeout=T Set the timeout for pexpect
-v, --verbose Produce more verbose output
-W, --no-dw-delete Don't remove default wallet from data dir after dw tests are done
-X, --exit-after=C Exit after command 'C'
-y, --segwit Generate and use Segwit addresses
-Y, --segwit-random Generate and use a random mix of Segwit and Legacy addrs
""",
'notes': """
If no command is given, the whole test suite is run.
"""
},
}
data_dir = get_data_dir() # include/common.py
# we need some opt values before running opts.init, so parse without initializing:
_uopts = opts.init(opts_data,parse_only=True).user_opts
# step 1: delete data_dir symlink in ./test;
2019-10-19 10:18:56 +00:00
if not ('resume' in _uopts or 'skip_deps' in _uopts):
try: os.unlink(data_dir)
except: pass
opts.UserOpts._reset_ok += ('skip_deps','no_daemon_autostart','names','no_timings')
# step 2: opts.init will create new data_dir in ./test (if not 'resume' or 'skip_deps'):
usr_args = opts.init(opts_data)
network_id = g.coin.lower() + ('_tn' if opt.testnet else '')
from mmgen.protocol import init_proto_from_opts
proto = init_proto_from_opts()
# step 3: move data_dir to /dev/shm and symlink it back to ./test:
trash_dir = os.path.join('test','trash')
if not (opt.resume or opt.skip_deps):
shm_dir = create_shm_dir(data_dir,trash_dir)
check_segwit_opts()
testing_segwit = opt.segwit or opt.segwit_random or opt.bech32
if g.test_suite_deterministic:
opt.no_timings = True
init_color(num_colors=0)
os.environ['MMGEN_DISABLE_COLOR'] = '1'
2020-05-10 13:39:53 +00:00
if opt.profile:
opt.names = True
if opt.exact_output:
def msg(s): pass
qmsg = qmsg_r = vmsg = vmsg_r = msg_r = msg
2020-05-10 13:39:53 +00:00
if opt.resume or opt.resume_after:
opt.skip_deps = True
2021-08-05 14:11:45 +00:00
opt.no_daemon_autostart = True
2020-05-10 13:39:53 +00:00
resume = opt.resume or opt.resume_after
else:
resume = False
cfgs = { # addr_idx_lists (except 31,32,33,34) must contain exactly 8 addresses
'1': { 'wpasswd': 'Dorian-α',
'kapasswd': 'Grok the blockchain',
'addr_idx_list': '12,99,5-10,5,12',
'dep_generators': {
pwfile: 'walletgen',
'mmdat': 'walletgen',
'addrs': 'addrgen',
'rawtx': 'txcreate',
'txbump': 'txbump',
'sigtx': 'txsign',
'mmwords': 'export_mnemonic',
'mmseed': 'export_seed',
'mmhex': 'export_hex',
'mmincog': 'export_incog',
'mmincox': 'export_incog_hex',
hincog_fn: 'export_incog_hidden',
incog_id_fn: 'export_incog_hidden',
'akeys.mmenc': 'keyaddrgen'
},
},
'2': { 'wpasswd': 'Hodling away',
'addr_idx_list': '37,45,3-6,22-23',
'seed_len': 128,
'dep_generators': {
'mmdat': 'walletgen2',
'addrs': 'addrgen2',
'rawtx': 'txcreate2',
'sigtx': 'txsign2',
'mmwords': 'export_mnemonic2',
},
},
'3': { 'wpasswd': 'Major miner',
'addr_idx_list': '73,54,1022-1023,2-5',
'dep_generators': {
'mmdat': 'walletgen3',
'addrs': 'addrgen3',
'rawtx': 'txcreate3',
'sigtx': 'txsign3'
},
2015-05-01 22:04:07 +03:00
},
'4': { 'wpasswd': 'Hashrate good',
'addr_idx_list': '63,1004,542-544,7-9',
'seed_len': 192,
'dep_generators': {
'mmdat': 'walletgen4',
'mmbrain': 'walletgen4',
'addrs': 'addrgen4',
'rawtx': 'txcreate4',
'sigtx': 'txsign4',
'txdo': 'txdo4',
},
'bw_filename': 'brainwallet.mmbrain',
'bw_params': '192,1',
2015-05-01 22:04:07 +03:00
},
'5': { 'wpasswd': 'My changed password-α',
'hash_preset': '2',
'dep_generators': {
'mmdat': 'passchg',
pwfile: 'passchg',
},
},
'6': { 'seed_len': 128,
'seed_id': 'FE3C6545',
'ref_bw_seed_id': '33F10310',
'wpasswd': 'reference password',
'kapasswd': '',
'dep_generators': {
'mmdat': 'ref_walletgen_brain_1',
pwfile: 'ref_walletgen_brain_1',
'addrs': 'refaddrgen_1',
'akeys.mmenc': 'refkeyaddrgen_1'
},
},
'7': { 'seed_len': 192,
'seed_id': '1378FC64',
'ref_bw_seed_id': 'CE918388',
'wpasswd': 'reference password',
'kapasswd': '',
'dep_generators': {
'mmdat': 'ref_walletgen_brain_2',
pwfile: 'ref_walletgen_brain_2',
'addrs': 'refaddrgen_2',
'akeys.mmenc': 'refkeyaddrgen_2'
},
2015-05-01 22:04:07 +03:00
},
'8': { 'seed_len': 256,
'seed_id': '98831F3A',
'ref_bw_seed_id': 'B48CD7FC',
'wpasswd': 'reference password',
'kapasswd': '',
'dep_generators': {
'mmdat': 'ref_walletgen_brain_3',
pwfile: 'ref_walletgen_brain_3',
'addrs': 'refaddrgen_3',
'akeys.mmenc': 'refkeyaddrgen_3'
},
2015-05-01 22:04:07 +03:00
},
'9': { 'tool_enc_infn': 'tool_encrypt.in',
'dep_generators': {
'tool_encrypt.in': 'tool_encrypt',
'tool_encrypt.in.mmenc': 'tool_encrypt',
},
2016-07-26 22:16:25 +03:00
},
'14': { 'kapasswd': 'Maxwell',
'wpasswd': 'The Halving',
'addr_idx_list': '61,998,502-504,7-9',
'seed_len': 256,
'dep_generators': {
'mmdat': 'walletgen14',
'addrs': 'addrgen14',
'akeys.mmenc': 'keyaddrgen14',
},
2015-05-01 22:04:07 +03:00
},
'15': { 'wpasswd': 'Dorian-α',
'kapasswd': 'Grok the blockchain',
'addr_idx_list': '12,99,5-10,5,12',
'dep_generators': {
pwfile: 'walletgen_dfl_wallet',
'addrs': 'addrgen_dfl_wallet',
'rawtx': 'txcreate_dfl_wallet',
'sigtx': 'txsign_dfl_wallet',
'mmseed': 'export_seed_dfl_wallet',
'del_dw_run': 'delete_dfl_wallet',
},
},
'16': { 'wpasswd': 'My changed password',
'hash_preset': '2',
'dep_generators': {
pwfile: 'passchg_dfl_wallet',
},
},
'17': {},
'18': {},
'19': { 'wpasswd':'abc' }, # B2X
'20': { 'wpasswd': 'Vsize it',
'addr_idx_list': '1-8',
'seed_len': 256,
'dep_generators': {
'mmdat': 'walletgen5',
'addrs': 'addrgen5',
'rawtx': 'txcreate5',
'sigtx': 'txsign5',
},
},
'21': { 'wpasswd': 'Vsize it',
'addr_idx_list': '1-8',
'seed_len': 256,
'dep_generators': {
'mmdat': 'walletgen6',
'addrs': 'addrgen6',
'rawtx': 'txcreate6',
'sigtx': 'txsign6',
},
},
'22': {},
'23': {},
# 26,27,28 are taken
'29': {}, # xmrwallet
'31': {},
'32': {},
'33': {},
'34': {},
'40': {},
'41': {},
'99': {}, # dummy
}
2019-10-19 10:18:56 +00:00
def fixup_cfgs():
for k in ('6','7','8'):
cfgs['2'+k] = {}
cfgs['2'+k].update(cfgs[k])
2019-10-19 10:18:56 +00:00
for k in cfgs:
cfgs[k]['tmpdir'] = os.path.join('test',f'tmp{k}')
2019-10-19 10:18:56 +00:00
cfgs[k]['segwit'] = randbool() if opt.segwit_random else bool(opt.segwit or opt.bech32)
2019-10-19 10:18:56 +00:00
from copy import deepcopy
for a,b in (('6','11'),('7','12'),('8','13')):
cfgs[b] = deepcopy(cfgs[a])
cfgs[b]['tmpdir'] = os.path.join('test','tmp'+b)
2019-10-19 10:18:56 +00:00
if g.debug_utf8:
for k in cfgs: cfgs[k]['tmpdir'] += '-α'
fixup_cfgs()
2018-03-09 13:16:10 +00:00
utils = {
# 'check_deps': 'check dependencies for specified command (WIP)', # TODO
'clean': 'clean specified tmp dir(s) (specify by integer, no arg = all dirs)',
}
def list_cmds():
gm = CmdGroupMgr()
cw,d = 0,[]
Msg(green('AVAILABLE COMMANDS:'))
for gname in gm.cmd_groups:
ts = gm.gm_init_group(None,gname,None)
d.append((gname,ts.__doc__.strip(),gm.cmd_list,gm.dpy_data))
cw = max(max(len(k) for k in gm.dpy_data),cw)
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
for gname,gdesc,clist,dpdata in d:
Msg('\n'+green(f'{gname!r} - {gdesc}:'))
for cmd in clist:
data = dpdata[cmd]
Msg(' {:{w}} - {}'.format(
cmd,
(data if type(data) == str else data[1]),
w = cw ))
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
w = max(map(len,utils))
Msg('\n'+green('AVAILABLE UTILITIES:'))
for cmd in sorted(utils):
Msg(' {:{w}} - {}'.format( cmd, utils[cmd], w=w ))
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
sys.exit(0)
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
def do_between():
if opt.pause:
confirm_continue()
elif (opt.verbose or opt.exact_output) and not opt.skip_deps:
sys.stderr.write('\n')
def list_tmpdirs():
return {k:cfgs[k]['tmpdir'] for k in cfgs}
def clean(usr_dirs=None,clean_overlay=True):
2020-03-12 16:38:02 +00:00
if opt.skip_deps:
return
all_dirs = list_tmpdirs()
dirnums = map(int,(usr_dirs if usr_dirs is not None else all_dirs))
dirlist = list(map(str,sorted(dirnums)))
for d in dirlist:
if d in all_dirs:
cleandir(all_dirs[d])
else:
die(1,f'{d}: invalid directory number')
if dirlist:
iqmsg(green('Cleaned tmp director{} {}'.format(
suf(dirlist,'ies'),
' '.join(dirlist))
))
cleandir(data_dir)
cleandir(trash_dir)
iqmsg(green(f'Cleaned directories {data_dir!r} {trash_dir!r}'))
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
if clean_overlay:
cleandir(overlay_dir)
iqmsg(green(f'Cleaned directory {os.path.relpath(overlay_dir)!r}'))
def create_tmp_dirs(shm_dir):
if g.platform == 'win':
for cfg in sorted(cfgs):
mk_tmpdir(cfgs[cfg]['tmpdir'])
else:
for cfg in sorted(cfgs):
src = os.path.join(shm_dir,cfgs[cfg]['tmpdir'].split('/')[-1])
mk_tmpdir(src)
try:
os.unlink(cfgs[cfg]['tmpdir'])
except OSError as e:
if e.errno != 2: raise
finally:
os.symlink(src,cfgs[cfg]['tmpdir'])
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
def set_environ_for_spawned_scripts():
from mmgen.term import get_terminal_size
os.environ['MMGEN_TERMINAL_WIDTH'] = str(get_terminal_size().width)
if os.getenv('MMGEN_DEBUG_ALL'):
for name in g.env_opts:
if name[:11] == 'MMGEN_DEBUG':
os.environ[name] = '1'
if not opt.pexpect_spawn:
os.environ['MMGEN_TEST_SUITE_POPEN_SPAWN'] = '1'
if not opt.system:
os.environ['PYTHONPATH'] = repo_root
if not opt.buf_keypress:
os.environ['MMGEN_DISABLE_HOLD_PROTECT'] = '1'
os.environ['MMGEN_NO_LICENSE'] = '1'
os.environ['MMGEN_MIN_URANDCHARS'] = '3'
os.environ['MMGEN_BOGUS_SEND'] = '1'
os.environ['MMGEN_TEST_SUITE_PEXPECT'] = '1'
def set_restore_term_at_exit():
import termios,atexit
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
def at_exit():
termios.tcsetattr(fd, termios.TCSADRAIN, old)
atexit.register(at_exit)
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
class CmdGroupMgr(object):
2020-03-12 16:38:02 +00:00
cmd_groups_dfl = {
'misc': ('TestSuiteMisc',{}),
'opts': ('TestSuiteOpts',{'full_data':True}),
'cfg': ('TestSuiteCfg',{'full_data':True}),
'helpscreens': ('TestSuiteHelp',{'modname':'misc','full_data':True}),
'main': ('TestSuiteMain',{'full_data':True}),
'conv': ('TestSuiteWalletConv',{'is3seed':True,'modname':'wallet'}),
2020-03-12 16:38:02 +00:00
'ref': ('TestSuiteRef',{}),
'ref3': ('TestSuiteRef3Seed',{'is3seed':True,'modname':'ref_3seed'}),
'ref3_addr': ('TestSuiteRef3Addr',{'is3seed':True,'modname':'ref_3seed'}),
'ref_altcoin': ('TestSuiteRefAltcoin',{}),
'seedsplit': ('TestSuiteSeedSplit',{}),
'tool': ('TestSuiteTool',{'full_data':True}),
'input': ('TestSuiteInput',{'full_data':True}),
2019-05-25 12:50:08 +00:00
'output': ('TestSuiteOutput',{'modname':'misc','full_data':True}),
2020-03-12 16:38:02 +00:00
'autosign': ('TestSuiteAutosign',{}),
'regtest': ('TestSuiteRegtest',{}),
# 'chainsplit': ('TestSuiteChainsplit',{}),
'ethdev': ('TestSuiteEthdev',{}),
'xmrwallet': ('TestSuiteXMRWallet',{}),
2020-03-12 16:38:02 +00:00
}
cmd_groups_extra = {
'autosign_btc': ('TestSuiteAutosignBTC',{'modname':'autosign'}),
'autosign_live': ('TestSuiteAutosignLive',{'modname':'autosign'}),
asyncio/aiohttp support Asynchronous HTTP significantly speeds up operations involving multiple JSON-RPC calls to the server, such as tracking wallet views for wallets with a large number of outputs. This patch adds base-level asyncio infrastructure plus aiohttp support to all applicable MMGen commands. The aiohttp package is not currently supported by MSYS2, so Windows users will have to choose one of the other backends ('curl' is the default). Tested on: Linux, Armbian, Windows; Python 3.6, 3.7, 3.8 New user features: - configurable RPC backends via the 'rpc_backend' option. Supported options are 'aiohttp' (Linux-only), 'httplib', 'requests' and 'curl' - configurable RPC queue size via the 'aiohttp_rpc_queue_len' option The patch also includes a rewrite/redesign of large parts of the MMGen code base, most importantly: - rpc.py - full rewrite of RPC library, new RPCBackends class - main_addrimport.py - full rewrite - main_autosign.py - LED code now handled by new LEDControl class - eth/tw.py, eth/tx.py - reworked logic for resolving token symbols and addresses - eth/tx.py - separate classes for signed and unsigned transactions Testing: # Set a backend (choose one): $ export MMGEN_RPC_BACKEND='aiohttp' # Linux-only $ export MMGEN_RPC_BACKEND='curl' # Windows $ export MMGEN_RPC_BACKEND='httplib' # compare performance with 'aiohttp' # Bitcoin: $ test/unit_tests.py rpc btc $ test/test.py main regtest autosign # Ethereum: $ test/unit_tests.py rpc eth $ test/tooltest2.py --coin=eth --testnet=1 txview $ test/test.py --coin=eth ethdev # Monero wallet: $ test/unit_tests.py rpc xmr_wallet $ test/test-release.sh -F xmr
2020-05-10 14:07:54 +00:00
'autosign_live_simulate': ('TestSuiteAutosignLiveSimulate',{'modname':'autosign'}),
'create_ref_tx': ('TestSuiteRefTX',{'modname':'misc','full_data':True}),
}
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
2020-03-12 16:38:02 +00:00
cmd_groups = cmd_groups_dfl.copy()
cmd_groups.update(cmd_groups_extra)
def load_mod(self,gname,modname=None):
clsname,kwargs = self.cmd_groups[gname]
if modname == None and 'modname' in kwargs:
modname = kwargs['modname']
import importlib
modpath = f'test.test_py_d.ts_{modname or gname}'
return getattr(importlib.import_module(modpath),clsname)
def create_group(self,gname,full_data=False,modname=None,is3seed=False,add_dpy=False):
"""
Initializes the list 'cmd_list' and dict 'dpy_data' from module's cmd_group data.
Alternatively, if called with 'add_dpy=True', updates 'dpy_data' from module data
without touching 'cmd_list'
"""
cls = self.load_mod(gname,modname)
cdata = []
2019-10-31 10:46:46 +00:00
def get_shared_deps(cmdname,tmpdir_idx):
"""
shared_deps are "implied" dependencies for all cmds in cmd_group that don't appear in
the cmd_group data or cmds' argument lists. Supported only for 3seed tests at present.
"""
2020-05-10 13:39:53 +00:00
if not hasattr(cls,'shared_deps'):
return []
2019-10-31 10:46:46 +00:00
return [k for k,v in cfgs[str(tmpdir_idx)]['dep_generators'].items()
if k in cls.shared_deps and v != cmdname]
for a,b in cls.cmd_group:
if is3seed:
for n,(i,j) in enumerate(zip(cls.tmpdir_nums,(128,192,256))):
k = f'{a}_{n+1}'
2020-02-12 10:36:15 +00:00
if hasattr(cls,'skip_cmds') and k in cls.skip_cmds:
continue
2019-10-31 10:46:46 +00:00
sdeps = get_shared_deps(k,i)
if type(b) == str:
cdata.append( (k, (i,f'{b} ({j}-bit)',[[[]+sdeps,i]])) )
else:
cdata.append( (k, (i,f'{b[1]} ({j}-bit)',[[b[0]+sdeps,i]])) )
else:
cdata.append( (a, b if full_data else (cls.tmpdir_nums[0],b,[[[],cls.tmpdir_nums[0]]])) )
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
if add_dpy:
self.dpy_data.update(dict(cdata))
else:
self.cmd_list = tuple(e[0] for e in cdata)
self.dpy_data = dict(cdata)
return cls
def gm_init_group(self,trunner,gname,spawn_prog):
kwargs = self.cmd_groups[gname][1]
cls = self.create_group(gname,**kwargs)
2020-05-10 13:39:53 +00:00
cls.group_name = gname
return cls(trunner,cfgs,spawn_prog)
2019-10-24 16:21:09 +00:00
def list_cmd_groups(self):
ginfo = []
2019-10-24 16:21:09 +00:00
for gname in self.cmd_groups:
clsname,kwargs = self.cmd_groups[gname]
cls = self.load_mod(gname,kwargs['modname'] if 'modname' in kwargs else None)
ginfo.append((gname,cls))
if opt.list_current_cmd_groups:
exclude = (opt.exclude_groups or '').split(',')
ginfo = [g for g in ginfo
if network_id in g[1].networks
and not g[0] in exclude
2020-06-27 10:22:01 +00:00
and g[0] in tuple(self.cmd_groups_dfl) + tuple(usr_args) ]
for name,cls in ginfo:
2021-10-07 13:12:10 +00:00
msg(f'{name:17} - {cls.__doc__.strip()}')
Die(0,'\n'+' '.join(e[0] for e in ginfo))
2019-10-24 16:21:09 +00:00
def find_cmd_in_groups(self,cmd,group=None):
"""
Search for a test command in specified group or all configured command groups
and return it as a string. Loads modules but alters no global variables.
"""
if group:
if not group in [e[0] for e in self.cmd_groups]:
die(1,f'{group!r}: unrecognized group')
groups = [self.cmd_groups[group]]
else:
groups = self.cmd_groups
for gname in groups:
clsname,kwargs = self.cmd_groups[gname]
cls = self.load_mod(gname,kwargs['modname'] if 'modname' in kwargs else None)
if cmd in cls.cmd_group: # first search the class
return gname
if cmd in dir(cls(None,None,None)): # then a throwaway instance
return gname # cmd might exist in more than one group - we'll go with the first
return None
class TestSuiteRunner(object):
'test suite runner'
def __del__(self):
if opt.log:
self.log_fd.close()
def __init__(self,data_dir,trash_dir):
self.data_dir = data_dir
self.trash_dir = trash_dir
self.cmd_total = 0
2020-04-09 19:34:24 +00:00
self.rebuild_list = {}
self.gm = CmdGroupMgr()
self.repo_root = repo_root
self.skipped_warnings = []
if opt.log:
self.log_fd = open(log_file,'a')
self.log_fd.write(f'\nLog started: {make_timestr()} UTC\n')
omsg(f'INFO → Logging to file {log_file!r}')
else:
self.log_fd = None
ERC20 token support (create/deploy, TX create/sign/send) This feature is EXPERIMENTAL. Until v0.9.9 is released, mainnet use is strictly at your own risk! To test on dev chain, run 'test/test.py -e ethdev' To test on Kovan, add '--testnet=1' option to all commands below Transaction example: Generate some ETH addresses with your default wallet: $ mmgen-addrgen --coin=eth 1-5 Create an EOS token tracking wallet and import the addresses into it: $ mmgen-addrimport --coin=eth --token=86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0 ABCDABCD-ETH[1-5].addrs Send 10+ EOS from an exchange or another wallet to address ABCDABCD:E:1 Create a TX sending 10 EOS to address aabbccdd..., with change to ABCDABCD:E:2: $ mmgen-txcreate --coin=eth --token=eos aabbccddaabbccddaabbccddaabbccddaabbccdd,10 ABCDABCD:E:2 On your offline machine, sign the TX: $ mmgen-txsign --coin=eth --token=eos ABC123-EOS[10,50000].rawtx On your online machine, send the TX: $ mmgen-txsend --coin=eth --token=eos ABC123-EOS[10,50000].sigtx View your EOS tracking wallet: $ mmgen-tool --coin=eth --token=eos twview Token creation/deployment example: Install the Solidity compiler ('solc') on your system. Create a token 'MFT' with default parameters, owned by ddeeff... (ABCDABCD:E:1): $ scripts/create-token.py --symbol=MFT --name='My First Token' ddeeffddeeffddeeffddeeffddeeffddeeffddee Deploy the token on the ETH blockchain: $ mmgen-txdo --coin=eth --tx-gas=200000 --contract-data=SafeMath.bin $ mmgen-txdo --coin=eth --tx-gas=250000 --contract-data=Owned.bin $ mmgen-txdo --coin=eth --tx-gas=1100000 --contract-data=Token.bin ... Token address: abcd1234abcd1234abcd1234abcd1234abcd1234 Create an MFT token tracking wallet and import your ETH addresses into it: $ mmgen-addrimport --coin=eth --token=abcd1234abcd1234abcd1234abcd1234abcd1234 ABCDABCD-ETH[1-5].addrs View your MFT tracking wallet: $ mmgen-tool --coin=eth --token=mft twview
2018-07-25 12:57:04 +00:00
if opt.coverage:
2021-10-10 20:18:13 +00:00
coverdir,accfile = init_coverage()
omsg(f'INFO → Writing coverage files to {coverdir!r}')
self.pre_args = ['python3','-m','trace','--count','--coverdir='+coverdir,'--file='+accfile]
else:
self.pre_args = ['python3'] if g.platform == 'win' else []
2021-10-07 13:12:10 +00:00
if opt.pexpect_spawn:
omsg(f'INFO → Using pexpect.spawn() for real terminal emulation')
def spawn_wrapper(self,cmd,
args = [],
extra_desc = '',
no_output = False,
msg_only = False,
no_msg = False,
cmd_dir = 'cmds',
no_exec_wrapper = False ):
desc = self.ts.test_name if opt.names else self.gm.dpy_data[self.ts.test_name][1]
2021-10-08 16:44:56 +00:00
if extra_desc:
desc += ' ' + extra_desc
2021-10-10 20:18:13 +00:00
cmd_path = (
cmd if opt.system # opt.system is broken for main test group with overlay tree
else os.path.relpath(os.path.join(repo_root,cmd_dir,cmd)) )
2021-10-10 20:18:13 +00:00
args = (
self.pre_args +
([] if no_exec_wrapper else ['scripts/exec_wrapper.py']) +
[cmd_path] +
self.passthru_opts +
self.ts.extra_spawn_args +
args )
for i in args:
2021-06-08 11:46:05 +00:00
if not isinstance(i,str):
2021-10-08 16:44:56 +00:00
die(2,'Error: missing input files in cmd line?:\nName: {}\nCmdline: {!r}'.format(
self.ts.test_name,
args ))
2021-10-08 16:44:56 +00:00
qargs = ['{q}{}{q}'.format( a, q = "'" if ' ' in a else '' ) for a in args]
cmd_disp = ' '.join(qargs).replace('\\','/') # for mingw
if not no_msg:
2021-09-29 21:17:56 +00:00
t_pfx = '' if opt.no_timings else f'[{time.time() - self.start_time:08.2f}] '
if opt.verbose or opt.print_cmdline or opt.exact_output:
2021-09-29 21:17:56 +00:00
omsg(green(f'{t_pfx}Testing: {desc}'))
if not msg_only:
2021-10-08 16:44:56 +00:00
clr1,clr2 = (nocolor,nocolor) if opt.print_cmdline else (green,cyan)
omsg(
clr1('Executing: ') +
clr2(repr(cmd_disp) if g.platform == 'win' else cmd_disp)
)
else:
2021-09-29 21:17:56 +00:00
omsg_r(f'{t_pfx}Testing {desc}: ')
2020-05-10 13:39:53 +00:00
if msg_only:
return
if opt.log:
2020-05-10 13:39:53 +00:00
self.log_fd.write('[{}][{}:{}] {}\n'.format(
proto.coin.lower(),
2020-05-10 13:39:53 +00:00
self.ts.group_name,
self.ts.test_name,
cmd_disp))
2018-05-16 15:36:51 +00:00
2021-07-26 18:26:21 +00:00
os.environ['MMGEN_FORCE_COLOR'] = '1' if self.ts.color else ''
env = { 'EXEC_WRAPPER_SPAWN':'1' }
if 'exec_wrapper_init' in globals(): # Python 3.9: OR the dicts
env.update({ 'EXEC_WRAPPER_NO_TRACEBACK':'1' })
env.update(os.environ)
from test.include.pexpect import MMGenPexpect
return MMGenPexpect( args, no_output=no_output, env=env )
2018-05-16 15:36:51 +00:00
def end_msg(self):
2021-06-28 19:04:25 +00:00
t = int(time.time() - self.start_time)
2021-09-29 21:17:56 +00:00
sys.stderr.write(green(
2021-10-06 13:22:33 +00:00
f'{self.cmd_total} test{suf(self.cmd_total)} performed' +
('\n' if opt.no_timings else f'. Elapsed time: {t//60:02d}:{t%60:02d}\n')
2021-09-29 21:17:56 +00:00
))
2018-05-16 15:36:51 +00:00
def init_group(self,gname,cmd=None,quiet=False,do_clean=True):
ts_cls = CmdGroupMgr().load_mod(gname)
for k in ('segwit','segwit_random','bech32'):
if getattr(opt,k):
segwit_opt = k
break
else:
segwit_opt = None
2020-05-16 16:07:28 +00:00
def gen_msg():
yield ('{g}:{c}' if cmd else 'test group {g!r}').format(g=gname,c=cmd)
if len(ts_cls.networks) != 1:
yield f' for {proto.coin} {proto.network}'
2020-05-16 16:07:28 +00:00
if segwit_opt:
yield ' (--{})'.format( segwit_opt.replace('_','-') )
2020-05-16 16:07:28 +00:00
m = ''.join(gen_msg())
if segwit_opt and not ts_cls.segwit_opts_ok:
iqmsg('INFO → skipping ' + m)
return False
# 'networks = ()' means all networks allowed
nws = [(e.split('_')[0],'testnet') if '_' in e else (e,'mainnet') for e in ts_cls.networks]
if nws:
coin = proto.coin.lower()
nw = ('mainnet','testnet')[proto.testnet]
for a,b in nws:
if a == coin and b == nw:
break
else:
iqmsg('INFO → skipping ' + m)
return False
2021-10-08 16:44:56 +00:00
if do_clean:
clean(ts_cls.tmpdir_nums,clean_overlay=False)
2019-10-31 10:46:46 +00:00
if not quiet:
bmsg('Executing ' + m)
2021-10-03 17:40:02 +00:00
if not self.daemons_started and network_id not in ('eth','etc','xmr'):
start_test_daemons(network_id,remove_datadir=True)
self.daemons_started = True
os.environ['MMGEN_BOGUS_WALLET_DATA'] = '' # zero this here, so test group doesn't have to
self.ts = self.gm.gm_init_group(self,gname,self.spawn_wrapper)
self.ts_clsname = type(self.ts).__name__
2021-10-08 16:44:56 +00:00
self.passthru_opts = ['--{}{}'.format(
k.replace('_','-'),
'=' + getattr(opt,k) if getattr(opt,k) != True else ''
) for k in self.ts.base_passthru_opts + self.ts.passthru_opts if getattr(opt,k)]
2020-05-10 13:39:53 +00:00
if opt.resume_after:
global resume
resume = self.gm.cmd_list[self.gm.cmd_list.index(resume)+1]
omsg(f'INFO → Resuming at command {resume!r}')
if opt.exit_after and opt.exit_after not in self.gm.cmd_list:
die(1,f'{opt.exit_after!r}: command not recognized')
2015-10-25 13:04:30 +03:00
return True
2015-10-25 13:04:30 +03:00
def run_tests(self,usr_args):
2021-06-28 19:04:25 +00:00
self.start_time = time.time()
2021-10-03 17:40:02 +00:00
self.daemons_started = False
2019-10-31 10:46:46 +00:00
gname_save = None
if usr_args:
for arg in usr_args:
2021-10-03 17:40:02 +00:00
if arg in utils:
params = usr_args[usr_args.index(arg)+1:]
globals()[arg](*params)
sys.exit(0)
if arg in self.gm.cmd_groups:
2020-05-10 13:39:53 +00:00
if not self.init_group(arg):
continue
for cmd in self.gm.cmd_list:
self.check_needs_rerun(cmd,build=True)
do_between()
else:
if ':' in arg:
gname,arg = arg.split(':')
else:
gname = self.gm.find_cmd_in_groups(arg)
if gname:
2019-10-31 10:46:46 +00:00
same_grp = gname == gname_save # same group as previous cmd: don't clean, suppress blue msg
if not self.init_group(gname,arg,quiet=same_grp,do_clean=not same_grp):
2019-10-31 10:46:46 +00:00
continue
2021-05-14 11:26:32 +00:00
try:
self.check_needs_rerun(arg,build=True)
except Exception as e: # allow calling of functions not in cmd_group
if isinstance(e,KeyError) and e.args[0] == arg:
2021-05-14 11:26:32 +00:00
ret = getattr(self.ts,arg)()
if type(ret).__name__ == 'coroutine':
run_session(ret)
else:
raise
do_between()
2019-10-31 10:46:46 +00:00
gname_save = gname
else:
die(1,f'{arg!r}: command not recognized')
else:
if opt.exclude_groups:
exclude = opt.exclude_groups.split(',')
for e in exclude:
2020-03-12 16:38:02 +00:00
if e not in self.gm.cmd_groups_dfl:
die(1,f'{e!r}: group not recognized')
2020-03-12 16:38:02 +00:00
for gname in self.gm.cmd_groups_dfl:
2020-05-10 13:39:53 +00:00
if opt.exclude_groups and gname in exclude:
continue
if not self.init_group(gname):
continue
for cmd in self.gm.cmd_list:
self.check_needs_rerun(cmd,build=True)
do_between()
self.end_msg()
def check_needs_rerun(self,cmd,
build = False,
root = True,
force_delete = False,
dpy = False ):
self.ts.test_name = cmd
if self.ts_clsname == 'TestSuiteMain' and testing_segwit and cmd not in self.ts.segwit_do:
return False
rerun = root # force_delete is not passed to recursive call
fns = []
if force_delete or not root:
# does cmd produce a needed dependency(ies)?
ret = self.get_num_exts_for_cmd(cmd,dpy)
if ret:
for ext in ret[1]:
fn = get_file_with_ext(cfgs[ret[0]]['tmpdir'],ext,delete=build)
if fn:
if force_delete: os.unlink(fn)
else: fns.append(fn)
else: rerun = True
fdeps = self.generate_file_deps(cmd)
cdeps = self.generate_cmd_deps(fdeps)
for fn in fns:
my_age = os.stat(fn).st_mtime
for num,ext in fdeps:
f = get_file_with_ext(cfgs[num]['tmpdir'],ext,delete=build)
if f and os.stat(f).st_mtime > my_age:
rerun = True
for cdep in cdeps:
if self.check_needs_rerun(cdep,build=build,root=False,dpy=cmd):
rerun = True
if build:
if rerun:
for fn in fns:
2022-01-06 20:24:22 +00:00
if not root:
os.unlink(fn)
if not (dpy and opt.skip_deps):
self.run_test(cmd)
2022-01-06 20:24:22 +00:00
if not root:
do_between()
else:
# If prog produces multiple files:
if cmd not in self.rebuild_list or rerun == True:
self.rebuild_list[cmd] = (rerun,fns[0] if fns else '') # FIX
return rerun
def run_test(self,cmd):
d = [(str(num),ext) for exts,num in self.gm.dpy_data[cmd][2] for ext in exts]
# delete files depended on by this cmd
arg_list = [get_file_with_ext(cfgs[num]['tmpdir'],ext) for num,ext in d]
2019-10-31 10:46:46 +00:00
# remove shared_deps from arg list
if hasattr(self.ts,'shared_deps'):
arg_list = arg_list[:-len(self.ts.shared_deps)]
2020-05-10 13:39:53 +00:00
global resume
if resume:
if cmd != resume:
return
bmsg(f'Resuming at {cmd!r}')
2020-05-10 13:39:53 +00:00
resume = False
opt.skip_deps = False
if opt.profile:
start = time.time()
self.ts.test_name = cmd # NB: Do not remove, this needs to be set twice
cdata = self.gm.dpy_data[cmd]
# self.ts.test_dpydata = cdata
self.ts.tmpdir_num = cdata[0]
# self.ts.cfg = cfgs[str(cdata[0])] # will remove this eventually
cfg = cfgs[str(cdata[0])]
for k in ( 'seed_len', 'seed_id',
'wpasswd', 'kapasswd',
'segwit', 'hash_preset',
'bw_filename', 'bw_params', 'ref_bw_seed_id',
'addr_idx_list', 'pass_idx_list' ):
if k in cfg:
setattr(self.ts,k,cfg[k])
asyncio/aiohttp support Asynchronous HTTP significantly speeds up operations involving multiple JSON-RPC calls to the server, such as tracking wallet views for wallets with a large number of outputs. This patch adds base-level asyncio infrastructure plus aiohttp support to all applicable MMGen commands. The aiohttp package is not currently supported by MSYS2, so Windows users will have to choose one of the other backends ('curl' is the default). Tested on: Linux, Armbian, Windows; Python 3.6, 3.7, 3.8 New user features: - configurable RPC backends via the 'rpc_backend' option. Supported options are 'aiohttp' (Linux-only), 'httplib', 'requests' and 'curl' - configurable RPC queue size via the 'aiohttp_rpc_queue_len' option The patch also includes a rewrite/redesign of large parts of the MMGen code base, most importantly: - rpc.py - full rewrite of RPC library, new RPCBackends class - main_addrimport.py - full rewrite - main_autosign.py - LED code now handled by new LEDControl class - eth/tw.py, eth/tx.py - reworked logic for resolving token symbols and addresses - eth/tx.py - separate classes for signed and unsigned transactions Testing: # Set a backend (choose one): $ export MMGEN_RPC_BACKEND='aiohttp' # Linux-only $ export MMGEN_RPC_BACKEND='curl' # Windows $ export MMGEN_RPC_BACKEND='httplib' # compare performance with 'aiohttp' # Bitcoin: $ test/unit_tests.py rpc btc $ test/test.py main regtest autosign # Ethereum: $ test/unit_tests.py rpc eth $ test/tooltest2.py --coin=eth --testnet=1 txview $ test/test.py --coin=eth ethdev # Monero wallet: $ test/unit_tests.py rpc xmr_wallet $ test/test-release.sh -F xmr
2020-05-10 14:07:54 +00:00
ret = getattr(self.ts,cmd)(*arg_list) # run the test
if type(ret).__name__ == 'coroutine':
ret = run_session(ret)
self.process_retval(cmd,ret)
if opt.profile:
omsg('\r\033[50C{:.4f}'.format( time.time() - start ))
if cmd == opt.exit_after:
sys.exit(0)
def warn_skipped(self):
if self.skipped_warnings:
print(yellow('The following tests were skipped and may require attention:'))
r = '-' * 72 + '\n'
print(r+('\n'+r).join(self.skipped_warnings))
def process_retval(self,cmd,ret):
if type(ret).__name__ == 'MMGenPexpect':
ret.ok()
self.cmd_total += 1
elif ret == 'ok':
ok()
self.cmd_total += 1
elif ret == 'skip':
pass
elif type(ret) == tuple and ret[0] == 'skip_warn':
self.skipped_warnings.append(
'Test {!r} was skipped:\n {}'.format(cmd,'\n '.join(ret[1].split('\n'))))
else:
rdie(1,f'{cmd!r} returned {ret}')
def check_deps(self,cmds): # TODO: broken
if len(cmds) != 1:
die(1,f'Usage: {g.prog_name} check_deps <command>')
cmd = cmds[0]
2015-10-25 13:04:30 +03:00
if cmd not in self.gm.cmd_list:
die(1,f'{cmd!r}: unrecognized command')
if not opt.quiet:
omsg(f'Checking dependencies for {cmd!r}')
self.check_needs_rerun(self.ts,cmd,build=False)
2016-07-27 22:55:47 +03:00
w = max(map(len,self.rebuild_list)) + 1
for cmd in self.rebuild_list:
c = self.rebuild_list[cmd]
m = 'Rebuild' if (c[0] and c[1]) else 'Build' if c[0] else 'OK'
omsg('cmd {:<{w}} {}'.format( cmd+':', m, w=w ))
def generate_file_deps(self,cmd):
return [(str(n),e) for exts,n in self.gm.dpy_data[cmd][2] for e in exts]
def generate_cmd_deps(self,fdeps):
return [cfgs[str(n)]['dep_generators'][ext] for n,ext in fdeps]
def get_num_exts_for_cmd(self,cmd,dpy=False): # dpy ignored here
try:
num = str(self.gm.dpy_data[cmd][0])
except KeyError:
qmsg_r(f'Missing dependency {cmd!r}')
gname = self.gm.find_cmd_in_groups(cmd)
if gname:
kwargs = self.gm.cmd_groups[gname][1]
kwargs.update({'add_dpy':True})
self.gm.create_group(gname,**kwargs)
num = str(self.gm.dpy_data[cmd][0])
qmsg(f' found in group {gname!r}')
else:
qmsg(' not found in any command group!')
raise
dgl = cfgs[num]['dep_generators']
if cmd in dgl.values():
exts = [k for k in dgl if dgl[k] == cmd]
return (num,exts)
else:
return None
# main()
if not opt.skip_deps: # do this before list cmds exit, so we stay in sync with shm_dir
create_tmp_dirs(shm_dir)
if opt.list_cmd_groups:
2019-10-24 16:21:09 +00:00
CmdGroupMgr().list_cmd_groups()
elif opt.list_cmds:
list_cmds()
if opt.pause:
set_restore_term_at_exit()
set_environ_for_spawned_scripts()
2018-10-31 18:20:59 +00:00
try:
tr = TestSuiteRunner(data_dir,trash_dir)
tr.run_tests(usr_args)
tr.warn_skipped()
if network_id not in ('eth','etc','xmr'):
2020-05-10 13:39:53 +00:00
stop_test_daemons(network_id)
2016-07-26 22:16:25 +03:00
except KeyboardInterrupt:
if network_id not in ('eth','etc','xmr'):
2020-05-10 13:39:53 +00:00
stop_test_daemons(network_id)
tr.warn_skipped()
2019-07-03 17:30:51 +00:00
die(1,'\ntest.py exiting at user request')
2018-10-31 18:20:59 +00:00
except TestSuiteException as e:
ydie(1,e.args[0])
except TestSuiteFatalException as e:
rdie(1,e.args[0])
except Exception:
2021-10-03 17:40:02 +00:00
if 'exec_wrapper_init' in globals(): # test.py itself is running under exec_wrapper
import traceback
print(''.join(traceback.format_exception(*sys.exc_info())))
2019-07-03 17:30:51 +00:00
msg(blue('Test script exited with error'))
2021-10-03 17:40:02 +00:00
else:
msg(blue('Spawned script exited with error'))
2019-07-10 13:01:52 +00:00
raise
except:
raise