From 3f1c0b2a9302eace98607257685ffbdafd71b4a3 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 29 Sep 2024 11:59:54 +0000 Subject: [PATCH] unit_tests.py: cleanups, add `desc` param to subtest methods --- test/unit_tests.py | 13 ++- test/unit_tests_d/ut_lockable.py | 134 ++++++++++++++++--------------- test/unit_tests_d/ut_misc.py | 6 +- test/unit_tests_d/ut_mn_entry.py | 23 ++---- test/unit_tests_d/ut_msg.py | 18 ++--- test/unit_tests_d/ut_obj.py | 14 ++-- test/unit_tests_d/ut_xmrseed.py | 114 ++++++++++++-------------- 7 files changed, 159 insertions(+), 163 deletions(-) diff --git a/test/unit_tests.py b/test/unit_tests.py index 92a9bce0..7f2211c6 100755 --- a/test/unit_tests.py +++ b/test/unit_tests.py @@ -158,9 +158,20 @@ def run_test(test,subtest=None): getattr(t,'_pre_subtest')(test,subtest,UnitTestHelpers(subtest)) try: - ret = getattr(t,subtest.replace('-','_'))(test,UnitTestHelpers(subtest)) + func = getattr(t,subtest.replace('-','_')) + c = func.__code__ + do_desc = c.co_varnames[c.co_argcount-1] == 'desc' + if do_desc: + if cfg.verbose: + msg(f'Testing {func.__defaults__[0]}') + elif not cfg.quiet: + msg_r(f'Testing {func.__defaults__[0]}...') + + ret = func(test, UnitTestHelpers(subtest)) if type(ret).__name__ == 'coroutine': ret = asyncio.run(ret) + if do_desc and not cfg.quiet: + msg('OK\n' if cfg.verbose else 'OK') except: if getattr(t,'silence_output',False): t._end_silence() diff --git a/test/unit_tests_d/ut_lockable.py b/test/unit_tests_d/ut_lockable.py index 859dbe2a..11b7388b 100755 --- a/test/unit_tests_d/ut_lockable.py +++ b/test/unit_tests_d/ut_lockable.py @@ -4,16 +4,12 @@ test.unit_tests_d.ut_lockable: unit test for the MMGen suite's Lockable class """ -from ..include.common import qmsg,qmsg_r,vmsg +from decimal import Decimal +from mmgen.base_obj import AttrCtrl, Lockable -class unit_test: +class unit_tests: - def run_test(self,name,ut): - - from mmgen.base_obj import AttrCtrl,Lockable - from decimal import Decimal - - qmsg_r('Testing class AttrCtrl...') + def attrctl(self, name, ut, desc='AttrCtrl class'): class MyAttrCtrl(AttrCtrl): _autolock = False @@ -52,8 +48,21 @@ class unit_test: assert acdn.bar is None, f'{acdn.bar}' assert acdn.baz is None, f'{acdn.baz}' - qmsg('OK') - qmsg_r('Testing class Lockable...') + def bad1(): ac.x = 1 + def bad2(): acc.foo = 1 + def bad3(): aca._lock() + def bad4(): acdn.baz = None + + ut.process_bad_data(( + ('attr', 'AttributeError', 'has no attr', bad1), + ('attr type', 'AttributeError', 'type', bad2), + ('call to _lock()', 'AssertionError', 'only once', bad3), + ('attr', 'AttributeError', 'has no attr', bad4), + )) + + return True + + def base(self, name, ut, desc='Lockable class'): class MyLockable(Lockable): # class without attrs _autolock = False @@ -83,6 +92,32 @@ class unit_test: lc.epsilon = [0] + def bad1(): lc.foo = 'fooval3' + def bad2(): lc.baz = 'str' + def bad3(): lc.qux = 2 + def bad4(): lc.x = 1 + def bad5(): lc.alpha = 0 + def bad6(): lc.beta = False + def bad7(): lc.gamma = Decimal('0') + def bad8(): lc.delta = float(0) + def bad9(): lc.epsilon = [0] + + ut.process_bad_data(( + ('attr (can’t reset)', 'AttributeError', 'reset', bad1), + ('attr type (2)', 'AttributeError', 'type', bad2), + ('attr (can’t set)', 'AttributeError', 'read-only', bad3), + ('attr (2)', 'AttributeError', 'has no attr', bad4), + ('attr (can’t reset)', 'AttributeError', 'reset', bad5), + ('attr (can’t reset)', 'AttributeError', 'reset', bad6), + ('attr (can’t reset)', 'AttributeError', 'reset', bad7), + ('attr (can’t reset)', 'AttributeError', 'reset', bad8), + ('attr (can’t reset)', 'AttributeError', 'reset', bad9), + )) + + return True + + def classattr(self, name, ut, desc='Lockable class with class attrs'): + class MyLockableClsCheck(Lockable): # class with attrs _autolock = False _use_class_attr = True @@ -102,8 +137,21 @@ class unit_test: lcc.baz = 3.2 lcc.baz = 3.1 # baz is in both lists - qmsg('OK') - qmsg_r('Testing class Lockable with autolock...') + def bad1(): lcc.bar = 'str' + def bad2(): lcc.qux = 'quxval2' + def bad3(): lcc.foo = 'fooval3' + def bad4(): lcc.x = 1 + + ut.process_bad_data(( + ('attr type (3)', 'AttributeError', 'type', bad1), + ('attr (can’t set)', 'AttributeError', 'read-only', bad2), + ('attr (can’t reset)', 'AttributeError', 'reset', bad3), + ('attr (3)', 'AttributeError', 'has no attr', bad4), + )) + + return True + + def autolock(self, name, ut, desc='Lockable class with autolock'): class MyLockableAutolock(Lockable): def __init__(self): @@ -126,60 +174,18 @@ class unit_test: _set_ok = ('foo','bar') foo = 1 - qmsg('OK') - qmsg_r('Checking error handling...') - vmsg('') - - def bad1(): ac.x = 1 - def bad2(): acc.foo = 1 - def bad3(): lc.foo = 'fooval3' - def bad4(): lc.baz = 'str' - def bad5(): lcc.bar = 'str' - def bad6(): lc.qux = 2 - def bad7(): lcc.qux = 'quxval2' - def bad8(): lcc.foo = 'fooval3' - def bad9(): lc.x = 1 - def bad10(): lcc.x = 1 - - def bad11(): lc.alpha = 0 - def bad12(): lc.beta = False - def bad13(): lc.gamma = Decimal('0') - def bad14(): lc.delta = float(0) - def bad15(): lc.epsilon = [0] - - def bad16(): lca.foo = None - def bad17(): MyLockableBad() - def bad18(): aca._lock() - - def bad19(): acdn.baz = None - def bad20(): lcdn.foo = 1 - def bad21(): lcdn.bar = None - def bad22(): del lcdn.foo + def bad1(): lca.foo = None + def bad2(): MyLockableBad() + def bad3(): lcdn.foo = 1 + def bad4(): lcdn.bar = None + def bad5(): del lcdn.foo ut.process_bad_data(( - ('attr (1)', 'AttributeError', 'has no attr', bad1 ), - ('attr (2)', 'AttributeError', 'has no attr', bad9 ), - ('attr (3)', 'AttributeError', 'has no attr', bad10 ), - ('attr (4)', 'AttributeError', 'has no attr', bad19 ), - ('attr (5)', 'AttributeError', 'has no attr', bad21 ), - ('attr type (1)', 'AttributeError', 'type', bad2 ), - ("attr type (2)", 'AttributeError', 'type', bad4 ), - ("attr type (3)", 'AttributeError', 'type', bad5 ), - ("attr (can't set)", 'AttributeError', 'read-only', bad6 ), - ("attr (can't set)", 'AttributeError', 'read-only', bad7 ), - ("attr (can't set)", 'AttributeError', 'read-only', bad20 ), - ("attr (can't reset)", 'AttributeError', 'reset', bad3 ), - ("attr (can't reset)", 'AttributeError', 'reset', bad8 ), - ("attr (can't reset)", 'AttributeError', 'reset', bad11 ), - ("attr (can't reset)", 'AttributeError', 'reset', bad12 ), - ("attr (can't reset)", 'AttributeError', 'reset', bad13 ), - ("attr (can't reset)", 'AttributeError', 'reset', bad14 ), - ("attr (can't reset)", 'AttributeError', 'reset', bad15 ), - ("attr (can't set)", 'AttributeError', 'read-only', bad16 ), - ("attr (bad _set_ok)", 'AssertionError', 'not found in',bad17 ), - ("attr (can’t delete)",'AttributeError', 'not be delet',bad22 ), - ("call to _lock()", 'AssertionError', 'only once', bad18 ), + ('attr (can’t set)', 'AttributeError', 'read-only', bad1), + ('attr (bad _set_ok)', 'AssertionError', 'not found in', bad2), + ('attr (can’t set)', 'AttributeError', 'read-only', bad3), + ('attr (5)', 'AttributeError', 'has no attr', bad4), + ('attr (can’t delete)', 'AttributeError', 'not be delet', bad5), )) - qmsg('OK') return True diff --git a/test/unit_tests_d/ut_misc.py b/test/unit_tests_d/ut_misc.py index 3ab31119..e08cd6ef 100755 --- a/test/unit_tests_d/ut_misc.py +++ b/test/unit_tests_d/ut_misc.py @@ -12,7 +12,7 @@ from ..include.common import vmsg class unit_tests: - def format_elapsed_hr(self, name, ut): + def format_elapsed_hr(self, name, ut, desc='function util.format_elapsed_hr()'): from mmgen.util2 import format_elapsed_hr vectors = ( @@ -63,7 +63,7 @@ class unit_tests: return True - def xmrwallet_uarg_info(self,name,ut): # WIP + def xmrwallet_uarg_info(self, name, ut, desc='dict xmrwallet.xmrwallet_uarg_info'): # WIP from mmgen.xmrwallet import xmrwallet_uarg_info as uarg_info vs = namedtuple('vector_data', ['text', 'groups']) fs = '{:16} {}' @@ -95,7 +95,7 @@ class unit_tests: return True - def pyversion(self,name,ut): + def pyversion(self, name, ut, desc='class pyversion.PythonVersion'): from mmgen.pyversion import PythonVersion,python_version ver = {} diff --git a/test/unit_tests_d/ut_mn_entry.py b/test/unit_tests_d/ut_mn_entry.py index a65d0d56..7387f353 100755 --- a/test/unit_tests_d/ut_mn_entry.py +++ b/test/unit_tests_d/ut_mn_entry.py @@ -4,10 +4,10 @@ test.unit_tests_d.ut_mn_entry: Mnemonic user entry unit test for the MMGen suite """ -from mmgen.util import msg,msg_r -from ..include.common import cfg,qmsg +from mmgen.mn_entry import mn_entry +from ..include.common import cfg, vmsg -class unit_test: +class unit_tests: vectors = { 'mmgen': { @@ -40,26 +40,19 @@ class unit_test: 'bip39': { 'usl': 4, 'sw': 3, 'lw': 8 }, } - def run_test(self,name,ut): - - msg_r('Testing MnemonicEntry methods...') - - from mmgen.mn_entry import mn_entry - - msg_r('\nTesting computed wordlist constants...') + def wl(self, name, ut, desc='MnemonicEntry - computed wordlist constants'): for wl_id in self.vectors: for j,k in (('uniq_ss_len','usl'),('shortest_word','sw'),('longest_word','lw')): a = getattr(mn_entry( cfg, wl_id ),j) b = self.vectors[wl_id][k] assert a == b, f'{wl_id}:{j} {a} != {b}' - msg('OK') + return True - msg_r('Testing idx()...') - qmsg('') + def idx(self, name, ut, desc='MnemonicEntry - idx()'): junk = 'a g z aa gg zz aaa ggg zzz aaaa gggg zzzz aaaaaaaaaaaaaa gggggggggggggg zzzzzzzzzzzzzz' for wl_id in self.vectors: m = mn_entry( cfg, wl_id ) - qmsg('Wordlist: '+wl_id) + vmsg('Wordlist: '+wl_id) for entry_mode in ('full','short'): for a,word in enumerate(m.wl): b = m.idx(word,entry_mode) @@ -78,6 +71,4 @@ class unit_test: assert type(b) is tuple, (type(b),tuple) elif type(chk) is int: assert b == chk, (b,chk) - msg('OK') - return True diff --git a/test/unit_tests_d/ut_msg.py b/test/unit_tests_d/ut_msg.py index 2425488a..9533eea3 100755 --- a/test/unit_tests_d/ut_msg.py +++ b/test/unit_tests_d/ut_msg.py @@ -6,7 +6,7 @@ test.unit_tests_d.ut_msg: message signing unit tests for the MMGen suite import os -from mmgen.util import msg,bmsg,pumsg,suf +from mmgen.util import msg, pumsg, suf from mmgen.protocol import CoinProtocol from mmgen.msg import NewMsg,UnsignedMsg,SignedMsg,SignedOnlineMsg,ExportedMsgSigs from mmgen.addr import MMGenID @@ -41,8 +41,6 @@ async def run_test(network_id,chksum,msghash_type='raw'): if not cfg.verbose: silence() - bmsg(f'\nTesting {coin.upper()} {network.upper()}:\n') - m = get_obj(coin,network,msghash_type) if m.proto.sign_mode == 'daemon': @@ -131,23 +129,23 @@ class unit_tests: altcoin_deps = ('ltc','bch','eth','eth_raw') - def btc(self,name,ut): + def btc(self, name, ut, desc='Bitcoin mainnet'): return run_test('btc','AA0DB5') - def btc_tn(self,name,ut): + def btc_tn(self, name, ut, desc='Bitcoin testnet'): return run_test('btc_tn','A88E1D') - def btc_rt(self,name,ut): + def btc_rt(self, name, ut, desc='Bitcoin regtest'): return run_test('btc_rt','578018') - def ltc(self,name,ut): + def ltc(self, name, ut, desc='Litecoin mainnet'): return run_test('ltc','BA7549') - def bch(self,name,ut): + def bch(self, name, ut, desc='Bitcoin Cash mainnet'): return run_test('bch','1B8065') - def eth(self,name,ut): + def eth(self, name, ut, desc='Ethereum mainnet'): return run_test('eth','35BAD9',msghash_type='eth_sign') - def eth_raw(self,name,ut): + def eth_raw(self, name, ut, desc='Ethereum mainnet (raw message)'): return run_test('eth','9D900C') diff --git a/test/unit_tests_d/ut_obj.py b/test/unit_tests_d/ut_obj.py index 88a4a7cb..987c0c03 100755 --- a/test/unit_tests_d/ut_obj.py +++ b/test/unit_tests_d/ut_obj.py @@ -6,11 +6,11 @@ test.unit_tests_d.ut_obj: data object unit tests for the MMGen suite from decimal import Decimal -from ..include.common import qmsg,qmsg_r,vmsg +from ..include.common import vmsg class unit_tests: - def coinamt(self,name,ut): + def coinamt(self, name, ut, desc='BTCAmt, LTCAmt, XMRAmt and ETHAmt classes'): from mmgen.amt import BTCAmt,LTCAmt,XMRAmt,ETHAmt @@ -27,8 +27,7 @@ class unit_tests: assert res == chk, f'{res} != {chk}' assert type(res) is cls, f'{type(res).__name__} != {cls.__name__}' - qmsg_r(f'Testing {cls.__name__} arithmetic operations...') - vmsg('') + vmsg(f'\nTesting {cls.__name__} arithmetic operations...') A,B = ( Decimal(aa), Decimal(bb) ) a,b = ( cls(aa), cls(bb) ) @@ -51,9 +50,7 @@ class unit_tests: do('a * b / a', a * b / a, A * B / A) do('a * b / b', a * b / b, A * B / B) - qmsg('OK') - qmsg_r(f'Checking {cls.__name__} error handling...') - vmsg('') + vmsg(f'\nChecking {cls.__name__} error handling...') bad_data = ( ('negation', 'NotImplementedError', 'not implemented', lambda: -a ), @@ -72,5 +69,6 @@ class unit_tests: ut.process_bad_data(bad_data) - qmsg('OK') + vmsg('OK') + return True diff --git a/test/unit_tests_d/ut_xmrseed.py b/test/unit_tests_d/ut_xmrseed.py index caf8d301..3bd54198 100755 --- a/test/unit_tests_d/ut_xmrseed.py +++ b/test/unit_tests_d/ut_xmrseed.py @@ -6,11 +6,11 @@ test/unit_tests_d/ut_xmrseed: Monero mnemonic unit test for the MMGen suite altcoin_dep = True -from mmgen.util import msg,msg_r,ymsg +from mmgen.util import ymsg +from mmgen.xmrseed import xmrseed +from ..include.common import cfg, vmsg -from ..include.common import cfg,qmsg,vmsg - -class unit_test: +class unit_tests: vectors = ( # private keys are reduced ( '148d78d2aba7dbca5cd8f6abcfb0b3c009ffbdbea1ff373d50ed94d78286640e', # Monero repo @@ -55,53 +55,49 @@ class unit_test: ), ) - def run_test(self,name,ut): + @property + def _use_monero_python(self): + if not hasattr(self,'_use_monero_python_'): + try: + from monero.wordlists.english import English + self.wl = English() + except ImportError: + self._use_monero_python_ = False + ymsg('Warning: unable to import monero-python, skipping external library checks') + else: + self._use_monero_python_ = True + return self._use_monero_python_ - def test_fromhex(b): - vmsg('') - qmsg('Checking seed to mnemonic conversion:') - for privhex,chk in self.vectors: - vmsg(f' {chk}') - chk = tuple(chk.split()) - res = b.fromhex(privhex) - if use_monero_python: - mp_chk = tuple( wl.encode(privhex).split() ) - assert res == mp_chk, f'check failed:\nres: {res}\nchk: {chk}' - assert res == chk, f'check failed:\nres: {res}\nchk: {chk}' - - def test_tohex(b): - vmsg('') - qmsg('Checking mnemonic to seed conversion:') - for chk,words in self.vectors: - vmsg(f' {chk}') - res = b.tohex( words.split() ) - if use_monero_python: - mp_chk = wl.decode( words ) - assert res == mp_chk, f'check failed:\nres: {res}\nchk: {mp_chk}' - assert res == chk, f'check failed:\nres: {res}\nchk: {chk}' - - msg_r('Testing xmrseed conversion routines...') - qmsg('') - - from mmgen.xmrseed import xmrseed + def wordlist(self, name, ut, desc='Monero wordlist'): + xmrseed().check_wordlist(cfg) + return True + def fromhex(self, name, ut, desc='fromhex() method'): b = xmrseed() - b.check_wordlist(cfg) + vmsg('Checking seed to mnemonic conversion:') + for privhex, chk in self.vectors: + vmsg(f' {chk}') + chk = tuple(chk.split()) + res = b.fromhex(privhex) + if self._use_monero_python: + mp_chk = tuple( self.wl.encode(privhex).split() ) + assert res == mp_chk, f'check failed:\nres: {res}\nchk: {chk}' + assert res == chk, f'check failed:\nres: {res}\nchk: {chk}' + return True - try: - from monero.wordlists.english import English - wl = English() - except ImportError: - use_monero_python = False - ymsg('Warning: unable to import monero-python, skipping external library checks') - else: - use_monero_python = True + def tohex(self, name, ut, desc='tohex() method'): + b = xmrseed() + vmsg('Checking mnemonic to seed conversion:') + for chk, words in self.vectors: + vmsg(f' {chk}') + res = b.tohex( words.split() ) + if self._use_monero_python: + mp_chk = self.wl.decode( words ) + assert res == mp_chk, f'check failed:\nres: {res}\nchk: {mp_chk}' + assert res == chk, f'check failed:\nres: {res}\nchk: {chk}' + return True - test_fromhex(b) - test_tohex(b) - - vmsg('') - qmsg('Checking error handling:') + def errors(self, name, ut, desc='error handling'): bad_chksum_mn = ('abbey ' * 21 + 'bamboo jaws jerseys donuts').split() bad_word_mn = "admire zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo".split() @@ -110,6 +106,7 @@ class unit_test: good_hex = self.vectors[0][0] bad_len_mn = good_mn[:22] + b = xmrseed() th = b.tohex fh = b.fromhex @@ -118,20 +115,15 @@ class unit_test: ase = 'AssertionError' mne = 'MnemonicError' - bad_data = ( - ('hex', hse, 'not a hexadecimal', lambda:fh('xx')), - ('seed len', sle, 'invalid seed byte len', lambda:fh(bad_seed)), - ('mnemonic type', ase, 'must be list', lambda:th('string')), - ('pad arg (fromhex)', ase, "invalid 'pad' arg", lambda:fh(good_hex,pad=23)), - ('pad arg (tohex)', ase, "invalid 'pad' arg", lambda:th(good_mn,pad=23)), - ('word', mne, "not in Monero", lambda:th(bad_word_mn)), - ('checksum', mne, "checksum", lambda:th(bad_chksum_mn)), - ('seed phrase len', mne, "phrase len", lambda:th(bad_len_mn)), - ) - - ut.process_bad_data(bad_data) - - vmsg('') - msg('OK') + ut.process_bad_data(( + ('hex', hse, 'not a hexadecimal', lambda: fh('xx')), + ('seed len', sle, 'invalid seed byte len', lambda: fh(bad_seed)), + ('mnemonic type', ase, 'must be list', lambda: th('string')), + ('pad arg (fromhex)', ase, "invalid 'pad' arg", lambda: fh(good_hex,pad=23)), + ('pad arg (tohex)', ase, "invalid 'pad' arg", lambda: th(good_mn,pad=23)), + ('word', mne, "not in Monero", lambda: th(bad_word_mn)), + ('checksum', mne, "checksum", lambda: th(bad_chksum_mn)), + ('seed phrase len', mne, "phrase len", lambda: th(bad_len_mn)), + )) return True