Files
glances/tests/test_sort_missing_values.py
Nguyen Thanh Dat 7928ee55ea fix(sort): read a missing sort value as zero instead of discarding the sort
sort_stats wraps the specific sort helpers in try/except and falls back to
cpu_percent for the whole list. _sort_cpu_times indexed cpu_times['user'] and
_sort_io_counters indexed io_counters[0..3], so one row with an empty value
raised and silently reordered every other row while the header still read TIME
or IOR/IOW.

Those empty values are expected rather than corrupt: programs.py builds a
program with p['cpu_times'] or {} and list(p['io_counters'] or NO_IO_COUNTERS),
and its own comment says some values can be None on macOS system processes.

Each helper now reads a missing value as zero, so the degenerate row sorts last
and the rest keep their order.
2026-08-26 23:38:44 +07:00

66 lines
2.7 KiB
Python

"""Sorting must not be discarded because one row has nothing to sort on.
`sort_stats` wraps the specific sort helpers in a try/except and falls back to
`cpu_percent` for the whole list. So a single row whose `cpu_times` is `{}` or whose
`io_counters` is empty does not fail loudly — it silently reorders every other row while
the header still reads TIME or IOR/IOW.
Those empty values are expected, not corrupt: `glances/programs.py` builds a program with
`p['cpu_times'] or {}` and `list(p['io_counters'] or NO_IO_COUNTERS)`, and its own comment
says "some values can be None, e.g. macOS system processes".
"""
from glances.processes import _sort_cpu_times, _sort_io_counters, sort_stats
def _row(name, cpu_times, io_counters, cpu_percent):
return {
'name': name,
'cpu_times': cpu_times,
'io_counters': io_counters,
'cpu_percent': cpu_percent,
'memory_percent': 1.0,
}
def test_sort_cpu_times_reads_a_missing_value_as_zero():
assert _sort_cpu_times({'cpu_times': {}}) == 0
assert _sort_cpu_times({'cpu_times': None}) == 0
assert _sort_cpu_times({}) == 0
assert _sort_cpu_times({'cpu_times': {'user': 5.0, 'system': 1.0}}) == 6.0
def test_sort_io_counters_reads_a_missing_value_as_zero():
assert _sort_io_counters({'io_counters': []}) == 0
assert _sort_io_counters({'io_counters': None}) == 0
assert _sort_io_counters({}) == 0
# A tuple is what programs.NO_IO_COUNTERS is, and a short list must not raise either.
assert _sort_io_counters({'io_counters': (900, 90, 100, 10, 1)}) == 880
assert _sort_io_counters({'io_counters': [900, 90]}) == 990
def test_one_row_without_cpu_times_does_not_reorder_the_others():
# 'idle' has no cpu_times and the highest cpu_percent, so a fallback to cpu_percent
# puts it first — which is exactly what used to happen.
rows = [
_row('editor', {'user': 5.0, 'system': 1.0}, [0, 0, 0, 0, 0], 1.0),
_row('idle', {}, [0, 0, 0, 0, 0], 99.0),
_row('compiler', {'user': 50.0, 'system': 9.0}, [0, 0, 0, 0, 0], 2.0),
]
ordered = sort_stats(rows, sorted_by='cpu_times', sorted_by_secondary='memory_percent')
assert [row['name'] for row in ordered] == ['compiler', 'editor', 'idle']
def test_one_row_without_io_counters_does_not_reorder_the_others():
rows = [
_row('editor', {'user': 0.0, 'system': 0.0}, [900, 90, 100, 10, 1], 1.0),
_row('idle', {'user': 0.0, 'system': 0.0}, [], 99.0),
_row('compiler', {'user': 0.0, 'system': 0.0}, [9000, 900, 1000, 100, 1], 2.0),
]
ordered = sort_stats(rows, sorted_by='io_counters', sorted_by_secondary='memory_percent')
assert [row['name'] for row in ordered] == ['compiler', 'editor', 'idle']