123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- #!/usr/bin/env python3
- #
- # MMGen Wallet, a terminal-based cryptocurrency wallet
- # Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- """
- test/objtest.py: Test MMGen data objects
- """
- import os, re
- try:
- from include import test_init
- except ImportError:
- from test.include import test_init
- # for objtest, violate MMGen Project best practices and allow use of the dev tools
- # in production code:
- from mmgen.devtools import pmsg
- if not os.getenv('MMGEN_DEVTOOLS'):
- from mmgen.devinit import init_dev
- init_dev()
- from mmgen.cfg import Config
- from mmgen.util import msg, msg_r, gmsg, capfirst, die
- from mmgen.color import red, yellow, blue, green, orange, purple, gray, nocolor
- from mmgen.obj import get_obj
- opts_data = {
- 'sets': [('super_silent', True, 'silent', True)],
- 'text': {
- 'desc': 'Test MMGen data objects',
- 'usage':'[options] [object]',
- 'options': """
- -h, --help Print this help message
- --, --longhelp Print help message for long (global) options
- -g, --getobj Instantiate objects with get_obj() wrapper
- -q, --quiet Produce quieter output
- -s, --silent Silence output of tested objects
- -S, --super-silent Silence all output except for errors
- -v, --verbose Produce more verbose output
- """
- }
- }
- cfg = Config(opts_data=opts_data)
- if cfg.verbose:
- from mmgen.objmethods import MMGenObject
- from test.include.common import set_globals
- set_globals(cfg)
- def run_test(mod, test, arg, input_data, arg1, exc_name):
- arg_copy = arg
- kwargs = {}
- ret_chk = arg
- ret_idx = None
- if input_data == 'good' and isinstance(arg, tuple):
- arg, ret_chk = arg
- if isinstance(arg, dict): # pass one arg + kwargs to constructor
- arg_copy = arg.copy()
- if 'arg' in arg:
- args = [arg['arg']]
- ret_chk = args[0]
- del arg['arg']
- else:
- args = []
- ret_chk = list(arg.values())[0] # assume only one key present
- if 'ret' in arg:
- ret_chk = arg['ret']
- del arg['ret']
- del arg_copy['ret']
- if 'exc_name' in arg:
- exc_name = arg['exc_name']
- del arg['exc_name']
- del arg_copy['exc_name']
- if 'ret_idx' in arg:
- ret_idx = arg['ret_idx']
- del arg['ret_idx']
- del arg_copy['ret_idx']
- kwargs.update(arg)
- elif isinstance(arg, tuple):
- args = arg
- else:
- args = [arg]
- if cfg.getobj:
- if args:
- assert len(args) == 1, 'objtest_chk1: only one positional arg is allowed'
- kwargs.update( { arg1: args[0] } )
- if cfg.silent:
- kwargs.update( { 'silent': True } )
- try:
- if not cfg.super_silent:
- arg_disp = repr(arg_copy[0] if isinstance(arg_copy, tuple) else arg_copy)
- if cfg.test_suite_deterministic and isinstance(arg_copy, dict):
- arg_disp = re.sub(r'object at 0x[0-9a-f]+', 'object at [SCRUBBED]', arg_disp)
- msg_r((green if input_data=='good' else orange)(f'{arg_disp+":":<22}'))
- cls = getattr(mod, test)
- if cfg.getobj:
- ret = get_obj(getattr(mod, test), **kwargs)
- else:
- ret = cls(*args, **kwargs)
- bad_ret = [] if issubclass(cls, list) else None
- if isinstance(ret_chk, str):
- ret_chk = ret_chk.encode()
- if isinstance(ret, str):
- ret = ret.encode()
- if cfg.getobj:
- if input_data == 'bad':
- assert ret is False, 'non-False return on bad input data'
- else:
- if (cfg.silent and input_data=='bad' and ret!=bad_ret) or (not cfg.silent and input_data=='bad'):
- raise UserWarning(f"Non-'None' return value {ret!r} with bad input data")
- if cfg.silent and input_data=='good' and ret==bad_ret:
- raise UserWarning("'None' returned with good input data")
- if input_data=='good':
- if ret_idx:
- ret_chk = arg[list(arg.keys())[ret_idx]].encode()
- if ret != ret_chk and repr(ret) != repr(ret_chk):
- raise UserWarning(f"Return value ({ret!r}) doesn't match expected value ({ret_chk!r})")
- if cfg.super_silent:
- return
- if cfg.getobj and (not cfg.silent and input_data == 'bad'):
- pass
- else:
- try:
- ret_disp = ret.decode()
- except:
- ret_disp = ret
- msg(f'==> {ret_disp!r}')
- if cfg.verbose and issubclass(cls, MMGenObject):
- ret.pmsg() if hasattr(ret, 'pmsg') else pmsg(ret)
- except UserWarning as e:
- msg(f'==> {ret!r}')
- die(2, red(str(e)))
- except Exception as e:
- if input_data == 'good':
- raise ValueError(f'Error on good input data: {e}') from e
- if not type(e).__name__ == exc_name:
- msg(f'Incorrect exception: expected {exc_name} but got {type(e).__name__}')
- raise
- if cfg.super_silent:
- pass
- elif cfg.silent:
- msg(f'==> {exc_name}')
- else:
- msg( yellow(f' {exc_name}:') + str(e) )
- except SystemExit as e:
- if input_data == 'good':
- raise ValueError('Error on good input data') from e
- if cfg.verbose:
- msg(f'exitval: {e.code}')
- def do_loop():
- import importlib
- modname = f'test.objtest_d.ot_{proto.coin.lower()}_{proto.network}'
- mod = importlib.import_module(modname)
- test_data = getattr(mod, 'tests')
- gmsg(f'Running data object tests for {proto.coin} {proto.network}')
- clr = None
- utests = cfg._args
- for test in test_data:
- arg1 = test_data[test].get('arg1')
- if utests and test not in utests:
- continue
- nl = ('\n', '')[bool(cfg.super_silent) or clr is None]
- clr = (blue, nocolor)[bool(cfg.super_silent)]
- if cfg.getobj and arg1 is None:
- msg(gray(f'{nl}Skipping {test}'))
- continue
- msg(clr(f'{nl}Testing {test}'))
- for k in ('bad', 'good'):
- if not cfg.super_silent:
- msg(purple(capfirst(k)+' input:'))
- for arg in test_data[test][k]:
- run_test(
- mod,
- test,
- arg,
- input_data = k,
- arg1 = arg1,
- exc_name = test_data[test].get('exc_name') or ('ObjectInitError', 'None')[k=='good'])
- proto = cfg._proto
- if __name__ == '__main__':
- do_loop()
|