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)
 
 	try:
-		cmdout = run(cmd, stdout=PIPE, check=True).stdout.decode()
+		cmdout = run(cmd, stdout=PIPE, check=True, text=True).stdout
 	except:
 		set_vt100()
 		return None

+ 2 - 2
mmgen/daemon.py

@@ -247,14 +247,14 @@ class Daemon(Lockable):
 	@classmethod
 	def get_exec_version_str(cls):
 		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:
 			die(2, f'{e}\nUnable to execute {cls.exec_fn}')
 
 		if cp.returncode:
 			die(2, f'Unable to execute {cls.exec_fn}')
 		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()
 
 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']
 		+ (['--verbose'] if cfg.verbose else [])
 		+ [str(fn)],
-		check=True)
+		check = True)
 	set_vt100()
 
 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)
 
 	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(
-		ret,
+		async_run(cfg, func, args=args, kwargs=kwargs) if isAsync(func) else func(*args, **kwargs),
 		pager = kwargs.get('pager'),
 		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')
 				return
 		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}]')
 		run(['diskutil', 'eraseVolume', 'APFS', self.label, self.dev_name], stdout=redir, check=True)
 		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(
 			payload = {'id': 1, 'jsonrpc': '2.0', 'method': method, 'params': params},
 			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):
 		"""

+ 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
 	"""
 	try:
-		cp = run(['solc', '--version'], check=True, stdout=PIPE)
+		cp = run(['solc', '--version'], check=True, text=True, stdout=PIPE)
 	except:
 		msg('solc missing or could not be executed') # this must go to stderr
 		return False
@@ -109,7 +109,7 @@ def check_solc_version():
 		Msg('solc exited with error')
 		return False
 
-	line = cp.stdout.decode().splitlines()[1]
+	line = cp.stdout.splitlines()[1]
 	version_str = re.sub(r'Version:\s*', '', line)
 	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
 
-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):
 	if sys.platform in ('win32', 'darwin'):
 		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
 		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:
 		from test.cmdtest_d.include.group_mgr import CmdGroupMgr
 		CmdGroupMgr(cfg).list_cmd_groups()
 		sys.exit(0)
-	elif cfg.list_cmds:
-		list_cmds()
-		sys.exit(0)
 
 	if cfg.pause:
 		set_restore_term_at_exit()

+ 1 - 0
test/cmdtest_d/base.py

@@ -38,6 +38,7 @@ class CmdTestBase:
 	need_daemon = False
 	platform_skip = ()
 	tmpdir_nums = []
+	skip_cmds = ()
 	test_name = None
 	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)
 
-		self.swap_server = ThornodeSwapServer()
+		self.swap_server = ThornodeSwapServer(cfg)
 		self.swap_server.start()
 
 	def txcreate1(self):

+ 7 - 10
test/cmdtest_d/ethdev.py

@@ -66,7 +66,7 @@ from .base import CmdTestBase
 from .shared import CmdTestShared
 from .httpd.etherscan import EtherscanServer
 
-etherscan_server = EtherscanServer()
+etherscan_server = EtherscanServer(cfg)
 
 del_addrs = ('4', '1')
 
@@ -829,9 +829,8 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 			write_to_file(pwfile, '')
 			run(['rm', '-rf', 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):
 			return {
@@ -875,9 +874,8 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 
 		def init_genesis(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.remove_datadir()
@@ -1349,10 +1347,9 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 			'--outdir=' + odir
 		] + cmd_args + [self.proto.checksummed_addr(dfl_devaddr)]
 		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:')
-			die(2, cp.stderr.decode())
+			die(2, cp.stderr)
 		imsg('ERC20 token {!r} compiled'.format(token_data['symbol']))
 		return 'ok'
 

+ 1 - 1
test/cmdtest_d/ethswap.py

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

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

@@ -24,6 +24,9 @@ class SilentRequestHandler(WSGIRequestHandler):
 
 class HTTPD:
 
+	def __init__(self, cfg):
+		self.cfg = cfg
+
 	def start(self):
 
 		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
 from wsgiref.util import request_uri
 
-from mmgen.cfg import Config
 from mmgen.amt import UniAmt
 from mmgen.protocol import init_proto
 
@@ -23,8 +22,6 @@ from ...include.common import eth_inbound_addr, thorchain_router_addr_file
 
 from . import ThornodeServer
 
-cfg = Config()
-
 # 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}
 gas_rate_units = {'ETH': 'gwei', 'BTC': 'satsperbyte'}
@@ -117,7 +114,7 @@ data_template_eth = {
 	'total_swap_seconds': 24
 }
 
-def make_inbound_addr(proto, mmtype):
+def make_inbound_addr(cfg, proto, mmtype):
 	if proto.is_evm:
 		return '0x' + eth_inbound_addr # non-checksummed as per ninerealms thornode
 	else:
@@ -155,15 +152,15 @@ class ThornodeSwapServer(ThornodeServer):
 		}
 
 		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({
-				'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],
 				'recommended_gas_rate': recommended_gas_rate[send_proto.base_proto_coin]
 			})
 
 		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:
 				raw_addr = fh.read().strip()
 			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
 """
+from collections import namedtuple
 
 from ...include.common import cfg
 from .common import pwfile, hincog_fn, incog_id_fn, randbool
 
 cmd_groups_altcoin = ['ref_altcoin', 'autosign', 'ethdev', 'xmrwallet', 'xmr_autosign']
+gd = namedtuple('cmd_groups_data', ['clsname', 'params'])
 
 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 = {
-	'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

+ 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
+from collections import namedtuple
 
 from mmgen.color import yellow, green, cyan
 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.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):
 
 		cmd_group_in = dict(cls.cmd_group_in)
@@ -80,12 +89,12 @@ class CmdGroupMgr:
 		return tuple(gen())
 
 	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
 		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):
 		"""
@@ -94,38 +103,33 @@ class CmdGroupMgr:
 		without touching 'cmd_list'
 		"""
 
-		cls = self.load_mod(gname, modname)
-		cdata = []
-
 		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.
 			"""
-			if not hasattr(cls, 'shared_deps'):
-				return []
-
 			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'):
 			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:
 			self.dpy_data.update(dict(cdata))
@@ -143,37 +147,52 @@ class CmdGroupMgr:
 		return cls
 
 	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
 		return cls(cfg, trunner, cfgs, spawn_prog)
 
 	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):
-		ginfo = []
-		for gname in self.cmd_groups:
-			ginfo.append((gname, self.get_cls_by_gname(gname)))
 
 		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'
 		else:
+			ginfo = {name: self.get_cls_by_gname(name) for name in self.cmd_groups}
 			desc = 'AVAILABLE'
 
 		def gen_output():
 			yield green(f'{desc} COMMAND GROUPS AND SUBGROUPS:')
 			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:
-					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__:
 						subgroups = {k:v for k, v in cls.cmd_subgroups.items() if not k.startswith('_')}
 						max_w = max(len(k) for k in subgroups)
@@ -182,8 +201,7 @@ class CmdGroupMgr:
 
 		from mmgen.ui import do_pager
 		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):
 		"""
@@ -191,7 +209,7 @@ class CmdGroupMgr:
 		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]:
+			if not group in self.cmd_groups:
 				die(1, f'{group!r}: unrecognized group')
 			groups = [self.cmd_groups[group]]
 		else:

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

@@ -76,14 +76,14 @@ class TestProxy:
 		if port_in_use(self.port):
 			omsg(f'Port {self.port} already in use. Assuming SSH SOCKS server is running')
 		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:
 				start_proxy()
-			elif 'onnection refused' in err:
+			elif 'onnection refused' in cp.stderr:
 				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'))
 				from mmgen.ui import keypress_confirm
 				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
+from collections import namedtuple
 
 from mmgen.cfg import gc
 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 (
 	cmdtest_py_log_fn,
@@ -30,7 +31,7 @@ from ...include.common import (
 )
 
 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
 
 def format_args(args):
@@ -141,7 +142,8 @@ class CmdTestRunner:
 
 		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:
 			desc += ' ' + extra_desc
 
@@ -347,8 +349,9 @@ class CmdTestRunner:
 								except Exception as e: # allow calling of functions not in cmd_group
 									if isinstance(e, KeyError) and e.args[0] == 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:
 										raise
 								self.do_between()
@@ -360,14 +363,7 @@ class CmdTestRunner:
 					else:
 						die(1, f'{arg!r}: command not recognized')
 		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):
 					for cmd in self.gm.cmd_list:
 						self.check_needs_rerun(cmd, build=True)
@@ -392,16 +388,16 @@ class CmdTestRunner:
 
 		fns = []
 		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:
 							os.unlink(fn)
-						else: fns.append(fn)
-					else: rerun = True
+						else:
+							fns.append(fn)
+					else:
+						rerun = True
 
 		fdeps = self.generate_file_deps(cmd)
 		cdeps = self.generate_cmd_deps(fdeps)
@@ -426,10 +422,8 @@ class CmdTestRunner:
 					self.run_test(cmd)
 				if not root:
 					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
 
@@ -439,9 +433,9 @@ class CmdTestRunner:
 			sys.exit(0)
 
 		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
-			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
 			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
 
 		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
 			for k in (test_cfg := cfgs[str(tmpdir_num)]):
 				if k in self.gm.cfg_attrs:
@@ -539,35 +533,31 @@ class CmdTestRunner:
 		self.check_needs_rerun(cmd)
 
 		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):
-		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):
 		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:
-			num = str(self.gm.dpy_data[cmd][0])
+			self.gm.dpy_data[cmd]
 		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})
+			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)
-				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)
+		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:
 			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]])
 			),
 		('walletgen14',
-			(14, 'wallet generation (14)', [[['del_dw_run'], 15]], 14)
+			(14, 'wallet generation (14)', [[['del_dw_run'], 15]])
 		),
 		('addrgen14',
 			(14, 'address generation (14)', [[['mmdat'], 14]])
 		),
 		('keyaddrgen14',
-			(14, 'key-address file generation (14)', [[['mmdat'], 14]], 14)
+			(14, 'key-address file generation (14)', [[['mmdat'], 14]])
 		),
 		('walletgen4',
 			(4, 'wallet generation (4) (brainwallet)', [[['del_dw_run'], 15]])
@@ -310,7 +310,7 @@ class CmdTestMain(CmdTestBase, CmdTestShared):
 			])
 		), # must go after txsign4
 		('walletgen5',
-			(20, 'wallet generation (5)', [[['del_dw_run'], 15]], 20)
+			(20, 'wallet generation (5)', [[['del_dw_run'], 15]])
 		),
 		('addrgen5',
 			(20, 'address generation (5)', [[['mmdat'], 20]])
@@ -322,7 +322,7 @@ class CmdTestMain(CmdTestBase, CmdTestShared):
 			(20, 'transaction signing with bad vsize', [[['mmdat', 'rawtx'], 20]])
 		),
 		('walletgen6',
-			(21, 'wallet generation (6)', [[['del_dw_run'], 15]], 21)
+			(21, 'wallet generation (6)', [[['del_dw_run'], 15]])
 		),
 		('addrgen6',
 			(21, 'address generation (6)', [[['mmdat'], 21]])

+ 38 - 38
test/cmdtest_d/ref_3seed.py

@@ -57,26 +57,26 @@ class CmdTestRef3Seed(CmdTestBase, CmdTestShared):
 	)
 	cmd_group = (
 		# 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:
-		('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):
@@ -333,15 +333,15 @@ class CmdTestRef3Addr(CmdTestRef3Seed):
 	}
 
 	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'):
@@ -419,17 +419,17 @@ class CmdTestRef3Passwd(CmdTestRef3Seed):
 	}
 
 	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):

+ 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.spawn_env['MMGEN_BOGUS_SEND'] = ''
 
-		self.rpc_server = ThornodeRPCServer()
+		self.rpc_server = ThornodeRPCServer(cfg)
 		self.rpc_server.start()
 
 		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)
 
-		self.swap_server = ThornodeSwapServer()
+		self.swap_server = ThornodeSwapServer(cfg)
 		self.swap_server.start()
 
 		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.swap_server = ThornodeSwapServer()
+		self.swap_server = ThornodeSwapServer(cfg)
 		self.swap_server.start()
 
 		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):
 	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)
 
@@ -130,23 +130,23 @@ class unit_tests:
 
 	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):
-	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 = {}
 
@@ -201,7 +201,7 @@ class GenToolKeyconv(GenTool):
 class GenToolZcash_mini(GenTool):
 	desc = 'zcash-mini'
 	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])
 
 class GenToolPycoin(GenTool):

+ 5 - 5
test/include/common.py

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

+ 3 - 3
test/modtest_d/dep.py

@@ -151,9 +151,9 @@ class unit_tests:
 				'--stdout',
 				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:
-				msg(cp.stderr.decode())
+				msg(cp.stderr)
 				return False
 		return True

+ 4 - 3
test/modtest_d/rune.py

@@ -17,7 +17,7 @@ from mmgen.proto.rune.tx.protobuf import (
 	deposit_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})
 
@@ -171,7 +171,8 @@ def test_tx(src, cfg, vec):
 	if tx.txid not in (vec.txid, vec_txid2):
 		raise ValueError(f'{tx.txid} not in ({vec.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:
 		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})
 		end_silence()
 
-		thornode_server = ThornodeRPCServer()
+		thornode_server = ThornodeRPCServer(test_cfg)
 		thornode_server.start()
 
 		addr = 'thor1lukwlve7hayy66qrdkp4k7sh0emjqwergy7tl3'

+ 3 - 2
test/tooltest.py

@@ -189,8 +189,9 @@ if cfg.testing_status:
 		'tooltest2.py': run(
 			['python3', 'test/tooltest2.py', '--list-tested-cmds'],
 			stdout = PIPE,
+			text = True,
 			check = True
-		).stdout.decode().split()
+		).stdout.split()
 	}
 	for v in cmd_data.values():
 		tested_in['tooltest.py'] += list(v['cmd_data'].keys())
@@ -460,7 +461,7 @@ class MMGenToolTestCmds:
 		test_msg('command piping')
 		if cfg.verbose:
 			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()
 		cmp_or_die(addr, res)
 		ok()