230 lines
7.2 KiB
Python
Executable file
230 lines
7.2 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
#
|
|
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
|
# Copyright (C)2013-2026 The MMGen Project <mmgen@tuta.io>
|
|
# Licensed under the GNU General Public License, Version 3:
|
|
# https://www.gnu.org/licenses
|
|
# Public project repositories:
|
|
# https://github.com/mmgen/mmgen-wallet
|
|
# https://gitlab.com/mmgen/mmgen-wallet
|
|
|
|
"""
|
|
cmdtest_d.include.group_mgr: Command group manager for the MMGen Wallet cmdtest suite
|
|
"""
|
|
|
|
import sys, os, time
|
|
from collections import namedtuple
|
|
|
|
from mmgen.color import yellow, green, cyan
|
|
from mmgen.util import Msg, die
|
|
|
|
from .cfg import cfgs, cmd_groups_dfl, cmd_groups_extra
|
|
|
|
class CmdGroupMgr:
|
|
|
|
dpy_data = None
|
|
|
|
cmd_groups = cmd_groups_dfl.copy()
|
|
cmd_groups.update(cmd_groups_extra)
|
|
|
|
cfg_attrs = (
|
|
'seed_len',
|
|
'seed_id',
|
|
'wpasswd',
|
|
'kapasswd',
|
|
'segwit',
|
|
'hash_preset',
|
|
'bw_filename',
|
|
'bw_params',
|
|
'ref_bw_seed_id',
|
|
'addr_idx_list',
|
|
'pass_idx_list')
|
|
|
|
def __init__(self, cfg):
|
|
self.cfg = cfg
|
|
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)
|
|
|
|
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, added_subgroups=[]):
|
|
|
|
if add_deps:
|
|
for dep in cmd_group_in['subgroup.'+key]:
|
|
yield from add_entries(dep)
|
|
|
|
assert isinstance(cls.cmd_subgroups[key][0], str), f'header for subgroup {key!r} missing!'
|
|
|
|
if not key in added_subgroups:
|
|
yield from cls.cmd_subgroups[key][1:]
|
|
added_subgroups.append(key)
|
|
|
|
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):
|
|
yield from add_entries(
|
|
sg_key,
|
|
add_deps = sg_name and not self.cfg.skipping_deps,
|
|
added_subgroups = [sg_name] if self.cfg.deps_only else [])
|
|
if self.cfg.deps_only and sg_key == sg_name:
|
|
return
|
|
else:
|
|
yield (name, data)
|
|
|
|
return tuple(gen())
|
|
|
|
def load_mod(self, gname, modname=None):
|
|
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), grp.clsname)
|
|
|
|
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
|
|
without touching 'cmd_list'
|
|
"""
|
|
|
|
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.
|
|
"""
|
|
return [k for k, v in cfgs[str(tmpdir_idx)]['dep_generators'].items()
|
|
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)
|
|
|
|
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))
|
|
else:
|
|
self.cmd_list = tuple(e[0] for e in cdata)
|
|
self.dpy_data = dict(cdata)
|
|
|
|
cls.full_data = full_data or is3seed
|
|
|
|
if not cls.full_data:
|
|
cls.tmpdir_num = cls.tmpdir_nums[0]
|
|
for k, v in cfgs[str(cls.tmpdir_num)].items():
|
|
setattr(cls, k, v)
|
|
|
|
return cls
|
|
|
|
def gm_init_group(self, cfg, trunner, gname, sg_name, spawn_prog):
|
|
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].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):
|
|
|
|
if self.cfg.list_current_cmd_groups:
|
|
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 ''
|
|
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(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)
|
|
for k, v in subgroups.items():
|
|
yield ' + {} ยท {}'.format(cyan(k.ljust(max_w+1)), v[0])
|
|
|
|
from mmgen.ui import do_pager
|
|
do_pager('\n'.join(gen_output()))
|
|
Msg('\n' + ' '.join(ginfo))
|
|
|
|
def find_cmd_in_groups(self, cmd, group=None):
|
|
"""
|
|
Search for a test command in specified group or all configured command groups
|
|
and return it as a string. Loads modules but alters no global variables.
|
|
"""
|
|
if group:
|
|
if not group in self.cmd_groups:
|
|
die(1, f'{group!r}: unrecognized group')
|
|
groups = [self.cmd_groups[group]]
|
|
else:
|
|
groups = self.cmd_groups
|
|
|
|
for gname in groups:
|
|
cls = self.get_cls_by_gname(gname)
|
|
|
|
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
|
|
|
|
if cmd in dir(cls(self.cfg, None, None, None)): # then a throwaway instance
|
|
return gname # cmd might exist in more than one group - we'll go with the first
|
|
|
|
return None
|