ct_base.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 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.cmdtest_d.ct_base: Base class for the cmdtest.py test suite
  20. """
  21. import sys, os
  22. from mmgen.util import msg
  23. from mmgen.color import gray, purple, yellow
  24. from ..include.common import cfg, write_to_file, read_from_file, imsg
  25. from .common import get_file_with_ext
  26. class CmdTestBase:
  27. 'initializer class for the cmdtest.py test suite'
  28. base_passthru_opts = ('data_dir', 'skip_cfg_file')
  29. passthru_opts = ()
  30. networks = ()
  31. segwit_opts_ok = False
  32. color = False
  33. need_daemon = False
  34. platform_skip = ()
  35. tmpdir_nums = []
  36. test_name = None
  37. def __init__(self, trunner, cfgs, spawn):
  38. if hasattr(self, 'name'): # init will be called multiple times for classes with multiple inheritance
  39. return
  40. self.name = type(self).__name__
  41. self.proto = cfg._proto
  42. self.tr = trunner
  43. self.cfgs = cfgs
  44. self.spawn = spawn
  45. self.have_dfl_wallet = False
  46. self.usr_rand_chars = (5, 30)[bool(cfg.usr_random)]
  47. self.usr_rand_arg = f'-r{self.usr_rand_chars}'
  48. self.tn_ext = ('', '.testnet')[self.proto.testnet]
  49. self.coin = self.proto.coin.lower()
  50. self.fork = 'btc' if self.coin == 'bch' and not cfg.cashaddr else self.coin
  51. self.altcoin_pfx = '' if self.fork == 'btc' else f'-{self.proto.coin}'
  52. self.testnet_opt = ['--testnet=1'] if cfg.testnet else []
  53. if len(self.tmpdir_nums) == 1:
  54. self.tmpdir_num = self.tmpdir_nums[0]
  55. if self.tr:
  56. self.spawn_env = dict(self.tr.spawn_env)
  57. self.spawn_env['MMGEN_TEST_SUITE_ENABLE_COLOR'] = '1' if self.color else ''
  58. else:
  59. self.spawn_env = {} # placeholder
  60. def get_altcoin_pfx(self, coin, cashaddr=True):
  61. coin = coin.lower()
  62. fork = 'btc' if coin == 'bch' and not cashaddr else coin
  63. return '' if fork == 'btc' else f'-{coin.upper()}'
  64. @property
  65. def tmpdir(self):
  66. return os.path.join('test', 'tmp', '{}{}'.format(self.tmpdir_num, '-α' if cfg.debug_utf8 else ''))
  67. def get_file_with_ext(self, ext, **kwargs):
  68. return get_file_with_ext(self.tmpdir, ext, **kwargs)
  69. def read_from_tmpfile(self, fn, binary=False):
  70. return read_from_file(os.path.join(self.tmpdir, fn), binary=binary)
  71. def write_to_tmpfile(self, fn, data, binary=False):
  72. return write_to_file(os.path.join(self.tmpdir, fn), data, binary=binary)
  73. def delete_tmpfile(self, fn):
  74. fn = os.path.join(self.tmpdir, fn)
  75. try:
  76. return os.unlink(fn)
  77. except:
  78. msg(f'{fn}: file does not exist or could not be deleted')
  79. def skip_for_platform(self, name, extra_msg=None):
  80. if sys.platform == name:
  81. msg(gray('Skipping test {!r} for {} platform{}'.format(
  82. self.test_name,
  83. name,
  84. f' ({extra_msg})' if extra_msg else "")))
  85. return True
  86. else:
  87. return False
  88. def skip_for_mac(self, extra_msg=None):
  89. return self.skip_for_platform('darwin', extra_msg)
  90. def skip_for_win(self, extra_msg=None):
  91. return self.skip_for_platform('win32', extra_msg)
  92. def spawn_chk(self, *args, **kwargs):
  93. """
  94. Drop-in replacement for spawn() + t.read() for tests that spawn more than one process.
  95. Ensures that test script execution stops when a spawned process fails.
  96. """
  97. t = self.spawn(*args, **kwargs)
  98. t.read()
  99. t.ok()
  100. t.skip_ok = True
  101. return t
  102. def noop(self):
  103. return 'ok'
  104. def _cashaddr_opt(self, val):
  105. return [f'--cashaddr={val}'] if self.proto.coin == 'BCH' else []
  106. def _kill_process_from_pid_file(self, fn, desc):
  107. self.spawn('', msg_only=True)
  108. pid = int(self.read_from_tmpfile(fn))
  109. self.delete_tmpfile(fn)
  110. from signal import SIGTERM
  111. imsg(purple(f'Killing {desc} [PID {pid}]'))
  112. try:
  113. os.kill(pid, SIGTERM)
  114. except:
  115. imsg(yellow(f'{pid}: no such process'))
  116. return 'ok'