mirror of
https://github.com/nicolargo/glances.git
synced 2026-10-07 09:21:56 -04:00
_GlancesCurses.load_config read [outputs] separator and disable_bg with the command-line value passed in as the default, so any value present in the file won: with separator=True in glances.conf, `glances --disable-separator` still drew the separator, and so did --disable-unicode, undoing main.py's "Unicode => No separator". With disable_bg=False in the file, --disable-bg was ignored. docs/config.rst says options given on the command line override the configuration files. Both flags only move one way -- --disable-separator can only turn the separator off and --disable-bg can only turn the background off -- so keep the value when the flag is set and let the file decide otherwise. With no flag, or with the key absent, the result is unchanged.
251 lines
8.5 KiB
Python
251 lines
8.5 KiB
Python
import curses
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock, Mock
|
|
|
|
import pytest
|
|
|
|
from glances.outputs.glances_curses import _GlancesCurses
|
|
|
|
|
|
@pytest.fixture
|
|
def glancescreen():
|
|
"""Create a lightweight _GlancesCurses instance for helper method testing.
|
|
|
|
The full curses interface is intentionally bypassed in order to isolate
|
|
and test layout/helper logic independently from terminal rendering.
|
|
"""
|
|
# Bypass full curses initialization and create a lightweight instance
|
|
screen = _GlancesCurses.__new__(_GlancesCurses)
|
|
|
|
# Mock runtime arguments required by helper methods
|
|
screen.args = SimpleNamespace(
|
|
disable_cpu=False,
|
|
disable_mem=False,
|
|
disable_load=False,
|
|
disable_quicklook=False,
|
|
full_quicklook=False,
|
|
)
|
|
|
|
# Mock terminal dimensions
|
|
screen.term_window = Mock()
|
|
screen.term_window.getmaxyx.return_value = (24, 120)
|
|
|
|
# Mock rendering-related methods to isolate layout logic
|
|
screen.display_plugin = Mock()
|
|
screen.new_column = Mock()
|
|
|
|
# Initialize required internal state
|
|
screen.space_between_column = 3
|
|
screen._quicklook_max_width = 100
|
|
screen._top = ['cpu', 'mem', 'load']
|
|
|
|
return screen
|
|
|
|
|
|
class TestDisplayTopHelpers:
|
|
"""Tests for helper methods extracted from __display_top."""
|
|
|
|
def test_get_stats_summary(self, glancescreen):
|
|
"""Ensure plugin width totals and active plugin counts are computed correctly."""
|
|
stat_display = {
|
|
'cpu': {'msgdict': ['cpu']},
|
|
'mem': {'msgdict': ['mem']},
|
|
'load': {'msgdict': []},
|
|
}
|
|
|
|
plugin_widths = {
|
|
'cpu': 10,
|
|
'mem': 20,
|
|
'load': 5,
|
|
}
|
|
|
|
stats_width, stats_number = glancescreen._get_stats_summary(
|
|
stat_display,
|
|
plugin_widths,
|
|
)
|
|
|
|
assert stats_width == 35 # nosec B101
|
|
assert stats_number == 2 # nosec B101
|
|
|
|
def test_compute_spacing_single_plugin(self, glancescreen):
|
|
"""Ensure spacing logic behaves correctly when only one plugin is displayed."""
|
|
stat_display = {
|
|
'cpu': {'msgdict': ['cpu']},
|
|
}
|
|
|
|
plugin_widths = {
|
|
'cpu': 10,
|
|
}
|
|
|
|
glancescreen._top = ['cpu']
|
|
|
|
plugin_display_optional, _ = glancescreen._compute_spacing_and_optional(
|
|
stat_display,
|
|
plugin_widths,
|
|
stats_width=10,
|
|
stats_number=1,
|
|
)
|
|
|
|
assert glancescreen.space_between_column == 0 # nosec B101
|
|
assert plugin_display_optional['cpu'] is True # nosec B101
|
|
|
|
def test_compute_spacing_disables_optional_stats(
|
|
self,
|
|
glancescreen,
|
|
):
|
|
"""Ensure optional CPU and MEM display elements are disabled when spacing is constrained."""
|
|
stat_display = {
|
|
'cpu': {'msgdict': ['cpu']},
|
|
'mem': {'msgdict': ['mem']},
|
|
}
|
|
|
|
plugin_widths = {
|
|
'cpu': 80,
|
|
'mem': 80,
|
|
}
|
|
|
|
glancescreen._top = ['cpu', 'mem']
|
|
|
|
# Simulate a constrained terminal width
|
|
glancescreen.term_window.getmaxyx.return_value = (24, 40)
|
|
|
|
# Mock reduced widths after optional content removal
|
|
glancescreen.get_stats_display_width = MagicMock(return_value=20)
|
|
|
|
plugin_display_optional, _ = glancescreen._compute_spacing_and_optional(
|
|
stat_display,
|
|
plugin_widths,
|
|
stats_width=160,
|
|
stats_number=2,
|
|
)
|
|
|
|
assert plugin_display_optional['cpu'] is False # nosec B101
|
|
assert plugin_display_optional['mem'] is False # nosec B101
|
|
assert glancescreen.space_between_column >= 1 # nosec B101
|
|
|
|
def test_get_plugin_width(self, glancescreen):
|
|
"""Ensure plugin widths are correctly retrieved and mapped."""
|
|
stat_display = {
|
|
'cpu': {'msgdict': ['cpu']},
|
|
'mem': {'msgdict': ['mem']},
|
|
}
|
|
|
|
glancescreen._top = ['cpu', 'mem']
|
|
|
|
glancescreen.args.disable_cpu = False
|
|
glancescreen.args.disable_mem = False
|
|
|
|
glancescreen.get_stats_display_width = MagicMock(side_effect=[10, 20])
|
|
|
|
plugin_widths = glancescreen._get_plugin_width(stat_display)
|
|
|
|
assert plugin_widths == {
|
|
'cpu': 10,
|
|
'mem': 20,
|
|
} # nosec B101
|
|
|
|
def test_handle_quicklook_plugin_unavailable(
|
|
self,
|
|
glancescreen,
|
|
):
|
|
"""Ensure unavailable quicklook plugins are handled gracefully."""
|
|
stats = Mock()
|
|
|
|
# Simulate unavailable quicklook plugin
|
|
stats.get_plugin.side_effect = AttributeError
|
|
|
|
stat_display = {
|
|
'quicklook': {'msgdict': []},
|
|
}
|
|
|
|
plugin_widths = {}
|
|
|
|
result_widths, result_stats_width = glancescreen._handle_quicklook_for_display(
|
|
stat_display,
|
|
stats,
|
|
plugin_widths,
|
|
stats_width=0,
|
|
stats_number=1,
|
|
)
|
|
|
|
assert result_widths == plugin_widths # nosec B101
|
|
assert result_stats_width == 0 # nosec B101
|
|
|
|
|
|
class TestCursorDisable:
|
|
"""Test cursor disabling in client/server mode (see issue #3221)."""
|
|
|
|
@pytest.fixture
|
|
def screen(self, glancescreen):
|
|
"""Extend the lightweight screen with cursor-related state."""
|
|
glancescreen.args.disable_cursor = True
|
|
glancescreen.args.cursor_process_name_position = 3
|
|
glancescreen.args.arrow_keys_sort = False
|
|
return glancescreen
|
|
|
|
def _dispatch(self, screen, key):
|
|
"""Invoke the dispatch table with a given pressed key."""
|
|
screen.pressedkey = key
|
|
screen.catch_other_actions_maybe_return_to_browser(return_to_browser=False)
|
|
|
|
def test_process_name_left_is_noop_when_cursor_disabled(self, screen):
|
|
"""Left arrow must not scroll the process name when cursor is disabled."""
|
|
self._dispatch(screen, curses.KEY_LEFT)
|
|
assert screen.args.cursor_process_name_position == 3 # nosec B101
|
|
|
|
def test_process_name_right_is_noop_when_cursor_disabled(self, screen):
|
|
"""Right arrow must not scroll the process name when cursor is disabled."""
|
|
self._dispatch(screen, curses.KEY_RIGHT)
|
|
assert screen.args.cursor_process_name_position == 3 # nosec B101
|
|
|
|
def test_process_name_right_advances_when_cursor_enabled(self, glancescreen):
|
|
"""When cursor is enabled, right arrow advances the name position."""
|
|
glancescreen.args.disable_cursor = False
|
|
glancescreen.args.cursor_process_name_position = 3
|
|
glancescreen.args.arrow_keys_sort = False
|
|
self._dispatch(glancescreen, curses.KEY_RIGHT)
|
|
assert glancescreen.args.cursor_process_name_position == 4 # nosec B101
|
|
|
|
def test_process_name_left_decrements_when_cursor_enabled(self, glancescreen):
|
|
"""When cursor is enabled, left arrow decrements the name position."""
|
|
glancescreen.args.disable_cursor = False
|
|
glancescreen.args.cursor_process_name_position = 3
|
|
glancescreen.args.arrow_keys_sort = False
|
|
self._dispatch(glancescreen, curses.KEY_LEFT)
|
|
assert glancescreen.args.cursor_process_name_position == 2 # nosec B101
|
|
|
|
|
|
class TestLoadConfigPrecedence:
|
|
"""docs/config.rst: options given on the command line override the file."""
|
|
|
|
@staticmethod
|
|
def load(outputs, **args):
|
|
from glances.config import Config
|
|
|
|
config = Config()
|
|
config.parser.read_dict({'outputs': outputs})
|
|
screen = _GlancesCurses.__new__(_GlancesCurses)
|
|
screen.args = SimpleNamespace(**args)
|
|
screen._left_sidebar = ['network']
|
|
screen.load_config(config)
|
|
return screen.args
|
|
|
|
def test_disable_separator_flag_beats_config(self):
|
|
# --disable-separator, or --disable-unicode via main.py, leaves it False.
|
|
args = self.load({'separator': 'True'}, enable_separator=False, disable_bg=False)
|
|
assert args.enable_separator is False
|
|
|
|
def test_disable_bg_flag_beats_config(self):
|
|
args = self.load({'disable_bg': 'False'}, enable_separator=True, disable_bg=True)
|
|
assert args.disable_bg is True
|
|
|
|
def test_config_applies_when_no_flag_is_given(self):
|
|
args = self.load({'separator': 'False', 'disable_bg': 'True'}, enable_separator=True, disable_bg=False)
|
|
assert args.enable_separator is False
|
|
assert args.disable_bg is True
|
|
|
|
def test_defaults_are_kept_when_the_keys_are_absent(self):
|
|
args = self.load({'left_menu': 'network'}, enable_separator=True, disable_bg=False)
|
|
assert args.enable_separator is True
|
|
assert args.disable_bg is False
|