term.py 5.2 KB

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