class TestGetManager(TestCase): def setUp(self): self.tty = MockTTY() def tearDown(self): self.tty.close() def test_get_manager_tty(self): # stdout is attached to a tty with redirect_output('stdout', self.tty.stdout): self.assertTrue(sys.stdout.isatty()) manager = _manager.get_manager(unit='knights') self.assertIsInstance(manager, _manager.Manager) self.assertTrue('unit' in manager.defaults) self.assertTrue('enabled' in manager.defaults) self.assertTrue(manager.enabled) self.assertTrue(manager.defaults['enabled']) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_get_manager_notty(self): # stdout is not attached to a tty with redirect_output('stdout', OUTPUT): self.assertFalse(sys.stdout.isatty()) manager = _manager.get_manager(unit='knights') self.assertIsInstance(manager, _manager.Manager) self.assertTrue('unit' in manager.defaults) self.assertFalse(manager.enabled) self.assertTrue('enabled' in manager.defaults) self.assertFalse(manager.defaults['enabled'])
def test_at_exit(self): tty = MockTTY() with mock.patch('%s.reset' % TERMINAL) as reset: manager = _manager.Manager(stream=tty.stdout, counter_class=MockCounter) term = manager.term # process_exit is False manager._at_exit() self.assertFalse(reset.called) # No output tty.stdout.write('X\n') self.assertEqual(tty.stdread.readline(), 'X\n') # process_exit is True, set_scroll False manager.process_exit = True manager.set_scroll = False manager._at_exit() self.assertFalse(reset.called) self.assertEqual(tty.stdread.readline(), term.move(25, 0) + term.cud1) # process_exit is True, set_scroll True manager.set_scroll = True manager._at_exit() self.assertEqual(reset.call_count, 1) self.assertEqual(tty.stdread.readline(), term.cud1) tty.close() manager._at_exit()
def setUp(self): self.tty = MockTTY() self.manager = MockManager(stream=self.tty.stdout) self.parent = Counter(total=10, desc='Test', unit='ticks', manager=self.manager)
def setUp(self): self.tty = MockTTY() self.manager = MockManager(stream=self.tty.stdout) self.ctr = Counter(total=10, desc='Test', unit='ticks', manager=self.manager) self.manager.counters[self.ctr] = 3 self.output = r'Test 0%\|[ ]+ \| 0/10 \[00:0\d<\?, 0.00 ticks/s\]'
class TestTerminal(TestCase): """ This is hard to test, so, for most tests, we'll just make sure the codes get passed through a tty """ def setUp(self): self.tty = MockTTY() self.terminal = _terminal.Terminal(stream=self.tty.stdout, kind='xterm-256color') def tearDown(self): self.tty.close() def test_caching(self): """ Make sure cached values are held. Return values aren't accurate for blessed, but are sufficient for this test """ handw = 'enlighten._terminal._Terminal._height_and_width' with mock.patch(handw, return_value=(1, 2)): self.assertEqual(self.terminal._height_and_width(), (1, 2)) with mock.patch(handw, return_value=(5, 6)): self.assertEqual(self.terminal._height_and_width(), (1, 2)) self.terminal.clear_cache() self.assertEqual(self.terminal._height_and_width(), (5, 6)) def test_reset(self): self.terminal.reset() self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), self.terminal.normal_cursor + self.terminal.csr(0, 24) + self.terminal.move(25, 0) + 'X\n') def test_feed(self): self.terminal.feed() self.assertEqual(self.tty.stdread.readline(), self.terminal.cud1) def test_change_scroll(self): self.terminal.change_scroll(4) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), self.terminal.hide_cursor + self.terminal.csr(0, 4) + self.terminal.move(4, 0) + 'X\n') def test_move_to(self): self.terminal.move_to(5, 10) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), self.terminal.move(10, 5) + 'X\n')
def test_at_exit(self): tty = MockTTY() with mock.patch('%s.reset' % TERMINAL) as reset: with mock.patch.object(tty, 'stdout', wraps=tty.stdout) as mockstdout: manager = _manager.Manager(stream=tty.stdout, counter_class=MockCounter) term = manager.term # process_exit is False manager._at_exit() self.assertFalse(reset.called) self.assertFalse(mockstdout.flush.called) # No output tty.stdout.write(u'X\n') self.assertEqual(tty.stdread.readline(), 'X\n') # process_exit is True, set_scroll False manager.process_exit = True manager.set_scroll = False manager._at_exit() self.assertFalse(reset.called) self.assertEqual(mockstdout.flush.call_count, 1) self.assertEqual(tty.stdread.readline(), term.move(25, 0) + term.cud1) # process_exit is True, set_scroll True manager.set_scroll = True manager._at_exit() self.assertEqual(reset.call_count, 1) self.assertEqual(mockstdout.flush.call_count, 2) self.assertEqual(tty.stdread.readline(), term.cud1) # Ensure companion stream gets flushed manager.companion_stream = tty.stdout manager._at_exit() self.assertEqual(reset.call_count, 2) self.assertEqual(mockstdout.flush.call_count, 4) self.assertEqual(tty.stdread.readline(), term.cud1) term = manager.term # Ensure no errors if tty closes before _at_exit is called tty.close() manager._at_exit()
def test_at_exit(self): tty = MockTTY() try: with mock.patch.object(tty, 'stdout', wraps=tty.stdout) as mockstdout: manager = enlighten.Manager(stream=tty.stdout, counter_class=MockCounter) term = manager.term reset = (term.normal_cursor + term.csr(0, term.height - 1) + term.move(term.height, 0)) # process_exit is False manager._at_exit() self.assertFalse(mockstdout.flush.called) # No output tty.stdout.write(u'X\n') self.assertEqual(tty.stdread.readline(), 'X\n') # process_exit is True, set_scroll False manager.process_exit = True manager.set_scroll = False manager._at_exit() self.assertEqual(mockstdout.flush.call_count, 1) self.assertEqual(tty.stdread.readline(), term.move(25, 0) + term.cud1) # process_exit is True, set_scroll True manager.set_scroll = True manager._at_exit() self.assertEqual(mockstdout.flush.call_count, 2) self.assertEqual(tty.stdread.readline(), reset + term.cud1) # Ensure companion stream gets flushed manager.companion_stream = tty.stdout manager._at_exit() self.assertEqual(mockstdout.flush.call_count, 4) self.assertEqual(tty.stdread.readline(), reset + term.cud1) term = manager.term finally: # Ensure no errors if tty closes before _at_exit is called tty.close() manager._at_exit()
class TestSubCounter(TestCase): """ Test the BaseCounter class """ def setUp(self): self.tty = MockTTY() self.manager = MockManager(stream=self.tty.stdout) self.parent = Counter(total=10, desc='Test', unit='ticks', manager=self.manager) def tearDown(self): self.tty.close() def test_init(self): """Ensure initial values are set""" counter = enlighten._counter.SubCounter(self.parent) self.assertIsNone(counter.color) self.assertEqual(counter.count, 0) self.assertFalse(counter.all_fields) self.assertIs(counter.parent, self.parent) self.assertIs(counter.manager, self.manager) self.parent.count = 4 counter = enlighten._counter.SubCounter(self.parent, color='green', count=4, all_fields=True) self.assertEqual(counter.color, 'green') self.assertEqual(counter.count, 4) self.assertTrue(counter.all_fields) with self.assertRaisesRegex(ValueError, 'Invalid count: 6'): counter = enlighten._counter.SubCounter(self.parent, count=6) def test_update(self): """Increment and update parent""" counter = enlighten._counter.SubCounter(self.parent) self.assertEqual(counter.count, 0) self.assertEqual(self.parent.count, 0) counter.update() self.assertEqual(counter.count, 1) self.assertEqual(self.parent.count, 1) self.parent.update(3) self.assertEqual(counter.count, 1) self.assertEqual(self.parent.count, 4) counter.update(2) self.assertEqual(counter.count, 3) self.assertEqual(self.parent.count, 6) def test_update_from_invalid_source(self): """Must be peer or parent""" counter = enlighten._counter.SubCounter(self.parent) notparent = Counter(manager=self.manager) with self.assertRaisesRegex(ValueError, 'source must be parent or peer'): counter.update_from(notparent) notpeer = enlighten._counter.SubCounter(notparent) with self.assertRaisesRegex(ValueError, 'source must be parent or peer'): counter.update_from(notpeer) def test_update_from_invalid_incr(self): """Increment can't make source negative""" counter = enlighten._counter.SubCounter(self.parent) with self.assertRaisesRegex(ValueError, 'Invalid increment: 1'): counter.update_from(self.parent) self.parent.count = 4 peer = enlighten._counter.SubCounter(self.parent, count=3) self.parent._subcounters.append(peer) with self.assertRaisesRegex(ValueError, 'Invalid increment: 4'): counter.update_from(peer, 4) with self.assertRaisesRegex(ValueError, 'Invalid increment: 2'): counter.update_from(self.parent, 2) def test_update_from_parent(self): """ subcounter should gain increment, parent should remain unchanged """ counter = enlighten._counter.SubCounter(self.parent) self.parent.count = 4 with mock.patch.object(self.parent, 'update', wraps=self.parent.update) as update: counter.update_from(self.parent) update.assert_called_with(0, False) self.assertEqual(self.parent.count, 4) self.assertEqual(counter.count, 1) counter.update_from(self.parent, 2) update.assert_called_with(0, False) self.assertEqual(self.parent.count, 4) self.assertEqual(counter.count, 3) def test_update_from_peer(self): """ Peer should lose increment, subcounter should gain increment """ counter = enlighten._counter.SubCounter(self.parent) self.parent.count = 6 peer = enlighten._counter.SubCounter(self.parent, count=4) with mock.patch.object(self.parent, 'update', wraps=self.parent.update) as update: counter.update_from(peer) update.assert_called_with(0, False) self.assertEqual(self.parent.count, 6) self.assertEqual(counter.count, 1) self.assertEqual(peer.count, 3) counter.update_from(peer, 3) update.assert_called_with(0, False) self.assertEqual(self.parent.count, 6) self.assertEqual(counter.count, 4) self.assertEqual(peer.count, 0)
class TestCounter(TestCase): """ Test the Counter classes We default to using enlighten.Counter and only use enlighten._counter.Counter when necessary """ def setUp(self): self.tty = MockTTY() self.manager = MockManager(stream=self.tty.stdout) self.ctr = Counter(total=10, desc='Test', unit='ticks', manager=self.manager) self.manager.counters[self.ctr] = 3 self.output = r'Test 0%\|[ ]+ \| 0/10 \[00:0\d<\?, 0.00 ticks/s\]' def tearDown(self): self.tty.close() def test_no_manager(self): """Raise an error if there is no manager specified""" with self.assertRaisesRegex(TypeError, 'manager must be specified'): enlighten._counter.Counter() enlighten._counter.Counter(manager=self.manager) def test_increment(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.count, 1) counter.update(5) self.assertEqual(counter.count, 6) def test_enabled(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.output, [1]) counter.update() self.assertEqual(counter.output, [1, 2]) counter.enabled = False counter.update() self.assertEqual(counter.output, [1, 2]) def test_delta(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.output, [1]) counter.update() self.assertEqual(counter.output, [1, 2]) counter.min_delta = 500 counter.update() self.assertEqual(counter.output, [1, 2]) counter.min_delta = .01 time.sleep(.01) counter.update() self.assertEqual(counter.output, [1, 2, 4]) def test_force(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.output, [1]) counter.min_delta = 500 counter.update() self.assertEqual(counter.output, [1]) counter.update(force=True) self.assertEqual(counter.output, [1, 3]) def test_refresh_total(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.output, [1]) counter.min_delta = 500 counter.update() self.assertEqual(counter.output, [1]) counter.update(98) self.assertEqual(counter.output, [1, 100]) def test_position(self): self.assertEqual(self.ctr.position, 3) def test_elapsed(self): ctr = self.ctr ctr.start = time.time() - 5.0 ctr.last_update = ctr.start + 3.0 self.assertEqual(int(ctr.elapsed), 5) # Clock stops running when total is reached ctr.count = ctr.total self.assertEqual(int(ctr.elapsed), 3) def test_refresh(self): self.ctr.refresh() self.assertRegex( self.manager.output[0], r'write\(output=%s, flush=True, position=3\)' % self.output) self.manager.output = [] self.ctr.refresh(flush=False) self.assertRegex( self.manager.output[0], r'write\(output=%s, flush=False, position=3\)' % self.output) self.manager.output = [] self.ctr.enabled = False self.ctr.refresh() self.assertEqual(len(self.manager.output), 0) def test_clear(self): self.ctr.clear() self.assertRegex(self.manager.output[0], r'write\(output=, flush=True, position=3\)') self.manager.output = [] self.ctr.clear(flush=False) self.assertRegex(self.manager.output[0], r'write\(output=, flush=False, position=3\)') self.manager.output = [] self.ctr.enabled = False self.ctr.clear() self.assertEqual(len(self.manager.output), 0) def test_get_subcounter(self): self.ctr.count = 6 subcounter1 = self.ctr.add_subcounter('green') subcounter2 = self.ctr.add_subcounter('red', all_fields=True) subcounter2.count = 4 subcounter3 = self.ctr.add_subcounter('white', count=1, all_fields=True) subcounters, fields = self.ctr._get_subcounters(8) self.assertEqual(subcounters, [(subcounter1, 0.0), (subcounter2, 0.4), (subcounter3, 0.1)]) self.assertEqual( fields, { 'percentage_1': 0.0, 'percentage_2': 40.0, 'percentage_3': 10.0, 'count_1': 0, 'count_2': 4, 'count_3': 1, 'rate_2': 0.5, 'eta_2': '00:12', 'rate_3': 0.0, 'eta_3': '?' }) subcounters, fields = self.ctr._get_subcounters(0) self.assertEqual(subcounters, [(subcounter1, 0.0), (subcounter2, 0.4), (subcounter3, 0.1)]) self.assertEqual( fields, { 'percentage_1': 0.0, 'percentage_2': 40.0, 'percentage_3': 10.0, 'count_1': 0, 'count_2': 4, 'count_3': 1, 'rate_2': 0.0, 'eta_2': '?', 'rate_3': 0.0, 'eta_3': '?' }) self.ctr = Counter(total=0, desc='Test', unit='ticks', manager=self.manager) subcounter1 = self.ctr.add_subcounter('red', all_fields=True) subcounters, fields = self.ctr._get_subcounters(8) self.assertEqual(subcounters, [(subcounter1, 0.0)]) self.assertEqual(fields, { 'percentage_1': 0.0, 'count_1': 0, 'rate_1': 0.0, 'eta_1': '00:00' }) def test_remove(self): self.ctr.leave = False self.assertTrue(self.ctr in self.manager.counters) self.ctr.close() self.assertRegex( self.manager.output[0], r'write\(output=%s, flush=True, position=3\)' % self.output) self.assertFalse(self.ctr in self.manager.counters) # If it runs again, it shouldn't throw an error self.ctr.close() def test_format_no_total(self): # No unit, No desc ctr = Counter(stream=self.tty.stdout, ) self.assertRegex(ctr.format(width=80), r'0 \[00:0\d, 0.00/s\]') ctr.count = 50 ctr.start = time.time() - 50 self.assertRegex(ctr.format(width=80), r'50 \[00:5\d, \d.\d\d/s\]') # With unit and description ctr = Counter(stream=self.tty.stdout, desc='Test', unit='ticks') rtn = ctr.format(width=80) self.assertEqual(len(rtn), 80) self.assertRegex(rtn, r'Test 0 ticks \[00:0\d, 0.00 ticks/s\]') ctr.count = 50 ctr.start = time.time() - 50 rtn = ctr.format(width=80) self.assertEqual(len(rtn), 80) self.assertRegex(rtn, r'Test 50 ticks \[00:5\d, \d.\d\d ticks/s\]') def test_format_count_gt_total(self): """ Counter should fall back to no-total format if count is greater than total """ ctr = Counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks') ctr.count = 50 ctr.start = time.time() - 50 rtn = ctr.format(width=80) self.assertEqual(len(rtn), 80) self.assertRegex(rtn, r'Test 50 ticks \[00:5\d, \d.\d\d ticks/s\]') def test_no_count(self): """ Test for an empty counter """ ctr = Counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks') formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 0%\|[ ]+ \| 0/10 \[00:0\d<\?, 0.00 ticks/s\]') # No unit, no description ctr = Counter(stream=self.tty.stdout, total=10) formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) self.assertRegex(formatted, r' 0%\|[ ]+ \| 0/10 \[00:0\d<\?, 0.00/s\]') def test_full_bar(self): ctr = Counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks') ctr.count = 10 ctr.start = time.time() - 10 formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 100%\|' + u'█+' + r'\| 10/10 \[00:\d\d<00:00, \d.\d\d ticks/s\]') def test_zero_total(self): """ If the total is 0, the bar should be full """ ctr = Counter(stream=self.tty.stdout, total=0, desc='Test', unit='ticks') formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 100%\|' u'█+' + r'\| 0/0 \[00:0\d<00:00, 0.00 ticks/s\]') def test_auto_offset(self): """ If offset is not specified, terminal codes should be automatically ignored when calculating bar length """ barFormat = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}|{count:{len_total}d}/{total:d} ' + \ u'[{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]' blueBarFormat = self.manager.term.blue(barFormat) self.assertNotEqual(len(barFormat), len(blueBarFormat)) ctr = self.manager.counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks', count=10, bar_format=barFormat) formatted1 = ctr.format(width=80) self.assertEqual(len(formatted1), 80) barLen1 = formatted1.count(u'█') offset = len(self.manager.term.blue('')) ctr = self.manager.counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks', count=10, bar_format=blueBarFormat) formatted2 = ctr.format(width=80) self.assertEqual(len(formatted2), 80 + offset) barLen2 = formatted2.count(u'█') self.assertTrue(barLen2 == barLen1) def test_offset(self): """ Offset reduces count of printable characters when formatting """ barFormat = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}|{count:{len_total}d}/{total:d} ' + \ u'[{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]' barFormat = self.manager.term.blue(barFormat) ctr = self.manager.counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks', count=10, bar_format=barFormat, offset=0) formatted1 = ctr.format(width=80) self.assertEqual(len(formatted1), 80) barLen1 = formatted1.count(u'█') offset = len(self.manager.term.blue('')) ctr = self.manager.counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks', count=10, bar_format=barFormat, offset=offset) formatted2 = ctr.format(width=80) self.assertEqual(len(formatted2), 80 + offset) barLen2 = formatted2.count(u'█') self.assertTrue(barLen2 == barLen1 + offset) # Test in counter format ctr = self.manager.counter(stream=self.tty.stdout, total=10, count=50, offset=0) formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) ctr = self.manager.counter(stream=self.tty.stdout, total=10, count=50, offset=10) formatted = ctr.format(width=80) self.assertEqual(len(formatted), 90) def test_partial_bar(self): ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks') ctr.count = 50 formatted = ctr.format(elapsed=50, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 50%\|' + u'█+[▏▎▍▌▋▊▉]?' + r'[ ]+\| 50/100 \[00:5\d<00:5\d, \d.\d\d ticks/s\]') ctr.count = 13 formatted = ctr.format(elapsed=13, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 13%\|' + u'█+[▏▎▍▌▋▊▉]?' + r'[ ]+\| 13/100 \[00:1\d<01:\d\d, \d.\d\d ticks/s\]') # Explicit test ctr.bar_format = u'{bar}' ctr.count = 50 formatted = ctr.format(width=10) self.assertEqual(formatted, u'█████ ') ctr.count = 13 formatted = ctr.format(width=10) self.assertEqual(formatted, u'█▎ ') def test_custom_series(self): ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks', series=[' ', '>', '-']) ctr.count = 50 formatted = ctr.format(elapsed=50, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 50%\|' + u'-+[>]?' + r'[ ]+\| 50/100 \[00:5\d<00:5\d, \d.\d\d ticks/s\]') ctr.count = 13 formatted = ctr.format(elapsed=13, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 13%\|' + u'---->' + r'[ ]+\| 13/100 \[00:1\d<01:\d\d, \d.\d\d ticks/s\]') ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks', series=[u'⭘', u'⬤']) ctr.count = 50 formatted = ctr.format(elapsed=50, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 50%\|' + u'⬤+⭘+' + r'\| 50/100 \[00:5\d<00:5\d, \d.\d\d ticks/s\]') def test_direct(self): ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks') self.assertIsInstance(ctr.manager, Manager) ctr.start = time.time() - 50 ctr.update(50, force=True) self.tty.stdout.write('X\n') value = self.tty.stdread.readline() if NEEDS_UNICODE_HELP: value = value.decode('utf-8') self.assertRegex( value, r'Test 50%\|' + u'█+[▏▎▍▌▋▊▉]?' + r'[ ]+\| 50/100 \[00:5\d<00:5\d, \d.\d\d ticks/s\]X\n') with mock.patch.object(self.tty, 'stdout', wraps=self.tty.stdout) as mockstdout: mockstdout.encoding = None ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks') ctr.refresh(flush=False) self.assertFalse(mockstdout.flush.called) ctr.refresh(flush=True) self.assertTrue(mockstdout.flush.called) def test_floats(self): """ Using floats for total and count is supported by the logic, but not by the default format strings """ ctr = Counter(stream=self.tty.stdout, total=100.2, desc='Test', unit='ticks', min_delta=500) ctr.update(50.1) self.assertEqual(ctr.count, 50.1) # Won't work with default formatting with self.assertRaises(ValueError): formatted = ctr.format(elapsed=50.1) ctr.bar_format = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}| {count:.1f}/{total:.1f} ' + \ u'[{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]' formatted = ctr.format(elapsed=50.1, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 50%\|' + u'█+' + r'[ ]+\| 50.1/100.2 \[00:5\d<00:5\d, \d.\d\d ticks/s\]') def test_color(self): """ Only bar characters should be colorized """ ctr = Counter(stream=self.tty.stdout, total=100, bar_format=u'|{bar}|', count=50, color='red') terminal = ctr.manager.term formatted = ctr.format(width=80) self.assertEqual(formatted, '|' + terminal.red(u'█' * 39 + ' ' * 39) + '|') def test_subcounter(self): """ When subcounter is present, bar will be drawn in multiple colors """ ctr = Counter(stream=self.tty.stdout, total=100, bar_format=u'{bar}') terminal = ctr.manager.term ctr.count = 50 subcounter1 = ctr.add_subcounter('yellow', all_fields=True) subcounter1.count = 5 ctr.add_subcounter('blue', count=10) formatted = ctr.format(width=80) bartext = terminal.blue(u'█' * 8) + terminal.yellow( u'█' * 4) + u'█' * 28 + ' ' * 40 self.assertEqual(formatted, bartext) ctr.bar_format = u'{count_0} {percentage_0} | {count_1} {percentage_1} {rate_1} {eta_1}' + \ u' | {count_2} {percentage_2}' formatted = ctr.format(elapsed=5, width=80) self.assertEqual(formatted, u'35 35.0 | 5 5.0 1.0 01:35 | 10 10.0') def test_close(self): manager = mock.Mock() # Clear is False ctr = MockCounter(manager=manager) ctr.close() self.assertEqual(ctr.calls, ['refresh(flush=True, elapsed=None)']) self.assertEqual(manager.remove.call_count, 1) # Clear is True, leave is True ctr = MockCounter(manager=manager, leave=True) ctr.close(clear=True) self.assertEqual(ctr.calls, ['refresh(flush=True, elapsed=None)']) self.assertEqual(manager.remove.call_count, 2) # Clear is True, leave is False ctr = MockCounter(manager=manager, leave=False) ctr.close(clear=True) self.assertEqual(ctr.calls, ['clear(flush=True)']) self.assertEqual(manager.remove.call_count, 3) def test_context_manager(self): mgr = Manager(stream=self.tty.stdout, enabled=False) with mgr.counter(total=10, leave=False) as ctr: self.assertTrue(ctr in mgr.counters) ctr.update() self.assertFalse(ctr in mgr.counters) def test_add_subcounter(self): self.assertEqual(self.ctr._subcounters, []) subcounter1 = self.ctr.add_subcounter('blue') self.assertEqual(len(self.ctr._subcounters), 1) self.assertEqual(self.ctr.subcount, 0) self.assertIs(self.ctr._subcounters[0], subcounter1) self.assertEqual(subcounter1.count, 0) self.assertFalse(subcounter1.all_fields) with self.assertRaisesRegex(ValueError, 'Invalid count: 5'): self.ctr.add_subcounter('yellow', count=5, all_fields=True) self.ctr.count = 5 subcounter2 = self.ctr.add_subcounter('yellow', count=5, all_fields=True) self.assertEqual(len(self.ctr._subcounters), 2) self.assertEqual(self.ctr.subcount, 5) self.assertIs(self.ctr._subcounters[1], subcounter2) self.assertEqual(subcounter2.count, 5) self.assertTrue(subcounter2.all_fields)
class TestCounter(TestCase): """ Test the Counter classes We default to using enlighten.Counter and only use enlighten._counter.Counter when necessary """ def setUp(self): self.tty = MockTTY() self.manager = MockManager(stream=self.tty.stdout) self.ctr = Counter(total=10, desc='Test', unit='ticks', manager=self.manager) self.manager.counters[self.ctr] = 3 self.output = r'Test 0%\|[ ]+ \| 0/10 \[00:0\d<\?, 0.00 ticks/s\]' def tearDown(self): self.tty.close() def test_repr(self): self.assertEqual( repr(self.ctr), "Counter(desc='Test', total=10, count=0, unit='ticks')") def test_repr_subcounter(self): self.ctr.count = 2 subcounter = self.ctr.add_subcounter('green', count=1) self.assertEqual( repr(subcounter), "SubCounter(count=1, color='green', all_fields=False)") def test_no_manager(self): """Raise an error if there is no manager specified""" with self.assertRaisesRegex(TypeError, 'manager must be specified'): enlighten._counter.Counter() enlighten._counter.Counter(manager=self.manager) def test_increment(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.count, 1) counter.update(5) self.assertEqual(counter.count, 6) def test_enabled(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.output, [1]) counter.update() self.assertEqual(counter.output, [1, 2]) counter.enabled = False counter.update() self.assertEqual(counter.output, [1, 2]) def test_delta(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.output, [1]) counter.update() self.assertEqual(counter.output, [1, 2]) counter.min_delta = 500 counter.update() self.assertEqual(counter.output, [1, 2]) counter.min_delta = .01 time.sleep(.01) counter.update() self.assertEqual(counter.output, [1, 2, 4]) def test_force(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.output, [1]) counter.min_delta = 500 counter.update() self.assertEqual(counter.output, [1]) counter.update(force=True) self.assertEqual(counter.output, [1, 3]) def test_refresh_total(self): counter = MockCounter(total=100, min_delta=0, manager=self.manager) counter.update() self.assertEqual(counter.output, [1]) counter.min_delta = 500 counter.update() self.assertEqual(counter.output, [1]) counter.update(98) self.assertEqual(counter.output, [1, 100]) def test_position(self): self.assertEqual(self.ctr.position, 3) def test_elapsed(self): ctr = self.ctr ctr.start = time.time() - 5.0 ctr.last_update = ctr.start + 3.0 self.assertEqual(int(ctr.elapsed), 5) # Clock stops running when total is reached ctr.count = ctr.total self.assertEqual(int(ctr.elapsed), 3) def test_refresh(self): self.ctr.last_update = 0 self.ctr.refresh() self.assertRegex( self.manager.output[0], r'write\(output=%s, flush=True, position=3\)' % self.output) self.assertAlmostEqual(self.ctr.last_update, time.time(), delta=0.3) self.manager.output = [] self.ctr.refresh(flush=False) self.assertRegex( self.manager.output[0], r'write\(output=%s, flush=False, position=3\)' % self.output) self.manager.output = [] self.ctr.enabled = False self.ctr.refresh() self.assertEqual(len(self.manager.output), 0) def test_clear(self): self.ctr.last_update = 100 self.ctr.clear() self.assertRegex(self.manager.output[0], r'write\(output=, flush=True, position=3\)') self.assertEqual(self.ctr.last_update, 0) self.manager.output = [] self.ctr.clear(flush=False) self.assertRegex(self.manager.output[0], r'write\(output=, flush=False, position=3\)') self.manager.output = [] self.ctr.enabled = False self.ctr.clear() self.assertEqual(len(self.manager.output), 0) def test_get_subcounter(self): self.ctr.count = 6 subcounter1 = self.ctr.add_subcounter('green') subcounter2 = self.ctr.add_subcounter('red', all_fields=True) subcounter2.count = 4 subcounter3 = self.ctr.add_subcounter('white', count=1, all_fields=True) subcounters, fields = self.ctr._get_subcounters(8) self.assertEqual(subcounters, [(subcounter1, 0.0), (subcounter2, 0.4), (subcounter3, 0.1)]) self.assertEqual( fields, { 'percentage_1': 0.0, 'percentage_2': 40.0, 'percentage_3': 10.0, 'count_1': 0, 'count_2': 4, 'count_3': 1, 'interval_2': 2.0, 'interval_3': 0.0, 'rate_2': 0.5, 'eta_2': '00:12', 'rate_3': 0.0, 'eta_3': '?' }) subcounters, fields = self.ctr._get_subcounters(0) self.assertEqual(subcounters, [(subcounter1, 0.0), (subcounter2, 0.4), (subcounter3, 0.1)]) self.assertEqual( fields, { 'percentage_1': 0.0, 'percentage_2': 40.0, 'percentage_3': 10.0, 'count_1': 0, 'count_2': 4, 'count_3': 1, 'interval_2': 0.0, 'interval_3': 0.0, 'rate_2': 0.0, 'eta_2': '?', 'rate_3': 0.0, 'eta_3': '?' }) self.ctr = Counter(total=0, desc='Test', unit='ticks', manager=self.manager) subcounter1 = self.ctr.add_subcounter('red', all_fields=True) subcounters, fields = self.ctr._get_subcounters(8) self.assertEqual(subcounters, [(subcounter1, 0.0)]) self.assertEqual( fields, { 'percentage_1': 0.0, 'count_1': 0, 'interval_1': 0.0, 'rate_1': 0.0, 'eta_1': '00:00' }) def test_get_subcounter_counter_format(self): self.ctr.count = 12 subcounter1 = self.ctr.add_subcounter('green') subcounter2 = self.ctr.add_subcounter('red', all_fields=True) subcounter2.count = 6 subcounter3 = self.ctr.add_subcounter('white', count=1, all_fields=True) subcounters, fields = self.ctr._get_subcounters(8, bar_fields=False) self.assertEqual(subcounters, [(subcounter1, 0.0), (subcounter2, 0.0), (subcounter3, 0.0)]) self.assertEqual( fields, { 'count_1': 0, 'count_2': 6, 'count_3': 1, 'interval_2': 0.75**-1, 'interval_3': 0.0, 'rate_2': 0.75, 'rate_3': 0.0 }) def test_remove(self): self.ctr.leave = False self.assertTrue(self.ctr in self.manager.counters) self.ctr.close() self.assertRegex( self.manager.output[0], r'write\(output=%s, flush=True, position=3\)' % self.output) self.assertFalse(self.ctr in self.manager.counters) # If it runs again, it shouldn't throw an error self.ctr.close() def test_format_no_total(self): # No unit, No desc ctr = Counter(stream=self.tty.stdout, ) self.assertRegex(ctr.format(width=80), r'0 \[00:0\d, 0.00/s\]') ctr.count = 50 ctr.start = time.time() - 50 self.assertRegex(ctr.format(width=80), r'50 \[00:5\d, \d.\d\d/s\]') # With unit and description ctr = Counter(stream=self.tty.stdout, desc='Test', unit='ticks') rtn = ctr.format(width=80) self.assertEqual(len(rtn), 80) self.assertRegex(rtn, r'Test 0 ticks \[00:0\d, 0.00 ticks/s\]') ctr.count = 50 ctr.start = time.time() - 50 rtn = ctr.format(width=80) self.assertEqual(len(rtn), 80) self.assertRegex(rtn, r'Test 50 ticks \[00:5\d, \d.\d\d ticks/s\]') def test_format_count_gt_total(self): """ Counter should fall back to no-total format if count is greater than total """ ctr = Counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks') ctr.count = 50 ctr.start = time.time() - 50 rtn = ctr.format(width=80) self.assertEqual(len(rtn), 80) self.assertRegex(rtn, r'Test 50 ticks \[00:5\d, \d.\d\d ticks/s\]') def test_no_count(self): """ Test for an empty counter """ ctr = Counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks') formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 0%\|[ ]+ \| 0/10 \[00:0\d<\?, 0.00 ticks/s\]') # No unit, no description ctr = Counter(stream=self.tty.stdout, total=10) formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) self.assertRegex(formatted, r' 0%\|[ ]+ \| 0/10 \[00:0\d<\?, 0.00/s\]') def test_full_bar(self): ctr = Counter(stream=self.tty.stdout, total=10, desc='Test', unit='ticks', series=SERIES_STD) ctr.count = 10 ctr.start = time.time() - 10 formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 100%\|' + u'█+' + r'\| 10/10 \[00:\d\d<00:00, \d.\d\d ticks/s\]') def test_zero_total(self): """ If the total is 0, the bar should be full """ ctr = Counter(stream=self.tty.stdout, total=0, desc='Test', unit='ticks', series=SERIES_STD) formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 100%\|' u'█+' + r'\| 0/0 \[00:0\d<00:00, 0.00 ticks/s\]') def test_auto_offset(self): """ If offset is not specified, terminal codes should be automatically ignored when calculating bar length """ barFormat = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}|{count:{len_total}d}/{total:d} ' + \ u'[{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]' blueBarFormat = self.manager.term.blue(barFormat) self.assertNotEqual(len(barFormat), len(blueBarFormat)) ctr = self.manager.counter(total=10, desc='Test', unit='ticks', count=10, bar_format=barFormat) formatted1 = ctr.format(width=80) self.assertEqual(len(formatted1), 80) barLen1 = formatted1.count(BLOCK) offset = len(self.manager.term.blue('')) ctr = self.manager.counter(total=10, desc='Test', unit='ticks', count=10, bar_format=blueBarFormat) formatted2 = ctr.format(width=80) self.assertEqual(len(formatted2), 80 + offset) barLen2 = formatted2.count(BLOCK) self.assertTrue(barLen2 == barLen1) def test_offset(self): """ Offset reduces count of printable characters when formatting """ barFormat = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}|{count:{len_total}d}/{total:d} ' + \ u'[{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]' barFormat = self.manager.term.blue(barFormat) ctr = self.manager.counter(total=10, desc='Test', unit='ticks', count=10, bar_format=barFormat, offset=0) formatted1 = ctr.format(width=80) self.assertEqual(len(formatted1), 80) barLen1 = formatted1.count(BLOCK) offset = len(self.manager.term.blue('')) ctr = self.manager.counter(total=10, desc='Test', unit='ticks', count=10, bar_format=barFormat, offset=offset) formatted2 = ctr.format(width=80) self.assertEqual(len(formatted2), 80 + offset) barLen2 = formatted2.count(BLOCK) self.assertTrue(barLen2 == barLen1 + offset) # Test in counter format ctr = self.manager.counter(total=10, count=50, offset=0) formatted = ctr.format(width=80) self.assertEqual(len(formatted), 80) ctr = self.manager.counter(total=10, count=50, offset=10) formatted = ctr.format(width=80) self.assertEqual(len(formatted), 90) def test_partial_bar(self): ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks', series=SERIES_STD) ctr.count = 50 formatted = ctr.format(elapsed=50, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 50%\|' + u'█+[▏▎▍▌▋▊▉]?' + r'[ ]+\| 50/100 \[00:5\d<00:5\d, \d.\d\d ticks/s\]') ctr.count = 13 formatted = ctr.format(elapsed=13, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 13%\|' + u'█+[▏▎▍▌▋▊▉]?' + r'[ ]+\| 13/100 \[00:1\d<01:\d\d, \d.\d\d ticks/s\]') # Explicit test ctr.bar_format = u'{bar}' ctr.count = 50 formatted = ctr.format(width=10) self.assertEqual(formatted, u'█████ ') ctr.count = 13 formatted = ctr.format(width=10) self.assertEqual(formatted, u'█▎ ') def test_custom_series(self): ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks', series=[' ', '>', '-']) ctr.count = 50 formatted = ctr.format(elapsed=50, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 50%\|' + u'-+[>]?' + r'[ ]+\| 50/100 \[00:5\d<00:5\d, \d.\d\d ticks/s\]') ctr.count = 13 formatted = ctr.format(elapsed=13, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 13%\|' + u'---->' + r'[ ]+\| 13/100 \[00:1\d<01:\d\d, \d.\d\d ticks/s\]') ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks', series=[u'⭘', u'⬤']) ctr.count = 50 formatted = ctr.format(elapsed=50, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 50%\|' + u'⬤+⭘+' + r'\| 50/100 \[00:5\d<00:5\d, \d.\d\d ticks/s\]') def test_direct(self): ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks', series=SERIES_STD) self.assertIsInstance(ctr.manager, Manager) ctr.start = time.time() - 50 ctr.update(50, force=True) self.tty.stdout.write(u'X\n') value = self.tty.stdread.readline() self.assertRegex( value, r'Test 50%\|' + u'█+[▏▎▍▌▋▊▉]?' + r'[ ]+\| 50/100 \[00:5\d<00:5\d, \d.\d\d ticks/s\]X\n') with mock.patch.object(self.tty, 'stdout', wraps=self.tty.stdout) as mockstdout: mockstdout.encoding = None ctr = Counter(stream=self.tty.stdout, total=100, desc='Test', unit='ticks') ctr.refresh(flush=False) self.assertFalse(mockstdout.flush.called) ctr.refresh(flush=True) self.assertTrue(mockstdout.flush.called) def test_floats(self): """ Using floats for total and count is supported by the logic, but not by the default format strings """ ctr = Counter(stream=self.tty.stdout, total=100.2, desc='Test', unit='ticks', min_delta=500, series=SERIES_STD) ctr.update(50.1) self.assertEqual(ctr.count, 50.1) # Won't work with default formatting with self.assertRaises(ValueError): formatted = ctr.format(elapsed=50.1) ctr.bar_format = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}| {count:.1f}/{total:.1f} ' + \ u'[{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]' formatted = ctr.format(elapsed=50.1, width=80) self.assertEqual(len(formatted), 80) self.assertRegex( formatted, r'Test 50%\|' + u'█+' + r'[ ]+\| 50.1/100.2 \[00:5\d<00:5\d, \d.\d\d ticks/s\]') def test_color(self): """ Only bar characters should be colorized """ ctr = Counter(stream=self.tty.stdout, total=100, bar_format=u'|{bar}|', count=50, color='red') terminal = ctr.manager.term formatted = ctr.format(width=80) self.assertEqual(formatted, '|' + terminal.red(BLOCK * 39 + ' ' * 39) + '|') def test_subcounter(self): """ When subcounter is present, bar will be drawn in multiple colors """ ctr = Counter(stream=self.tty.stdout, total=100, bar_format=u'{bar}') terminal = ctr.manager.term ctr.count = 50 subcounter1 = ctr.add_subcounter('yellow', all_fields=True) subcounter1.count = 5 ctr.add_subcounter('blue', count=10) formatted = ctr.format(width=80) bartext = terminal.blue(BLOCK * 8) + terminal.yellow( BLOCK * 4) + BLOCK * 28 + ' ' * 40 self.assertEqual(formatted, bartext) ctr.bar_format = u'{count_0} {percentage_0} | {count_1} {percentage_1} {rate_1} {eta_1}' + \ u' | {count_2} {percentage_2}' formatted = ctr.format(elapsed=5, width=80) self.assertEqual(formatted, u'35 35.0 | 5 5.0 1.0 01:35 | 10 10.0') def test_subcounter_count_gt_total(self): """ When total is exceeded, subcounter fields are still populated """ counter_format = u'{count_0} | {count_1} {rate_1} | {count_2}' ctr = Counter(stream=self.tty.stdout, total=100, counter_format=counter_format) ctr.count = 500 subcounter1 = ctr.add_subcounter('yellow', all_fields=True) subcounter1.count = 50 ctr.add_subcounter('blue', count=100) formatted = ctr.format(elapsed=50, width=80) self.assertEqual(formatted, u'350 | 50 1.0 | 100') def test_close(self): manager = MockManager() # Clear is False ctr = MockCounter(manager=manager) ctr.close() self.assertEqual(ctr.calls, ['refresh(flush=True, elapsed=None)']) self.assertEqual(manager.remove_calls, 1) # Clear is True, leave is True ctr = MockCounter(manager=manager, leave=True) ctr.close(clear=True) self.assertEqual(ctr.calls, ['refresh(flush=True, elapsed=None)']) self.assertEqual(manager.remove_calls, 2) # Clear is True, leave is False ctr = MockCounter(manager=manager, leave=False) ctr.close(clear=True) self.assertEqual(ctr.calls, ['clear(flush=True)']) self.assertEqual(manager.remove_calls, 3) def test_context_manager(self): mgr = Manager(stream=self.tty.stdout, enabled=False) with mgr.counter(total=10, leave=False) as ctr: self.assertTrue(ctr in mgr.counters) ctr.update() self.assertFalse(ctr in mgr.counters) def test_add_subcounter(self): self.assertEqual(self.ctr._subcounters, []) subcounter1 = self.ctr.add_subcounter('blue') self.assertEqual(len(self.ctr._subcounters), 1) self.assertEqual(self.ctr.subcount, 0) self.assertIs(self.ctr._subcounters[0], subcounter1) self.assertEqual(subcounter1.count, 0) self.assertFalse(subcounter1.all_fields) with self.assertRaisesRegex(ValueError, 'Invalid count: 5'): self.ctr.add_subcounter('yellow', count=5, all_fields=True) self.ctr.count = 5 subcounter2 = self.ctr.add_subcounter('yellow', count=5, all_fields=True) self.assertEqual(len(self.ctr._subcounters), 2) self.assertEqual(self.ctr.subcount, 5) self.assertIs(self.ctr._subcounters[1], subcounter2) self.assertEqual(subcounter2.count, 5) self.assertTrue(subcounter2.all_fields) def test_additional_fields(self): """ Add additional fields to format """ bar_format = ctr_format = u'{arg1:s} {count:d}' ctr = Counter(stream=self.tty.stdout, total=10, count=1, bar_format=bar_format, fields={'arg1': 'hello'}) self.assertEqual(ctr.format(), 'hello 1') ctr = Counter(stream=self.tty.stdout, count=1, counter_format=ctr_format, fields={'arg1': 'hello'}) self.assertEqual(ctr.format(), 'hello 1') def test_additional_fields_missing(self): """ Raise a ValueError when a keyword is missing """ bar_format = ctr_format = u'{arg1:s} {count:d}' ctr = Counter(stream=self.tty.stdout, total=10, count=1, bar_format=bar_format) with self.assertRaisesRegex( ValueError, "'arg1' specified in format, but not provided"): ctr.format() ctr = Counter(stream=self.tty.stdout, count=1, counter_format=ctr_format) with self.assertRaisesRegex( ValueError, "'arg1' specified in format, but not provided"): ctr.format() def test_additional_fields_changed(self): """ Change additional fields """ bar_format = ctr_format = u'{arg1:s} {count:d}' additional_fields = {'arg1': 'hello'} ctr = Counter(stream=self.tty.stdout, total=10, count=1, bar_format=bar_format, fields=additional_fields) self.assertEqual(ctr.format(), 'hello 1') additional_fields['arg1'] = 'goodbye' self.assertEqual(ctr.format(), 'goodbye 1') ctr = Counter(stream=self.tty.stdout, count=1, counter_format=ctr_format, fields=additional_fields) self.assertEqual(ctr.format(), 'goodbye 1') additional_fields['arg1'] = 'hello' self.assertEqual(ctr.format(), 'hello 1') def test_additional_fields_no_overwrite(self): """ Additional fields can not overwrite dynamic fields """ bar_format = ctr_format = u'{arg1:s} {count:d}' additional_fields = {'arg1': 'hello'} ctr = Counter(stream=self.tty.stdout, total=10, count=1, bar_format=bar_format, fields=additional_fields) self.assertEqual(ctr.format(), 'hello 1') ctr = Counter(stream=self.tty.stdout, count=1, counter_format=ctr_format, fields=additional_fields) self.assertEqual(ctr.format(), 'hello 1') def test_kwarg_fields(self): """ Additional fields to format via keyword arguments """ bar_format = ctr_format = u'{arg1:s} {count:d}' ctr = Counter(stream=self.tty.stdout, total=10, count=1, bar_format=bar_format, arg1='hello') self.assertEqual(ctr.format(), 'hello 1') ctr.update(arg1='goodbye') self.assertEqual(ctr.format(), 'goodbye 2') ctr = Counter(stream=self.tty.stdout, count=1, counter_format=ctr_format, arg1='hello') self.assertEqual(ctr.format(), 'hello 1') ctr.update(arg1='goodbye') self.assertEqual(ctr.format(), 'goodbye 2') def test_kwarg_fields_precedence(self): """ Keyword arguments take precedence over fields """ bar_format = u'{arg1:s} {count:d}' additional_fields = {'arg1': 'hello'} ctr = Counter(stream=self.tty.stdout, total=10, count=1, bar_format=bar_format, fields=additional_fields) self.assertEqual(ctr.format(), 'hello 1') ctr.update(arg1='goodbye') self.assertEqual(ctr.format(), 'goodbye 2') def test_fill_setter(self): """Fill must be one printable character""" ctr = Counter(stream=self.tty.stdout, fill='a') with self.assertRaisesRegex(ValueError, 'fill character must be a length of 1'): ctr.fill = 'hello' with self.assertRaisesRegex(ValueError, 'fill character must be a length of 1'): ctr.fill = '' def test_fill(self): """ Fill uses remaining space """ ctr_format = u'{fill}HI' ctr = Counter(stream=self.tty.stdout, count=1, counter_format=ctr_format, fill=u'-') self.assertEqual(ctr.format(), u'-' * 78 + 'HI') ctr_format = u'{fill}HI{fill}' ctr = Counter(stream=self.tty.stdout, count=1, counter_format=ctr_format, fill=u'-') self.assertEqual(ctr.format(), u'-' * 39 + 'HI' + u'-' * 39) @unittest.skipIf(PY2, 'Skip warnings tests in Python 2') def test_reserved_fields(self): """ When reserved fields are used, a warning is raised """ ctr = Counter(stream=self.tty.stdout, total=10, count=1, fields={'elapsed': 'reserved'}) with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn: ctr.format() self.assertRegex(__file__, warn.filename) ctr = Counter(stream=self.tty.stdout, total=10, fields={'elapsed': 'reserved'}) with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn: ctr.format() self.assertRegex(__file__, warn.filename) ctr = Counter(stream=self.tty.stdout, total=10, count=1, elapsed='reserved') with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn: ctr.format() self.assertRegex(__file__, warn.filename) ctr = Counter(stream=self.tty.stdout, total=10, elapsed='reserved') with self.assertWarns(EnlightenWarning) as warn: ctr.format() self.assertRegex(__file__, warn.filename) def test_builtin_bar_fields(self): """ Ensure all built-in fields are populated as expected """ bar_fields = tuple(field for field in enlighten._counter.COUNTER_FIELDS if field != 'fill') bar_format = u', '.join(u'%s: {%s}' % (field, field) for field in sorted(bar_fields)) ctr = Counter(stream=self.tty.stdout, total=100, bar_format=bar_format, unit='parsecs', desc='Kessel runs') ctr.count = 50 fields = 'bar: , count: 50, desc: Kessel runs, desc_pad: , elapsed: 00:50, eta: 00:50, ' \ 'interval: 1.0, len_total: 3, percentage: 50.0, rate: 1.0, total: 100, ' \ 'unit: parsecs, unit_pad: ' self.assertEqual(ctr.format(elapsed=50, width=80), fields)
class TestManager(TestCase): def setUp(self): self.tty = MockTTY() self.resize_sig = signal.getsignal(signal.SIGWINCH) def tearDown(self): self.tty.close() signal.signal(signal.SIGWINCH, self.resize_sig) def test_init_safe(self): with redirect_output('stdout', self.tty.stdout): # Companion stream is stderr if stream is stdout manager = _manager.Manager() self.assertIs(manager.stream, sys.stdout) self.assertIs(manager.term.stream, sys.stdout) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init(self): # Companion stream is stderr if stream is stdout manager = _manager.Manager() self.assertIs(manager.stream, sys.stdout) self.assertIs(manager.term.stream, sys.stdout) # This will fail building rpm packages since stderr is redirected if sys.__stderr__.isatty(): self.assertIs(manager.companion_stream, sys.__stderr__) self.assertIs(manager.companion_term.stream, sys.__stderr__) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_companion_hc(self): # Hard-coded companion stream always wins manager = _manager.Manager(companion_stream=OUTPUT) self.assertIs(manager.companion_stream, OUTPUT) self.assertIs(manager.companion_term.stream, OUTPUT) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_stderr(self): # Companion stream is stdout if stream is stderr manager = _manager.Manager(stream=sys.__stderr__) self.assertIs(manager.stream, sys.__stderr__) self.assertIs(manager.term.stream, sys.__stderr__) # This will fail building rpm packages since stderr is redirected if sys.__stdout__.isatty(): self.assertIs(manager.companion_stream, sys.__stdout__) self.assertIs(manager.companion_term.stream, sys.__stdout__) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_redirect(self): # If stdout is redirected, but stderr is still a tty, use it for companion with redirect_output('stdout', OUTPUT): manager = _manager.Manager() self.assertIs(manager.stream, sys.stdout) self.assertIs(manager.term.stream, sys.stdout) # This will fail building rpm packages since stderr is redirected if sys.__stderr__.isatty(): self.assertIs(manager.companion_stream, sys.stderr) self.assertIs(manager.companion_term.stream, sys.stderr) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_stderr_redirect(self): # If stderr is redirected, but stdout is still a tty, use it for companion with redirect_output('stderr', OUTPUT): manager = _manager.Manager(stream=sys.stderr) self.assertIs(manager.stream, sys.stderr) self.assertIs(manager.term.stream, sys.stderr) # This will fail building rpm packages since stderr is redirected if sys.__stdout__.isatty(): self.assertIs(manager.companion_stream, sys.stdout) self.assertIs(manager.companion_term.stream, sys.stdout) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_stderr_companion_hc(self): # Hard-coded companion stream always wins manager = _manager.Manager(stream=sys.__stderr__, companion_stream=OUTPUT) self.assertIs(manager.companion_stream, OUTPUT) self.assertIs(manager.companion_term.stream, OUTPUT) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_hc(self): # Nonstandard stream doesn't get a companion stream by default manager = _manager.Manager(stream=OUTPUT) self.assertIs(manager.stream, OUTPUT) self.assertIs(manager.term.stream, OUTPUT) self.assertIsNone(manager.companion_stream) self.assertIsNone(manager.companion_term) def test_repr(self): manager = _manager.Manager() self.assertEqual(repr(manager), "Manager(stream=%r)" % sys.stdout) def test_counter_and_remove(self): # pylint: disable=no-member,assigning-non-slot manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) self.assertEqual(len(manager.counters), 0) with mock.patch.object(manager, '_set_scroll_area') as ssa: counter1 = manager.counter(leave=True) self.assertTrue(counter1.leave) self.assertEqual(len(manager.counters), 1) self.assertEqual(manager.counters[counter1], 1) self.assertEqual(counter1.calls, []) self.assertEqual(ssa.call_count, 1) with mock.patch.object(manager, '_set_scroll_area') as ssa: counter2 = manager.counter(leave=False) self.assertFalse(counter2.leave) self.assertEqual(len(manager.counters), 2) self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 1) self.assertEqual(counter1.calls, ['clear(flush=False)', 'refresh(flush=False, elapsed=None)']) self.assertEqual(counter2.calls, []) self.assertEqual(ssa.call_count, 1) counter1.calls = [] with mock.patch.object(manager, '_set_scroll_area') as ssa: counter3 = manager.counter(leave=False) self.assertFalse(counter3.leave) self.assertEqual(len(manager.counters), 3) self.assertEqual(manager.counters[counter1], 3) self.assertEqual(manager.counters[counter2], 2) self.assertEqual(manager.counters[counter3], 1) self.assertEqual(counter1.calls, ['clear(flush=False)', 'refresh(flush=False, elapsed=None)']) self.assertEqual(counter2.calls, ['clear(flush=False)', 'refresh(flush=False, elapsed=None)']) self.assertEqual(counter3.calls, []) self.assertEqual(ssa.call_count, 1) counter1.calls = [] counter2.calls = [] manager.remove(counter3) self.assertEqual(len(manager.counters), 2) self.assertFalse(counter3 in manager.counters) # Remove again, no error manager.remove(counter3) self.assertEqual(len(manager.counters), 2) manager.remove(counter1) self.assertEqual(len(manager.counters), 2) self.assertTrue(counter1 in manager.counters) with mock.patch.object(manager, '_set_scroll_area') as ssa: counter4 = manager.counter(leave=False) self.assertFalse(counter4.leave) self.assertEqual(len(manager.counters), 3) self.assertEqual(manager.counters[counter1], 3) self.assertEqual(manager.counters[counter2], 2) self.assertEqual(manager.counters[counter4], 1) self.assertEqual(counter1.calls, []) self.assertEqual(counter2.calls, []) self.assertEqual(counter4.calls, []) self.assertEqual(ssa.call_count, 1) def test_counter_position(self): manager = _manager.Manager(stream=self.tty.stdout, set_scroll=False) counter1 = manager.counter(position=4) self.assertEqual(manager.counters[counter1], 4) with self.assertRaisesRegex(ValueError, 'Counter position 4 is already occupied'): manager.counter(position=4) with self.assertRaisesRegex(ValueError, 'Counter position 200 is greater than terminal height'): manager.counter(position=200) def test_counter_position_pinned(self): """If a position is taken, use next available""" manager = _manager.Manager(stream=self.tty.stdout, set_scroll=False) counter1 = manager.counter(position=2) self.assertEqual(manager.counters[counter1], 2) counter2 = manager.counter() self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 1) counter3 = manager.counter() self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 3) self.assertEqual(manager.counters[counter3], 1) status1 = manager.status_bar(position=3) self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 4) self.assertEqual(manager.counters[counter3], 1) self.assertEqual(manager.counters[status1], 3) status2 = manager.status_bar() self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 5) self.assertEqual(manager.counters[counter3], 4) self.assertEqual(manager.counters[status1], 3) self.assertEqual(manager.counters[status2], 1) def test_inherit_kwargs(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, unit='knights', not_real=True, desc='Default') self.assertTrue('unit' in manager.defaults) self.assertTrue('desc' in manager.defaults) self.assertTrue('not_real' in manager.defaults) with mock.patch.object(manager, '_set_scroll_area'): ctr = manager.counter(desc='Huzzah') self.assertEqual(ctr.unit, 'knights') self.assertEqual(ctr.desc, 'Huzzah') self.assertFalse(hasattr(ctr, 'not_real')) def test_write(self): msg = 'test message' with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa: manager = _manager.Manager(stream=self.tty.stdout) counter = manager.counter(position=3) term = manager.term manager.write(msg, counter=counter) self.tty.stdout.write(u'X\n') # Carriage return is getting converted to newline self.assertEqual(self.tty.stdread.readline(), term.move(22, 0) + '\r' + term.clear_eol + msg + 'X\n') self.assertEqual(ssa.call_count, 2) def test_write_no_flush(self): """ Output is stored in buffer, but not flushed to stream """ msg = u'test message' with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa: manager = _manager.Manager(stream=self.tty.stdout, companion_stream=OUTPUT) counter = manager.counter(position=3) term = manager.term manager.write(msg, counter=counter, flush=False) self.assertEqual(manager._buffer, [term.move(term.height - 3, 0), '\r', term.clear_eol, msg]) self.assertEqual(manager._companion_buffer, []) self.tty.stdout.write(u'X\n') # No output self.assertEqual(self.tty.stdread.readline(), 'X\n') self.assertEqual(ssa.call_count, 2) def test_flush_companion_buffer(self): """ Output is stored in buffer, but only written in companion stream is defined """ manager = _manager.Manager(stream=self.tty.stdout) msg = u'test message' manager._companion_buffer = [msg] manager._flush_streams() # Companion buffer flushed, but not outputted self.assertEqual(manager._companion_buffer, []) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') # set companion stream and test again manager.companion_stream = OUTPUT manager._companion_buffer = [msg] manager._flush_streams() self.assertEqual(manager._companion_buffer, []) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') self.assertEqual(OUTPUT.getvalue(), msg) def test_autorefresh(self): """ Ensure auto-refreshed counters are updated when others are """ manager = _manager.Manager(stream=self.tty.stdout) counter1 = manager.counter(count=1, total=0, counter_format=u'counter1', autorefresh=True) counter2 = manager.counter(count=1, total=0, counter_format=u'counter2') self.tty.clear() # Counter 1 in auto-refresh list self.assertIn(counter1, manager.autorefresh) # If auto-refreshed counter hasn't been refreshed recently refresh counter1.last_update = 0 counter2.refresh() self.tty.stdout.write(u'X\n') output = self.tty.stdread.readline() self.assertRegex(output, 'counter2.+counter1') # If auto-refreshed counter has been refreshed recently, skip counter1.last_update = time.time() + 5 counter2.refresh() self.tty.stdout.write(u'X\n') output = self.tty.stdread.readline() self.assertRegex(output, 'counter2') self.assertNotRegex(output, 'counter1') # If already auto-refreshing, skip manager.refresh_lock = True counter1.last_update = 0 counter2.refresh() # Have to explicitly flush manager._flush_streams() self.tty.stdout.write(u'X\n') output = self.tty.stdread.readline() self.assertRegex(output, 'counter2') self.assertNotRegex(output, 'counter1') def test_set_scroll_area_disabled(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, set_scroll=False) manager.counters['dummy'] = 3 manager._set_scroll_area() self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') def test_set_scroll_area_no_change(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters['dummy'] = 3 manager.scroll_offset = 4 manager._set_scroll_area() self.assertEqual(manager._buffer, [manager.term.move(21, 0)]) def test_set_scroll_area_companion(self): """ Ensure when no change is made, a term.move is still called for the companion stream """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, companion_stream=self.tty.stdout) manager.counters['dummy'] = 3 manager.scroll_offset = 4 term = manager.term manager._set_scroll_area() self.assertEqual(manager._buffer, [term.move(21, 0)]) self.assertEqual(manager._companion_buffer, [term.move(21, 0)]) def test_set_scroll_area(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters['dummy'] = 3 term = manager.term stdread = self.tty.stdread self.assertEqual(manager.scroll_offset, 1) self.assertFalse(manager.process_exit) self.assertNotEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize) old_offset = manager.scroll_offset with mock.patch('enlighten._manager.atexit') as atexit: manager._set_scroll_area() self.assertEqual(manager.scroll_offset, 4) self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize) self.assertTrue(manager.process_exit) atexit.register.assert_called_with(manager._at_exit) offset = manager.scroll_offset scroll_position = term.height - offset self.assertEqual(manager._buffer, [term.move(term.height - old_offset, 0), '\n' * (offset - old_offset), term.hide_cursor, term.csr(0, scroll_position), term.move(scroll_position, 0)]) # No companion buffer defined self.assertEqual(manager._companion_buffer, []) # Make sure nothing was flushed self.tty.stdout.write(u'X\n') self.assertEqual(stdread.readline(), 'X\n') # Run it again and make sure exit handling isn't reset del manager._buffer[:] del manager._companion_buffer[:] with mock.patch('enlighten._manager.atexit') as atexit: manager._set_scroll_area(force=True) self.assertFalse(atexit.register.called) self.assertEqual(manager._buffer, [term.hide_cursor, term.csr(0, scroll_position), term.move(scroll_position, 0)]) # Set max counter lower and make sure scroll_offset hasn't changed manager.counters['dummy'] = 1 with mock.patch('enlighten._manager.atexit') as atexit: manager._set_scroll_area() self.assertEqual(manager.scroll_offset, 4) def test_set_scroll_area_force(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters['dummy'] = 3 manager.scroll_offset = 4 manager.height = 20 scroll_position = manager.height - manager.scroll_offset term = manager.term with mock.patch('enlighten._manager.atexit') as atexit: manager._set_scroll_area(force=True) self.assertEqual(manager.scroll_offset, 4) self.assertTrue(manager.process_exit) self.assertEqual(manager._buffer, [term.hide_cursor, term.csr(0, scroll_position), term.move(scroll_position, 0)]) self.assertEqual(manager._companion_buffer, []) atexit.register.assert_called_with(manager._at_exit) def test_at_exit(self): tty = MockTTY() try: with mock.patch.object(tty, 'stdout', wraps=tty.stdout) as mockstdout: manager = _manager.Manager(stream=tty.stdout, counter_class=MockCounter) term = manager.term reset = (term.normal_cursor + term.csr(0, term.height - 1) + term.move(term.height, 0)) # process_exit is False manager._at_exit() self.assertFalse(mockstdout.flush.called) # No output tty.stdout.write(u'X\n') self.assertEqual(tty.stdread.readline(), 'X\n') # process_exit is True, set_scroll False manager.process_exit = True manager.set_scroll = False manager._at_exit() self.assertEqual(mockstdout.flush.call_count, 1) self.assertEqual(tty.stdread.readline(), term.move(25, 0) + term.cud1) # process_exit is True, set_scroll True manager.set_scroll = True manager._at_exit() self.assertEqual(mockstdout.flush.call_count, 2) self.assertEqual(tty.stdread.readline(), reset + term.cud1) # Ensure companion stream gets flushed manager.companion_stream = tty.stdout manager._at_exit() self.assertEqual(mockstdout.flush.call_count, 4) self.assertEqual(tty.stdread.readline(), reset + term.cud1) term = manager.term finally: # Ensure no errors if tty closes before _at_exit is called tty.close() manager._at_exit() def test_stop(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 term = manager.term self.assertIsNone(manager.companion_term) with mock.patch('enlighten._manager.atexit'): manager._set_scroll_area() self.assertEqual(manager.scroll_offset, 5) self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize) self.assertTrue(manager.process_exit) # Clear buffer del manager._buffer[:] manager.enabled = False manager.stop() # No output, No changes self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize) self.assertTrue(manager.process_exit) manager.enabled = True manager.stop() self.assertEqual(signal.getsignal(signal.SIGWINCH), manager.sigwinch_orig) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), term.move(term.height - 2, 0) + term.clear_eol + term.move(term.height - 1, 0) + term.clear_eol + term.normal_cursor + term.csr(0, term.height - 1) + term.move(term.height, 0) + 'X\n') self.assertFalse(manager.process_exit) self.assertFalse(manager.enabled) for counter in manager.counters: self.assertFalse(counter.enabled) def test_stop_no_set_scroll(self): """ set_scroll is False """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, set_scroll=False) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 term = manager.term with mock.patch('enlighten._manager.atexit'): with mock.patch.object(term, 'change_scroll'): manager._set_scroll_area() self.assertEqual(manager.scroll_offset, 5) self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize) self.assertTrue(manager.process_exit) # Stream empty self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') manager.stop() self.assertEqual(signal.getsignal(signal.SIGWINCH), manager.sigwinch_orig) self.assertFalse(manager.process_exit) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), term.move(term.height - 2, 0) + term.clear_eol + term.move(term.height - 1, 0) + term.clear_eol + term.move(25, 0) + 'X\n') def test_stop_never_used(self): """ In this case, _set_scroll_area() was never called """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 term = manager.term self.assertFalse(manager.process_exit) manager.stop() self.assertEqual(signal.getsignal(signal.SIGWINCH), manager.sigwinch_orig) # Only reset terminal self.tty.stdout.write(u'X\n') reset = term.normal_cursor + term.csr(0, term.height - 1) + term.move(term.height, 0) self.assertEqual(self.tty.stdread.readline(), reset + 'X\n') def test_stop_companion(self): """ In this case, we have a companion terminal Just make sure we have an extra reset """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, companion_stream=self.tty.stdout) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 term = manager.term with mock.patch('enlighten._manager.atexit'): manager._set_scroll_area() del manager._buffer[:] del manager._companion_buffer[:] with mock.patch.object(manager, '_flush_streams'): manager.stop() self.assertEqual(manager._buffer, [term.move(term.height - 2, 0), term.clear_eol, term.move(term.height - 1, 0), term.clear_eol, term.normal_cursor, term.csr(0, term.height - 1), term.move(term.height, 0)]) self.assertEqual(manager._companion_buffer, [term.normal_cursor, term.csr(0, term.height - 1), term.move(term.height, 0)]) def test_stop_position_1(self): """ Ensure a line feed is given if there is a counter at position 1 """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) term = manager.term manager.counters[MockCounter(manager=manager)] = 3 with mock.patch.object(manager, '_flush_streams'): manager.stop() self.assertEqual(manager._buffer, [term.normal_cursor, term.csr(0, term.height - 1), term.move(term.height, 0)]) del manager._buffer[:] manager.enabled = True manager.counters[MockCounter(manager=manager)] = 1 with mock.patch.object(manager, '_flush_streams'): manager.stop() self.assertEqual(manager._buffer, [term.normal_cursor, term.csr(0, term.height - 1), term.move(term.height, 0), term.cud1 or '\n']) def test_resize(self): """ Resize lock must be False for handler to run Terminal size is cached unless resize handler runs """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) counter3 = MockCounter(manager=manager) manager.counters[counter3] = 3 manager.scroll_offset = 4 term = manager.term with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockwidth: mockwidth.return_value = 70 manager.resize_lock = True with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa: manager._stage_resize() self.assertFalse(ssa.called) self.assertEqual(manager.width, 80) self.assertTrue(manager.resize_lock) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') self.assertEqual(counter3.calls, []) manager.resize_lock = False with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa: manager._resize_handler() self.assertEqual(ssa.call_count, 1) self.assertEqual(manager.width, 70) self.assertFalse(manager.resize_lock) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), term.move(21, 0) + term.clear_eos + 'X\n') self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)']) def test_threaded_eval(self): """ Dynamic value for threaded determined when scroll area is first set """ # Not dynamic if explicitly True manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, threaded=True) self.assertTrue(manager.threaded) with mock.patch('threading.active_count', return_value=4): manager.counter() self.assertTrue(manager.threaded) # Not dynamic if explicitly False manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, threaded=False) self.assertFalse(manager.threaded) with mock.patch('threading.active_count', return_value=4): manager.counter() self.assertFalse(manager.threaded) # False by default manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) self.assertIsNone(manager.threaded) manager.counter() self.assertFalse(manager.threaded) # True if threaded manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) self.assertIsNone(manager.threaded) with mock.patch('threading.active_count', return_value=4): manager.counter() self.assertTrue(manager.threaded) # True if has child processes manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) self.assertIsNone(manager.threaded) with mock.patch('multiprocessing.active_children', return_value=[1, 2]): manager.counter() self.assertTrue(manager.threaded) # True if is child processes manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) self.assertIsNone(manager.threaded) with mock.patch('multiprocessing.current_process') as c_process: c_process.name = 'Process1' manager.counter() self.assertTrue(manager.threaded) def test_resize_threaded(self): """ Test a resize event threading behavior """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, threaded=True) counter3 = MockCounter(manager=manager) counter3.last_update = time.time() manager.counters[counter3] = 3 manager.scroll_offset = 4 term = manager.term # simulate resize manager._stage_resize() self.assertTrue(manager._resize) self.assertEqual(counter3.last_update, 0) with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockwidth: mockwidth.return_value = 70 # resize doesn't happen until a write is called self.assertEqual(manager.width, 80) with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa: manager.write() self.assertEqual(ssa.call_count, 1) self.assertEqual(manager.width, 70) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), term.move(21, 0) + term.clear_eos + 'X\n') self.assertFalse(manager.resize_lock) self.assertFalse(manager._resize) self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)']) def test_resize_handler_height_less(self): with mock.patch('%s.height' % TERMINAL, new_callable=mock.PropertyMock) as mockheight: mockheight.side_effect = [25, 23] manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) counter3 = MockCounter(manager=manager) manager.counters[counter3] = 3 manager.scroll_offset = 4 with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa: manager._resize_handler() self.assertEqual(ssa.call_count, 1) self.assertEqual(manager.height, 23) self.assertEqual(self.tty.stdread.readline(), manager.term.move(19, 0) + '\n') for _ in range(5): self.assertEqual(self.tty.stdread.readline(), '\n') self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)']) def test_resize_handler_height_greater_threaded(self): with mock.patch('%s.height' % TERMINAL, new_callable=mock.PropertyMock) as mockheight: mockheight.side_effect = [25, 27] manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, threaded=True) counter3 = MockCounter(manager=manager) manager.counters[counter3] = 3 manager.scroll_offset = 4 term = manager.term with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa: manager._resize_handler() self.assertEqual(ssa.call_count, 1) self.assertEqual(manager.height, 27) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), term.move(27, 0) + '\n') self.assertEqual(self.tty.stdread.readline(), '\n') self.assertEqual(self.tty.stdread.readline(), '\n') self.assertEqual(self.tty.stdread.readline(), term.move(23, 0) + term.clear_eos + 'X\n') self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)']) def test_disable(self): mgr = _manager.Manager(stream=self.tty.stdout, enabled=False) self.assertFalse(mgr.enabled) ctr = mgr.counter() self.assertIsInstance(ctr, _manager.Counter) self.assertFalse(ctr.enabled) # Make sure this doesn't error ctr.update() ctr.update(4) ctr.refresh() ctr.close() ctr.leave = False ctr.close() mgr.write() mgr.stop() # No Output self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') def test_context_manager(self): mgr = None with _manager.Manager(stream=self.tty.stdout) as manager: self.assertIsInstance(manager, _manager.Manager) self.assertTrue(manager.enabled) mgr = manager self.assertFalse(mgr.enabled) def test_no_resize_signal(self): # Test normal case initialization stdmgr = _manager.Manager(stream=self.tty.stdout) self.assertTrue(hasattr(stdmgr, 'sigwinch_orig')) stdmgr.counters[MockCounter(manager=stdmgr)] = 3 stdmgr.counters[MockCounter(manager=stdmgr)] = 4 # Test no resize signal initialization with mock.patch.object(_manager, 'RESIZE_SUPPORTED', False): manager = _manager.Manager(stream=self.tty.stdout) self.assertFalse(hasattr(manager, 'sigwinch_orig')) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 # Test set_scroll_area() with mock.patch.object(_manager.signal, 'signal', wraps=_manager.signal.signal) as mocksignal: with mock.patch('enlighten._manager.atexit'): # Test no resize signal set_scroll_area with mock.patch.object(_manager, 'RESIZE_SUPPORTED', False): with mock.patch.object(manager.term, 'change_scroll'): manager._set_scroll_area() self.assertFalse(mocksignal.called) # Test normal case set_scroll_area with mock.patch.object(stdmgr.term, 'change_scroll'): stdmgr._set_scroll_area() self.assertTrue(mocksignal.called) # Test stop() with mock.patch.object(_manager.signal, 'signal', wraps=_manager.signal.signal) as mocksignal: # Test no resize signal stop with mock.patch.object(_manager, 'RESIZE_SUPPORTED', False): manager.stop() self.assertFalse(mocksignal.called) # Test normal case stop stdmgr.stop() self.assertTrue(mocksignal.called) def test_no_resize(self): with mock.patch.object(_manager.signal, 'signal', wraps=_manager.signal.signal) as mocksignal: manager = _manager.Manager(stream=self.tty.stdout, no_resize=True) self.assertFalse(hasattr(manager, 'sigwinch_orig')) self.assertFalse(mocksignal.called) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 with mock.patch.object(manager.term, 'change_scroll'): manager._set_scroll_area() self.assertFalse(mocksignal.called) manager.stop() self.assertFalse(mocksignal.called)
def setUp(self): self.tty = MockTTY() self.terminal = _terminal.Terminal(stream=self.tty.stdout, kind='xterm-256color')
def setUpClass(cls): cls.tty = MockTTY() cls.term = blessed.Terminal(stream=cls.tty.stdout, kind='xterm-256color', force_styling=True) cls.term.number_of_colors = 1 << 24
class TestManager(TestCase): def setUp(self): self.tty = MockTTY() self.resize_sig = signal.getsignal(signal.SIGWINCH) def tearDown(self): self.tty.close() signal.signal(signal.SIGWINCH, self.resize_sig) def test_init_safe(self): with redirect_output('stdout', self.tty.stdout): # Companion stream is stderr if stream is stdout manager = _manager.Manager() self.assertIs(manager.stream, sys.stdout) self.assertIs(manager.term.stream, sys.stdout) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init(self): # Companion stream is stderr if stream is stdout manager = _manager.Manager() self.assertIs(manager.stream, sys.stdout) self.assertIs(manager.term.stream, sys.stdout) # This will fail building rpm packages since stderr is redirected if sys.__stderr__.isatty(): self.assertIs(manager.companion_stream, sys.__stderr__) self.assertIs(manager.companion_term.stream, sys.__stderr__) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_companion_hc(self): # Hard-coded companion stream always wins manager = _manager.Manager(companion_stream=OUTPUT) self.assertIs(manager.companion_stream, OUTPUT) self.assertIs(manager.companion_term.stream, OUTPUT) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_stderr(self): # Companion stream is stdout if stream is stderr manager = _manager.Manager(stream=sys.__stderr__) self.assertIs(manager.stream, sys.__stderr__) self.assertIs(manager.term.stream, sys.__stderr__) # This will fail building rpm packages since stderr is redirected if sys.__stdout__.isatty(): self.assertIs(manager.companion_stream, sys.__stdout__) self.assertIs(manager.companion_term.stream, sys.__stdout__) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_redirect(self): # If stdout is redirected, but stderr is still a tty, use it for companion with redirect_output('stdout', OUTPUT): manager = _manager.Manager() self.assertIs(manager.stream, sys.stdout) self.assertIs(manager.term.stream, sys.stdout) # This will fail building rpm packages since stderr is redirected if sys.__stderr__.isatty(): self.assertIs(manager.companion_stream, sys.stderr) self.assertIs(manager.companion_term.stream, sys.stderr) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_stderr_redirect(self): # If stderr is redirected, but stdout is still a tty, use it for companion with redirect_output('stderr', OUTPUT): manager = _manager.Manager(stream=sys.stderr) self.assertIs(manager.stream, sys.stderr) self.assertIs(manager.term.stream, sys.stderr) # This will fail building rpm packages since stderr is redirected if sys.__stdout__.isatty(): self.assertIs(manager.companion_stream, sys.stdout) self.assertIs(manager.companion_term.stream, sys.stdout) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_stderr_companion_hc(self): # Hard-coded companion stream always wins manager = _manager.Manager(stream=sys.__stderr__, companion_stream=OUTPUT) self.assertIs(manager.companion_stream, OUTPUT) self.assertIs(manager.companion_term.stream, OUTPUT) @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout') def test_init_hc(self): # Nonstandard stream doesn't get a companion stream by default manager = _manager.Manager(stream=OUTPUT) self.assertIs(manager.stream, OUTPUT) self.assertIs(manager.term.stream, OUTPUT) self.assertIsNone(manager.companion_stream) self.assertIsNone(manager.companion_term) def test_repr(self): manager = _manager.Manager() self.assertEqual(repr(manager), "Manager(stream=%r)" % sys.stdout) def test_counter_and_remove(self): # pylint: disable=no-member,assigning-non-slot manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) self.assertEqual(len(manager.counters), 0) with mock.patch.object(manager, '_set_scroll_area') as ssa: counter1 = manager.counter(leave=True) self.assertTrue(counter1.leave) self.assertEqual(len(manager.counters), 1) self.assertEqual(manager.counters[counter1], 1) self.assertEqual(counter1.calls, []) self.assertEqual(ssa.call_count, 1) with mock.patch.object(manager, '_set_scroll_area') as ssa: counter2 = manager.counter(leave=False) self.assertFalse(counter2.leave) self.assertEqual(len(manager.counters), 2) self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 1) self.assertEqual( counter1.calls, ['clear(flush=False)', 'refresh(flush=True, elapsed=None)']) self.assertEqual(counter2.calls, []) self.assertEqual(ssa.call_count, 1) counter1.calls = [] with mock.patch.object(manager, '_set_scroll_area') as ssa: counter3 = manager.counter(leave=False) self.assertFalse(counter3.leave) self.assertEqual(len(manager.counters), 3) self.assertEqual(manager.counters[counter1], 3) self.assertEqual(manager.counters[counter2], 2) self.assertEqual(manager.counters[counter3], 1) self.assertEqual( counter1.calls, ['clear(flush=False)', 'refresh(flush=True, elapsed=None)']) self.assertEqual( counter2.calls, ['clear(flush=False)', 'refresh(flush=True, elapsed=None)']) self.assertEqual(counter3.calls, []) self.assertEqual(ssa.call_count, 1) counter1.calls = [] counter2.calls = [] manager.remove(counter3) self.assertEqual(len(manager.counters), 2) self.assertFalse(counter3 in manager.counters) # Remove again, no error manager.remove(counter3) self.assertEqual(len(manager.counters), 2) manager.remove(counter1) self.assertEqual(len(manager.counters), 2) self.assertTrue(counter1 in manager.counters) with mock.patch.object(manager, '_set_scroll_area') as ssa: counter4 = manager.counter(leave=False) self.assertFalse(counter4.leave) self.assertEqual(len(manager.counters), 3) self.assertEqual(manager.counters[counter1], 3) self.assertEqual(manager.counters[counter2], 2) self.assertEqual(manager.counters[counter4], 1) self.assertEqual(counter1.calls, []) self.assertEqual(counter2.calls, []) self.assertEqual(counter4.calls, []) self.assertEqual(ssa.call_count, 1) def test_counter_position(self): manager = _manager.Manager(stream=self.tty.stdout, set_scroll=False) counter1 = manager.counter(position=4) self.assertEqual(manager.counters[counter1], 4) with self.assertRaisesRegex(ValueError, 'Counter position 4 is already occupied'): manager.counter(position=4) with self.assertRaisesRegex( ValueError, 'Counter position 200 is greater than terminal height'): manager.counter(position=200) def test_counter_position_pinned(self): """If a position is taken, use next available""" manager = _manager.Manager(stream=self.tty.stdout, set_scroll=False) counter1 = manager.counter(position=2) self.assertEqual(manager.counters[counter1], 2) counter2 = manager.counter() self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 1) counter3 = manager.counter() self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 3) self.assertEqual(manager.counters[counter3], 1) status1 = manager.status_bar(position=3) self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 4) self.assertEqual(manager.counters[counter3], 1) self.assertEqual(manager.counters[status1], 3) status2 = manager.status_bar() self.assertEqual(manager.counters[counter1], 2) self.assertEqual(manager.counters[counter2], 5) self.assertEqual(manager.counters[counter3], 4) self.assertEqual(manager.counters[status1], 3) self.assertEqual(manager.counters[status2], 1) def test_inherit_kwargs(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, unit='knights', not_real=True, desc='Default') self.assertTrue('unit' in manager.defaults) self.assertTrue('desc' in manager.defaults) self.assertTrue('not_real' in manager.defaults) with mock.patch.object(manager, '_set_scroll_area'): ctr = manager.counter(desc='Huzzah') self.assertEqual(ctr.unit, 'knights') self.assertEqual(ctr.desc, 'Huzzah') self.assertFalse(hasattr(ctr, 'not_real')) def test_write(self): msg = 'test message' with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa: manager = _manager.Manager(stream=self.tty.stdout) counter = manager.counter(position=3) term = manager.term manager.write(msg, counter=counter) self.tty.stdout.write(u'X\n') # Carriage return is getting converted to newline self.assertEqual( self.tty.stdread.readline(), term.move(22, 0) + '\r' + term.clear_eol + msg + 'X\n') self.assertEqual(ssa.call_count, 2) def test_write_no_flush(self): """ No real difference in our tests because stream is flushed on each new line If we don't flush, reading will just hang But we added this for coverage and as a framework future tests """ msg = 'test message' with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa: manager = _manager.Manager(stream=self.tty.stdout) counter = manager.counter(position=3) term = manager.term manager.write(msg, counter=counter, flush=False) self.tty.stdout.write(u'X\n') # Carriage return is getting converted to newline self.assertEqual( self.tty.stdread.readline(), term.move(22, 0) + '\r' + term.clear_eol + msg + 'X\n') self.assertEqual(ssa.call_count, 2) def test_autorefresh(self): """ Ensure auto-refreshed counters are updated when others are """ manager = _manager.Manager(stream=self.tty.stdout) counter1 = manager.counter(count=1, total=0, counter_format=u'counter1', autorefresh=True) counter2 = manager.counter(count=1, total=0, counter_format=u'counter2') self.tty.clear() # Counter 1 in auto-refresh list self.assertIn(counter1, manager.autorefresh) # If auto-refreshed counter hasn't been refreshed recently refresh counter1.last_update = 0 counter2.refresh() self.tty.stdout.write(u'X\n') output = self.tty.stdread.readline() self.assertRegex(output, 'counter2.+counter1') # If auto-refreshed counter has been refreshed recently, skip counter1.last_update = time.time() + 5 counter2.refresh() self.tty.stdout.write(u'X\n') output = self.tty.stdread.readline() self.assertRegex(output, 'counter2') self.assertNotRegex(output, 'counter1') def test_set_scroll_area_disabled(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, set_scroll=False) manager.counters['dummy'] = 3 manager._set_scroll_area() self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') def test_set_scroll_area_no_change(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters['dummy'] = 3 manager.scroll_offset = 4 manager._set_scroll_area() self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), manager.term.move(21, 0) + 'X\n') def test_set_scroll_area_companion(self): """ Ensure when no change is made, a term.move is still called for the companion stream """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, companion_stream=self.tty.stdout) manager.counters['dummy'] = 3 manager.scroll_offset = 4 term = manager.term manager._set_scroll_area() self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), term.move(21, 0) + term.move(21, 0) + 'X\n') def test_set_scroll_area(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters['dummy'] = 3 term = manager.term stdread = self.tty.stdread self.assertEqual(manager.scroll_offset, 1) self.assertFalse(manager.process_exit) self.assertNotEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler) with mock.patch('enlighten._manager.atexit') as atexit: with mock.patch.object(term, 'change_scroll'): manager._set_scroll_area() self.assertEqual(term.change_scroll.call_count, 1) # pylint: disable=no-member self.assertEqual(manager.scroll_offset, 4) self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler) self.assertEqual(stdread.readline(), term.move(24, 0) + '\n') self.assertEqual(stdread.readline(), '\n') self.assertEqual(stdread.readline(), '\n') self.assertTrue(manager.process_exit) atexit.register.assert_called_with(manager._at_exit) self.tty.stdout.write(u'X\n') self.assertEqual(stdread.readline(), term.move(21, 0) + 'X\n') # Run it again and make sure exit handling isn't reset with mock.patch('enlighten._manager.atexit') as atexit: with mock.patch.object(term, 'change_scroll'): manager._set_scroll_area(force=True) self.assertEqual(term.change_scroll.call_count, 1) # pylint: disable=no-member self.assertFalse(atexit.register.called) def test_set_scroll_area_height(self): manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters['dummy'] = 3 manager.scroll_offset = 4 manager.height = 20 term = manager.term with mock.patch('enlighten._manager.atexit') as atexit: with mock.patch.object(term, 'change_scroll'): manager._set_scroll_area() self.assertEqual(term.change_scroll.call_count, 1) # pylint: disable=no-member self.assertEqual(manager.scroll_offset, 4) self.assertEqual(manager.height, 25) self.assertTrue(manager.process_exit) term.stream.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), term.move(21, 0) + 'X\n') atexit.register.assert_called_with(manager._at_exit) def test_at_exit(self): tty = MockTTY() with mock.patch('%s.reset' % TERMINAL) as reset: with mock.patch.object(tty, 'stdout', wraps=tty.stdout) as mockstdout: manager = _manager.Manager(stream=tty.stdout, counter_class=MockCounter) term = manager.term # process_exit is False manager._at_exit() self.assertFalse(reset.called) self.assertFalse(mockstdout.flush.called) # No output tty.stdout.write(u'X\n') self.assertEqual(tty.stdread.readline(), 'X\n') # process_exit is True, set_scroll False manager.process_exit = True manager.set_scroll = False manager._at_exit() self.assertFalse(reset.called) self.assertEqual(mockstdout.flush.call_count, 1) self.assertEqual(tty.stdread.readline(), term.move(25, 0) + term.cud1) # process_exit is True, set_scroll True manager.set_scroll = True manager._at_exit() self.assertEqual(reset.call_count, 1) self.assertEqual(mockstdout.flush.call_count, 2) self.assertEqual(tty.stdread.readline(), term.cud1) # Ensure companion stream gets flushed manager.companion_stream = tty.stdout manager._at_exit() self.assertEqual(reset.call_count, 2) self.assertEqual(mockstdout.flush.call_count, 4) self.assertEqual(tty.stdread.readline(), term.cud1) term = manager.term # Ensure no errors if tty closes before _at_exit is called tty.close() manager._at_exit() def test_stop(self): with mock.patch('%s.reset' % TERMINAL) as reset: manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 term = manager.term self.assertIsNone(manager.companion_term) with mock.patch('enlighten._manager.atexit'): with mock.patch.object(term, 'change_scroll'): manager._set_scroll_area() self.assertEqual(manager.scroll_offset, 5) self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler) self.assertTrue(manager.process_exit) # Clear stream self.tty.stdout.write(u'X\n') for num in range(4 + 1): # pylint: disable=unused-variable self.tty.stdread.readline() self.assertFalse(reset.called) manager.enabled = False manager.stop() # No output, No changes self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler) self.assertTrue(manager.process_exit) manager.enabled = True manager.stop() self.assertEqual(signal.getsignal(signal.SIGWINCH), manager.sigwinch_orig) self.assertEqual(reset.call_count, 1) self.tty.stdout.write(u'X\n') self.assertEqual( self.tty.stdread.readline(), term.move(23, 0) + term.clear_eol + term.move(24, 0) + term.clear_eol + 'X\n') self.assertFalse(manager.process_exit) self.assertFalse(manager.enabled) for counter in manager.counters: self.assertFalse(counter.enabled) def test_stop_no_set_scroll(self): """ set_scroll is False """ with mock.patch('%s.reset' % TERMINAL) as reset: manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, set_scroll=False) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 term = manager.term with mock.patch('enlighten._manager.atexit'): with mock.patch.object(term, 'change_scroll'): manager._set_scroll_area() self.assertEqual(manager.scroll_offset, 5) self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler) self.assertTrue(manager.process_exit) # Stream empty self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') manager.stop() self.assertEqual(signal.getsignal(signal.SIGWINCH), manager.sigwinch_orig) self.assertFalse(reset.called) self.tty.stdout.write(u'X\n') self.assertEqual( self.tty.stdread.readline(), term.move(23, 0) + term.clear_eol + term.move(24, 0) + term.clear_eol + term.move(25, 0) + 'X\n') self.assertFalse(manager.process_exit) def test_stop_never_used(self): """ In this case, _set_scroll_area() was never called """ with mock.patch('%s.reset' % TERMINAL) as reset: manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 self.assertFalse(manager.process_exit) manager.stop() self.assertEqual(signal.getsignal(signal.SIGWINCH), manager.sigwinch_orig) self.assertEqual(reset.call_count, 1) # No output self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') def test_stop_companion(self): """ In this case, we have a companion terminal Just make sure we have an extra reset """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter, companion_stream=self.tty.stdout) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 term = manager.term with mock.patch('enlighten._manager.atexit'): with mock.patch.object(term, 'change_scroll'): manager._set_scroll_area() with mock.patch.object(manager.companion_term, 'reset') as compReset: manager.stop() self.assertEqual(compReset.call_count, 1) def test_stop_position_1(self): """ Ensure a line feed is given if there is a counter at position 1 """ manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) manager.counters[MockCounter(manager=manager)] = 3 with mock.patch.object(manager.term, 'feed') as termfeed: manager.stop() self.assertFalse(termfeed.called) manager.enabled = True manager.counters[MockCounter(manager=manager)] = 1 with mock.patch.object(manager.term, 'feed') as termfeed: manager.stop() self.assertTrue(termfeed.called) def test_resize_handler(self): with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockheight: mockheight.side_effect = [80, 85, 87, 70, 70] manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) counter3 = MockCounter(manager=manager) manager.counters[counter3] = 3 manager.scroll_offset = 4 term = manager.term manager.resize_lock = True with mock.patch( 'enlighten._manager.Manager._set_scroll_area') as ssa: manager._resize_handler() self.assertFalse(ssa.called) self.assertEqual(manager.width, 80) self.assertTrue(manager.resize_lock) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') self.assertEqual(counter3.calls, []) manager.resize_lock = False mockheight.side_effect = [80, 85, 87, 70, 70] with mock.patch( 'enlighten._manager.Manager._set_scroll_area') as ssa: manager._resize_handler() self.assertEqual(ssa.call_count, 1) self.assertEqual(manager.width, 70) self.assertFalse(manager.resize_lock) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), term.move(19, 0) + term.clear_eos + 'X\n') self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)']) def test_resize_handler_no_change(self): with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockheight: mockheight.side_effect = [80, 85, 87, 80, 80] manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) counter3 = MockCounter(manager=manager) manager.counters[counter3] = 3 manager.scroll_offset = 4 with mock.patch( 'enlighten._manager.Manager._set_scroll_area') as ssa: manager._resize_handler() self.assertEqual(ssa.call_count, 1) self.assertEqual(manager.width, 80) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)']) def test_resize_handler_height_only(self): with mock.patch('%s.height' % TERMINAL, new_callable=mock.PropertyMock) as mockheight: mockheight.side_effect = [25, 23, 28, 30, 30] manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter) counter3 = MockCounter(manager=manager) manager.counters[counter3] = 3 manager.scroll_offset = 4 with mock.patch( 'enlighten._manager.Manager._set_scroll_area') as ssa: manager._resize_handler() self.assertEqual(ssa.call_count, 1) # Height is set in _set_scroll_area which is mocked self.assertEqual(manager.height, 25) self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)']) def test_disable(self): mgr = _manager.Manager(stream=self.tty.stdout, enabled=False) self.assertFalse(mgr.enabled) ctr = mgr.counter() self.assertIsInstance(ctr, _manager.Counter) self.assertFalse(ctr.enabled) # Make sure this doesn't error ctr.update() ctr.update(4) ctr.refresh() ctr.close() ctr.leave = False ctr.close() mgr.write() mgr.stop() # No Output self.tty.stdout.write(u'X\n') self.assertEqual(self.tty.stdread.readline(), 'X\n') def test_context_manager(self): mgr = None with _manager.Manager(stream=self.tty.stdout) as manager: self.assertIsInstance(manager, _manager.Manager) self.assertTrue(manager.enabled) mgr = manager self.assertFalse(mgr.enabled) def test_no_resize_signal(self): # Test normal case initialization stdmgr = _manager.Manager(stream=self.tty.stdout) self.assertTrue(hasattr(stdmgr, 'sigwinch_orig')) stdmgr.counters[MockCounter(manager=stdmgr)] = 3 stdmgr.counters[MockCounter(manager=stdmgr)] = 4 # Test no resize signal initialization with mock.patch.object(_manager, 'RESIZE_SUPPORTED', False): manager = _manager.Manager(stream=self.tty.stdout) self.assertFalse(hasattr(manager, 'sigwinch_orig')) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 # Test set_scroll_area() with mock.patch.object(_manager.signal, 'signal', wraps=_manager.signal.signal) as mocksignal: with mock.patch('enlighten._manager.atexit'): # Test no resize signal set_scroll_area with mock.patch.object(_manager, 'RESIZE_SUPPORTED', False): with mock.patch.object(manager.term, 'change_scroll'): manager._set_scroll_area() self.assertFalse(mocksignal.called) # Test normal case set_scroll_area with mock.patch.object(stdmgr.term, 'change_scroll'): stdmgr._set_scroll_area() self.assertTrue(mocksignal.called) # Test stop() with mock.patch.object(_manager.signal, 'signal', wraps=_manager.signal.signal) as mocksignal: with mock.patch('%s.reset' % TERMINAL): # Test no resize signal stop with mock.patch.object(_manager, 'RESIZE_SUPPORTED', False): manager.stop() self.assertFalse(mocksignal.called) # Test normal case stop stdmgr.stop() self.assertTrue(mocksignal.called) def test_no_resize(self): with mock.patch.object(_manager.signal, 'signal', wraps=_manager.signal.signal) as mocksignal: manager = _manager.Manager(stream=self.tty.stdout, no_resize=True) self.assertFalse(hasattr(manager, 'sigwinch_orig')) self.assertFalse(mocksignal.called) manager.counters[MockCounter(manager=manager)] = 3 manager.counters[MockCounter(manager=manager)] = 4 with mock.patch.object(manager.term, 'change_scroll'): manager._set_scroll_area() self.assertFalse(mocksignal.called) with mock.patch('%s.reset' % TERMINAL): manager.stop() self.assertFalse(mocksignal.called)
class TestStatusBar(TestCase): """ Test the StatusBar class """ def setUp(self): self.tty = MockTTY() self.manager = MockManager(stream=self.tty.stdout) def tearDown(self): self.tty.close() def test_static(self): """ Basic static status bar """ sbar = self.manager.status_bar('Hello', 'World!') self.assertEqual(sbar.format(), 'Hello World!' + ' ' * 68) sbar.update('Goodbye, World!') self.assertEqual(sbar.format(), 'Goodbye, World!' + ' ' * 65) def test_static_justify(self): """ Justified static status bar """ sbar = self.manager.status_bar('Hello', 'World!', justify=Justify.LEFT) self.assertEqual(sbar.format(), 'Hello World!' + ' ' * 68) sbar = self.manager.status_bar('Hello', 'World!', justify=Justify.RIGHT) self.assertEqual(sbar.format(), ' ' * 68 + 'Hello World!') sbar = self.manager.status_bar('Hello', 'World!', justify=Justify.CENTER) self.assertEqual(sbar.format(), ' ' * 34 + 'Hello World!' + ' ' * 34) def test_formatted(self): """ Basic formatted status bar """ sbar = self.manager.status_bar(status_format=u'Stage: {stage}, Status: {status}', stage=1, fields={'status': 'All good!'}) self.assertEqual(sbar.format(), 'Stage: 1, Status: All good!' + ' ' * 53) sbar.update(stage=2) self.assertEqual(sbar.format(), 'Stage: 2, Status: All good!' + ' ' * 53) sbar.update(stage=3, status='Meh') self.assertEqual(sbar.format(), 'Stage: 3, Status: Meh' + ' ' * 59) def test_formatted_justify(self): """ Justified formatted status bar """ sbar = self.manager.status_bar(status_format=u'Stage: {stage}, Status: {status}', stage=1, fields={'status': 'All good!'}, justify=Justify.LEFT) self.assertEqual(sbar.format(), 'Stage: 1, Status: All good!' + ' ' * 53) sbar = self.manager.status_bar(status_format=u'Stage: {stage}, Status: {status}', stage=1, fields={'status': 'All good!'}, justify=Justify.RIGHT) self.assertEqual(sbar.format(), ' ' * 53 + 'Stage: 1, Status: All good!') sbar = self.manager.status_bar(status_format=u'Stage: {stage}, Status: {status}', stage=1, fields={'status': 'All good'}, justify=Justify.CENTER) self.assertEqual(sbar.format(), ' ' * 27 + 'Stage: 1, Status: All good' + ' ' * 27) def test_formatted_missing_field(self): """ ValueError raised when a field is missing when updating status bar """ fields = {'status': 'All good!'} sbar = self.manager.status_bar(status_format=u'Stage: {stage}, Status: {status}', stage=1, fields=fields) del fields['status'] sbar.last_update = sbar.start - 5.0 with self.assertRaisesRegex(ValueError, "'status' specified in format, but not provided"): sbar.update() def test_bad_justify(self): """ ValueError raised when justify is given an invalid value """ with self.assertRaisesRegex(ValueError, 'justify must be one of Justify.LEFT, '): self.manager.status_bar('Hello', 'World!', justify='justice') def test_update(self): """ update() does not refresh is bar is disabled or min_delta hasn't passed """ self.manager.status_bar_class = MockStatusBar sbar = self.manager.status_bar('Hello', 'World!') self.assertEqual(sbar.called, 1) sbar.last_update = sbar.start - 1.0 sbar.update() self.assertEqual(sbar.called, 2) sbar.last_update = sbar.start + 5.0 sbar.update() self.assertEqual(sbar.called, 2) sbar.last_update = sbar.last_update - 10.0 sbar.enabled = False sbar.update() self.assertEqual(sbar.called, 2) sbar.enabled = True sbar.update() self.assertEqual(sbar.called, 3) def test_fill(self): """ Fill uses remaining space """ sbar = self.manager.status_bar(status_format=u'{fill}HI', fill='-') self.assertEqual(sbar.format(), u'-' * 78 + 'HI') sbar = self.manager.status_bar(status_format=u'{fill}HI{fill}', fill='-') self.assertEqual(sbar.format(), u'-' * 39 + 'HI' + u'-' * 39) def test_fill_uneven(self): """ Extra fill should be equal """ print(self.manager.term.width) sbar = self.manager.status_bar( status_format=u'{fill}Helloooo!{fill}Woooorld!{fill}', fill='-' ) self.assertEqual(sbar.format(), u'-' * 20 + 'Helloooo!' + u'-' * 21 + 'Woooorld!' + u'-' * 21)
class TestBaseCounter(TestCase): """ Test the BaseCounter class """ def setUp(self): self.tty = MockTTY() self.manager = MockManager(stream=self.tty.stdout) def tearDown(self): self.tty.close() def test_init_default(self): """Ensure default values are set""" counter = BaseCounter(manager=self.manager) self.assertIsNone(counter.color) self.assertIsNone(counter.color) self.assertIs(counter.manager, self.manager) self.assertEqual(counter.count, 0) self.assertEqual(counter.start_count, 0) def test_no_manager(self): """Raise an error if there is no manager specified""" with self.assertRaisesRegex(TypeError, 'manager must be specified'): BaseCounter() def test_color_invalid(self): """Color must be a valid string, RGB, or int 0 - 255""" # Unsupported type with self.assertRaisesRegex(AttributeError, 'Invalid color specified: 1.0'): BaseCounter(manager=self.manager, color=1.0) # Invalid String with self.assertRaisesRegex(AttributeError, 'Invalid color specified: buggersnot'): BaseCounter(manager=self.manager, color='buggersnot') # Invalid integer with self.assertRaisesRegex(AttributeError, 'Invalid color specified: -1'): BaseCounter(manager=self.manager, color=-1) with self.assertRaisesRegex(AttributeError, 'Invalid color specified: 256'): BaseCounter(manager=self.manager, color=256) # Invalid iterable with self.assertRaisesRegex(AttributeError, r'Invalid color specified: \[\]'): BaseCounter(manager=self.manager, color=[]) with self.assertRaisesRegex(AttributeError, r'Invalid color specified: \[1\]'): BaseCounter(manager=self.manager, color=[1]) with self.assertRaisesRegex(AttributeError, r'Invalid color specified: \(1, 2\)'): BaseCounter(manager=self.manager, color=(1, 2)) with self.assertRaisesRegex( AttributeError, r'Invalid color specified: \(1, 2, 3, 4\)'): BaseCounter(manager=self.manager, color=(1, 2, 3, 4)) def test_colorize_none(self): """If color is None, return content unchanged""" counter = BaseCounter(manager=self.manager) self.assertEqual(counter._colorize('test'), 'test') def test_colorize_string(self): """Return string formatted with color (string)""" counter = BaseCounter(manager=self.manager, color='red') self.assertEqual(counter.color, 'red') self.assertEqual(counter._color, ('red', self.manager.term.red)) self.assertNotEqual(counter._colorize('test'), 'test') self.assertEqual(counter._colorize('test'), self.manager.term.red('test')) def test_colorize_string_compound(self): """Return string formatted with compound color (string)""" counter = BaseCounter(manager=self.manager, color='bold_red_on_blue') self.assertEqual(counter.color, 'bold_red_on_blue') self.assertEqual( counter._color, ('bold_red_on_blue', self.manager.term.bold_red_on_blue)) self.assertNotEqual(counter._colorize('test'), 'test') self.assertEqual(counter._colorize('test'), self.manager.term.bold_red_on_blue('test')) def test_colorize_int(self): """Return string formatted with color (int)""" counter = BaseCounter(manager=self.manager, color=40) self.assertEqual(counter.color, 40) self.assertEqual(counter._color, (40, self.manager.term.color(40))) self.assertNotEqual(counter._colorize('test'), 'test') self.assertEqual(counter._colorize('test'), self.manager.term.color(40)('test')) def test_colorize_rgb(self): """Return string formatted with color (RGB)""" counter = BaseCounter(manager=self.manager, color=(50, 40, 60)) self.assertEqual(counter.color, (50, 40, 60)) self.assertEqual( counter._color, ((50, 40, 60), self.manager.term.color_rgb(50, 40, 60))) self.assertNotEqual(counter._colorize('test'), 'test') self.assertEqual(counter._colorize('test'), self.manager.term.color_rgb(50, 40, 60)('test')) def test_call(self): """Returns generator when used as a function""" # Bad arguments counter = MockBaseCounter(manager=self.manager) with self.assertRaisesRegex(TypeError, 'Argument type int is not iterable'): list(counter(1)) with self.assertRaisesRegex(TypeError, 'Argument type bool is not iterable'): list(counter([1, 2, 3], True)) # Expected counter = MockBaseCounter(manager=self.manager) rtn = counter([1, 2, 3]) self.assertIsInstance(rtn, GeneratorType) self.assertEqual(list(rtn), [1, 2, 3]) self.assertEqual(counter.count, 3) # Multiple arguments counter = MockBaseCounter(manager=self.manager) rtn = counter([1, 2, 3], (3, 2, 1)) self.assertIsInstance(rtn, GeneratorType) self.assertEqual(tuple(rtn), (1, 2, 3, 3, 2, 1)) self.assertEqual(counter.count, 6)
def setUp(self): self.tty = MockTTY()
def setUp(self): self.tty = MockTTY() self.resize_sig = signal.getsignal(signal.SIGWINCH)
def setUp(self): self.tty = MockTTY() self.manager = MockManager(stream=self.tty.stdout)
class TestBaseCounter(TestCase): """ Test the BaseCounter class """ def setUp(self): self.tty = MockTTY() self.manager = MockManager(stream=self.tty.stdout) def tearDown(self): self.tty.close() def test_init_default(self): """Ensure default values are set""" counter = enlighten._counter.BaseCounter(manager=self.manager) self.assertIsNone(counter.color) self.assertIsNone(counter.color) self.assertIs(counter.manager, self.manager) self.assertEqual(counter.count, 0) self.assertEqual(counter.start_count, 0) def test_no_manager(self): """Raise an error if there is no manager specified""" with self.assertRaisesRegex(TypeError, 'manager must be specified'): enlighten._counter.BaseCounter() def test_color(self): """Color must be a valid string or int 0 - 255""" # Unsupported type with self.assertRaisesRegex(TypeError, 'color must be a string or integer'): enlighten._counter.BaseCounter(manager=self.manager, color=[]) # Color is a string counter = enlighten._counter.BaseCounter(manager=self.manager, color='red') self.assertEqual(counter.color, 'red') with self.assertRaisesRegex(ValueError, 'Unsupported color: banana'): enlighten._counter.BaseCounter(manager=self.manager, color='banana') # Color is an integer counter = enlighten._counter.BaseCounter(manager=self.manager, color=15) self.assertEqual(counter.color, 15) with self.assertRaisesRegex(ValueError, 'Unsupported color: -1'): enlighten._counter.BaseCounter(manager=self.manager, color=-1) with self.assertRaisesRegex(ValueError, 'Unsupported color: 256'): enlighten._counter.BaseCounter(manager=self.manager, color=256) def test_colorize_none(self): """If color is None, return content unchanged""" counter = enlighten._counter.BaseCounter(manager=self.manager) self.assertEqual(counter._colorize('test'), 'test') def test_colorize(self): """Return string formatted with color""" # Color is a string counter = enlighten._counter.BaseCounter(manager=self.manager, color='red') self.assertIsNone(counter._color) self.assertNotEqual(counter._colorize('test'), 'test') cache = counter._color self.assertEqual(counter._colorize('test'), self.manager.term.red('test')) self.assertEqual(counter._color[0], 'red') self.assertIs(counter._color[1], self.manager.term.red) self.assertIs(counter._color, cache) # Color is an integer counter = enlighten._counter.BaseCounter(manager=self.manager, color=40) self.assertIsNone(counter._color) self.assertNotEqual(counter._colorize('test'), 'test') cache = counter._color self.assertEqual(counter._colorize('test'), self.manager.term.color(40)('test')) self.assertEqual(counter._color[0], 40) # New instance is generated each time, so just compare strings self.assertEqual(counter._color[1], self.manager.term.color(40)) self.assertIs(counter._color, cache) def test_call(self): """Returns generator when used as a function""" # Bad arguments counter = MockBaseCounter(manager=self.manager) with self.assertRaisesRegex(TypeError, 'Argument type int is not iterable'): list(counter(1)) with self.assertRaisesRegex(TypeError, 'Argument type bool is not iterable'): list(counter([1, 2, 3], True)) # Expected counter = MockBaseCounter(manager=self.manager) rtn = counter([1, 2, 3]) self.assertIsInstance(rtn, GeneratorType) self.assertEqual(list(rtn), [1, 2, 3]) self.assertEqual(counter.count, 3) # Multiple arguments counter = MockBaseCounter(manager=self.manager) rtn = counter([1, 2, 3], (3, 2, 1)) self.assertIsInstance(rtn, GeneratorType) self.assertEqual(tuple(rtn), (1, 2, 3, 3, 2, 1)) self.assertEqual(counter.count, 6)