1"""
2Unit tests for nyx.
3"""
4
5import collections
6import inspect
7import os
8import time
9import unittest
10
11import nyx.curses
12
13from nyx import expand_path, chroot, join, uses_settings
14
15try:
16  # added in python 3.3
17  from unittest.mock import Mock, patch
18except ImportError:
19  from mock import Mock, patch
20
21__all__ = [
22  'arguments',
23  'curses',
24  'installation',
25  'log',
26  'menu',
27  'panel',
28  'popups',
29  'tracker',
30]
31
32NYX_BASE = os.path.sep.join(__file__.split(os.path.sep)[:-2])
33OUR_SCREEN_SIZE = None
34TEST_SCREEN_SIZE = nyx.curses.Dimensions(80, 25)
35
36RenderResult = collections.namedtuple('RenderResult', ['content', 'return_value', 'runtime'])
37
38
39def require_curses(func):
40  """
41  Skips the test unless curses is available with a minimal dimension needed by
42  our tests.
43  """
44
45  if OUR_SCREEN_SIZE is None:
46    def _check_screen_size():
47      global OUR_SCREEN_SIZE
48      OUR_SCREEN_SIZE = nyx.curses.screen_size()
49
50    nyx.curses.start(_check_screen_size)
51
52  def wrapped(self, *args, **kwargs):
53    if OUR_SCREEN_SIZE.width < TEST_SCREEN_SIZE.width:
54      self.skipTest("screen isn't wide enough")
55    elif OUR_SCREEN_SIZE.height < TEST_SCREEN_SIZE.height:
56      self.skipTest("screen isn't tall enough")
57    else:
58      with patch('nyx.curses.screen_size', Mock(return_value = TEST_SCREEN_SIZE)):
59        return func(self, *args, **kwargs)
60
61  return wrapped
62
63
64class mock_keybindings(object):
65  """
66  Mocks the given keyboard inputs.
67  """
68
69  def __init__(self, *keys):
70    self._mock = patch('nyx.curses.key_input', side_effect = [nyx.curses.KeyInput(key) for key in keys])
71
72  def __enter__(self, *args):
73    self._mock.__enter__(*args)
74
75  def __exit__(self, *args):
76    self._mock.__exit__(*args)
77
78
79def render(func, *args, **kwargs):
80  """
81  Runs the given curses function, providing content that's rendered on the
82  screen. If the function starts with an argument named 'subwindow' then it's
83  provided one through :func:`~nyx.curses.draw`.
84
85  :param function func: draw function to be invoked
86
87  :returns: :data:`~test.RenderResult` with information about what was rendered
88  """
89
90  attr = {}
91
92  def draw_func():
93    nyx.curses._disable_acs()
94    nyx.curses.CURSES_SCREEN.erase()
95    start_time = time.time()
96
97    func_args = inspect.getargspec(func).args
98
99    if func_args[:1] == ['subwindow'] or func_args[:2] == ['self', 'subwindow']:
100      def _draw(subwindow):
101        return func(subwindow, *args, **kwargs)
102
103      attr['return_value'] = nyx.curses.draw(_draw)
104    else:
105      attr['return_value'] = func(*args, **kwargs)
106
107    attr['runtime'] = time.time() - start_time
108    attr['content'] = nyx.curses.screenshot()
109
110  with patch('nyx.curses.key_input', return_value = nyx.curses.KeyInput(27)):
111    nyx.curses.start(draw_func, transparent_background = True, cursor = False)
112
113  return RenderResult(attr.get('content'), attr.get('return_value'), attr.get('runtime'))
114
115
116class TestBaseUtil(unittest.TestCase):
117  def setUp(self):
118    nyx.CHROOT = None
119
120  def tearDown(self):
121    nyx.CHROOT = None
122
123  @patch('nyx.chroot', Mock(return_value = ''))
124  @patch('nyx.tor_controller', Mock())
125  @patch('stem.util.system.cwd', Mock(return_value = '/your_cwd'))
126  def test_expand_path(self):
127    self.assertEqual('/absolute/path/to/torrc', expand_path('/absolute/path/to/torrc'))
128    self.assertEqual('/your_cwd/torrc', expand_path('torrc'))
129
130  @patch('nyx.chroot', Mock(return_value = '/chroot'))
131  @patch('nyx.tor_controller', Mock())
132  @patch('stem.util.system.cwd', Mock(return_value = '/your_cwd'))
133  def test_expand_path_with_chroot(self):
134    self.assertEqual('/chroot/absolute/path/to/torrc', expand_path('/absolute/path/to/torrc'))
135    self.assertEqual('/chroot/your_cwd/torrc', expand_path('torrc'))
136
137  @patch('platform.system', Mock(return_value = 'Linux'))
138  @patch('os.path.exists', Mock(return_value = True))
139  @uses_settings
140  def test_chroot_uses_config(self, config):
141    config.set('tor_chroot', '/chroot/path')
142    self.assertEqual('/chroot/path', chroot())
143    config.set('tor_chroot', None)
144
145  @patch('platform.system', Mock(return_value = 'Linux'))
146  @patch('os.path.exists', Mock(return_value = False))
147  @uses_settings
148  def test_chroot_requires_path_to_exist(self, config):
149    config.set('tor_chroot', '/chroot/path')
150    self.assertEqual('', chroot())
151    config.set('tor_chroot', None)
152
153  def test_join(self):
154    # check our pydoc examples
155
156    self.assertEqual('This is a looooong', join(['This', 'is', 'a', 'looooong', 'message'], size = 18))
157    self.assertEqual('This is a', join(['This', 'is', 'a', 'looooong', 'message'], size = 17))
158    self.assertEqual('', join(['This', 'is', 'a', 'looooong', 'message'], size = 2))
159
160    # include a joining character
161
162    self.assertEqual('Download: 5 MB, Upload: 3 MB', join(['Download: 5 MB', 'Upload: 3 MB', 'Other: 2 MB'], ', ', 30))
163