objtest.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2023 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. test/objtest.py: Test MMGen data objects
  20. """
  21. import sys,os,re
  22. from include.tests_header import repo_root
  23. from test.overlay import overlay_setup
  24. sys.path.insert(0,overlay_setup(repo_root))
  25. from mmgen.devinit import init_dev
  26. init_dev()
  27. os.environ['MMGEN_TEST_SUITE'] = '1'
  28. # Import these _after_ local path's been added to sys.path
  29. from mmgen.common import *
  30. from mmgen.obj import *
  31. from mmgen.seedsplit import *
  32. from mmgen.addr import *
  33. from mmgen.addrlist import *
  34. from mmgen.addrdata import *
  35. from mmgen.tw.shared import *
  36. from mmgen.amt import *
  37. from mmgen.key import *
  38. from mmgen.rpc import IPPort
  39. opts_data = {
  40. 'sets': [('super_silent', True, 'silent', True)],
  41. 'text': {
  42. 'desc': 'Test MMGen data objects',
  43. 'usage':'[options] [object]',
  44. 'options': """
  45. -h, --help Print this help message
  46. --, --longhelp Print help message for long options (common options)
  47. -g, --getobj Instantiate objects with get_obj() wrapper
  48. -q, --quiet Produce quieter output
  49. -s, --silent Silence output of tested objects
  50. -S, --super-silent Silence all output except for errors
  51. -v, --verbose Produce more verbose output
  52. """
  53. }
  54. }
  55. cmd_args = opts.init(opts_data)
  56. def run_test(test,arg,input_data,arg1,exc_name):
  57. arg_copy = arg
  58. kwargs = {}
  59. ret_chk = arg
  60. ret_idx = None
  61. if input_data == 'good' and type(arg) == tuple:
  62. arg,ret_chk = arg
  63. if type(arg) == dict: # pass one arg + kwargs to constructor
  64. arg_copy = arg.copy()
  65. if 'arg' in arg:
  66. args = [arg['arg']]
  67. ret_chk = args[0]
  68. del arg['arg']
  69. else:
  70. args = []
  71. ret_chk = list(arg.values())[0] # assume only one key present
  72. if 'ret' in arg:
  73. ret_chk = arg['ret']
  74. del arg['ret']
  75. del arg_copy['ret']
  76. if 'exc_name' in arg:
  77. exc_name = arg['exc_name']
  78. del arg['exc_name']
  79. del arg_copy['exc_name']
  80. if 'ret_idx' in arg:
  81. ret_idx = arg['ret_idx']
  82. del arg['ret_idx']
  83. del arg_copy['ret_idx']
  84. kwargs.update(arg)
  85. elif type(arg) == tuple:
  86. args = arg
  87. else:
  88. args = [arg]
  89. if opt.getobj:
  90. if args:
  91. assert len(args) == 1, 'objtest_chk1: only one positional arg is allowed'
  92. kwargs.update( { arg1: args[0] } )
  93. if opt.silent:
  94. kwargs.update( { 'silent': True } )
  95. try:
  96. if not opt.super_silent:
  97. arg_disp = repr(arg_copy[0] if type(arg_copy) == tuple else arg_copy)
  98. if g.test_suite_deterministic and isinstance(arg_copy,dict):
  99. arg_disp = re.sub(r'object at 0x[0-9a-f]+','object at [SCRUBBED]',arg_disp)
  100. msg_r((green if input_data=='good' else orange)(f'{arg_disp+":":<22}'))
  101. cls = globals()[test]
  102. if opt.getobj:
  103. ret = get_obj(globals()[test],**kwargs)
  104. else:
  105. ret = cls(*args,**kwargs)
  106. bad_ret = list() if issubclass(cls,list) else None
  107. if isinstance(ret_chk,str): ret_chk = ret_chk.encode()
  108. if isinstance(ret,str): ret = ret.encode()
  109. if opt.getobj:
  110. if input_data == 'bad':
  111. assert ret == False, 'non-False return on bad input data'
  112. else:
  113. if (opt.silent and input_data=='bad' and ret!=bad_ret) or (not opt.silent and input_data=='bad'):
  114. raise UserWarning(f"Non-'None' return value {ret!r} with bad input data")
  115. if opt.silent and input_data=='good' and ret==bad_ret:
  116. raise UserWarning("'None' returned with good input data")
  117. if input_data=='good':
  118. if ret_idx:
  119. ret_chk = arg[list(arg.keys())[ret_idx]].encode()
  120. if ret != ret_chk and repr(ret) != repr(ret_chk):
  121. raise UserWarning(f"Return value ({ret!r}) doesn't match expected value ({ret_chk!r})")
  122. if opt.super_silent:
  123. return
  124. if opt.getobj and (not opt.silent and input_data == 'bad'):
  125. pass
  126. else:
  127. try: ret_disp = ret.decode()
  128. except: ret_disp = ret
  129. msg(f'==> {ret_disp!r}')
  130. if opt.verbose and issubclass(cls,MMGenObject):
  131. ret.pmsg() if hasattr(ret,'pmsg') else pmsg(ret)
  132. except Exception as e:
  133. if input_data == 'good':
  134. raise ValueError('Error on good input data')
  135. if not type(e).__name__ == exc_name:
  136. msg(f'Incorrect exception: expected {exc_name} but got {type(e).__name__}')
  137. raise
  138. if opt.super_silent:
  139. pass
  140. elif opt.silent:
  141. msg(f'==> {exc_name}')
  142. else:
  143. msg( yellow(f' {exc_name}:') + str(e) )
  144. except SystemExit as e:
  145. if input_data == 'good':
  146. raise ValueError('Error on good input data')
  147. if opt.verbose:
  148. msg(f'exitval: {e.code}')
  149. except UserWarning as e:
  150. msg(f'==> {ret!r}')
  151. die(2,red(str(e)))
  152. def do_loop():
  153. import importlib
  154. modname = f'test.objtest_py_d.ot_{proto.coin.lower()}_{proto.network}'
  155. test_data = importlib.import_module(modname).tests
  156. gmsg(f'Running data object tests for {proto.coin} {proto.network}')
  157. clr = None
  158. utests = cmd_args
  159. for test in test_data:
  160. arg1 = test_data[test].get('arg1')
  161. if utests and test not in utests: continue
  162. nl = ('\n','')[bool(opt.super_silent) or clr == None]
  163. clr = (blue,nocolor)[bool(opt.super_silent)]
  164. if opt.getobj and arg1 is None:
  165. msg(gray(f'{nl}Skipping {test}'))
  166. continue
  167. else:
  168. msg(clr(f'{nl}Testing {test}'))
  169. for k in ('bad','good'):
  170. if not opt.super_silent:
  171. msg(purple(capfirst(k)+' input:'))
  172. for arg in test_data[test][k]:
  173. run_test(
  174. test,
  175. arg,
  176. input_data = k,
  177. arg1 = arg1,
  178. exc_name = test_data[test].get('exc_name') or ('ObjectInitError','None')[k=='good'],
  179. )
  180. from mmgen.protocol import init_proto_from_opts
  181. proto = init_proto_from_opts(need_amt=True)
  182. do_loop()