Browse Source

`test.py`: support command subgroups

Example:

    # List available command groups and subgroups
    $ test/test.py -L

    # Run the ‘msg’ subgroup of ‘regtest’, leaving the daemon running:
    $ test/test.py -D regtest.msg

    # Re-run the subgroup, skipping dependencies and viewing output:
    $ test/test.py -Se regtest.msg

    # Re-run the individual test ‘bob_msgverify’ in the subgroup:
    $ test/test.py -Se regtest.msg:bob_msgverify

    # Same as above:
    $ test/test.py -Se regtest:bob_msgverify
The MMGen Project 2 years ago
parent
commit
170a9ead
4 changed files with 471 additions and 337 deletions
  1. 1 1
      mmgen/data/version
  2. 89 29
      test/test.py
  3. 216 183
      test/test_py_d/ts_ethdev.py
  4. 165 124
      test/test_py_d/ts_regtest.py

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-13.2.dev10
+13.2.dev11

+ 89 - 29
test/test.py

@@ -96,7 +96,7 @@ opts_data = {
 	'sets': [('list_current_cmd_groups',True,'list_cmd_groups',True)],
 	'text': {
 		'desc': 'Test suite for the MMGen suite',
-		'usage':'[options] [command(s) or metacommand(s)]',
+		'usage':'[options] [command [..command]] | [command_group[.command_subgroup][:command]]',
 		'options': """
 -h, --help           Print this help message
 --, --longhelp       Print help message for long options (common options)
@@ -114,7 +114,7 @@ opts_data = {
 -e, --exact-output   Show the exact output of the MMGen script(s) being run
 -G, --exclude-groups=G Exclude the specified command groups (comma-separated)
 -l, --list-cmds      List the test script’s available commands
--L, --list-cmd-groups Output a list of command groups with descriptions
+-L, --list-cmd-groups List the test script’s command groups and subgroups
 -g, --list-current-cmd-groups List command groups for current configuration
 -n, --names          Display command names instead of descriptions
 -N, --no-timings     Suppress display of timing information
@@ -345,6 +345,36 @@ class CmdGroupMgr(object):
 	cmd_groups = cmd_groups_dfl.copy()
 	cmd_groups.update(cmd_groups_extra)
 
+	@staticmethod
+	def create_cmd_group(cls,sg_name=None):
+
+		cmd_group_in = dict(cls.cmd_group_in)
+
+		if sg_name and 'subgroup.' + sg_name not in cmd_group_in:
+			die(1,f'{sg_name!r}: no such subgroup in test group {cls.__name__}')
+
+		def add_entries(key,add_deps=True):
+			if add_deps:
+				for dep in cmd_group_in['subgroup.'+key]:
+					for e in add_entries(dep):
+						yield e
+			for e in cls.cmd_subgroups[key][1:]:
+				yield e
+
+		def gen():
+			for name,data in cls.cmd_group_in:
+				if name.startswith('subgroup.'):
+					sg_key = name.removeprefix('subgroup.')
+					if sg_name in (None,sg_key):
+						for e in add_entries(
+								sg_key,
+								add_deps = sg_name and not skipping_deps ):
+							yield e
+				elif not skipping_deps:
+					yield (name,data)
+
+		return tuple(gen())
+
 	def load_mod(self,gname,modname=None):
 		clsname,kwargs = self.cmd_groups[gname]
 		if modname == None and 'modname' in kwargs:
@@ -353,7 +383,7 @@ class CmdGroupMgr(object):
 		modpath = f'test.test_py_d.ts_{modname or gname}'
 		return getattr(importlib.import_module(modpath),clsname)
 
-	def create_group(self,gname,full_data=False,modname=None,is3seed=False,add_dpy=False):
+	def create_group(self,gname,sg_name,full_data=False,modname=None,is3seed=False,add_dpy=False):
 		"""
 		Initializes the list 'cmd_list' and dict 'dpy_data' from module's cmd_group data.
 		Alternatively, if called with 'add_dpy=True', updates 'dpy_data' from module data
@@ -374,6 +404,9 @@ class CmdGroupMgr(object):
 			return [k for k,v in cfgs[str(tmpdir_idx)]['dep_generators'].items()
 						if k in cls.shared_deps and v != cmdname]
 
+		if not hasattr(cls,'cmd_group'):
+			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))):
@@ -396,9 +429,9 @@ class CmdGroupMgr(object):
 
 		return cls
 
-	def gm_init_group(self,trunner,gname,spawn_prog):
+	def gm_init_group(self,trunner,gname,sg_name,spawn_prog):
 		kwargs = self.cmd_groups[gname][1]
-		cls = self.create_group(gname,**kwargs)
+		cls = self.create_group(gname,sg_name,**kwargs)
 		cls.group_name = gname
 		return cls(trunner,cfgs,spawn_prog)
 
@@ -415,12 +448,23 @@ class CmdGroupMgr(object):
 						if network_id in g[1].networks
 							and not g[0] in exclude
 							and g[0] in tuple(self.cmd_groups_dfl) + tuple(usr_args) ]
-
-		for name,cls in ginfo:
-			msg('{:17} - {}'.format(
-				name,
-				cls.__doc__.strip() if cls.__doc__ else cls.__name__
-			))
+			desc = 'CONFIGURED'
+		else:
+			desc = 'AVAILABLE'
+
+		def gen_output():
+			yield green(f'{desc} COMMAND GROUPS AND SUBGROUPS:')
+			yield ''
+			for name,cls in ginfo:
+				yield '  {} - {}'.format(
+					yellow(name.ljust(13)),
+					(cls.__doc__.strip() if cls.__doc__ else cls.__name__) )
+				if hasattr(cls,'cmd_subgroups'):
+					max_w = max(len(k) for k in cls.cmd_subgroups)
+					for k,v in cls.cmd_subgroups.items():
+						yield '    + {} · {}'.format( cyan(k.ljust(max_w+1)), v[0] )
+
+		do_pager('\n'.join(gen_output()))
 
 		Msg( '\n' + ' '.join(e[0] for e in ginfo) )
 		sys.exit(0)
@@ -441,6 +485,9 @@ class CmdGroupMgr(object):
 			clsname,kwargs = self.cmd_groups[gname]
 			cls = self.load_mod(gname,kwargs['modname'] if 'modname' in kwargs else None)
 
+			if not hasattr(cls,'cmd_group'):
+				cls.cmd_group = self.create_cmd_group(cls)
+
 			if cmd in cls.cmd_group:             # first search the class
 				return gname
 
@@ -559,7 +606,7 @@ class TestSuiteRunner(object):
 			('\n' if opt.no_timings else f'.  Elapsed time: {t//60:02d}:{t%60:02d}\n')
 		))
 
-	def init_group(self,gname,cmd=None,quiet=False,do_clean=True):
+	def init_group(self,gname,sg_name=None,cmd=None,quiet=False,do_clean=True):
 
 		ts_cls = CmdGroupMgr().load_mod(gname)
 
@@ -607,7 +654,7 @@ class TestSuiteRunner(object):
 
 		os.environ['MMGEN_BOGUS_UNSPENT_DATA'] = '' # zero this here, so test groups don't have to
 
-		self.ts = self.gm.gm_init_group(self,gname,self.spawn_wrapper)
+		self.ts = self.gm.gm_init_group(self,gname,sg_name,self.spawn_wrapper)
 		self.ts_clsname = type(self.ts).__name__
 
 		self.passthru_opts = ['--{}{}'.format(
@@ -641,24 +688,37 @@ class TestSuiteRunner(object):
 						self.check_needs_rerun(cmd,build=True)
 						do_between()
 				else:
-					if ':' in arg:
-						gname,arg = arg.split(':')
-					else:
-						gname = self.gm.find_cmd_in_groups(arg)
+					def parse_arg(arg):
+						if '.' in arg:
+							a,b = arg.split('.')
+							return [a] + b.split(':') if ':' in b else [a,b,None]
+						elif ':' in arg:
+							a,b = arg.split(':')
+							return [a,None,b]
+						else:
+							return [self.gm.find_cmd_in_groups(arg),None,arg]
+
+					gname,sg_name,cmdname = parse_arg(arg)
+
 					if gname:
 						same_grp = gname == gname_save # same group as previous cmd: don't clean, suppress blue msg
-						if not self.init_group(gname,arg,quiet=same_grp,do_clean=not same_grp):
+						if not self.init_group(gname,sg_name,cmdname,quiet=same_grp,do_clean=not same_grp):
 							continue
-						try:
-							self.check_needs_rerun(arg,build=True)
-						except Exception as e: # allow calling of functions not in cmd_group
-							if isinstance(e,KeyError) and e.args[0] == arg:
-								ret = getattr(self.ts,arg)()
-								if type(ret).__name__ == 'coroutine':
-									run_session(ret)
-							else:
-								raise
-						do_between()
+						if cmdname:
+							try:
+								self.check_needs_rerun(cmdname,build=True)
+							except Exception as e: # allow calling of functions not in cmd_group
+								if isinstance(e,KeyError) and e.args[0] == cmdname:
+									ret = getattr(self.ts,cmdname)()
+									if type(ret).__name__ == 'coroutine':
+										run_session(ret)
+								else:
+									raise
+							do_between()
+						else:
+							for cmd in self.gm.cmd_list:
+								self.check_needs_rerun(cmd,build=True)
+								do_between()
 						gname_save = gname
 					else:
 						die(1,f'{arg!r}: command not recognized')
@@ -838,7 +898,7 @@ class TestSuiteRunner(object):
 			if gname:
 				kwargs = self.gm.cmd_groups[gname][1]
 				kwargs.update({'add_dpy':True})
-				self.gm.create_group(gname,**kwargs)
+				self.gm.create_group(gname,None,**kwargs)
 				num = str(self.gm.dpy_data[cmd][0])
 				qmsg(f' found in group {gname!r}')
 			else:

+ 216 - 183
test/test_py_d/ts_ethdev.py

@@ -56,7 +56,8 @@ vbal2 = '99.997088755'
 vbal3 = '1.23142525'
 vbal4 = '127.0287909'
 vbal5 = '1000126.14828654512345678'
-vbal6 = '1000124.91944564512345678'
+vbal6 = '1000126.14933654512345678'
+vbal7 = '1000124.91944564512345678'
 
 bals = {
 	'1': [  ('98831F3A:E:1','123.456')],
@@ -130,148 +131,195 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 	extra_spawn_args = ['--regtest=1']
 	tmpdir_nums = [22]
 	color = True
-	cmd_group = (
-		('setup',                          f'dev mode tests for coin {coin} (start daemon)'),
-		('daemon_version',                  'mmgen-tool daemon_version'),
-		('wallet_upgrade1',                 'upgrading the tracking wallet (v1 -> v2)'),
-		('wallet_upgrade2',                 'upgrading the tracking wallet (v2 -> v3)'),
-		('addrgen',                         'generating addresses'),
-		('addrimport',                      'importing addresses'),
-		('addrimport_dev_addr',             "importing dev faucet address 'Ox00a329c..'"),
-
-		('fund_dev_address',                'funding the default (Parity dev) address'),
-
-		('msgsign_chk',                     "signing a message (low-level, check against 'eth_sign' RPC call)"),
-		('msgcreate',                       'creating a message file'),
-		('msgsign',                         'signing the message file'),
-		('msgverify',                       'verifying the message file'),
-		('msgexport',                       'exporting the message file data to JSON for third-party verifier'),
-		('msgverify_export',                'verifying the exported JSON data'),
-
-		('msgcreate_raw',                   'creating a message file (--msghash-type=raw)'),
-		('msgsign_raw',                     'signing the message file (msghash_type=raw)'),
-		('msgverify_raw',                   'verifying the message file (msghash_type=raw)'),
-		('msgexport_raw',                   'exporting the message file data to JSON (msghash_type=raw)'),
-		('msgverify_export_raw',            'verifying the exported JSON data (msghash_type=raw)'),
-
-		('txcreate1',                       'creating a transaction (spend from dev address to address :1)'),
-		('txview1_raw',                     'viewing the raw transaction'),
-		('txsign1',                         'signing the transaction'),
-		('txview1_sig',                     'viewing the signed transaction'),
-		('tx_status0_bad',                  'getting the transaction status'),
-		('txsign1_ni',                      'signing the transaction (non-interactive)'),
-		('txsend1',                         'sending the transaction'),
-		('bal1',                           f'the {coin} balance'),
-
-		('txcreate2',                       'creating a transaction (spend from dev address to address :11)'),
-		('txsign2',                         'signing the transaction'),
-		('txsend2',                         'sending the transaction'),
-		('bal2',                           f'the {coin} balance'),
-
-		('txcreate3',                       'creating a transaction (spend from dev address to address :21)'),
-		('txsign3',                         'signing the transaction'),
-		('txsend3',                         'sending the transaction'),
-		('bal3',                           f'the {coin} balance'),
-
-		('tx_status1',                      'getting the transaction status'),
-
-		('txcreate4',                       'creating a transaction (spend from MMGen address, low TX fee)'),
-		('txbump',                          'bumping the transaction fee'),
-
-		('txsign4',                         'signing the transaction'),
-		('txsend4',                         'sending the transaction'),
-		('tx_status1a',                     'getting the transaction status'),
-		('bal4',                           f'the {coin} balance'),
-
-		('txcreate5',                       'creating a transaction (fund burn address)'),
-		('txsign5',                         'signing the transaction'),
-		('txsend5',                         'sending the transaction'),
-
-		('addrimport_burn_addr',            'importing burn address'),
-		('bal5',                           f'the {coin} balance'),
-
-		('add_label1',                      'adding a UTF-8 label (zh)'),
-		('chk_label1',                      'the label'),
-		('add_label2',                      'adding a UTF-8 label (lat+cyr+gr)'),
-		('chk_label2',                      'the label'),
-		('remove_label',                    'removing the label'),
-
-		('token_compile1',                  'compiling ERC20 token #1'),
-
-		('token_deploy1a',                  'deploying ERC20 token #1 (SafeMath)'),
-		('token_deploy1b',                  'deploying ERC20 token #1 (Owned)'),
-		('token_deploy1c',                  'deploying ERC20 token #1 (Token)'),
-
-		('tx_status2',                      'getting the transaction status'),
-		('bal6',                           f'the {coin} balance'),
-
-		('token_compile2',                  'compiling ERC20 token #2'),
-
-		('token_deploy2a',                  'deploying ERC20 token #2 (SafeMath)'),
-		('token_deploy2b',                  'deploying ERC20 token #2 (Owned)'),
-		('token_deploy2c',                  'deploying ERC20 token #2 (Token)'),
-
-		('contract_deploy',                 'deploying contract (create,sign,send)'),
-
-		('token_fund_users',                'transferring token funds from dev to user'),
-		('token_user_bals',                 'show balances after transfer'),
-		('token_addrgen',                   'generating token addresses'),
-		('token_addrimport_badaddr1',       'importing token addresses (no token address)'),
-		('token_addrimport_badaddr2',       'importing token addresses (bad token address)'),
-		('token_addrimport_addr1',          'importing token addresses using token address (MM1)'),
-		('token_addrimport_addr2',          'importing token addresses using token address (MM2)'),
-		('token_addrimport_batch',          'importing token addresses (dummy batch mode) (MM1)'),
-		('token_addrimport_sym',            'importing token addresses using token symbol (MM2)'),
-
-		('bal7',                           f'the {coin} balance'),
-		('token_bal1',                     f'the {coin} balance and token balance'),
-
-		('token_txcreate1',                 'creating a token transaction'),
-		('token_txview1_raw',               'viewing the raw transaction'),
-		('token_txsign1',                   'signing the transaction'),
-		('token_txsend1',                   'sending the transaction'),
-		('token_txview1_sig',               'viewing the signed transaction'),
-		('tx_status3',                      'getting the transaction status'),
-		('token_bal2',                     f'the {coin} balance and token balance'),
-
-		('token_txcreate2',                 'creating a token transaction (to burn address)'),
-		('token_txbump',                    'bumping the transaction fee'),
-
-		('token_txsign2',                   'signing the transaction'),
-		('token_txsend2',                   'sending the transaction'),
-		('token_bal3',                     f'the {coin} balance and token balance'),
-
-		('del_dev_addr',                    'deleting the dev address'),
-
-		('bal1_getbalance',                f'the {coin} balance (getbalance)'),
-
-		('addrimport_token_burn_addr',      'importing the token burn address'),
-
-		('token_bal4',                     f'the {coin} balance and token balance'),
-		('token_bal_getbalance',            'the token balance (getbalance)'),
-
-		('txcreate_noamt',                  'creating a transaction (full amount send)'),
-		('txsign_noamt',                    'signing the transaction'),
-		('txsend_noamt',                    'sending the transaction'),
-
-		('bal8',                           f'the {coin} balance'),
-		('token_bal5',                      'the token balance'),
-
-		('token_txcreate_noamt',            'creating a token transaction (full amount send)'),
-		('token_txsign_noamt',              'signing the transaction'),
-		('token_txsend_noamt',              'sending the transaction'),
-
-		('bal9',                           f'the {coin} balance'),
-		('token_bal6',                      'the token balance'),
-
-		('listaddresses1',                  'listaddresses'),
-		('listaddresses2',                  'listaddresses minconf=999999999 (ignored)'),
-		('listaddresses3',                  'listaddresses sort=age (ignored)'),
-		('listaddresses4',                  'listaddresses showempty=1 sort=age (ignored)'),
-
-		('token_listaddresses1',            'listaddresses --token=mm1'),
-		('token_listaddresses2',            'listaddresses --token=mm1 showempty=1'),
-
+	cmd_group_in = (
+		('setup',             f'dev mode tests for coin {coin} (start daemon)'),
+		('subgroup.misc',     []),
+		('subgroup.init',     []),
+		('subgroup.msg',      ['init']),
+		('subgroup.main',     ['init']),
+		('subgroup.contract', ['main']),
+		('subgroup.token',    ['contract']),
+		('subgroup.twexport', ['token']),
+		('subgroup.cached',   ['token']),
+		('subgroup.view',     ['cached']),
+		('subgroup.label',    ['cached']),
+		('subgroup.remove',   ['cached']),
+		('stop',              'stopping daemon'),
+	)
+	cmd_subgroups = {
+	'misc': (
+		'miscellaneous commands',
+		('daemon_version', 'mmgen-tool daemon_version'),
+	),
+	'init': (
+		'initializing wallets',
+		('wallet_upgrade1',     'upgrading the tracking wallet (v1 -> v2)'),
+		('wallet_upgrade2',     'upgrading the tracking wallet (v2 -> v3)'),
+		('addrgen',             'generating addresses'),
+		('addrimport',          'importing addresses'),
+		('addrimport_dev_addr', "importing dev faucet address 'Ox00a329c..'"),
+		('fund_dev_address',    'funding the default (Parity dev) address'),
+	),
+	'msg': (
+		'message signing',
+		('msgsign_chk',          "signing a message (low-level, check against 'eth_sign' RPC call)"),
+		('msgcreate',            'creating a message file'),
+		('msgsign',              'signing the message file'),
+		('msgverify',            'verifying the message file'),
+		('msgexport',            'exporting the message file data to JSON for third-party verifier'),
+		('msgverify_export',     'verifying the exported JSON data'),
+
+		('msgcreate_raw',        'creating a message file (--msghash-type=raw)'),
+		('msgsign_raw',          'signing the message file (msghash_type=raw)'),
+		('msgverify_raw',        'verifying the message file (msghash_type=raw)'),
+		('msgexport_raw',        'exporting the message file data to JSON (msghash_type=raw)'),
+		('msgverify_export_raw', 'verifying the exported JSON data (msghash_type=raw)'),
+	),
+	'main': (
+		'creating, signing, sending and bumping Ethereum transactions',
+		('txcreate1',            'creating a transaction (spend from dev address to address :1)'),
+		('txview1_raw',          'viewing the raw transaction'),
+		('txsign1',              'signing the transaction'),
+		('txview1_sig',          'viewing the signed transaction'),
+		('tx_status0_bad',       'getting the transaction status'),
+		('txsign1_ni',           'signing the transaction (non-interactive)'),
+		('txsend1',              'sending the transaction'),
+		('bal1',                 f'the {coin} balance'),
+
+		('txcreate2',            'creating a transaction (spend from dev address to address :11)'),
+		('txsign2',              'signing the transaction'),
+		('txsend2',              'sending the transaction'),
+		('bal2',                 f'the {coin} balance'),
+
+		('txcreate3',            'creating a transaction (spend from dev address to address :21)'),
+		('txsign3',              'signing the transaction'),
+		('txsend3',              'sending the transaction'),
+		('bal3',                 f'the {coin} balance'),
+
+		('tx_status1',           'getting the transaction status'),
+
+		('txcreate4',            'creating a transaction (spend from MMGen address, low TX fee)'),
+		('txbump',               'bumping the transaction fee'),
+
+		('txsign4',              'signing the transaction'),
+		('txsend4',              'sending the transaction'),
+		('tx_status1a',          'getting the transaction status'),
+		('bal4',                 f'the {coin} balance'),
+
+		('txcreate5',            'creating a transaction (fund burn address)'),
+		('txsign5',              'signing the transaction'),
+		('txsend5',              'sending the transaction'),
+
+		('addrimport_burn_addr', 'importing burn address'),
+		('bal5',                 f'the {coin} balance'),
+
+		('add_label1',           'adding a UTF-8 label (zh)'),
+		('chk_label1',           'checking the label'),
+		('add_label2',           'adding a UTF-8 label (lat+cyr+gr)'),
+		('chk_label2',           'checking the label'),
+		('remove_label',         'removing the label'),
+	),
+	'contract': (
+		'creating and deploying ERC20 tokens',
+		('token_compile1',  'compiling ERC20 token #1'),
+		('token_deploy1a',  'deploying ERC20 token #1 (SafeMath)'),
+		('token_deploy1b',  'deploying ERC20 token #1 (Owned)'),
+		('token_deploy1c',  'deploying ERC20 token #1 (Token)'),
+
+		('tx_status2',      'getting the transaction status'),
+		('bal6',            f'the {coin} balance'),
+
+		('token_compile2',  'compiling ERC20 token #2'),
+		('token_deploy2a',  'deploying ERC20 token #2 (SafeMath)'),
+		('token_deploy2b',  'deploying ERC20 token #2 (Owned)'),
+		('token_deploy2c',  'deploying ERC20 token #2 (Token)'),
+
+		('contract_deploy', 'deploying contract (create,sign,send)'),
+	),
+	'token': (
+		'creating, signing, sending and bumping ERC20 token transactions',
+
+		('token_fund_users',           'transferring token funds from dev to user'),
+		('token_user_bals',            'show balances after transfer'),
+		('token_addrgen',              'generating token addresses'),
+		('token_addrimport_badaddr1',  'importing token addresses (no token address)'),
+		('token_addrimport_badaddr2',  'importing token addresses (bad token address)'),
+		('token_addrimport_addr1',     'importing token addresses using token address (MM1)'),
+		('token_addrimport_addr2',     'importing token addresses using token address (MM2)'),
+		('token_addrimport_batch',     'importing token addresses (dummy batch mode) (MM1)'),
+		('token_addrimport_sym',       'importing token addresses using token symbol (MM2)'),
+
+		('bal7',                       f'the {coin} balance'),
+		('token_bal1',                 f'the {coin} balance and token balance'),
+
+		('token_txcreate1',            'creating a token transaction'),
+		('token_txview1_raw',          'viewing the raw transaction'),
+		('token_txsign1',              'signing the transaction'),
+		('token_txsend1',              'sending the transaction'),
+		('token_txview1_sig',          'viewing the signed transaction'),
+		('tx_status3',                 'getting the transaction status'),
+		('token_bal2',                 f'the {coin} balance and token balance'),
+
+		('token_txcreate2',            'creating a token transaction (to burn address)'),
+		('token_txbump',               'bumping the transaction fee'),
+
+		('token_txsign2',              'signing the transaction'),
+		('token_txsend2',              'sending the transaction'),
+		('token_bal3',                 f'the {coin} balance and token balance'),
+
+		('del_dev_addr',               'deleting the dev address'),
+
+		('bal1_getbalance',            f'the {coin} balance (getbalance)'),
+
+		('addrimport_token_burn_addr', 'importing the token burn address'),
+
+		('token_bal4',                 f'the {coin} balance and token balance'),
+		('token_bal_getbalance',       'the token balance (getbalance)'),
+
+		('txcreate_noamt',             'creating a transaction (full amount send)'),
+		('txsign_noamt',               'signing the transaction'),
+		('txsend_noamt',               'sending the transaction'),
+
+		('bal8',                       f'the {coin} balance'),
+		('token_bal5',                 'the token balance'),
+
+		('token_txcreate_noamt',       'creating a token transaction (full amount send)'),
+		('token_txsign_noamt',         'signing the transaction'),
+		('token_txsend_noamt',         'sending the transaction'),
+
+		('bal9',                       f'the {coin} balance'),
+		('token_bal6',                 'the token balance'),
+
+		('listaddresses1',             'listaddresses'),
+		('listaddresses2',             'listaddresses minconf=999999999 (ignored)'),
+		('listaddresses3',             'listaddresses sort=age (ignored)'),
+		('listaddresses4',             'listaddresses showempty=1 sort=age (ignored)'),
+
+		('token_listaddresses1',       'listaddresses --token=mm1'),
+		('token_listaddresses2',       'listaddresses --token=mm1 showempty=1'),
+	),
+	'twexport': (
+		'exporting and importing tracking wallet to JSON',
+		('twexport_noamt',       'exporting the tracking wallet (include_amts=0)'),
+		('twmove',               'moving the tracking wallet'),
+		('twimport',             'importing the tracking wallet'),
+		('twview7',              'twview (cached_balances=1)'),
+		('twview8',              'twview'),
+		('twexport',             'exporting the tracking wallet'),
+		('tw_chktotal',          'checking total value in tracking wallet dump'),
+		('twmove',               'moving the tracking wallet'),
+		('twimport',             'importing the tracking wallet'),
+		('twcompare',            'comparing imported tracking wallet with original'),
+		('edit_json_twdump',     'editing the tracking wallet JSON dump'),
+		('twmove',               'moving the tracking wallet'),
+		('twimport_nochksum',    'importing the edited tracking wallet JSON dump (ignore_checksum=1)'),
+
+		('token_listaddresses3', 'listaddresses --token=mm1 showempty=1'),
+		('token_listaddresses4', 'listaddresses --token=mm2 showempty=1'),
+		('twview9',              'twview (check balance)'),
+	),
+	'cached': (
+		'creating and sending transactions using cached balances',
 		('twview_cached_balances',          'twview (cached balances)'),
 		('token_twview_cached_balances',    'token twview (cached balances)'),
 		('txcreate_cached_balances',        'txcreate (cached balances)'),
@@ -279,54 +327,39 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 
 		('txdo_cached_balances',            'txdo (cached balances)'),
 		('txcreate_refresh_balances',       'refreshing balances'),
-		('bal10',                          f'the {coin} balance'),
+		('bal10',                           f'the {coin} balance'),
 
 		('token_txdo_cached_balances',      'token txdo (cached balances)'),
 		('token_txcreate_refresh_balances', 'refreshing token balances'),
 		('token_bal7',                      'the token balance'),
-
-		('twview1',                         'twview'),
-		('twview2',                         'twview wide=1'),
-		('twview3',                         'twview wide=1 sort=age (ignored)'),
-		('twview4',                         'twview wide=1 minconf=999999999 (ignored)'),
-		('twview5',                         'twview wide=1 minconf=0 (ignored)'),
-
-		('token_twview1',                   'twview --token=mm1'),
-		('token_twview2',                   'twview --token=mm1 wide=1'),
-		('token_twview3',                   'twview --token=mm1 wide=1 sort=age (ignored)'),
-
-		('edit_label1',        f'adding label to addr #{del_addrs[0]} in {coin} tracking wallet (zh)'),
-		('edit_label2',        f'adding label to addr #{del_addrs[1]} in {coin} tracking wallet (lat+cyr+gr)'),
-		('edit_label3',        f'removing label from addr #{del_addrs[0]} in {coin} tracking wallet'),
-
-		('token_edit_label1',  f'adding label to addr #{del_addrs[0]} in {coin} token tracking wallet'),
-
+	),
+	'view': (
+		'viewing addresses and unspent outputs',
+		('twview1',       'twview'),
+		('twview2',       'twview wide=1'),
+		('twview3',       'twview wide=1 sort=age (ignored)'),
+		('twview4',       'twview wide=1 minconf=999999999 (ignored)'),
+		('twview5',       'twview wide=1 minconf=0 (ignored)'),
+		('token_twview1', 'twview --token=mm1'),
+		('token_twview2', 'twview --token=mm1 wide=1'),
+		('token_twview3', 'twview --token=mm1 wide=1 sort=age (ignored)'),
+	),
+	'label': (
+		'creating, editing and removing labels',
+		('edit_label1',       f'adding label to addr #{del_addrs[0]} in {coin} tracking wallet (zh)'),
+		('edit_label2',       f'adding label to addr #{del_addrs[1]} in {coin} tracking wallet (lat+cyr+gr)'),
+		('edit_label3',       f'removing label from addr #{del_addrs[0]} in {coin} tracking wallet'),
+		('token_edit_label1', f'adding label to addr #{del_addrs[0]} in {coin} token tracking wallet'),
+	),
+	'remove': (
+		'removing addresses from tracking wallet',
 		('remove_addr1',       f'removing addr #{del_addrs[0]} from {coin} tracking wallet'),
 		('twview6',            'twview (balance reduced after address removal)'),
 		('remove_addr2',       f'removing addr #{del_addrs[1]} from {coin} tracking wallet'),
 		('token_remove_addr1', f'removing addr #{del_addrs[0]} from {coin} token tracking wallet'),
 		('token_remove_addr2', f'removing addr #{del_addrs[1]} from {coin} token tracking wallet'),
-
-		('twexport_noamt',     'exporting the tracking wallet (include_amts=0)'),
-		('twmove',             'moving the tracking wallet'),
-		('twimport',           'importing the tracking wallet'),
-		('twview7',            'twview (cached_balances=1)'),
-		('twview8',            'twview'),
-		('twexport',           'exporting the tracking wallet'),
-		('tw_chktotal',        'checking total value in tracking wallet dump'),
-		('twmove',             'moving the tracking wallet'),
-		('twimport',           'importing the tracking wallet'),
-		('twcompare',          'comparing imported tracking wallet with original'),
-		('edit_json_twdump',   'editing the tracking wallet JSON dump'),
-		('twmove',             'moving the tracking wallet'),
-		('twimport_nochksum',  'importing the edited tracking wallet JSON dump (ignore_checksum=1)'),
-
-		('token_listaddresses3','listaddresses --token=mm1 showempty=1'),
-		('token_listaddresses4','listaddresses --token=mm2 showempty=1'),
-		('twview9',            'twview (check balance)'),
-
-		('stop',               'stopping daemon'),
-	)
+	),
+	}
 
 	def __init__(self,trunner,cfgs,spawn):
 		TestSuiteBase.__init__(self,trunner,cfgs,spawn)
@@ -1223,7 +1256,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 	def twview5(self):
 		return self.twview(tool_args=['wide=1','minconf=0'])
 	def twview6(self):
-		return self.twview(expect_str=vbal6)
+		return self.twview(expect_str=vbal7)
 	def twview7(self):
 		return self.twview(args=['--cached-balances'])
 	def twview8(self):

+ 165 - 124
test/test_py_d/ts_regtest.py

@@ -151,90 +151,140 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 	color = True
 	deterministic = False
 	test_rbf = False
-	cmd_group = (
-		('setup',                    'regtest (Bob and Alice) mode setup'),
-		('daemon_version',           'mmgen-tool daemon_version'),
-		('halving_calculator_bob',   'halving calculator (Bob)'),
-		('walletgen_bob',            'wallet generation (Bob)'),
-		('walletgen_alice',          'wallet generation (Alice)'),
-		('addrgen_bob',              'address generation (Bob)'),
-		('addrgen_alice',            'address generation (Alice)'),
-		('addrimport_bob',           "importing Bob's addresses"),
-		('addrimport_alice',         "importing Alice's addresses"),
-		('bob_import_miner_addr',    "importing miner’s coinbase addr into Bob’s wallet"),
-		('fund_bob_deterministic',   "funding Bob’s first MMGen address (deterministic method)"),
-		('fund_alice_deterministic', "funding Alice’s first MMGen address (deterministic method)"),
-		('bob_recreate_tracking_wallet','creation of new tracking wallet (Bob)'),
-		('addrimport_bob2',          "reimporting Bob's addresses"),
-		('fund_bob',                 "funding Bob's wallet"),
-		('fund_alice',               "funding Alice's wallet"),
-		('generate',                 'mining a block'),
-		('bob_bal1',                 "Bob's balance"),
-		('bob_add_label',            "adding an 80-screen-width label (lat+cyr+gr)"),
-		('bob_twview1',              "viewing Bob's tracking wallet"),
-		('bob_split1',               "splitting Bob's funds"),
-		('generate',                 'mining a block'),
-		('bob_bal2',                 "Bob's balance"),
-		('bob_rbf_1output_create',   'creating RBF tx with one output'),
-		('bob_rbf_1output_bump',     'bumping RBF tx with one output'),
-		('bob_bal2a',                "Bob's balance (age_fmt=confs)"),
-		('bob_bal2b',                "Bob's balance (showempty=1)"),
-		('bob_bal2c',                "Bob's balance (showempty=1 minconf=2 age_fmt=days)"),
-		('bob_bal2d',                "Bob's balance (minconf=2)"),
-		('bob_bal2e',                "Bob's balance (showempty=1 sort=age)"),
-		('bob_bal2f',                "Bob's balance (showempty=1 sort=age,reverse)"),
-		('bob_send_maybe_rbf',       'sending funds to Alice (RBF, if supported)'),
-		('get_mempool1',             'mempool (before RBF bump)'),
-		('bob_rbf_status1',          'getting status of transaction'),
-		('bob_rbf_bump',             'bumping RBF transaction'),
-		('get_mempool2',             'mempool (after RBF bump)'),
-		('bob_rbf_status2',          'getting status of transaction after replacement'),
-		('bob_rbf_status3',          'getting status of replacement transaction (mempool)'),
-		('generate',                 'mining a block'),
-		('bob_rbf_status4',          'getting status of transaction after confirmed (1) replacement'),
-		('bob_rbf_status5',          'getting status of replacement transaction (confirmed)'),
-		('generate',                 'mining a block'),
-		('bob_rbf_status6',          'getting status of transaction after confirmed (2) replacement'),
-		('bob_bal3',                 "Bob's balance"),
-		('bob_pre_import',           'sending to non-imported address'),
-		('generate',                 'mining a block'),
-		('bob_import_addr',          'importing non-MMGen address'),
-		('bob_bal4',                 "Bob's balance (after import)"),
-		('bob_import_list',          'importing flat address list'),
-		('bob_import_list_rescan',   'importing flat address list with --rescan'),
-		('bob_import_list_rescan_aio','importing flat address list with --rescan (aiohttp backend)'),
-		('bob_resolve_addr',         'resolving an address in the tracking wallet'),
-		('bob_rescan_addr',          'rescanning an address'),
-		('bob_rescan_blockchain_all','rescanning the blockchain (full rescan)'),
-		('bob_rescan_blockchain_gb', 'rescanning the blockchain (Genesis block)'),
-		('bob_rescan_blockchain_one','rescanning the blockchain (single block)'),
-		('bob_rescan_blockchain_ss', 'rescanning the blockchain (range of blocks)'),
-
-		('bob_twexport',             'exporting a tracking wallet to JSON'),
-		('carol_twimport',           'importing a tracking wallet JSON dump'),
-		('carol_delete_wallet',      'unloading and deleting Carol’s tracking wallet'),
-		('bob_twexport_noamt',       'exporting a tracking wallet to JSON (include_amts=0)'),
-		('carol_twimport_nochksum',  'importing a tracking wallet JSON dump (ignore_checksum=1)'),
-		('carol_delete_wallet',      'unloading and deleting Carol’s tracking wallet'),
-		('carol_twimport_batch',     'importing a tracking wallet JSON dump (batch=1)'),
-		('bob_twexport_pretty',      'exporting a tracking wallet to JSON (pretty=1)'),
-		('bob_edit_json_twdump',     'editing a tracking wallet JSON dump'),
-		('carol_delete_wallet',      'unloading and deleting Carol’s tracking wallet'),
-		('carol_twimport_pretty',    'importing an edited tracking wallet JSON dump (ignore_checksum=1)'),
-		('carol_listaddresses',      'viewing Carol’s tracking wallet'),
-
-		('bob_split2',               "splitting Bob's funds"),
-		('bob_0conf0_getbalance',    "Bob's balance (unconfirmed, minconf=0)"),
-		('bob_0conf1_getbalance',    "Bob's balance (unconfirmed, minconf=1)"),
-		('generate',                 'mining a block'),
-		('bob_1conf1_getbalance',    "Bob's balance (confirmed, minconf=1)"),
-		('bob_1conf2_getbalance',    "Bob's balance (confirmed, minconf=2)"),
-		('bob_bal5',                 "Bob's balance"),
-		('bob_send_non_mmgen',       'sending funds to Alice (from non-MMGen addrs)'),
-		('generate',                 'mining a block'),
-		('alice_send_estimatefee',   'tx creation with no fee on command line'),
-		('generate',                 'mining a block'),
-		('bob_bal6',                 "Bob's balance"),
+	cmd_group_in = (
+		('setup',               'regtest (Bob and Alice) mode setup'),
+		('subgroup.misc',       []),
+		('subgroup.init_bob',   []),
+		('subgroup.init_alice', []),
+		('subgroup.fund_users', ['init_bob','init_alice']),
+		('subgroup.msg',        ['init_bob']),
+		('subgroup.twexport',   ['fund_users']),
+		('subgroup.rescan',     ['fund_users']),
+		('subgroup.main',       ['fund_users']),
+		('subgroup.txhist',     ['main']),
+		('subgroup.label',      ['main']),
+		('subgroup.view',       ['label']),
+		('stop',                'stopping regtest daemon'),
+	)
+	cmd_subgroups = {
+	'misc': (
+		'miscellaneous commands',
+		('daemon_version',         'mmgen-tool daemon_version'),
+		('halving_calculator_bob', 'halving calculator (Bob)'),
+	),
+	'init_bob': (
+		'creating Bob’s MMGen wallet and tracking wallet',
+		('walletgen_bob',  'wallet generation (Bob)'),
+		('addrgen_bob',    'address generation (Bob)'),
+		('addrimport_bob', "importing Bob's addresses"),
+	),
+	'init_alice': (
+		'creating Alice’s MMGen wallet and tracking wallet',
+		('walletgen_alice',  'wallet generation (Alice)'),
+		('addrgen_alice',    'address generation (Alice)'),
+		('addrimport_alice', "importing Alice's addresses"),
+	),
+	'fund_users': (
+		'funding Bob and Alice’s wallets',
+		('bob_import_miner_addr',        "importing miner’s coinbase addr into Bob’s wallet"),
+		('fund_bob_deterministic',       "funding Bob’s first MMGen address (deterministic method)"),
+		('fund_alice_deterministic',     "funding Alice’s first MMGen address (deterministic method)"),
+		('bob_recreate_tracking_wallet', 'creation of new tracking wallet (Bob)'),
+		('addrimport_bob2',              "reimporting Bob's addresses"),
+		('fund_bob',                     "funding Bob's wallet"),
+		('fund_alice',                   "funding Alice's wallet"),
+		('generate',                     'mining a block'),
+		('bob_bal1',                     "Bob's balance"),
+	),
+	'msg': (
+		'message signing',
+		('bob_msgcreate',               'creating a message file for signing'),
+		('bob_msgsign',                 'signing the message file (default wallet)'),
+		('bob_walletconv_words',        'creating an MMGen mnemonic wallet'),
+		('bob_subwalletgen_bip39',      'creating a BIP39 mnemonic subwallet'),
+		('bob_msgsign_userwallet',      'signing the message file (user-specified wallet)'),
+		('bob_msgsign_userwallets',     'signing the message file (user-specified wallets)'),
+		('bob_msgverify',               'verifying the message file (all addresses)'),
+		('bob_msgverify_raw',           'verifying the raw message file (all addresses)'),
+		('bob_msgverify_single',        'verifying the message file (single address)'),
+		('bob_msgexport_single',        'exporting the message file (single address)'),
+		('bob_msgexport',               'exporting the message file (all addresses)'),
+		('bob_msgverify_export',        'verifying the exported JSON data (all addresses)'),
+		('bob_msgverify_export_single', 'verifying the exported JSON data (single address)'),
+	),
+	'twexport': (
+		'exporting and importing tracking wallet to JSON',
+		('bob_twexport',            'exporting a tracking wallet to JSON'),
+		('carol_twimport',          'importing a tracking wallet JSON dump'),
+		('carol_delete_wallet',     'unloading and deleting Carol’s tracking wallet'),
+		('bob_twexport_noamt',      'exporting a tracking wallet to JSON (include_amts=0)'),
+		('carol_twimport_nochksum', 'importing a tracking wallet JSON dump (ignore_checksum=1)'),
+		('carol_delete_wallet',     'unloading and deleting Carol’s tracking wallet'),
+		('carol_twimport_batch',    'importing a tracking wallet JSON dump (batch=1)'),
+		('bob_twexport_pretty',     'exporting a tracking wallet to JSON (pretty=1)'),
+		('bob_edit_json_twdump',    'editing a tracking wallet JSON dump'),
+		('carol_delete_wallet',     'unloading and deleting Carol’s tracking wallet'),
+		('carol_twimport_pretty',   'importing an edited tracking wallet JSON dump (ignore_checksum=1)'),
+		('carol_listaddresses',     'viewing Carol’s tracking wallet'),
+		('carol_delete_wallet',     'unloading and deleting Carol’s tracking wallet'),
+	),
+	'rescan': (
+		'rescanning address and blockchain',
+		('bob_resolve_addr',          'resolving an address in the tracking wallet'),
+		('bob_rescan_addr',           'rescanning an address'),
+		('bob_rescan_blockchain_all', 'rescanning the blockchain (full rescan)'),
+		('bob_rescan_blockchain_gb',  'rescanning the blockchain (Genesis block)'),
+		('bob_rescan_blockchain_one', 'rescanning the blockchain (single block)'),
+		('bob_rescan_blockchain_ss',  'rescanning the blockchain (range of blocks)'),
+	),
+	'main': (
+		'creating, signing, sending and bumping transactions',
+		('bob_add_label',              "adding an 80-screen-width label (lat+cyr+gr)"),
+		('bob_twview1',                "viewing Bob's tracking wallet"),
+		('bob_split1',                 "splitting Bob's funds"),
+		('generate',                   'mining a block'),
+		('bob_bal2',                   "Bob's balance"),
+		('bob_rbf_1output_create',     'creating RBF tx with one output'),
+		('bob_rbf_1output_bump',       'bumping RBF tx with one output'),
+		('bob_bal2a',                  "Bob's balance (age_fmt=confs)"),
+		('bob_bal2b',                  "Bob's balance (showempty=1)"),
+		('bob_bal2c',                  "Bob's balance (showempty=1 minconf=2 age_fmt=days)"),
+		('bob_bal2d',                  "Bob's balance (minconf=2)"),
+		('bob_bal2e',                  "Bob's balance (showempty=1 sort=age)"),
+		('bob_bal2f',                  "Bob's balance (showempty=1 sort=age,reverse)"),
+		('bob_send_maybe_rbf',         'sending funds to Alice (RBF, if supported)'),
+		('get_mempool1',               'mempool (before RBF bump)'),
+		('bob_rbf_status1',            'getting status of transaction'),
+		('bob_rbf_bump',               'bumping RBF transaction'),
+		('get_mempool2',               'mempool (after RBF bump)'),
+		('bob_rbf_status2',            'getting status of transaction after replacement'),
+		('bob_rbf_status3',            'getting status of replacement transaction (mempool)'),
+		('generate',                   'mining a block'),
+		('bob_rbf_status4',            'getting status of transaction after confirmed (1) replacement'),
+		('bob_rbf_status5',            'getting status of replacement transaction (confirmed)'),
+		('generate',                   'mining a block'),
+		('bob_rbf_status6',            'getting status of transaction after confirmed (2) replacement'),
+		('bob_bal3',                   "Bob's balance"),
+		('bob_pre_import',             'sending to non-imported address'),
+		('generate',                   'mining a block'),
+		('bob_import_addr',            'importing non-MMGen address'),
+		('bob_bal4',                   "Bob's balance (after import)"),
+		('bob_import_list',            'importing flat address list'),
+		('bob_import_list_rescan',     'importing flat address list with --rescan'),
+		('bob_import_list_rescan_aio', 'importing flat address list with --rescan (aiohttp backend)'),
+
+		('bob_split2',                 "splitting Bob's funds"),
+		('bob_0conf0_getbalance',      "Bob's balance (unconfirmed, minconf=0)"),
+		('bob_0conf1_getbalance',      "Bob's balance (unconfirmed, minconf=1)"),
+		('generate',                   'mining a block'),
+		('bob_1conf1_getbalance',      "Bob's balance (confirmed, minconf=1)"),
+		('bob_1conf2_getbalance',      "Bob's balance (confirmed, minconf=2)"),
+		('bob_bal5',                   "Bob's balance"),
+		('bob_send_non_mmgen',         'sending funds to Alice (from non-MMGen addrs)'),
+		('generate',                   'mining a block'),
+		('alice_send_estimatefee',     'tx creation with no fee on command line'),
+		('generate',                   'mining a block'),
+		('bob_bal6',                   "Bob's balance"),
 
 		('bob_subwallet_addrgen1',     "generating Bob's addrs from subwallet 29L"),
 		('bob_subwallet_addrgen2',     "generating Bob's addrs from subwallet 127S"),
@@ -250,19 +300,22 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		('generate',                   'mining a block'),
 		('bob_twview4',                "viewing Bob's tracking wallet"),
 
-		('bob_alice_bal',            "Bob and Alice's balances"),
-
-		('bob_nochg_burn',           'zero-change transaction to burn address'),
-		('generate',                 'mining a block'),
-
-		('bob_txhist1',              "viewing Bob's transaction history (sort=age)"),
-		('bob_txhist2',              "viewing Bob's transaction history (sort=blockheight reverse=1)"),
-		('bob_txhist3',              "viewing Bob's transaction history (sort=blockheight sinceblock=-7)"),
-		('bob_txhist4',              "viewing Bob's transaction history (sinceblock=399 detail=1)"),
-		('bob_txhist_interactive',   "viewing Bob's transaction history (age_fmt=date_time interactive=true)"),
+		('bob_alice_bal',              "Bob and Alice's balances"),
 
+		('bob_nochg_burn',             'zero-change transaction to burn address'),
+		('generate',                   'mining a block'),
+	),
+	'txhist': (
+		'viewing transaction history',
+		('bob_txhist1',            "viewing Bob's transaction history (sort=age)"),
+		('bob_txhist2',            "viewing Bob's transaction history (sort=blockheight reverse=1)"),
+		('bob_txhist3',            "viewing Bob's transaction history (sort=blockheight sinceblock=-7)"),
+		('bob_txhist4',            "viewing Bob's transaction history (sinceblock=399 detail=1)"),
+		('bob_txhist_interactive', "viewing Bob's transaction history (age_fmt=date_time interactive=true)"),
+	),
+	'label': (
+		'adding, removing and editing labels',
 		('alice_bal2',               "Alice's balance"),
-
 		('alice_add_label1',         'adding a label'),
 		('alice_chk_label1',         'the label'),
 		('alice_add_label2',         'adding a label'),
@@ -278,32 +331,20 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		('alice_add_label_badaddr2', 'adding a label with invalid address for this chain'),
 		('alice_add_label_badaddr3', 'adding a label with wrong MMGen address'),
 		('alice_add_label_badaddr4', 'adding a label with wrong coin address'),
-		('alice_listaddresses1',                'listaddresses'),
-		('alice_listaddresses_days',            'listaddresses (age_fmt=days)'),
-		('alice_listaddresses_date',            'listaddresses (age_fmt=date)'),
-		('alice_listaddresses_date_time',       'listaddresses (age_fmt=date_time)'),
-		('alice_twview1',                'twview'),
-		('alice_twview_days',            'twview (age_fmt=days)'),
-		('alice_twview_date',            'twview (age_fmt=date)'),
-		('alice_twview_date_time',       'twview (age_fmt=date_time)'),
-		('alice_txcreate_info',          'txcreate -i'),
-
-		('bob_msgcreate',          'creating a message file for signing'),
-		('bob_msgsign',            'signing the message file (default wallet)'),
-		('bob_walletconv_words',   'creating an MMGen mnemonic wallet'),
-		('bob_subwalletgen_bip39', 'creating a BIP39 mnemonic subwallet'),
-		('bob_msgsign_userwallet',  'signing the message file (user-specified wallet)'),
-		('bob_msgsign_userwallets', 'signing the message file (user-specified wallets)'),
-		('bob_msgverify',           'verifying the message file (all addresses)'),
-		('bob_msgverify_raw',       'verifying the raw message file (all addresses)'),
-		('bob_msgverify_single',    'verifying the message file (single address)'),
-		('bob_msgexport_single',    'exporting the message file (single address)'),
-		('bob_msgexport',           'exporting the message file (all addresses)'),
-		('bob_msgverify_export',    'verifying the exported JSON data (all addresses)'),
-		('bob_msgverify_export_single','verifying the exported JSON data (single address)'),
-
-		('stop',                 'stopping regtest daemon'),
-	)
+	),
+	'view': (
+		'viewing addresses and unspent outputs',
+		('alice_listaddresses1',          'listaddresses'),
+		('alice_listaddresses_days',      'listaddresses (age_fmt=days)'),
+		('alice_listaddresses_date',      'listaddresses (age_fmt=date)'),
+		('alice_listaddresses_date_time', 'listaddresses (age_fmt=date_time)'),
+		('alice_twview1',                 'twview'),
+		('alice_twview_days',             'twview (age_fmt=days)'),
+		('alice_twview_date',             'twview (age_fmt=date)'),
+		('alice_twview_date_time',        'twview (age_fmt=date_time)'),
+		('alice_txcreate_info',           'txcreate -i'),
+	),
+	}
 
 	def __init__(self,trunner,cfgs,spawn):
 		TestSuiteBase.__init__(self,trunner,cfgs,spawn)
@@ -974,7 +1015,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		return t
 
 	def bob_rescan_blockchain_all(self):
-		return self.bob_rescan_blockchain([],'400-400')
+		return self.bob_rescan_blockchain([],'300-396')
 
 	def bob_rescan_blockchain_gb(self):
 		return self.bob_rescan_blockchain(['start_block=0','stop_block=0'],'0-0')
@@ -1013,10 +1054,10 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		if expect_str:
 			t.expect(expect_str)
 		elif 'batch=true' in add_parms:
-			t.expect('{} addresses imported'.format(15 if self.proto.coin == 'BCH' else 25))
+			t.expect('{} addresses imported'.format(10 if self.proto.coin == 'BCH' else 20))
 		else:
 			t.expect('import completed OK')
-		t.expect('Found 3 unspent outputs')
+		t.expect('Found 1 unspent output')
 		return t
 
 	def carol_twimport_nochksum(self):