new ClassFlags,ClassOpts classes

This commit is contained in:
The MMGen Project 2021-08-10 19:38:20 +00:00
commit f379cde0fc
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 169 additions and 0 deletions

View file

@ -48,6 +48,7 @@ class BaseConversionError(Exception): mmcode = 2
class BaseConversionPadError(Exception): mmcode = 2
class TransactionChainMismatch(Exception):mmcode = 2
class ObjectInitError(Exception): mmcode = 2
class ClassFlagsError(Exception): mmcode = 2
# 3: yellow hl, 'MMGen Error' + exception + message
class RPCFailure(Exception): mmcode = 3

88
mmgen/flags.py Executable file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2021 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/>.
"""
flags.py: Class flags and opts for the MMGen suite
"""
from .exception import ClassFlagsError
from .base_obj import AttrCtrl,Lockable
from .util import fmt_list
class ClassFlags(AttrCtrl):
_name = 'flags'
_desc = 'flag'
reserved_attrs = ('lock',)
def __init__(self,parent,arg):
self._parent = parent
self._available = getattr(self._parent,'avail_'+self._name)
for a in self._available:
if a.startswith('_'):
raise ClassFlagsError(f'{a!r}: {self._desc} cannot begin with an underscore')
for b in self.reserved_attrs:
if a == b:
raise ClassFlagsError(f'{a!r}: {b} is a reserved name for {self._desc}')
if arg:
assert type(arg) in (list,tuple), f"{arg!r}: {self._name!r} must be list or tuple"
else:
arg = []
for e in arg:
if e not in self._available:
self.not_available_error(e)
for e in self._available:
setattr(self,e,e in arg)
def __dir__(self):
return [k for k in self.__dict__ if not k.startswith('_') and not k in self.reserved_attrs]
def __str__(self):
return ' '.join(f'{k}={getattr(self,k)}' for k in dir(self))
def __setattr__(self,name,val):
if self._lock:
if name not in self._available:
self.not_available_error(name)
if self._name == 'flags':
assert type(val) is bool, f'{val!r} not boolean'
old_val = getattr(self,name)
if val and old_val:
raise ClassFlagsError(f'{self._desc} {name!r} already set')
if not val and not old_val:
raise ClassFlagsError(f'{self._desc} {name!r} not set, so cannot be unset')
super().__setattr__(name,val)
def not_available_error(self,name):
raise ClassFlagsError('{!r}: unrecognized {} for {}: (available {}: {})'.format(
name,
self._desc,
type(self._parent).__name__,
self._name,
fmt_list(self._available,fmt='bare') ))
class ClassOpts(ClassFlags,Lockable):
_name = 'opts'
_desc = 'opt'

View file

@ -127,6 +127,7 @@ setup(
'mmgen.ed25519ll_djbec',
'mmgen.exception',
'mmgen.filename',
'mmgen.flags',
'mmgen.globalvars',
'mmgen.help',
'mmgen.keccak',

79
test/unit_tests_d/ut_flags.py Executable file
View file

@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""
test/unit_tests_d/ut_flags.py: unit test for the MMGen suite's ClassFlags class
"""
from mmgen.common import *
from mmgen.exception import *
from mmgen.flags import *
class unit_test(object):
def run_test(self,name,ut):
class cls1:
avail_opts = ()
avail_flags = ()
def __init__(self,opts=None,flags=None):
self.opt = ClassOpts(self,opts)
self.flag = ClassFlags(self,flags)
class cls2(cls1):
avail_opts = ('foo','bar')
avail_flags = ('baz',)
class cls3(cls1):
avail_opts = ('_foo',)
class cls4(cls1):
avail_opts = ('lock',)
def test_flags():
def gen():
for n,cls in enumerate((
cls1(),
cls2(),
cls2(opts=['bar']),
cls2(flags=['baz']),
)):
vmsg(f'Cfg {n+1}:')
for k in ('opt','flag'):
vmsg(' {}s: {}'.format( k, getattr(cls,k) ))
yield cls
return list(gen())
def test_flags_err(d):
def bad1(): d[1].flag.foo = False
def bad2(): d[1].opt.baz = False
def bad3(): cls3()
def bad4(): cls4()
def bad5(): cls1(opts='foo')
def bad6(): cls2(opts=['qux'])
def bad7(): d[1].flag.baz = False
def bad8(): d[3].flag.baz = True
def bad9(): d[1].flag.baz = 'x'
ut.process_bad_data((
('flag (1)', 'ClassFlagsError', 'unrecognized flag', bad1 ),
('opt (1)', 'ClassFlagsError', 'unrecognized opt', bad2 ),
('avail_opts (1)', 'ClassFlagsError', 'underscore', bad3 ),
('avail_opts (1)', 'ClassFlagsError', 'reserved name', bad4 ),
('class invocation (1)', 'AssertionError', 'list or tuple', bad5 ),
('class invocation (2)', 'ClassFlagsError', 'unrecognized opt', bad6 ),
('flag (2)', 'ClassFlagsError', 'not set', bad7 ),
('flag (3)', 'ClassFlagsError', 'already set', bad8 ),
('flag (4)', 'AssertionError', 'not boolean', bad9 ),
))
qmsg_r('Testing flags and opts...')
vmsg('')
classes = test_flags()
qmsg('OK')
qmsg_r('Testing error handling for flags and opts...')
vmsg('')
test_flags_err(classes)
qmsg('OK')
return True