commit 7719621a1e184e5e94fec94a06a70ded0d2171e0 Author: Damian Johnson atagar@torproject.org Date: Sat Jun 25 12:34:24 2016 -0700
Test for drawing a blank subgraph
Not an overly useful test but first step in breaking these into functions and adding test coverage. --- nyx/panel/graph.py | 216 ++++++++++++++++++++++++++-------------------------- test/panel/graph.py | 30 ++++++++ 2 files changed, 140 insertions(+), 106 deletions(-)
diff --git a/nyx/panel/graph.py b/nyx/panel/graph.py index d3ed768..68c136a 100644 --- a/nyx/panel/graph.py +++ b/nyx/panel/graph.py @@ -556,152 +556,156 @@ class GraphPanel(nyx.panel.Panel):
subwindow.addstr(0, 0, attr.stat.title(subwindow.width), HIGHLIGHT)
- self._draw_subgraph(subwindow, attr, attr.stat.primary, 0, PRIMARY_COLOR) - self._draw_subgraph(subwindow, attr, attr.stat.secondary, attr.subgraph_width, SECONDARY_COLOR) + _draw_subgraph(subwindow, attr, attr.stat.primary, 0, PRIMARY_COLOR) + _draw_subgraph(subwindow, attr, attr.stat.secondary, attr.subgraph_width, SECONDARY_COLOR)
if attr.stat.stat_type() == GraphStat.BANDWIDTH and accounting_stats: _draw_accounting_stats(subwindow, DEFAULT_CONTENT_HEIGHT + attr.subgraph_height - 2, accounting_stats)
- def _draw_subgraph(self, subwindow, attr, data, x, color): - # Concering our subgraph colums, the y-axis label can be at most six - # characters, with two spaces of padding on either side of the graph. - # Starting with the smallest size, then possibly raise it after determing - # the y_axis_labels. + def _update_accounting(self, event): + if not CONFIG['features.graph.bw.accounting.show']: + self._accounting_stats = None + elif not self._accounting_stats or time.time() - self._accounting_stats.retrieved >= ACCOUNTING_RATE: + old_accounting_stats = self._accounting_stats + self._accounting_stats = tor_controller().get_accounting_stats(None)
- subgraph_columns = attr.subgraph_width - 8 - min_bound, max_bound = self._get_graph_bounds(attr, data, subgraph_columns) + if not self.is_paused(): + # if we either added or removed accounting info then redraw the whole + # screen to account for resizing
- x_axis_labels = self._get_x_axis_labels(attr, subgraph_columns) - y_axis_labels = self._get_y_axis_labels(attr, data, min_bound, max_bound) - subgraph_columns = max(subgraph_columns, attr.subgraph_width - max([len(label) for label in y_axis_labels.values()]) - 2) - axis_offset = max([len(label) for label in y_axis_labels.values()]) + if bool(old_accounting_stats) != bool(self._accounting_stats): + nyx.controller.get_controller().redraw()
- subwindow.addstr(x, 1, data.header(attr.subgraph_width), color, BOLD) + def _update_stats(self, event): + for stat in self._stats.values(): + stat.bandwidth_event(event)
- for x_offset, label in x_axis_labels.items(): - if attr.right_to_left: - subwindow.addstr(x + attr.subgraph_width - x_offset, attr.subgraph_height, label, color) - else: - subwindow.addstr(x + x_offset + axis_offset, attr.subgraph_height, label, color) + if self.displayed_stat: + param = self._stats[self.displayed_stat] + update_rate = INTERVAL_SECONDS[self.update_interval]
- for y, label in y_axis_labels.items(): - subwindow.addstr(x, y, label, color) + if param.primary.tick % update_rate == 0: + self.redraw(True)
- for col in range(subgraph_columns): - column_count = int(data.values[attr.interval][col]) - min_bound - column_height = int(min(attr.subgraph_height - 2, (attr.subgraph_height - 2) * column_count / (max(1, max_bound) - min_bound)))
- for row in range(column_height): - if attr.right_to_left: - subwindow.addstr(x + attr.subgraph_width - col - 1, attr.subgraph_height - 1 - row, ' ', color, HIGHLIGHT) - else: - subwindow.addstr(x + col + axis_offset + 1, attr.subgraph_height - 1 - row, ' ', color, HIGHLIGHT) +def _draw_subgraph(subwindow, attr, data, x, color): + # Concering our subgraph colums, the y-axis label can be at most six + # characters, with two spaces of padding on either side of the graph. + # Starting with the smallest size, then possibly raise it after determing + # the y_axis_labels.
- def _get_graph_bounds(self, attr, data, subgraph_columns): - """ - Provides the range the graph shows (ie, its minimum and maximum value). - """ + subgraph_columns = attr.subgraph_width - 8 + min_bound, max_bound = _get_graph_bounds(attr, data, subgraph_columns)
- min_bound, max_bound = 0, 0 - values = data.values[attr.interval][:subgraph_columns] + x_axis_labels = _x_axis_labels(attr.interval, subgraph_columns) + y_axis_labels = _y_axis_labels(attr.subgraph_height, data, min_bound, max_bound) + subgraph_columns = max(subgraph_columns, attr.subgraph_width - max([len(label) for label in y_axis_labels.values()]) - 2) + axis_offset = max([len(label) for label in y_axis_labels.values()])
- if attr.bounds_type == Bounds.GLOBAL_MAX: - max_bound = data.max_value[attr.interval] - elif subgraph_columns > 0: - max_bound = max(values) # local maxima + subwindow.addstr(x, 1, data.header(attr.subgraph_width), color, BOLD)
- if attr.bounds_type == Bounds.TIGHT and subgraph_columns > 0: - min_bound = min(values) + for x_offset, label in x_axis_labels.items(): + if attr.right_to_left: + subwindow.addstr(x + attr.subgraph_width - x_offset, attr.subgraph_height, label, color) + else: + subwindow.addstr(x + x_offset + axis_offset, attr.subgraph_height, label, color)
- # if the max = min pick zero so we still display something + for y, label in y_axis_labels.items(): + subwindow.addstr(x, y, label, color)
- if min_bound == max_bound: - min_bound = 0 + for col in range(subgraph_columns): + column_count = int(data.values[attr.interval][col]) - min_bound + column_height = int(min(attr.subgraph_height - 2, (attr.subgraph_height - 2) * column_count / (max(1, max_bound) - min_bound)))
- return min_bound, max_bound + for row in range(column_height): + if attr.right_to_left: + subwindow.addstr(x + attr.subgraph_width - col - 1, attr.subgraph_height - 1 - row, ' ', color, HIGHLIGHT) + else: + subwindow.addstr(x + col + axis_offset + 1, attr.subgraph_height - 1 - row, ' ', color, HIGHLIGHT)
- def _get_y_axis_labels(self, attr, data, min_bound, max_bound): - """ - Provides the labels for the y-axis. This is a mapping of the position it - should be drawn at to its text. - """
- y_axis_labels = { - 2: data.y_axis_label(max_bound), - attr.subgraph_height - 1: data.y_axis_label(min_bound), - } +def _get_graph_bounds(attr, data, subgraph_columns): + """ + Provides the range the graph shows (ie, its minimum and maximum value). + """
- ticks = (attr.subgraph_height - 5) / 2 + min_bound, max_bound = 0, 0 + values = data.values[attr.interval][:subgraph_columns]
- for i in range(ticks): - row = attr.subgraph_height - (2 * i) - 5 + if attr.bounds_type == Bounds.GLOBAL_MAX: + max_bound = data.max_value[attr.interval] + elif subgraph_columns > 0: + max_bound = max(values) # local maxima
- if attr.subgraph_height % 2 == 0 and i >= (ticks / 2): - row -= 1 # make extra gap be in the middle when we're an even size + if attr.bounds_type == Bounds.TIGHT and subgraph_columns > 0: + min_bound = min(values)
- val = (max_bound - min_bound) * (attr.subgraph_height - row - 3) / (attr.subgraph_height - 3) + # if the max = min pick zero so we still display something
- if val not in (min_bound, max_bound): - y_axis_labels[row + 2] = data.y_axis_label(val) + if min_bound == max_bound: + min_bound = 0
- return y_axis_labels + return min_bound, max_bound
- def _get_x_axis_labels(self, attr, subgraph_columns): - """ - Provides the labels for the x-axis. We include the units for only its first - value, then bump the precision for subsequent units. For example...
- 10s, 20, 30, 40, 50, 1m, 1.1, 1.3, 1.5 - """ +def _x_axis_labels(interval, subgraph_columns): + """ + Provides the labels for the x-axis. We include the units for only its first + value, then bump the precision for subsequent units. For example...
- x_axis_labels = {} + 10s, 20, 30, 40, 50, 1m, 1.1, 1.3, 1.5 + """
- interval_sec = INTERVAL_SECONDS[attr.interval] - interval_spacing = 10 if subgraph_columns >= WIDE_LABELING_GRAPH_COL else 5 - units_label, decimal_precision = None, 0 + x_axis_labels = {}
- for i in range((subgraph_columns - 4) / interval_spacing): - x = (i + 1) * interval_spacing - time_label = str_tools.time_label(x * interval_sec, decimal_precision) + interval_sec = INTERVAL_SECONDS[interval] + interval_spacing = 10 if subgraph_columns >= WIDE_LABELING_GRAPH_COL else 5 + units_label, decimal_precision = None, 0
- if not units_label: - units_label = time_label[-1] - elif units_label != time_label[-1]: - # upped scale so also up precision of future measurements - units_label = time_label[-1] - decimal_precision += 1 - else: - # if constrained on space then strips labeling since already provided - time_label = time_label[:-1] + for i in range((subgraph_columns - 4) / interval_spacing): + x = (i + 1) * interval_spacing + time_label = str_tools.time_label(x * interval_sec, decimal_precision)
- x_axis_labels[x] = time_label + if not units_label: + units_label = time_label[-1] + elif units_label != time_label[-1]: + # upped scale so also up precision of future measurements + units_label = time_label[-1] + decimal_precision += 1 + else: + # if constrained on space then strips labeling since already provided + time_label = time_label[:-1]
- return x_axis_labels + x_axis_labels[x] = time_label
- def _update_accounting(self, event): - if not CONFIG['features.graph.bw.accounting.show']: - self._accounting_stats = None - elif not self._accounting_stats or time.time() - self._accounting_stats.retrieved >= ACCOUNTING_RATE: - old_accounting_stats = self._accounting_stats - self._accounting_stats = tor_controller().get_accounting_stats(None) + return x_axis_labels
- if not self.is_paused(): - # if we either added or removed accounting info then redraw the whole - # screen to account for resizing
- if bool(old_accounting_stats) != bool(self._accounting_stats): - nyx.controller.get_controller().redraw() +def _y_axis_labels(subgraph_height, data, min_bound, max_bound): + """ + Provides the labels for the y-axis. This is a mapping of the position it + should be drawn at to its text. + """
- def _update_stats(self, event): - for stat in self._stats.values(): - stat.bandwidth_event(event) + y_axis_labels = { + 2: data.y_axis_label(max_bound), + subgraph_height - 1: data.y_axis_label(min_bound), + }
- if self.displayed_stat: - param = self._stats[self.displayed_stat] - update_rate = INTERVAL_SECONDS[self.update_interval] + ticks = (subgraph_height - 5) / 2
- if param.primary.tick % update_rate == 0: - self.redraw(True) + for i in range(ticks): + row = subgraph_height - (2 * i) - 5 + + if subgraph_height % 2 == 0 and i >= (ticks / 2): + row -= 1 # make extra gap be in the middle when we're an even size + + val = (max_bound - min_bound) * (subgraph_height - row - 3) / (subgraph_height - 3) + + if val not in (min_bound, max_bound): + y_axis_labels[row + 2] = data.y_axis_label(val) + + return y_axis_labels
def _draw_accounting_stats(subwindow, y, accounting): diff --git a/test/panel/graph.py b/test/panel/graph.py index f2eca4b..26fd7da 100644 --- a/test/panel/graph.py +++ b/test/panel/graph.py @@ -7,12 +7,23 @@ import unittest
import stem.control
+import nyx.curses import nyx.panel.graph import test
from test import require_curses from mock import patch
+EXPECTED_BLANK_GRAPH = """ +Download: +0 b + + + +0 b + 5s 10 15 +""".rstrip() + EXPECTED_ACCOUNTING = """ Accounting (awake) Time to reset: 01:02 37.7 Kb / 842.0 Kb 16.0 Kb / 74.1 Kb @@ -22,6 +33,25 @@ Accounting (awake) Time to reset: 01:02 class TestGraph(unittest.TestCase): @require_curses @patch('nyx.panel.graph.tor_controller') + def test_draw_subgraph_blank(self, tor_controller_mock): + tor_controller_mock().get_info.return_value = None + + attr = nyx.panel.graph.DrawAttributes( + stat = None, + subgraph_height = 7, + subgraph_width = 30, + interval = nyx.panel.graph.Interval.EACH_SECOND, + bounds_type = nyx.panel.graph.Bounds.LOCAL_MAX, + right_to_left = False, + ) + + data = nyx.panel.graph.BandwidthStats() + + rendered = test.render(nyx.panel.graph._draw_subgraph, attr, data.primary, 0, nyx.curses.Color.CYAN) + self.assertEqual(EXPECTED_BLANK_GRAPH, rendered.content) + + @require_curses + @patch('nyx.panel.graph.tor_controller') def test_draw_accounting_stats(self, tor_controller_mock): tor_controller_mock().is_alive.return_value = True
tor-commits@lists.torproject.org