10 Commits a3e7c08f84 ... 62b360bb7d

Author SHA1 Message Date
  The MMGen Project 62b360bb7d daemontest_d.msg: make test methods async (bugfix) 2 months ago
  The MMGen Project 2b7080c227 subprocess.run(): use `text` arg (10 files) 2 months ago
  The MMGen Project 89c3e0e8a9 cmdtest.py: various cleanups (4 files) 2 months ago
  The MMGen Project 6ef0aef487 whitespace, minor cleanups (5 files) 2 months ago
  The MMGen Project f44ca874ec cmdtest_d.httpd: initialize with cfg 2 months ago
  The MMGen Project e9aeb9d8cd cmdtest.list_cmds(): cleanup, move to CmdGroupMgr 2 months ago
  The MMGen Project 6d74059489 cmdtest.py unify `is3seed` cmd_group format 2 months ago
  The MMGen Project f62f89ce31 new `CmdGroupMgr:get_cmd_groups()` method 2 months ago
  The MMGen Project e049111110 CmdGroupMgr:list_cmd_groups(): cleanups 2 months ago
  The MMGen Project a127ec4363 cmdtest_d.include.cfg: new `cmd_groups_data` namedtuple 2 months ago

+ 1 - 1
mmgen/color.py

@@ -61,7 +61,7 @@ def get_terminfo_colors(term=None):
 		cmd.append(term)
 		cmd.append(term)
 
 
 	try:
 	try:
-		cmdout = run(cmd, stdout=PIPE, check=True).stdout.decode()
+		cmdout = run(cmd, stdout=PIPE, check=True, text=True).stdout
 	except:
 	except:
 		set_vt100()
 		set_vt100()
 		return None
 		return None

+ 2 - 2
mmgen/daemon.py

@@ -247,14 +247,14 @@ class Daemon(Lockable):
 	@classmethod
 	@classmethod
 	def get_exec_version_str(cls):
 	def get_exec_version_str(cls):
 		try:
 		try:
-			cp = run([cls.exec_fn, cls.version_info_arg], stdout=PIPE, stderr=PIPE, check=True)
+			cp = run([cls.exec_fn, cls.version_info_arg], stdout=PIPE, stderr=PIPE, check=True, text=True)
 		except Exception as e:
 		except Exception as e:
 			die(2, f'{e}\nUnable to execute {cls.exec_fn}')
 			die(2, f'{e}\nUnable to execute {cls.exec_fn}')
 
 
 		if cp.returncode:
 		if cp.returncode:
 			die(2, f'Unable to execute {cls.exec_fn}')
 			die(2, f'Unable to execute {cls.exec_fn}')
 		else:
 		else:
-			res = cp.stdout.decode().splitlines()
+			res = cp.stdout.splitlines()
 			return (res[0] if len(res) == 1 else [s for s in res if 'ersion' in s][0]).strip()
 			return (res[0] if len(res) == 1 else [s for s in res if 'ersion' in s][0]).strip()
 
 
 class RPCDaemon(Daemon):
 class RPCDaemon(Daemon):

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-16.1.dev3
+16.1.dev4

+ 1 - 1
mmgen/fileutil.py

@@ -64,7 +64,7 @@ def shred_file(cfg, fn, *, iterations=30):
 		['shred', '--force', f'--iterations={iterations}', '--zero', '--remove=wipesync']
 		['shred', '--force', f'--iterations={iterations}', '--zero', '--remove=wipesync']
 		+ (['--verbose'] if cfg.verbose else [])
 		+ (['--verbose'] if cfg.verbose else [])
 		+ [str(fn)],
 		+ [str(fn)],
-		check=True)
+		check = True)
 	set_vt100()
 	set_vt100()
 
 
 def _check_file_type_and_access(fname, ftype, *, blkdev_ok=False):
 def _check_file_type_and_access(fname, ftype, *, blkdev_ok=False):

+ 1 - 2
mmgen/main_tool.py

@@ -392,9 +392,8 @@ if gc.prog_name.endswith('-tool'):
 	args, kwargs = process_args(cmd, args, cls)
 	args, kwargs = process_args(cmd, args, cls)
 
 
 	func = getattr(cls(cfg, cmdname=cmd), cmd)
 	func = getattr(cls(cfg, cmdname=cmd), cmd)
-	ret = async_run(cfg, func, args=args, kwargs=kwargs) if isAsync(func) else func(*args, **kwargs)
 
 
 	process_result(
 	process_result(
-		ret,
+		async_run(cfg, func, args=args, kwargs=kwargs) if isAsync(func) else func(*args, **kwargs),
 		pager = kwargs.get('pager'),
 		pager = kwargs.get('pager'),
 		print_result = True)
 		print_result = True)

+ 6 - 2
mmgen/platform/darwin/util.py

@@ -70,8 +70,12 @@ class MacOSRamDisk:
 				self.cfg._util.qmsg(f'{self.desc.capitalize()} {self.label.hl()} at path {self.path} already exists')
 				self.cfg._util.qmsg(f'{self.desc.capitalize()} {self.label.hl()} at path {self.path} already exists')
 				return
 				return
 		self.cfg._util.qmsg(f'Creating {self.desc} {self.label.hl()} of size {self.size}MB')
 		self.cfg._util.qmsg(f'Creating {self.desc} {self.label.hl()} of size {self.size}MB')
-		cp = run(['hdiutil', 'attach', '-nomount', f'ram://{2048 * self.size}'], stdout=PIPE, check=True)
-		self.dev_name = cp.stdout.decode().strip()
+		cp = run(
+			['hdiutil', 'attach', '-nomount', f'ram://{2048 * self.size}'],
+			stdout = PIPE,
+			text = True,
+			check = True)
+		self.dev_name = cp.stdout.strip()
 		self.cfg._util.qmsg(f'Created {self.desc} {self.label.hl()} [{self.dev_name}]')
 		self.cfg._util.qmsg(f'Created {self.desc} {self.label.hl()} [{self.dev_name}]')
 		run(['diskutil', 'eraseVolume', 'APFS', self.label, self.dev_name], stdout=redir, check=True)
 		run(['diskutil', 'eraseVolume', 'APFS', self.label, self.dev_name], stdout=redir, check=True)
 		diskutil_size = self.get_diskutil_size()
 		diskutil_size = self.get_diskutil_size()

+ 1 - 2
mmgen/rpc/local.py

@@ -83,8 +83,7 @@ class RPCClient:
 		return self.process_http_resp(await self.backend.run(
 		return self.process_http_resp(await self.backend.run(
 			payload = {'id': 1, 'jsonrpc': '2.0', 'method': method, 'params': params},
 			payload = {'id': 1, 'jsonrpc': '2.0', 'method': method, 'params': params},
 			timeout = timeout,
 			timeout = timeout,
-			host_path = self.make_host_path(wallet)
-		))
+			host_path = self.make_host_path(wallet)))
 
 
 	async def batch_call(self, method, param_list, *, timeout=None, wallet=None):
 	async def batch_call(self, method, param_list, *, timeout=None, wallet=None):
 		"""
 		"""

+ 2 - 2
scripts/create-token.py

@@ -100,7 +100,7 @@ def check_solc_version():
 	The output is used by other programs, so write to stdout only
 	The output is used by other programs, so write to stdout only
 	"""
 	"""
 	try:
 	try:
-		cp = run(['solc', '--version'], check=True, stdout=PIPE)
+		cp = run(['solc', '--version'], check=True, text=True, stdout=PIPE)
 	except:
 	except:
 		msg('solc missing or could not be executed') # this must go to stderr
 		msg('solc missing or could not be executed') # this must go to stderr
 		return False
 		return False
@@ -109,7 +109,7 @@ def check_solc_version():
 		Msg('solc exited with error')
 		Msg('solc exited with error')
 		return False
 		return False
 
 
-	line = cp.stdout.decode().splitlines()[1]
+	line = cp.stdout.splitlines()[1]
 	version_str = re.sub(r'Version:\s*', '', line)
 	version_str = re.sub(r'Version:\s*', '', line)
 	m = re.match(r'(\d+)\.(\d+)\.(\d+)', version_str)
 	m = re.match(r'(\d+)\.(\d+)\.(\d+)', version_str)
 
 

+ 5 - 31
test/cmdtest.py

@@ -239,34 +239,6 @@ if cfg.skipping_deps:
 
 
 from test.cmdtest_d.include.cfg import cfgs
 from test.cmdtest_d.include.cfg import cfgs
 
 
-def list_cmds():
-
-	def gen_output():
-
-		from test.cmdtest_d.include.group_mgr import CmdGroupMgr
-		gm = CmdGroupMgr(cfg)
-		cw, d = 0, []
-
-		yield green('AVAILABLE COMMANDS:')
-
-		for gname in gm.cmd_groups:
-			tg = gm.gm_init_group(cfg, None, gname, None, None)
-			gdesc = tg.__doc__.strip() if tg.__doc__ else type(tg).__name__
-			d.append((gname, gdesc, gm.cmd_list, gm.dpy_data))
-			cw = max(max(len(k) for k in gm.dpy_data), cw)
-
-		for gname, gdesc, cmd_list, dpy_data in d:
-			yield '\n'+green(f'{gname!r} - {gdesc}:')
-			for cmd in cmd_list:
-				data = dpy_data[cmd]
-				yield '    {:{w}} - {}'.format(
-					cmd,
-					(data if isinstance(data, str) else data[1]),
-					w = cw)
-
-	from mmgen.ui import do_pager
-	do_pager('\n'.join(gen_output()))
-
 def create_tmp_dirs(shm_dir):
 def create_tmp_dirs(shm_dir):
 	if sys.platform in ('win32', 'darwin'):
 	if sys.platform in ('win32', 'darwin'):
 		for cfg in sorted(cfgs):
 		for cfg in sorted(cfgs):
@@ -297,13 +269,15 @@ if __name__ == '__main__':
 	if not cfg.skipping_deps: # do this before list cmds exit, so we stay in sync with shm_dir
 	if not cfg.skipping_deps: # do this before list cmds exit, so we stay in sync with shm_dir
 		create_tmp_dirs(shm_dir)
 		create_tmp_dirs(shm_dir)
 
 
+	if cfg.list_cmds:
+		from test.cmdtest_d.include.group_mgr import CmdGroupMgr
+		CmdGroupMgr(cfg).list_cmds()
+		sys.exit(0)
+
 	if cfg.list_cmd_groups:
 	if cfg.list_cmd_groups:
 		from test.cmdtest_d.include.group_mgr import CmdGroupMgr
 		from test.cmdtest_d.include.group_mgr import CmdGroupMgr
 		CmdGroupMgr(cfg).list_cmd_groups()
 		CmdGroupMgr(cfg).list_cmd_groups()
 		sys.exit(0)
 		sys.exit(0)
-	elif cfg.list_cmds:
-		list_cmds()
-		sys.exit(0)
 
 
 	if cfg.pause:
 	if cfg.pause:
 		set_restore_term_at_exit()
 		set_restore_term_at_exit()

+ 1 - 0
test/cmdtest_d/base.py

@@ -38,6 +38,7 @@ class CmdTestBase:
 	need_daemon = False
 	need_daemon = False
 	platform_skip = ()
 	platform_skip = ()
 	tmpdir_nums = []
 	tmpdir_nums = []
+	skip_cmds = ()
 	test_name = None
 	test_name = None
 	is_helper = False
 	is_helper = False
 
 

+ 1 - 1
test/cmdtest_d/ethbump.py

@@ -285,7 +285,7 @@ class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMe
 
 
 		globals()[self.cross_group] = self.create_cross_runner(trunner)
 		globals()[self.cross_group] = self.create_cross_runner(trunner)
 
 
-		self.swap_server = ThornodeSwapServer()
+		self.swap_server = ThornodeSwapServer(cfg)
 		self.swap_server.start()
 		self.swap_server.start()
 
 
 	def txcreate1(self):
 	def txcreate1(self):

+ 7 - 10
test/cmdtest_d/ethdev.py

@@ -66,7 +66,7 @@ from .base import CmdTestBase
 from .shared import CmdTestShared
 from .shared import CmdTestShared
 from .httpd.etherscan import EtherscanServer
 from .httpd.etherscan import EtherscanServer
 
 
-etherscan_server = EtherscanServer()
+etherscan_server = EtherscanServer(cfg)
 
 
 del_addrs = ('4', '1')
 del_addrs = ('4', '1')
 
 
@@ -829,9 +829,8 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 			write_to_file(pwfile, '')
 			write_to_file(pwfile, '')
 			run(['rm', '-rf', self.keystore_dir])
 			run(['rm', '-rf', self.keystore_dir])
 			cmd = f'geth account new --password={pwfile} --lightkdf --keystore {self.keystore_dir}'
 			cmd = f'geth account new --password={pwfile} --lightkdf --keystore {self.keystore_dir}'
-			cp = run(cmd.split(), stdout=PIPE, stderr=PIPE)
-			if cp.returncode:
-				die(1, cp.stderr.decode())
+			if (cp := run(cmd.split(), stdout=PIPE, stderr=PIPE, text=True)).returncode:
+				die(1, cp.stderr)
 
 
 		def make_genesis(signer_addr, prealloc_addr):
 		def make_genesis(signer_addr, prealloc_addr):
 			return {
 			return {
@@ -875,9 +874,8 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 
 
 		def init_genesis(fn):
 		def init_genesis(fn):
 			cmd = f'{d.exec_fn} init --datadir {d.datadir} {fn}'
 			cmd = f'{d.exec_fn} init --datadir {d.datadir} {fn}'
-			cp = run(cmd.split(), stdout=PIPE, stderr=PIPE)
-			if cp.returncode:
-				die(1, cp.stderr.decode())
+			if (cp := run(cmd.split(), stdout=PIPE, stderr=PIPE, text=True)).returncode:
+				die(1, cp.stderr)
 
 
 		d.stop(quiet=True)
 		d.stop(quiet=True)
 		d.remove_datadir()
 		d.remove_datadir()
@@ -1349,10 +1347,9 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 			'--outdir=' + odir
 			'--outdir=' + odir
 		] + cmd_args + [self.proto.checksummed_addr(dfl_devaddr)]
 		] + cmd_args + [self.proto.checksummed_addr(dfl_devaddr)]
 		imsg('Executing: {}'.format(' '.join(cmd)))
 		imsg('Executing: {}'.format(' '.join(cmd)))
-		cp = run(cmd, stdout=DEVNULL, stderr=PIPE)
-		if cp.returncode != 0:
+		if (cp := run(cmd, stdout=DEVNULL, stderr=PIPE, text=True)).returncode:
 			rmsg('solc failed with the following output:')
 			rmsg('solc failed with the following output:')
-			die(2, cp.stderr.decode())
+			die(2, cp.stderr)
 		imsg('ERC20 token {!r} compiled'.format(token_data['symbol']))
 		imsg('ERC20 token {!r} compiled'.format(token_data['symbol']))
 		return 'ok'
 		return 'ok'
 
 

+ 1 - 1
test/cmdtest_d/ethswap.py

@@ -260,7 +260,7 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
 			trunner,
 			trunner,
 			add_cfg = {'eth_daemon_id': trunner.cfg.eth_daemon_id})
 			add_cfg = {'eth_daemon_id': trunner.cfg.eth_daemon_id})
 
 
-		self.swap_server = ThornodeSwapServer()
+		self.swap_server = ThornodeSwapServer(cfg)
 		self.swap_server.start()
 		self.swap_server.start()
 
 
 		TestProxy(self, cfg)
 		TestProxy(self, cfg)

+ 3 - 0
test/cmdtest_d/httpd/__init__.py

@@ -24,6 +24,9 @@ class SilentRequestHandler(WSGIRequestHandler):
 
 
 class HTTPD:
 class HTTPD:
 
 
+	def __init__(self, cfg):
+		self.cfg = cfg
+
 	def start(self):
 	def start(self):
 
 
 		if port_in_use(self.port):
 		if port_in_use(self.port):

+ 4 - 7
test/cmdtest_d/httpd/thornode/swap.py

@@ -15,7 +15,6 @@ test.cmdtest_d.httpd.thornode.swap: Thornode swap HTTP server
 import time, re, json
 import time, re, json
 from wsgiref.util import request_uri
 from wsgiref.util import request_uri
 
 
-from mmgen.cfg import Config
 from mmgen.amt import UniAmt
 from mmgen.amt import UniAmt
 from mmgen.protocol import init_proto
 from mmgen.protocol import init_proto
 
 
@@ -23,8 +22,6 @@ from ...include.common import eth_inbound_addr, thorchain_router_addr_file
 
 
 from . import ThornodeServer
 from . import ThornodeServer
 
 
-cfg = Config()
-
 # https://thornode.ninerealms.com/thorchain/quote/swap?from_asset=BCH.BCH&to_asset=LTC.LTC&amount=1000000
 # https://thornode.ninerealms.com/thorchain/quote/swap?from_asset=BCH.BCH&to_asset=LTC.LTC&amount=1000000
 prices = {'BTC': 97000, 'LTC': 115, 'BCH': 330, 'ETH': 2304, 'MM1': 0.998, 'RUNE': 1.4}
 prices = {'BTC': 97000, 'LTC': 115, 'BCH': 330, 'ETH': 2304, 'MM1': 0.998, 'RUNE': 1.4}
 gas_rate_units = {'ETH': 'gwei', 'BTC': 'satsperbyte'}
 gas_rate_units = {'ETH': 'gwei', 'BTC': 'satsperbyte'}
@@ -117,7 +114,7 @@ data_template_eth = {
 	'total_swap_seconds': 24
 	'total_swap_seconds': 24
 }
 }
 
 
-def make_inbound_addr(proto, mmtype):
+def make_inbound_addr(cfg, proto, mmtype):
 	if proto.is_evm:
 	if proto.is_evm:
 		return '0x' + eth_inbound_addr # non-checksummed as per ninerealms thornode
 		return '0x' + eth_inbound_addr # non-checksummed as per ninerealms thornode
 	else:
 	else:
@@ -155,15 +152,15 @@ class ThornodeSwapServer(ThornodeServer):
 		}
 		}
 
 
 		if send_asset != 'RUNE':
 		if send_asset != 'RUNE':
-			send_proto = init_proto(cfg, send_chain, network='regtest', need_amt=True)
+			send_proto = init_proto(self.cfg, send_chain, network='regtest', need_amt=True)
 			data.update({
 			data.update({
-				'inbound_address': make_inbound_addr(send_proto, send_proto.preferred_mmtypes[0]),
+				'inbound_address': make_inbound_addr(self.cfg, send_proto, send_proto.preferred_mmtypes[0]),
 				'gas_rate_units': gas_rate_units[send_proto.base_proto_coin],
 				'gas_rate_units': gas_rate_units[send_proto.base_proto_coin],
 				'recommended_gas_rate': recommended_gas_rate[send_proto.base_proto_coin]
 				'recommended_gas_rate': recommended_gas_rate[send_proto.base_proto_coin]
 			})
 			})
 
 
 		if send_asset == 'MM1':
 		if send_asset == 'MM1':
-			eth_proto = init_proto(cfg, 'eth', network='regtest')
+			eth_proto = init_proto(self.cfg, 'eth', network='regtest')
 			with open(thorchain_router_addr_file) as fh:
 			with open(thorchain_router_addr_file) as fh:
 				raw_addr = fh.read().strip()
 				raw_addr = fh.read().strip()
 			data['router'] = '0x' + eth_proto.checksummed_addr(raw_addr)
 			data['router'] = '0x' + eth_proto.checksummed_addr(raw_addr)

+ 40 - 38
test/cmdtest_d/include/cfg.py

@@ -11,54 +11,56 @@
 """
 """
 test.cmdtest_d.include.cfg: configuration data for cmdtest.py
 test.cmdtest_d.include.cfg: configuration data for cmdtest.py
 """
 """
+from collections import namedtuple
 
 
 from ...include.common import cfg
 from ...include.common import cfg
 from .common import pwfile, hincog_fn, incog_id_fn, randbool
 from .common import pwfile, hincog_fn, incog_id_fn, randbool
 
 
 cmd_groups_altcoin = ['ref_altcoin', 'autosign', 'ethdev', 'xmrwallet', 'xmr_autosign']
 cmd_groups_altcoin = ['ref_altcoin', 'autosign', 'ethdev', 'xmrwallet', 'xmr_autosign']
+gd = namedtuple('cmd_groups_data', ['clsname', 'params'])
 
 
 cmd_groups_dfl = {
 cmd_groups_dfl = {
-	'misc':               ('CmdTestMisc',              {}),
-	'opts':               ('CmdTestOpts',              {'full_data': True}),
-	'cfgfile':            ('CmdTestCfgFile',           {'full_data': True}),
-	'help':               ('CmdTestHelp',              {'full_data': True}),
-	'main':               ('CmdTestMain',              {'full_data': True}),
-	'conv':               ('CmdTestWalletConv',        {'is3seed': True, 'modname': 'wallet'}),
-	'ref':                ('CmdTestRef',               {}),
-	'ref3':               ('CmdTestRef3Seed',          {'is3seed': True, 'modname': 'ref_3seed'}),
-	'ref3_addr':          ('CmdTestRef3Addr',          {'is3seed': True, 'modname': 'ref_3seed'}),
-	'ref3_pw':            ('CmdTestRef3Passwd',        {'is3seed': True, 'modname': 'ref_3seed'}),
-	'ref_altcoin':        ('CmdTestRefAltcoin',        {}),
-	'seedsplit':          ('CmdTestSeedSplit',         {}),
-	'tool':               ('CmdTestTool',              {'full_data': True}),
-	'input':              ('CmdTestInput',             {}),
-	'output':             ('CmdTestOutput',            {'modname': 'misc', 'full_data': True}),
-	'autosign_clean':     ('CmdTestAutosignClean',     {'modname': 'autosign'}),
-	'autosign':           ('CmdTestAutosign',          {}),
-	'autosign_automount': ('CmdTestAutosignAutomount', {'modname': 'automount'}),
-	'autosign_eth':       ('CmdTestAutosignETH',       {'modname': 'automount_eth'}),
-	'regtest':            ('CmdTestRegtest',           {}),
-	'swap':               ('CmdTestSwap',              {}),
-	'ethswap':            ('CmdTestEthSwap',           {}),
-	# 'chainsplit':         ('CmdTestChainsplit',      {}),
-	'ethdev':             ('CmdTestEthdev',            {}),
-	'ethbump':            ('CmdTestEthBump',           {}),
-	'rune':               ('CmdTestRune',              {}),
-	'runeswap':           ('CmdTestRuneSwap',          {}),
-	'xmrwallet':          ('CmdTestXMRWallet',         {}),
-	'xmr_autosign':       ('CmdTestXMRAutosign',       {}),
+	'misc':               gd('CmdTestMisc',              {}),
+	'opts':               gd('CmdTestOpts',              {'full_data': True}),
+	'cfgfile':            gd('CmdTestCfgFile',           {'full_data': True}),
+	'help':               gd('CmdTestHelp',              {'full_data': True}),
+	'main':               gd('CmdTestMain',              {'full_data': True}),
+	'conv':               gd('CmdTestWalletConv',        {'is3seed': True, 'modname': 'wallet'}),
+	'ref':                gd('CmdTestRef',               {}),
+	'ref3':               gd('CmdTestRef3Seed',          {'is3seed': True, 'modname': 'ref_3seed'}),
+	'ref3_addr':          gd('CmdTestRef3Addr',          {'is3seed': True, 'modname': 'ref_3seed'}),
+	'ref3_pw':            gd('CmdTestRef3Passwd',        {'is3seed': True, 'modname': 'ref_3seed'}),
+	'ref_altcoin':        gd('CmdTestRefAltcoin',        {}),
+	'seedsplit':          gd('CmdTestSeedSplit',         {}),
+	'tool':               gd('CmdTestTool',              {'full_data': True}),
+	'input':              gd('CmdTestInput',             {}),
+	'output':             gd('CmdTestOutput',            {'modname': 'misc', 'full_data': True}),
+	'autosign_clean':     gd('CmdTestAutosignClean',     {'modname': 'autosign'}),
+	'autosign':           gd('CmdTestAutosign',          {}),
+	'autosign_automount': gd('CmdTestAutosignAutomount', {'modname': 'automount'}),
+	'autosign_eth':       gd('CmdTestAutosignETH',       {'modname': 'automount_eth'}),
+	'regtest':            gd('CmdTestRegtest',           {}),
+	'swap':               gd('CmdTestSwap',              {}),
+	'ethswap':            gd('CmdTestEthSwap',           {}),
+	# 'chainsplit':         gd('CmdTestChainsplit',      {}),
+	'ethdev':             gd('CmdTestEthdev',            {}),
+	'ethbump':            gd('CmdTestEthBump',           {}),
+	'rune':               gd('CmdTestRune',              {}),
+	'runeswap':           gd('CmdTestRuneSwap',          {}),
+	'xmrwallet':          gd('CmdTestXMRWallet',         {}),
+	'xmr_autosign':       gd('CmdTestXMRAutosign',       {}),
 }
 }
 
 
 cmd_groups_extra = {
 cmd_groups_extra = {
-	'ethswap_eth':            ('CmdTestEthSwapEth',           {'modname': 'ethswap'}),
-	'ethbump_ltc':            ('CmdTestEthBumpLTC',           {'modname': 'ethbump'}),
-	'runeswap_rune':          ('CmdTestRuneSwapRune',         {'modname': 'runeswap'}),
-	'dev':                    ('CmdTestDev',                  {'modname': 'misc'}),
-	'regtest_legacy':         ('CmdTestRegtestBDBWallet',     {'modname': 'regtest'}),
-	'autosign_btc':           ('CmdTestAutosignBTC',          {'modname': 'autosign'}),
-	'autosign_live':          ('CmdTestAutosignLive',         {'modname': 'autosign'}),
-	'autosign_live_simulate': ('CmdTestAutosignLiveSimulate', {'modname': 'autosign'}),
-	'create_ref_tx':          ('CmdTestRefTX',                {'modname': 'misc', 'full_data': True}),
+	'ethswap_eth':            gd('CmdTestEthSwapEth',           {'modname': 'ethswap'}),
+	'ethbump_ltc':            gd('CmdTestEthBumpLTC',           {'modname': 'ethbump'}),
+	'runeswap_rune':          gd('CmdTestRuneSwapRune',         {'modname': 'runeswap'}),
+	'dev':                    gd('CmdTestDev',                  {'modname': 'misc'}),
+	'regtest_legacy':         gd('CmdTestRegtestBDBWallet',     {'modname': 'regtest'}),
+	'autosign_btc':           gd('CmdTestAutosignBTC',          {'modname': 'autosign'}),
+	'autosign_live':          gd('CmdTestAutosignLive',         {'modname': 'autosign'}),
+	'autosign_live_simulate': gd('CmdTestAutosignLiveSimulate', {'modname': 'autosign'}),
+	'create_ref_tx':          gd('CmdTestRefTX',                {'modname': 'misc', 'full_data': True}),
 }
 }
 
 
 cfgs = { # addr_idx_lists (except 31, 32, 33, 34) must contain exactly 8 addresses
 cfgs = { # addr_idx_lists (except 31, 32, 33, 34) must contain exactly 8 addresses

+ 62 - 44
test/cmdtest_d/include/group_mgr.py

@@ -13,6 +13,7 @@ cmdtest_d.include.group_mgr: Command group manager for the MMGen Wallet cmdtest
 """
 """
 
 
 import sys, os, time
 import sys, os, time
+from collections import namedtuple
 
 
 from mmgen.color import yellow, green, cyan
 from mmgen.color import yellow, green, cyan
 from mmgen.util import Msg, die
 from mmgen.util import Msg, die
@@ -44,6 +45,14 @@ class CmdGroupMgr:
 		self.network_id = cfg._proto.coin.lower() + ('_tn' if cfg._proto.testnet else '')
 		self.network_id = cfg._proto.coin.lower() + ('_tn' if cfg._proto.testnet else '')
 		self.name = type(self).__name__
 		self.name = type(self).__name__
 
 
+	@classmethod
+	def get_cmd_groups(cls, cfg):
+		exclude = cfg.exclude_groups.split(',') if cfg.exclude_groups else []
+		for e in exclude:
+			if e not in cmd_groups_dfl:
+				die(1, f'{e!r}: group not recognized')
+		return [s for s in cmd_groups_dfl if s not in exclude]
+
 	def create_cmd_group(self, cls, sg_name=None):
 	def create_cmd_group(self, cls, sg_name=None):
 
 
 		cmd_group_in = dict(cls.cmd_group_in)
 		cmd_group_in = dict(cls.cmd_group_in)
@@ -80,12 +89,12 @@ class CmdGroupMgr:
 		return tuple(gen())
 		return tuple(gen())
 
 
 	def load_mod(self, gname, modname=None):
 	def load_mod(self, gname, modname=None):
-		clsname, kwargs = self.cmd_groups[gname]
-		if modname is None and 'modname' in kwargs:
-			modname = kwargs['modname']
+		grp = self.cmd_groups[gname]
+		if modname is None and 'modname' in grp.params:
+			modname = grp.params['modname']
 		import importlib
 		import importlib
 		modpath = f'test.cmdtest_d.{modname or gname}'
 		modpath = f'test.cmdtest_d.{modname or gname}'
-		return getattr(importlib.import_module(modpath), clsname)
+		return getattr(importlib.import_module(modpath), grp.clsname)
 
 
 	def create_group(self, gname, sg_name, full_data=False, modname=None, is3seed=False, add_dpy=False):
 	def create_group(self, gname, sg_name, full_data=False, modname=None, is3seed=False, add_dpy=False):
 		"""
 		"""
@@ -94,38 +103,33 @@ class CmdGroupMgr:
 		without touching 'cmd_list'
 		without touching 'cmd_list'
 		"""
 		"""
 
 
-		cls = self.load_mod(gname, modname)
-		cdata = []
-
 		def get_shared_deps(cmdname, tmpdir_idx):
 		def get_shared_deps(cmdname, tmpdir_idx):
 			"""
 			"""
 			shared_deps are "implied" dependencies for all cmds in cmd_group that don't appear in
 			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.
 			the cmd_group data or cmds' argument lists.  Supported only for 3seed tests at present.
 			"""
 			"""
-			if not hasattr(cls, 'shared_deps'):
-				return []
-
 			return [k for k, v in cfgs[str(tmpdir_idx)]['dep_generators'].items()
 			return [k for k, v in cfgs[str(tmpdir_idx)]['dep_generators'].items()
-						if k in cls.shared_deps and v != cmdname]
+				if k in cls.shared_deps and v != cmdname] if hasattr(cls, 'shared_deps') else []
+
+		cls = self.load_mod(gname, modname)
 
 
 		if not 'cmd_group' in cls.__dict__ and hasattr(cls, 'cmd_group_in'):
 		if not 'cmd_group' in cls.__dict__ and hasattr(cls, 'cmd_group_in'):
 			cls.cmd_group = self.create_cmd_group(cls, sg_name)
 			cls.cmd_group = self.create_cmd_group(cls, sg_name)
 
 
-		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}'
-					if hasattr(cls, 'skip_cmds') and k in cls.skip_cmds:
-						continue
-					sdeps = get_shared_deps(k, i)
-					if isinstance(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]])))
-			elif full_data:
-				cdata.append((a, b))
-			else:
-				cdata.append((a, (cls.tmpdir_nums[0], b, [[[], cls.tmpdir_nums[0]]])))
+		def gen_cdata():
+			cgd = namedtuple('cmd_group_data', ['tmpdir_num', 'desc', 'dpy_list'])
+			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}'
+						if not k in cls.skip_cmds:
+							yield (k, cgd(i, f'{b} ({j}-bit)', [[get_shared_deps(k, i), i]]))
+				elif full_data:
+					yield (a, cgd(*b))
+				else:
+					yield (a, cgd(cls.tmpdir_nums[0], b, [[[], cls.tmpdir_nums[0]]]))
+
+		cdata = tuple(gen_cdata()) # cannot use dict() here because of repeated keys
 
 
 		if add_dpy:
 		if add_dpy:
 			self.dpy_data.update(dict(cdata))
 			self.dpy_data.update(dict(cdata))
@@ -143,37 +147,52 @@ class CmdGroupMgr:
 		return cls
 		return cls
 
 
 	def gm_init_group(self, cfg, trunner, gname, sg_name, spawn_prog):
 	def gm_init_group(self, cfg, trunner, gname, sg_name, spawn_prog):
-		kwargs = self.cmd_groups[gname][1]
-		cls = self.create_group(gname, sg_name, **kwargs)
+		cls = self.create_group(gname, sg_name, **self.cmd_groups[gname].params)
 		cls.group_name = gname
 		cls.group_name = gname
 		return cls(cfg, trunner, cfgs, spawn_prog)
 		return cls(cfg, trunner, cfgs, spawn_prog)
 
 
 	def get_cls_by_gname(self, gname):
 	def get_cls_by_gname(self, gname):
-		return self.load_mod(gname, self.cmd_groups[gname][1].get('modname'))
+		return self.load_mod(gname, self.cmd_groups[gname].params.get('modname'))
+
+	def list_cmds(self):
+
+		def gen_output():
+			yield green('AVAILABLE COMMANDS:')
+			for gname in self.cmd_groups:
+				tg = self.gm_init_group(self.cfg, None, gname, None, None)
+				gdesc = tg.__doc__.strip() if tg.__doc__ else type(tg).__name__
+				yield '\n' + green(f'{gname!r} - {gdesc}:')
+				name_w = max(len(cmd) for cmd in self.cmd_list)
+				for cmd in self.cmd_list:
+					data = self.dpy_data[cmd]
+					yield '    {a:{w}} - {b}'.format(
+						a = cmd,
+						b = data if isinstance(data, str) else data.desc,
+						w = name_w)
+
+		from mmgen.ui import do_pager
+		do_pager('\n'.join(gen_output()))
 
 
 	def list_cmd_groups(self):
 	def list_cmd_groups(self):
-		ginfo = []
-		for gname in self.cmd_groups:
-			ginfo.append((gname, self.get_cls_by_gname(gname)))
 
 
 		if self.cfg.list_current_cmd_groups:
 		if self.cfg.list_current_cmd_groups:
-			exclude = (self.cfg.exclude_groups or '').split(',')
-			ginfo = [g for g in ginfo
-						if self.network_id in g[1].networks
-							and not g[0] in exclude
-							and g[0] in tuple(self.cmd_groups_dfl) + tuple(self.cfg._args)]
+			names = tuple(cmd_groups_dfl) + tuple(self.cfg._args)
+			exclude = self.cfg.exclude_groups.split(',') if self.cfg.exclude_groups else []
+			ginfo = {name: cls
+				for name, cls in [(gname, self.get_cls_by_gname(gname)) for gname in names]
+				if self.network_id in cls.networks and not name in exclude}
 			desc = 'CONFIGURED'
 			desc = 'CONFIGURED'
 		else:
 		else:
+			ginfo = {name: self.get_cls_by_gname(name) for name in self.cmd_groups}
 			desc = 'AVAILABLE'
 			desc = 'AVAILABLE'
 
 
 		def gen_output():
 		def gen_output():
 			yield green(f'{desc} COMMAND GROUPS AND SUBGROUPS:')
 			yield green(f'{desc} COMMAND GROUPS AND SUBGROUPS:')
 			yield ''
 			yield ''
-			for name, cls in ginfo:
+			name_w = max(len(name) for name in ginfo)
+			for name, cls in ginfo.items():
 				if not cls.is_helper:
 				if not cls.is_helper:
-					yield '  {} - {}'.format(
-						yellow(name.ljust(13)),
-						(cls.__doc__.strip() if cls.__doc__ else cls.__name__))
+					yield '  {} - {}'.format(yellow(name.ljust(name_w)), cls.__doc__.strip())
 					if 'cmd_subgroups' in cls.__dict__:
 					if 'cmd_subgroups' in cls.__dict__:
 						subgroups = {k:v for k, v in cls.cmd_subgroups.items() if not k.startswith('_')}
 						subgroups = {k:v for k, v in cls.cmd_subgroups.items() if not k.startswith('_')}
 						max_w = max(len(k) for k in subgroups)
 						max_w = max(len(k) for k in subgroups)
@@ -182,8 +201,7 @@ class CmdGroupMgr:
 
 
 		from mmgen.ui import do_pager
 		from mmgen.ui import do_pager
 		do_pager('\n'.join(gen_output()))
 		do_pager('\n'.join(gen_output()))
-
-		Msg('\n' + ' '.join(e[0] for e in ginfo))
+		Msg('\n' + ' '.join(ginfo))
 
 
 	def find_cmd_in_groups(self, cmd, group=None):
 	def find_cmd_in_groups(self, cmd, group=None):
 		"""
 		"""
@@ -191,7 +209,7 @@ class CmdGroupMgr:
 		and return it as a string.  Loads modules but alters no global variables.
 		and return it as a string.  Loads modules but alters no global variables.
 		"""
 		"""
 		if group:
 		if group:
-			if not group in [e[0] for e in self.cmd_groups]:
+			if not group in self.cmd_groups:
 				die(1, f'{group!r}: unrecognized group')
 				die(1, f'{group!r}: unrecognized group')
 			groups = [self.cmd_groups[group]]
 			groups = [self.cmd_groups[group]]
 		else:
 		else:

+ 5 - 5
test/cmdtest_d/include/proxy.py

@@ -76,14 +76,14 @@ class TestProxy:
 		if port_in_use(self.port):
 		if port_in_use(self.port):
 			omsg(f'Port {self.port} already in use. Assuming SSH SOCKS server is running')
 			omsg(f'Port {self.port} already in use. Assuming SSH SOCKS server is running')
 		else:
 		else:
-			cp = run(a + b0 + b1, stdout=PIPE, stderr=PIPE)
-			if err := cp.stderr.decode():
-				omsg(err)
+			cp = run(a + b0 + b1, stdout=PIPE, stderr=PIPE, text=True)
+			if cp.stderr:
+				omsg(cp.stderr)
 			if cp.returncode == 0:
 			if cp.returncode == 0:
 				start_proxy()
 				start_proxy()
-			elif 'onnection refused' in err:
+			elif 'onnection refused' in cp.stderr:
 				die(2, fmt(self.no_ssh_errmsg, indent='    '))
 				die(2, fmt(self.no_ssh_errmsg, indent='    '))
-			elif 'ermission denied' in err:
+			elif 'ermission denied' in cp.stderr:
 				msg(fmt(self.bad_perm_errmsg.format(' '.join(a + b2)), indent='    ', strip_char='\t'))
 				msg(fmt(self.bad_perm_errmsg.format(' '.join(a + b2)), indent='    ', strip_char='\t'))
 				from mmgen.ui import keypress_confirm
 				from mmgen.ui import keypress_confirm
 				keypress_confirm(cfg, 'Continue?', do_exit=True)
 				keypress_confirm(cfg, 'Continue?', do_exit=True)

+ 34 - 44
test/cmdtest_d/include/runner.py

@@ -13,10 +13,11 @@ test.cmdtest_d.include.runner: test runner for the MMGen Wallet cmdtest suite
 """
 """
 
 
 import sys, os, time, asyncio
 import sys, os, time, asyncio
+from collections import namedtuple
 
 
 from mmgen.cfg import gc
 from mmgen.cfg import gc
 from mmgen.color import red, yellow, green, blue, cyan, gray, nocolor
 from mmgen.color import red, yellow, green, blue, cyan, gray, nocolor
-from mmgen.util import msg, Msg, rmsg, ymsg, bmsg, die, suf, make_timestr, isAsync
+from mmgen.util import msg, Msg, rmsg, ymsg, bmsg, die, suf, make_timestr, isAsync, capfirst
 
 
 from ...include.common import (
 from ...include.common import (
 	cmdtest_py_log_fn,
 	cmdtest_py_log_fn,
@@ -30,7 +31,7 @@ from ...include.common import (
 )
 )
 
 
 from .common import get_file_with_ext, confirm_continue
 from .common import get_file_with_ext, confirm_continue
-from .cfg import cfgs, cmd_groups_dfl
+from .cfg import cfgs
 from .group_mgr import CmdGroupMgr
 from .group_mgr import CmdGroupMgr
 
 
 def format_args(args):
 def format_args(args):
@@ -141,7 +142,8 @@ class CmdTestRunner:
 
 
 		self.exit_val = exit_val
 		self.exit_val = exit_val
 
 
-		desc = self.tg.test_name if self.cfg.names else self.gm.dpy_data[self.tg.test_name][1]
+		desc = self.tg.test_name if self.cfg.names else self.gm.dpy_data[self.tg.test_name].desc
+
 		if extra_desc:
 		if extra_desc:
 			desc += ' ' + extra_desc
 			desc += ' ' + extra_desc
 
 
@@ -347,8 +349,9 @@ class CmdTestRunner:
 								except Exception as e: # allow calling of functions not in cmd_group
 								except Exception as e: # allow calling of functions not in cmd_group
 									if isinstance(e, KeyError) and e.args[0] == cmdname:
 									if isinstance(e, KeyError) and e.args[0] == cmdname:
 										func = getattr(self.tg, cmdname)
 										func = getattr(self.tg, cmdname)
-										ret = asyncio.run(func()) if isAsync(func) else func()
-										self.process_retval(cmdname, ret)
+										self.process_retval(
+											cmdname,
+											asyncio.run(func()) if isAsync(func) else func())
 									else:
 									else:
 										raise
 										raise
 								self.do_between()
 								self.do_between()
@@ -360,14 +363,7 @@ class CmdTestRunner:
 					else:
 					else:
 						die(1, f'{arg!r}: command not recognized')
 						die(1, f'{arg!r}: command not recognized')
 		else:
 		else:
-			if self.cfg.exclude_groups:
-				exclude = self.cfg.exclude_groups.split(',')
-				for e in exclude:
-					if e not in cmd_groups_dfl:
-						die(1, f'{e!r}: group not recognized')
-			for gname in cmd_groups_dfl:
-				if self.cfg.exclude_groups and gname in exclude:
-					continue
+			for gname in CmdGroupMgr.get_cmd_groups(self.cfg):
 				if self.init_group(gname):
 				if self.init_group(gname):
 					for cmd in self.gm.cmd_list:
 					for cmd in self.gm.cmd_list:
 						self.check_needs_rerun(cmd, build=True)
 						self.check_needs_rerun(cmd, build=True)
@@ -392,16 +388,16 @@ class CmdTestRunner:
 
 
 		fns = []
 		fns = []
 		if force_delete or not root:
 		if force_delete or not root:
-			# does cmd produce a needed dependency(ies)?
-			ret = self.get_num_exts_for_cmd(cmd)
-			if ret:
-				for ext in ret[1]:
-					fn = get_file_with_ext(cfgs[ret[0]]['tmpdir'], ext, delete=build)
-					if fn:
+			# does cmd produce a required dependency(ies)?
+			if deps := self.get_cmd_deps(cmd):
+				for ext in deps.exts:
+					if fn := get_file_with_ext(cfgs[deps.cfgnum]['tmpdir'], ext, delete=build):
 						if force_delete:
 						if force_delete:
 							os.unlink(fn)
 							os.unlink(fn)
-						else: fns.append(fn)
-					else: rerun = True
+						else:
+							fns.append(fn)
+					else:
+						rerun = True
 
 
 		fdeps = self.generate_file_deps(cmd)
 		fdeps = self.generate_file_deps(cmd)
 		cdeps = self.generate_cmd_deps(fdeps)
 		cdeps = self.generate_cmd_deps(fdeps)
@@ -426,10 +422,8 @@ class CmdTestRunner:
 					self.run_test(cmd)
 					self.run_test(cmd)
 				if not root:
 				if not root:
 					self.do_between()
 					self.do_between()
-		else:
-			# If prog produces multiple files:
-			if cmd not in self.rebuild_list or rerun is True:
-				self.rebuild_list[cmd] = (rerun, fns[0] if fns else '') # FIX
+		elif rerun or cmd not in self.rebuild_list:
+			self.rebuild_list[cmd] = 'rebuild' if rerun and fns else 'build' if rerun else 'OK'
 
 
 		return rerun
 		return rerun
 
 
@@ -439,9 +433,9 @@ class CmdTestRunner:
 			sys.exit(0)
 			sys.exit(0)
 
 
 		if self.tg.full_data:
 		if self.tg.full_data:
-			d = [(str(num), ext) for exts, num in self.gm.dpy_data[cmd][2] for ext in exts]
+			d = [(num, ext) for exts, num in self.gm.dpy_data[cmd].dpy_list for ext in exts]
 			# delete files depended on by this cmd
 			# delete files depended on by this cmd
-			arg_list = [get_file_with_ext(cfgs[num]['tmpdir'], ext) for num, ext in d]
+			arg_list = [get_file_with_ext(cfgs[str(num)]['tmpdir'], ext) for num, ext in d]
 
 
 			# remove shared_deps from arg list
 			# remove shared_deps from arg list
 			if hasattr(self.tg, 'shared_deps'):
 			if hasattr(self.tg, 'shared_deps'):
@@ -463,7 +457,7 @@ class CmdTestRunner:
 		self.tg.test_name = cmd # NB: Do not remove, this needs to be set twice
 		self.tg.test_name = cmd # NB: Do not remove, this needs to be set twice
 
 
 		if self.tg.full_data:
 		if self.tg.full_data:
-			tmpdir_num = self.gm.dpy_data[cmd][0]
+			tmpdir_num = self.gm.dpy_data[cmd].tmpdir_num
 			self.tg.tmpdir_num = tmpdir_num
 			self.tg.tmpdir_num = tmpdir_num
 			for k in (test_cfg := cfgs[str(tmpdir_num)]):
 			for k in (test_cfg := cfgs[str(tmpdir_num)]):
 				if k in self.gm.cfg_attrs:
 				if k in self.gm.cfg_attrs:
@@ -539,35 +533,31 @@ class CmdTestRunner:
 		self.check_needs_rerun(cmd)
 		self.check_needs_rerun(cmd)
 
 
 		w = max(map(len, self.rebuild_list)) + 1
 		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))
+		for cmd, desc in self.rebuild_list.items():
+			omsg('cmd {:<{w}} {}'.format(cmd+':', capfirst(desc), w=w))
 
 
 	def generate_file_deps(self, cmd):
 	def generate_file_deps(self, cmd):
-		return [(str(n), e) for exts, n in self.gm.dpy_data[cmd][2] for e in exts]
+		return [(str(n), e) for exts, n in self.gm.dpy_data[cmd].dpy_list for e in exts]
 
 
 	def generate_cmd_deps(self, fdeps):
 	def generate_cmd_deps(self, fdeps):
 		return [cfgs[str(n)]['dep_generators'][ext] for n, ext in fdeps]
 		return [cfgs[str(n)]['dep_generators'][ext] for n, ext in fdeps]
 
 
-	def get_num_exts_for_cmd(self, cmd):
+	def get_cmd_deps(self, cmd):
 		try:
 		try:
-			num = str(self.gm.dpy_data[cmd][0])
+			self.gm.dpy_data[cmd]
 		except KeyError:
 		except KeyError:
 			qmsg_r(f'Missing dependency {cmd!r}')
 			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})
+			if gname := self.gm.find_cmd_in_groups(cmd):
+				kwargs = self.gm.cmd_groups[gname].params | {'add_dpy': True}
 				self.gm.create_group(gname, None, **kwargs)
 				self.gm.create_group(gname, None, **kwargs)
-				num = str(self.gm.dpy_data[cmd][0])
 				qmsg(f' found in group {gname!r}')
 				qmsg(f' found in group {gname!r}')
 			else:
 			else:
 				qmsg(' not found in any command group!')
 				qmsg(' not found in any command group!')
 				raise
 				raise
-		dgl = cfgs[num]['dep_generators']
-		if cmd in dgl.values():
-			exts = [k for k in dgl if dgl[k] == cmd]
-			return (num, exts)
+		num = str(self.gm.dpy_data[cmd].tmpdir_num)
+		dep_gens = cfgs[num]['dep_generators']
+		if cmd in dep_gens.values():
+			cd = namedtuple('cmd_deps', ['cfgnum', 'exts'])
+			return cd(num, [k for k in dep_gens if dep_gens[k] == cmd])
 		else:
 		else:
 			return None
 			return None

+ 4 - 4
test/cmdtest_d/main.py

@@ -241,13 +241,13 @@ class CmdTestMain(CmdTestBase, CmdTestShared):
 			(3, 'tx signing with inputs and outputs from two wallets', [[['mmdat'], 1], [['mmdat', 'rawtx'], 3]])
 			(3, 'tx signing with inputs and outputs from two wallets', [[['mmdat'], 1], [['mmdat', 'rawtx'], 3]])
 			),
 			),
 		('walletgen14',
 		('walletgen14',
-			(14, 'wallet generation (14)', [[['del_dw_run'], 15]], 14)
+			(14, 'wallet generation (14)', [[['del_dw_run'], 15]])
 		),
 		),
 		('addrgen14',
 		('addrgen14',
 			(14, 'address generation (14)', [[['mmdat'], 14]])
 			(14, 'address generation (14)', [[['mmdat'], 14]])
 		),
 		),
 		('keyaddrgen14',
 		('keyaddrgen14',
-			(14, 'key-address file generation (14)', [[['mmdat'], 14]], 14)
+			(14, 'key-address file generation (14)', [[['mmdat'], 14]])
 		),
 		),
 		('walletgen4',
 		('walletgen4',
 			(4, 'wallet generation (4) (brainwallet)', [[['del_dw_run'], 15]])
 			(4, 'wallet generation (4) (brainwallet)', [[['del_dw_run'], 15]])
@@ -310,7 +310,7 @@ class CmdTestMain(CmdTestBase, CmdTestShared):
 			])
 			])
 		), # must go after txsign4
 		), # must go after txsign4
 		('walletgen5',
 		('walletgen5',
-			(20, 'wallet generation (5)', [[['del_dw_run'], 15]], 20)
+			(20, 'wallet generation (5)', [[['del_dw_run'], 15]])
 		),
 		),
 		('addrgen5',
 		('addrgen5',
 			(20, 'address generation (5)', [[['mmdat'], 20]])
 			(20, 'address generation (5)', [[['mmdat'], 20]])
@@ -322,7 +322,7 @@ class CmdTestMain(CmdTestBase, CmdTestShared):
 			(20, 'transaction signing with bad vsize', [[['mmdat', 'rawtx'], 20]])
 			(20, 'transaction signing with bad vsize', [[['mmdat', 'rawtx'], 20]])
 		),
 		),
 		('walletgen6',
 		('walletgen6',
-			(21, 'wallet generation (6)', [[['del_dw_run'], 15]], 21)
+			(21, 'wallet generation (6)', [[['del_dw_run'], 15]])
 		),
 		),
 		('addrgen6',
 		('addrgen6',
 			(21, 'address generation (6)', [[['mmdat'], 21]])
 			(21, 'address generation (6)', [[['mmdat'], 21]])

+ 38 - 38
test/cmdtest_d/ref_3seed.py

@@ -57,26 +57,26 @@ class CmdTestRef3Seed(CmdTestBase, CmdTestShared):
 	)
 	)
 	cmd_group = (
 	cmd_group = (
 		# reading saved reference wallets
 		# reading saved reference wallets
-		('ref_wallet_chk',   ([], 'saved reference wallet')),
-		('ref_seed_chk',     ([], 'saved seed file')),
-		('ref_hex_chk',      ([], 'saved mmhex file')),
-		('ref_plainhex_chk', ([], 'saved hex file')),
-		('ref_dieroll_chk',  ([], 'saved dieroll (b6d) file')),
-		('ref_mn_chk',       ([], 'saved native MMGen mnemonic file')),
-		('ref_bip39_chk',    ([], 'saved BIP39 mnemonic file')),
-		('ref_hincog_chk',   ([], 'saved hidden incog reference wallet')),
-		('ref_brain_chk',    ([], 'saved brainwallet')),                    # in shared
+		('ref_wallet_chk',   'saved reference wallet'),
+		('ref_seed_chk',     'saved seed file'),
+		('ref_hex_chk',      'saved mmhex file'),
+		('ref_plainhex_chk', 'saved hex file'),
+		('ref_dieroll_chk',  'saved dieroll (b6d) file'),
+		('ref_mn_chk',       'saved native MMGen mnemonic file'),
+		('ref_bip39_chk',    'saved BIP39 mnemonic file'),
+		('ref_hincog_chk',   'saved hidden incog reference wallet'),
+		('ref_brain_chk',    'saved brainwallet'),                    # in shared
 
 
 		# generating new reference ('abc' brainwallet) wallets for filename checks:
 		# generating new reference ('abc' brainwallet) wallets for filename checks:
-		('ref_walletgen_brain',         ([], 'generating new reference wallet + filename check (brain)')),
-		('ref_walletconv_words',        ([], 'wallet filename (native mnemonic)')),
-		('ref_walletconv_bip39',        ([], 'wallet filename (bip39)')),
-		('ref_walletconv_seed',         ([], 'wallet filename (seed)')),
-		('ref_walletconv_hexseed',      ([], 'wallet filename (hex seed)')),
-		('ref_walletconv_plainhexseed', ([], 'wallet filename (plain hex seed)')),
-		('ref_walletconv_dieroll',      ([], 'wallet filename (dieroll (b6d) seed)')),
-		('ref_walletconv_incog',        ([], 'wallet filename (incog)')),
-		('ref_walletconv_hexincog',     ([], 'wallet filename (hex incog)')),
+		('ref_walletgen_brain',         'generating new reference wallet + filename check (brain)'),
+		('ref_walletconv_words',        'wallet filename (native mnemonic)'),
+		('ref_walletconv_bip39',        'wallet filename (bip39)'),
+		('ref_walletconv_seed',         'wallet filename (seed)'),
+		('ref_walletconv_hexseed',      'wallet filename (hex seed)'),
+		('ref_walletconv_plainhexseed', 'wallet filename (plain hex seed)'),
+		('ref_walletconv_dieroll',      'wallet filename (dieroll (b6d) seed)'),
+		('ref_walletconv_incog',        'wallet filename (incog)'),
+		('ref_walletconv_hexincog',     'wallet filename (hex incog)'),
 	)
 	)
 
 
 	def __init__(self, cfg, trunner, cfgs, spawn):
 	def __init__(self, cfg, trunner, cfgs, spawn):
@@ -333,15 +333,15 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 	}
 	}
 
 
 	cmd_group = (
 	cmd_group = (
-		('ref_walletgen_brain',       ([], 'generating new reference wallet + filename check (brain)')),
-		('refaddrgen_legacy',         ([], 'new refwallet addr chksum (uncompressed)')),
-		('refaddrgen_compressed',     ([], 'new refwallet addr chksum (compressed)')),
-		('refaddrgen_segwit',         ([], 'new refwallet addr chksum (segwit)')),
-		('refaddrgen_bech32',         ([], 'new refwallet addr chksum (bech32)')),
-		('refkeyaddrgen_legacy',      ([], 'new refwallet key-addr chksum (uncompressed)')),
-		('refkeyaddrgen_compressed',  ([], 'new refwallet key-addr chksum (compressed)')),
-		('refkeyaddrgen_segwit',      ([], 'new refwallet key-addr chksum (segwit)')),
-		('refkeyaddrgen_bech32',      ([], 'new refwallet key-addr chksum (bech32)')),
+		('ref_walletgen_brain',       'generating new reference wallet + filename check (brain)'),
+		('refaddrgen_legacy',         'new refwallet addr chksum (uncompressed)'),
+		('refaddrgen_compressed',     'new refwallet addr chksum (compressed)'),
+		('refaddrgen_segwit',         'new refwallet addr chksum (segwit)'),
+		('refaddrgen_bech32',         'new refwallet addr chksum (bech32)'),
+		('refkeyaddrgen_legacy',      'new refwallet key-addr chksum (uncompressed)'),
+		('refkeyaddrgen_compressed',  'new refwallet key-addr chksum (compressed)'),
+		('refkeyaddrgen_segwit',      'new refwallet key-addr chksum (segwit)'),
+		('refkeyaddrgen_bech32',      'new refwallet key-addr chksum (bech32)'),
 	)
 	)
 
 
 	def call_addrgen(self, mmtype, name='addrgen'):
 	def call_addrgen(self, mmtype, name='addrgen'):
@@ -419,17 +419,17 @@ class CmdTestRef3Passwd(CmdTestRef3Seed):
 	}
 	}
 
 
 	cmd_group = (
 	cmd_group = (
-		('ref_walletgen_brain',        ([], 'generating new reference wallet + filename check (brain)')),
-		('refpasswdgen',               ([], 'new refwallet passwd file chksum')),
-		('refpasswdgen_half',          ([], 'new refwallet passwd file chksum (half-length)')),
-		('ref_b32passwdgen',           ([], 'new refwallet passwd file chksum (base32)')),
-		('ref_hexpasswdgen',           ([], 'new refwallet passwd file chksum (hex)')),
-		('ref_hexpasswdgen_half',      ([], 'new refwallet passwd file chksum (hex, half-length)')),
-		('ref_bip39_12_passwdgen',     ([], 'new refwallet passwd file chksum (BIP39, 12 words)')),
-		('ref_bip39_18_passwdgen',     ([], 'new refwallet passwd file chksum (BIP39, up to 18 words)')),
-		('ref_bip39_24_passwdgen',     ([], 'new refwallet passwd file chksum (BIP39, up to 24 words)')),
-		('ref_xmrseed_25_passwdgen',   ([], 'new refwallet passwd file chksum (Monero 25-word mnemonic)')),
-		('ref_hex2bip39_24_passwdgen', ([], 'new refwallet passwd file chksum (hex-to-BIP39, up to 24 words)')),
+		('ref_walletgen_brain',        'generating new reference wallet + filename check (brain)'),
+		('refpasswdgen',               'new refwallet passwd file chksum'),
+		('refpasswdgen_half',          'new refwallet passwd file chksum (half-length)'),
+		('ref_b32passwdgen',           'new refwallet passwd file chksum (base32)'),
+		('ref_hexpasswdgen',           'new refwallet passwd file chksum (hex)'),
+		('ref_hexpasswdgen_half',      'new refwallet passwd file chksum (hex, half-length)'),
+		('ref_bip39_12_passwdgen',     'new refwallet passwd file chksum (BIP39, 12 words)'),
+		('ref_bip39_18_passwdgen',     'new refwallet passwd file chksum (BIP39, up to 18 words)'),
+		('ref_bip39_24_passwdgen',     'new refwallet passwd file chksum (BIP39, up to 24 words)'),
+		('ref_xmrseed_25_passwdgen',   'new refwallet passwd file chksum (Monero 25-word mnemonic)'),
+		('ref_hex2bip39_24_passwdgen', 'new refwallet passwd file chksum (hex-to-BIP39, up to 24 words)'),
 	)
 	)
 
 
 	def pwgen(self, ftype, id_str, pwfmt=None, pwlen=None, extra_opts=[], stdout=False):
 	def pwgen(self, ftype, id_str, pwfmt=None, pwlen=None, extra_opts=[], stdout=False):

+ 1 - 1
test/cmdtest_d/rune.py

@@ -70,7 +70,7 @@ class CmdTestRune(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 		self.proto = init_proto(cfg, network_id=self.proto.coin + '_rt', need_amt=True)
 		self.proto = init_proto(cfg, network_id=self.proto.coin + '_rt', need_amt=True)
 		self.spawn_env['MMGEN_BOGUS_SEND'] = ''
 		self.spawn_env['MMGEN_BOGUS_SEND'] = ''
 
 
-		self.rpc_server = ThornodeRPCServer()
+		self.rpc_server = ThornodeRPCServer(cfg)
 		self.rpc_server.start()
 		self.rpc_server.start()
 
 
 		TestProxy(self, cfg)
 		TestProxy(self, cfg)

+ 1 - 1
test/cmdtest_d/runeswap.py

@@ -78,7 +78,7 @@ class CmdTestRuneSwap(CmdTestSwapMethods, CmdTestRegtest):
 
 
 		globals()[self.cross_group] = self.create_cross_runner(trunner)
 		globals()[self.cross_group] = self.create_cross_runner(trunner)
 
 
-		self.swap_server = ThornodeSwapServer()
+		self.swap_server = ThornodeSwapServer(cfg)
 		self.swap_server.start()
 		self.swap_server.start()
 
 
 		TestProxy(self, cfg)
 		TestProxy(self, cfg)

+ 1 - 1
test/cmdtest_d/swap.py

@@ -469,7 +469,7 @@ class CmdTestSwap(CmdTestSwapMethods, CmdTestRegtest, CmdTestAutosignThreaded):
 
 
 		self.protos = [init_proto(cfg, k, network='regtest', need_amt=True) for k in ('btc', 'ltc', 'bch')]
 		self.protos = [init_proto(cfg, k, network='regtest', need_amt=True) for k in ('btc', 'ltc', 'bch')]
 
 
-		self.swap_server = ThornodeSwapServer()
+		self.swap_server = ThornodeSwapServer(cfg)
 		self.swap_server.start()
 		self.swap_server.start()
 
 
 		self.opts.append('--bob')
 		self.opts.append('--bob')

+ 15 - 15
test/daemontest_d/msg.py

@@ -35,7 +35,7 @@ def get_obj(coin, network, msghash_type):
 def print_total(n):
 def print_total(n):
 	msg(f'{n} signature{suf(n)} verified')
 	msg(f'{n} signature{suf(n)} verified')
 
 
-async def run_test(network_id, chksum, msghash_type='raw'):
+async def do_test(network_id, chksum, msghash_type='raw'):
 
 
 	coin, network = CoinProtocol.Base.parse_network_id(network_id)
 	coin, network = CoinProtocol.Base.parse_network_id(network_id)
 
 
@@ -130,23 +130,23 @@ class unit_tests:
 
 
 	altcoin_deps = ('ltc', 'bch', 'eth', 'eth_raw')
 	altcoin_deps = ('ltc', 'bch', 'eth', 'eth_raw')
 
 
-	def btc(self, name, ut, desc='Bitcoin mainnet'):
-		return run_test('btc', 'AA0DB5')
+	async def btc(self, name, ut, desc='Bitcoin mainnet'):
+		return await do_test('btc', 'AA0DB5')
 
 
-	def btc_tn(self, name, ut, desc='Bitcoin testnet'):
-		return run_test('btc_tn', 'A88E1D')
+	async def btc_tn(self, name, ut, desc='Bitcoin testnet'):
+		return await do_test('btc_tn', 'A88E1D')
 
 
-	def btc_rt(self, name, ut, desc='Bitcoin regtest'):
-		return run_test('btc_rt', '578018')
+	async def btc_rt(self, name, ut, desc='Bitcoin regtest'):
+		return await do_test('btc_rt', '578018')
 
 
-	def ltc(self, name, ut, desc='Litecoin mainnet'):
-		return run_test('ltc', 'BA7549')
+	async def ltc(self, name, ut, desc='Litecoin mainnet'):
+		return await do_test('ltc', 'BA7549')
 
 
-	def bch(self, name, ut, desc='Bitcoin Cash mainnet'):
-		return run_test('bch', '1B8065')
+	async def bch(self, name, ut, desc='Bitcoin Cash mainnet'):
+		return await do_test('bch', '1B8065')
 
 
-	def eth(self, name, ut, desc='Ethereum mainnet'):
-		return run_test('eth', '35BAD9', msghash_type='eth_sign')
+	async def eth(self, name, ut, desc='Ethereum mainnet'):
+		return await do_test('eth', '35BAD9', msghash_type='eth_sign')
 
 
-	def eth_raw(self, name, ut, desc='Ethereum mainnet (raw message)'):
-		return run_test('eth', '9D900C')
+	async def eth_raw(self, name, ut, desc='Ethereum mainnet (raw message)'):
+		return await do_test('eth', '9D900C')

+ 2 - 2
test/gentest.py

@@ -138,7 +138,7 @@ SUPPORTED EXTERNAL TOOLS:
 }
 }
 
 
 def get_cmd_output(cmd, input=None):
 def get_cmd_output(cmd, input=None):
-	return run(cmd, input=input, stdout=PIPE, stderr=DEVNULL).stdout.decode().splitlines()
+	return run(cmd, input=input, stdout=PIPE, stderr=DEVNULL, text=True).stdout.splitlines()
 
 
 saved_results = {}
 saved_results = {}
 
 
@@ -201,7 +201,7 @@ class GenToolKeyconv(GenTool):
 class GenToolZcash_mini(GenTool):
 class GenToolZcash_mini(GenTool):
 	desc = 'zcash-mini'
 	desc = 'zcash-mini'
 	def run(self, sec, vcoin):
 	def run(self, sec, vcoin):
-		o = get_cmd_output(['zcash-mini', '-key', '-simple'], input=(sec.wif+'\n').encode())
+		o = get_cmd_output(['zcash-mini', '-key', '-simple'], input=sec.wif+'\n')
 		return gtr(o[1], o[0], o[-1])
 		return gtr(o[1], o[0], o[-1])
 
 
 class GenToolPycoin(GenTool):
 class GenToolPycoin(GenTool):

+ 5 - 5
test/include/common.py

@@ -324,10 +324,10 @@ tested_solc_ver = '0.8.26'
 def check_solc_ver():
 def check_solc_ver():
 	cmd = 'python3 scripts/create-token.py --check-solc-version'
 	cmd = 'python3 scripts/create-token.py --check-solc-version'
 	try:
 	try:
-		cp = run(cmd.split(), check=False, stdout=PIPE)
+		cp = run(cmd.split(), check=False, stdout=PIPE, text=True)
 	except Exception as e:
 	except Exception as e:
 		die(4, f'Unable to execute {cmd!r}: {e}')
 		die(4, f'Unable to execute {cmd!r}: {e}')
-	res = cp.stdout.decode().strip()
+	res = cp.stdout.strip()
 	if cp.returncode == 0:
 	if cp.returncode == 0:
 		omsg(
 		omsg(
 			orange(f'Found supported solc version {res}') if res == tested_solc_ver else
 			orange(f'Found supported solc version {res}') if res == tested_solc_ver else
@@ -352,7 +352,7 @@ def get_ethkey():
 	return None
 	return None
 
 
 def do_run(cmd, check=True):
 def do_run(cmd, check=True):
-	return run(cmd, stdout=PIPE, stderr=DEVNULL, check=check)
+	return run(cmd, stdout=PIPE, stderr=DEVNULL, check=check, text=True)
 
 
 def test_exec(cmd):
 def test_exec(cmd):
 	try:
 	try:
@@ -459,10 +459,10 @@ class VirtBlockDeviceLinux(VirtBlockDeviceBase):
 
 
 	def _get_associations(self):
 	def _get_associations(self):
 		cmd = ['sudo', 'losetup', '-n', '-O', 'NAME', '-j', str(self.img_path)]
 		cmd = ['sudo', 'losetup', '-n', '-O', 'NAME', '-j', str(self.img_path)]
-		return do_run(cmd).stdout.decode().splitlines()
+		return do_run(cmd).stdout.splitlines()
 
 
 	def get_new_dev(self):
 	def get_new_dev(self):
-		return do_run(['sudo', 'losetup', '-f']).stdout.decode().strip()
+		return do_run(['sudo', 'losetup', '-f']).stdout.strip()
 
 
 	def do_create(self, size, path):
 	def do_create(self, size, path):
 		do_run(['truncate', f'--size={size}', str(path)])
 		do_run(['truncate', f'--size={size}', str(path)])

+ 3 - 3
test/modtest_d/dep.py

@@ -151,9 +151,9 @@ class unit_tests:
 				'--stdout',
 				'--stdout',
 				init_proto(cfg, 'eth').checksummed_addr('deadbeef'*5),
 				init_proto(cfg, 'eth').checksummed_addr('deadbeef'*5),
 			]
 			]
-			cp = run(cmd, stdout=PIPE, stderr=PIPE)
-			vmsg(cp.stderr.decode())
+			cp = run(cmd, stdout=PIPE, stderr=PIPE, text=True)
+			vmsg(cp.stderr)
 			if cp.returncode:
 			if cp.returncode:
-				msg(cp.stderr.decode())
+				msg(cp.stderr)
 				return False
 				return False
 		return True
 		return True

+ 4 - 3
test/modtest_d/rune.py

@@ -17,7 +17,7 @@ from mmgen.proto.rune.tx.protobuf import (
 	deposit_tx_parms,
 	deposit_tx_parms,
 	swap_tx_parms)
 	swap_tx_parms)
 
 
-from ..include.common import vmsg, silence, end_silence
+from ..include.common import vmsg, qmsg, silence, end_silence
 
 
 test_cfg = Config({'coin': 'rune', 'test_suite': True})
 test_cfg = Config({'coin': 'rune', 'test_suite': True})
 
 
@@ -171,7 +171,8 @@ def test_tx(src, cfg, vec):
 	if tx.txid not in (vec.txid, vec_txid2):
 	if tx.txid not in (vec.txid, vec_txid2):
 		raise ValueError(f'{tx.txid} not in ({vec.txid}, {vec_txid2})')
 		raise ValueError(f'{tx.txid} not in ({vec.txid}, {vec_txid2})')
 	if tx.txid == vec_txid2:
 	if tx.txid == vec_txid2:
-		ymsg('\nWarning: non-standard TxID produced')
+		qmsg('')
+		ymsg('Warning: non-standard TxID produced')
 
 
 	if src == 'parse' and parms.from_addr:
 	if src == 'parse' and parms.from_addr:
 		built_tx = build_tx(cfg, proto, parms, null_fee=vec.null_fee)
 		built_tx = build_tx(cfg, proto, parms, null_fee=vec.null_fee)
@@ -209,7 +210,7 @@ class unit_tests:
 		regtest_cfg = Config({'coin': 'rune', 'regtest': True, 'test_suite': True})
 		regtest_cfg = Config({'coin': 'rune', 'regtest': True, 'test_suite': True})
 		end_silence()
 		end_silence()
 
 
-		thornode_server = ThornodeRPCServer()
+		thornode_server = ThornodeRPCServer(test_cfg)
 		thornode_server.start()
 		thornode_server.start()
 
 
 		addr = 'thor1lukwlve7hayy66qrdkp4k7sh0emjqwergy7tl3'
 		addr = 'thor1lukwlve7hayy66qrdkp4k7sh0emjqwergy7tl3'

+ 3 - 2
test/tooltest.py

@@ -189,8 +189,9 @@ if cfg.testing_status:
 		'tooltest2.py': run(
 		'tooltest2.py': run(
 			['python3', 'test/tooltest2.py', '--list-tested-cmds'],
 			['python3', 'test/tooltest2.py', '--list-tested-cmds'],
 			stdout = PIPE,
 			stdout = PIPE,
+			text = True,
 			check = True
 			check = True
-		).stdout.decode().split()
+		).stdout.split()
 	}
 	}
 	for v in cmd_data.values():
 	for v in cmd_data.values():
 		tested_in['tooltest.py'] += list(v['cmd_data'].keys())
 		tested_in['tooltest.py'] += list(v['cmd_data'].keys())
@@ -460,7 +461,7 @@ class MMGenToolTestCmds:
 		test_msg('command piping')
 		test_msg('command piping')
 		if cfg.verbose:
 		if cfg.verbose:
 			sys.stderr.write(green('Executing ') + cyan(cmd) + '\n')
 			sys.stderr.write(green('Executing ') + cyan(cmd) + '\n')
-		res = run(cmd, stdout=PIPE, shell=True).stdout.decode().strip()
+		res = run(cmd, stdout=PIPE, shell=True, text=True).stdout.strip()
 		addr = read_from_tmpfile(tcfg, 'wif2addr3.out').strip()
 		addr = read_from_tmpfile(tcfg, 'wif2addr3.out').strip()
 		cmp_or_die(addr, res)
 		cmp_or_die(addr, res)
 		ok()
 		ok()