term.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. #!/usr/bin/env python3
  2. import sys, os
  3. pn = os.path.abspath(os.path.dirname(sys.argv[0]))
  4. parpar = os.path.dirname(os.path.dirname(pn))
  5. os.chdir(parpar)
  6. sys.path[0] = os.curdir
  7. from mmgen.cfg import Config, gc
  8. from mmgen.color import yellow, blue, cyan, set_vt100
  9. from mmgen.util import msg, ymsg, gmsg, fmt, fmt_list, die
  10. commands = [
  11. 'start',
  12. 'get_terminal_size',
  13. 'color',
  14. 'license',
  15. 'line_input',
  16. 'urand',
  17. 'txview',
  18. 'get_char_one',
  19. 'get_char_one_raw',
  20. ]
  21. match sys.platform:
  22. case 'linux' | 'darwin':
  23. commands.extend([
  24. 'get_char',
  25. 'get_char_immed_chars',
  26. 'get_char_raw',
  27. ])
  28. case 'win32':
  29. commands.extend([
  30. 'get_char_one_char_immed_chars',
  31. ])
  32. opts_data = {
  33. 'text': {
  34. 'desc': 'Interactively test MMGen terminal functionality',
  35. 'usage': 'command',
  36. 'options': """
  37. -h, --help Print this help message
  38. """,
  39. 'notes': f"""
  40. available commands for platform {sys.platform!r}:
  41. {fmt_list(commands, fmt='col', indent=' ')}
  42. """
  43. }
  44. }
  45. cfg = Config(opts_data=opts_data)
  46. from mmgen.term import get_char_raw, get_terminal_size, get_term
  47. from mmgen.ui import line_input, keypress_confirm, do_license_msg
  48. import mmgen.term as term_mod
  49. def cmsg(m):
  50. msg('\n'+cyan(m))
  51. def confirm(m):
  52. if not keypress_confirm(cfg, m):
  53. if keypress_confirm(cfg, 'Are you sure you want to exit test?'):
  54. die(1, 'Exiting test at user request')
  55. else:
  56. msg('Continuing...')
  57. def tt_start():
  58. m = fmt("""
  59. We will now test MMGen Wallet’s terminal capabilities.
  60. This is a non-automated test and requires user interaction.
  61. Continue?
  62. """)
  63. confirm(m.strip())
  64. def tt_get_terminal_size():
  65. cmsg('Testing get_terminal_size():')
  66. msg('X' * get_terminal_size().width)
  67. confirm('Do the X’s exactly fill the width of the screen?')
  68. def tt_color():
  69. cmsg('Testing color:')
  70. confirm(blue('THIS TEXT') + ' should be blue. Is it?')
  71. def tt_license():
  72. cmsg('Testing do_license_msg() with pager')
  73. ymsg('Press "w" to test the pager, then "c" to continue')
  74. do_license_msg(cfg)
  75. def tt_line_input():
  76. set_vt100()
  77. cmsg('Testing line_input():')
  78. msg(fmt("""
  79. At the Ready? prompt type and hold down "y".
  80. Then Enter some text, followed by held-down ENTER.
  81. The held-down "y" and ENTER keys should be blocked, not affecting the output
  82. on screen or entered text.
  83. """))
  84. get_char_raw('Ready? ', num_bytes=1)
  85. reply = line_input(cfg, '\nEnter text: ')
  86. confirm(f'Did you enter the text {reply!r}?')
  87. def _tt_get_char(raw=False, one_char=False, immed_chars=''):
  88. funcname = ('get_char', 'get_char_raw')[raw]
  89. fs = fmt("""
  90. Press some keys in quick succession.
  91. {}{}
  92. {}
  93. When you’re finished, use Ctrl-C to exit.
  94. """).strip()
  95. m1 = (
  96. 'You should experience a delay with quickly repeated entry.',
  97. 'Your entry should be repeated back to you immediately.'
  98. )[raw]
  99. m2 = (
  100. '',
  101. f'\nThe characters {immed_chars!r} will be repeated immediately, the others with delay.'
  102. )[bool(immed_chars)]
  103. m3 = 'The F1-F12 keys will be ' + (
  104. 'blocked entirely.'
  105. if one_char and not raw else
  106. "echoed AS A SINGLE character '\\x1b'."
  107. if one_char else
  108. 'echoed as a FULL CONTROL SEQUENCE.'
  109. )
  110. if sys.platform == 'win32':
  111. if raw:
  112. m3 = 'The Escape and F1-F12 keys will be returned as two-character strings.'
  113. else:
  114. m3 = 'The Escape and F1-F12 keys will be returned as single characters.'
  115. kwargs = {}
  116. if one_char:
  117. kwargs.update({'num_bytes':1})
  118. if immed_chars:
  119. kwargs.update({'immed_chars':immed_chars})
  120. cmsg('Testing {}({}):'.format(
  121. funcname,
  122. ','.join(f'{a}={b!r}' for a, b in kwargs.items())
  123. ))
  124. msg(fs.format(m1, yellow(m2), yellow(m3)))
  125. try:
  126. while True:
  127. ret = getattr(term_mod, funcname)('Enter a letter: ', **kwargs)
  128. msg(f'You typed {ret!r}')
  129. except KeyboardInterrupt:
  130. msg('\nDone')
  131. def tt_urand():
  132. cmsg('Testing _get_random_data_from_user():')
  133. from mmgen.crypto import Crypto
  134. ret = Crypto(cfg)._get_random_data_from_user(uchars=10, desc='data').decode()
  135. msg(f'USER ENTROPY (user input + keystroke timings):\n\n{fmt(ret, " ")}')
  136. times = ret.splitlines()[1:]
  137. avg_prec = sum(len(t.split('.')[1]) for t in times) // len(times)
  138. if avg_prec < gc.min_time_precision:
  139. ymsg(f'WARNING: Avg. time precision of only {avg_prec} decimal points. User entropy quality is degraded!')
  140. else:
  141. msg(f'Average time precision: {avg_prec} decimal points - OK')
  142. line_input(cfg, 'Press ENTER to continue: ')
  143. def tt_txview():
  144. cmsg('Testing tx.info.view_with_prompt() (try each viewing option)')
  145. from mmgen.tx import UnsignedTX
  146. fn = 'test/ref/0B8D5A[15.31789,14,tl=1320969600].rawtx'
  147. tx = UnsignedTX(cfg=cfg, filename=fn, quiet_open=True)
  148. while True:
  149. tx.info.view_with_prompt('View data for transaction?', pause=False)
  150. set_vt100()
  151. if not keypress_confirm(cfg, 'Continue testing transaction view?', default_yes=True):
  152. break
  153. def tt_get_char_one():
  154. _tt_get_char(one_char=True)
  155. def tt_get_char_one_raw():
  156. _tt_get_char(one_char=True, raw=True)
  157. def tt_get_char():
  158. _tt_get_char(one_char=False)
  159. def tt_get_char_immed_chars():
  160. _tt_get_char(one_char=False, immed_chars='asdf')
  161. def tt_get_char_raw():
  162. _tt_get_char(one_char=False, raw=True)
  163. def tt_get_char_one_char_immed_chars():
  164. _tt_get_char(one_char=True, immed_chars='asdf')
  165. get_term().register_cleanup()
  166. if cfg._args:
  167. locals()['tt_'+cfg._args[0]]()
  168. else:
  169. for command in commands:
  170. locals()['tt_'+command]()
  171. gmsg('\nTest completed')