1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
"""Hardware integration tests -- run only on real machines with sensors.
All tests are marked ``@pytest.mark.hardware`` and will be automatically
skipped when CI=true or GITHUB_ACTIONS=true (see conftest.py).
Run locally with::
pytest -m hardware -v
"""
import psutil
import pytest
from s_tui.sources.fan_source import FanSource
from s_tui.sources.freq_source import FreqSource
from s_tui.sources.rapl_power_source import RaplPowerSource
from s_tui.sources.rapl_read import get_power_reader
from s_tui.sources.temp_source import TempSource
from s_tui.sources.util_source import UtilSource
pytestmark = pytest.mark.hardware
# =====================================================================
# UtilSource
# =====================================================================
class TestUtilSourceHW:
def test_init_and_available(self):
"""UtilSource should always be available on a Linux machine."""
src = UtilSource()
assert src.get_is_available() is True
def test_sensor_list_matches_cpu_count(self):
"""Sensor list should cover all present cores (including offline) + Avg."""
from s_tui.sources.source import Source
src = UtilSource()
expected = Source._get_total_core_count() + 1 # all cores + Avg
assert len(src.get_sensor_list()) == expected
def test_update_produces_readings(self):
"""After update(), readings should be populated."""
src = UtilSource()
src.update()
readings = src.get_reading_list()
assert len(readings) > 0
# All values should be between 0 and 100
for v in readings:
assert 0.0 <= v <= 100.0
def test_summary(self):
"""get_summary() should return a non-empty dict."""
src = UtilSource()
src.update()
summary = src.get_summary()
assert len(summary) > 0
# =====================================================================
# FreqSource
# =====================================================================
class TestFreqSourceHW:
def test_init_and_available(self):
"""FreqSource should be available when cpu_freq works."""
src = FreqSource()
if psutil.cpu_freq() is not None:
assert src.get_is_available() is True
def test_update(self):
"""After update(), readings should contain positive values."""
src = FreqSource()
if not src.get_is_available():
pytest.skip("FreqSource not available on this hardware")
src.update()
readings = src.get_reading_list()
assert len(readings) > 0
# =====================================================================
# TempSource
# =====================================================================
class TestTempSourceHW:
def test_init(self):
"""TempSource should init without crashing."""
TempSource()
def test_update_if_available(self):
"""If available, update produces temperature readings."""
src = TempSource()
if not src.get_is_available():
pytest.skip("TempSource not available on this hardware")
src.update()
readings = src.get_reading_list()
assert len(readings) > 0
# Temperatures should be reasonable (> -40 and < 200)
for v in readings:
assert -40.0 < v < 200.0
# =====================================================================
# FanSource
# =====================================================================
class TestFanSourceHW:
def test_init(self):
"""FanSource should init without crashing."""
FanSource()
def test_update_if_available(self):
"""If available, update produces fan readings."""
src = FanSource()
if not src.get_is_available():
pytest.skip("FanSource not available on this hardware")
src.update()
readings = src.get_reading_list()
assert len(readings) > 0
# =====================================================================
# RaplPowerSource
# =====================================================================
class TestRaplPowerSourceHW:
def test_init(self):
"""RaplPowerSource should init without crashing."""
RaplPowerSource()
def test_update_if_available(self):
"""If available, update produces power readings.
Note: RAPL sysfs files may require root permissions, so this test
skips when readings are empty due to PermissionError.
"""
src = RaplPowerSource()
if not src.get_is_available():
pytest.skip("RaplPowerSource not available on this hardware")
src.update()
readings = src.get_reading_list()
if len(readings) == 0:
pytest.skip("RAPL readings empty (likely permission denied)")
assert len(readings) > 0
# =====================================================================
# get_power_reader
# =====================================================================
class TestGetPowerReaderHW:
def test_reader_type(self):
"""get_power_reader returns a reader or None."""
reader = get_power_reader()
if reader is not None:
# Should have read_power method
assert hasattr(reader, "read_power")
result = reader.read_power()
assert isinstance(result, list)
# =====================================================================
# Multi-update stability
# =====================================================================
class TestMultiUpdateStability:
def test_util_multiple_updates(self):
"""UtilSource survives multiple update() calls."""
src = UtilSource()
for _ in range(10):
src.update()
assert len(src.get_reading_list()) > 0
def test_freq_multiple_updates(self):
"""FreqSource survives multiple update() calls."""
src = FreqSource()
if not src.get_is_available():
pytest.skip("FreqSource not available")
for _ in range(10):
src.update()
assert len(src.get_reading_list()) > 0
|