ts_cfgfile.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. # Project source code repository: https://github.com/mmgen/mmgen
  7. # Licensed according to the terms of GPL Version 3. See LICENSE for details.
  8. """
  9. test.test_py_d.ts_cfgfile: CfgFile tests for the MMGen test.py test suite
  10. """
  11. import shutil
  12. from ..include.common import *
  13. from .ts_base import *
  14. from mmgen.cfgfile import *
  15. class TestSuiteCfgFile(TestSuiteBase):
  16. 'CfgFile API'
  17. networks = ('btc',)
  18. tmpdir_nums = [40]
  19. base_passthru_opts = ()
  20. color = True
  21. cmd_group = (
  22. ('sysfile', (40,'init with system cfg sample file in place', [])),
  23. ('no_metadata_sample', (40,'init with unversioned cfg sample file', [])),
  24. ('altered_sample', (40,'init with user-modified cfg sample file', [])),
  25. ('old_sample', (40,'init with old v2 cfg sample file', [])),
  26. ('old_sample_bad_var', (40,'init with old v2 cfg sample file and bad variable in mmgen.cfg', [])),
  27. ('autoset_opts', (40,'setting autoset opts', [])),
  28. ('autoset_opts_cmdline', (40,'setting autoset opts (override on cmdline)', [])),
  29. ('autoset_opts_bad', (40,'setting autoset opts (bad value in cfg file)', [])),
  30. ('autoset_opts_bad_cmdline', (40,'setting autoset opts (bad param on cmdline)', [])),
  31. ('coin_specific_vars', (40,'setting coin-specific vars', [])),
  32. ('chain_names', (40,'setting chain names', [])),
  33. ('mnemonic_entry_modes', (40,'setting mnemonic entry modes', [])),
  34. )
  35. def __init__(self,trunner,cfgs,spawn):
  36. TestSuiteBase.__init__(self,trunner,cfgs,spawn)
  37. self.spawn_env['MMGEN_TEST_SUITE_CFGTEST'] = '1'
  38. def spawn_test(self,args=[],extra_desc='',pexpect_spawn=None):
  39. return self.spawn(
  40. 'test/misc/cfg.py',
  41. [f'--data-dir={self.path("data_dir")}'] + args,
  42. cmd_dir = '.',
  43. extra_desc = extra_desc,
  44. pexpect_spawn = pexpect_spawn )
  45. def path(self,id_str):
  46. return {
  47. 'ref': 'test/ref/mmgen.cfg',
  48. 'data_dir': '{}/data_dir'.format(self.tmpdir),
  49. 'shared_data': '{}/data_dir/{}'.format(self.tmpdir,CfgFileSampleSys.test_fn_subdir),
  50. 'usr': '{}/data_dir/mmgen.cfg'.format(self.tmpdir),
  51. 'sys': '{}/data_dir/{}/mmgen.cfg'.format(self.tmpdir,CfgFileSampleSys.test_fn_subdir),
  52. 'sample': '{}/data_dir/mmgen.cfg.sample'.format(os.path.abspath(self.tmpdir)),
  53. }[id_str]
  54. def copy_sys_sample(self):
  55. os.makedirs(self.path('shared_data'),exist_ok=True)
  56. shutil.copy2(self.path('ref'),self.path('sys'))
  57. def sysfile(self):
  58. self.copy_sys_sample()
  59. t = self.spawn_test()
  60. t.read()
  61. u = read_from_file(self.path('usr'))
  62. S = read_from_file(self.path('sys'))
  63. assert u[-1] == '\n', u
  64. assert u.replace('\r\n','\n') == S, 'u != S'
  65. self.check_replaced_sample()
  66. return t
  67. def check_replaced_sample(self):
  68. S = read_from_file(self.path('sys'))
  69. s = read_from_file(self.path('sample'))
  70. assert s[-1] == '\n', s
  71. assert S.splitlines() == s.splitlines()[:-1], 'sys != sample[:-1]'
  72. def bad_sample(self,s,e):
  73. write_to_file(self.path('sample'),s)
  74. t = self.spawn_test()
  75. t.expect(e)
  76. t.read()
  77. self.check_replaced_sample()
  78. return t
  79. def no_metadata_sample(self):
  80. self.copy_sys_sample()
  81. s = read_from_file(self.path('sys'))
  82. e = CfgFileSampleUsr.out_of_date_fs.format(self.path('sample'))
  83. return self.bad_sample(s,e)
  84. def altered_sample(self):
  85. s = '\n'.join(read_from_file(self.path('sample')).splitlines()[1:]) + '\n'
  86. e = CfgFileSampleUsr.altered_by_user_fs.format(self.path('sample'))
  87. return self.bad_sample(s,e)
  88. def old_sample_common(self,old_set=False,args=[],pexpect_spawn=False):
  89. s = read_from_file(self.path('sys'))
  90. d = s.replace('monero_','zcash_').splitlines()
  91. a1 = ['','# Uncomment to make foo true:','# foo true']
  92. a2 = ['','# Uncomment to make bar false:','# bar false']
  93. d = d + a1 + a2
  94. chk = cfg_file_sample.cls_make_metadata(d)
  95. write_to_file(self.path('sample'),'\n'.join(d+chk) + '\n')
  96. t = self.spawn_test(args=args,pexpect_spawn=pexpect_spawn)
  97. t.expect('options have changed')
  98. for s in ('have been added','monero_','have been removed','zcash_','foo','bar'):
  99. t.expect(s)
  100. if old_set:
  101. for s in ('must be deleted','bar','foo'):
  102. t.expect(s)
  103. cp = CfgFileSampleUsr.details_confirm_prompt + ' (y/N): '
  104. t.expect(cp,'y')
  105. for s in ('CHANGES','Removed','# zcash_','# foo','# bar','Added','# monero_'):
  106. t.expect(s)
  107. if t.pexpect_spawn: # view and exit pager
  108. time.sleep(1 if cfg.exact_output else t.send_delay)
  109. t.send('q')
  110. t.expect(cp,'n')
  111. if old_set:
  112. t.expect('unrecognized option')
  113. t.req_exit_val = 1
  114. if args == ['parse_test']:
  115. t.expect('parsed chunks: 29')
  116. t.expect('usr cfg: testnet=true rpc_password=passwOrd')
  117. if not old_set:
  118. self.check_replaced_sample()
  119. return t
  120. def old_sample(self):
  121. d = ['testnet true','rpc_password passwOrd']
  122. write_to_file(self.path('usr'),'\n'.join(d) + '\n')
  123. return self.old_sample_common(args=['parse_test'])
  124. def old_sample_bad_var(self):
  125. d = ['foo true','bar false']
  126. write_to_file(self.path('usr'),'\n'.join(d) + '\n')
  127. return self.old_sample_common(
  128. old_set = True,
  129. pexpect_spawn = False if gc.platform == 'win' else True )
  130. def _autoset_opts(self,args=[],text='rpc_backend aiohttp\n'):
  131. write_to_file( self.path('usr'), text )
  132. imsg(yellow(f'Wrote cfg file:\n {text}'))
  133. return self.spawn_test(args=args)
  134. def autoset_opts(self):
  135. return self._autoset_opts(args=['autoset_opts'])
  136. def autoset_opts_cmdline(self):
  137. return self._autoset_opts(args=['--rpc-backend=curl','autoset_opts_cmdline'])
  138. def _autoset_opts_bad(self,kwargs):
  139. t = self._autoset_opts(**kwargs)
  140. t.req_exit_val = 1
  141. return t
  142. def autoset_opts_bad(self):
  143. return self._autoset_opts_bad({'text':'rpc_backend foo\n'})
  144. def autoset_opts_bad_cmdline(self):
  145. return self._autoset_opts_bad({'args':['--rpc-backend=foo']})
  146. def coin_specific_vars(self):
  147. """
  148. ensure that derived classes explicitly set these variables
  149. """
  150. d = [
  151. 'btc_max_tx_fee 1.2345',
  152. 'eth_max_tx_fee 5.4321',
  153. 'btc_ignore_daemon_version true',
  154. 'eth_ignore_daemon_version true'
  155. ]
  156. write_to_file(self.path('usr'),'\n'.join(d) + '\n')
  157. imsg(yellow('Wrote cfg file:\n {}'.format('\n '.join(d))))
  158. for coin,res1_chk,res2_chk,res2_chk_eq in (
  159. ('BTC','True', '1.2345',True),
  160. ('LTC','False','1.2345',False),
  161. ('BCH','False','1.2345',False),
  162. ('ETH','True', '5.4321',True),
  163. ('ETC','False','5.4321',False)
  164. ):
  165. if cfg.no_altcoin and coin != 'BTC':
  166. continue
  167. t = self.spawn_test(
  168. args = [
  169. f'--coin={coin}',
  170. 'coin_specific_vars',
  171. 'ignore_daemon_version',
  172. 'max_tx_fee'
  173. ],
  174. extra_desc=f'({coin})' )
  175. res1 = t.expect_getend('ignore_daemon_version: ')
  176. res2 = t.expect_getend('max_tx_fee: ')
  177. assert res1 == res1_chk, f'{res1} != {res1_chk}'
  178. if res2_chk_eq:
  179. assert res2 == res2_chk, f'{res2} != {res2_chk}'
  180. else:
  181. assert res2 != res2_chk, f'{res2} == {res2_chk}'
  182. t.read()
  183. t.ok()
  184. t.skip_ok = True
  185. return t
  186. def mnemonic_entry_modes(self):
  187. def run(modes_chk):
  188. t = self.spawn_test(args=['mnemonic_entry_modes'])
  189. modes = t.expect_getend('mnemonic_entry_modes: ')
  190. assert modes_chk == modes, f'{modes_chk} != {modes}'
  191. return t
  192. txt = 'mnemonic_entry_modes mmgen:full bip39:short'
  193. write_to_file(self.path('usr'),txt+'\n')
  194. imsg(yellow(f'Wrote cfg file: {txt!r}'))
  195. t = run("{'mmgen': 'full', 'bip39': 'short'}")
  196. # check that set_dfl_entry_mode() set the mode correctly:
  197. t.expect('mmgen: full')
  198. t.expect('bip39: short')
  199. return t
  200. def chain_names(self):
  201. if cfg.no_altcoin:
  202. return 'skip'
  203. def run(chk,testnet):
  204. for coin,chain_chk in (('ETH',chk),('ETC',None)):
  205. t = self.spawn_test(
  206. args = [f'--coin={coin}',f'--testnet={(0,1)[testnet]}','coin_specific_vars','chain_names'],
  207. extra_desc = f'({coin} testnet={testnet!r:5} chain_names={chain_chk})' )
  208. chain = t.expect_getend('chain_names: ')
  209. if chain_chk:
  210. assert chain == chain_chk, f'{chain} != {chain_chk}'
  211. else:
  212. assert chain != chain_chk, f'{chain} == {chain_chk}'
  213. t.read()
  214. t.ok()
  215. return t
  216. txt = 'eth_mainnet_chain_names istanbul constantinople'
  217. write_to_file(self.path('usr'),txt+'\n')
  218. imsg(yellow(f'Wrote cfg file: {txt!r}'))
  219. t = run("['istanbul', 'constantinople']",False)
  220. t = run(None,True)
  221. txt = 'eth_testnet_chain_names rinkeby'
  222. write_to_file(self.path('usr'),txt+'\n')
  223. imsg(yellow(f'Wrote cfg file: {txt!r}'))
  224. t = run(None,False)
  225. t = run("['rinkeby']",True)
  226. t.skip_ok = True
  227. return t