"""Tests for hytop.gpu.parser.""" from __future__ import annotations import json import pytest from hytop.gpu.parser import parse_hy_smi_output, parse_number, strip_ansi # --------------------------------------------------------------------------- # Real hy-smi JSON fixture (from actual 8-card Hygon DCU node) # --------------------------------------------------------------------------- # Full-flag output (--showtemp --showpower --showhcuclocks --showmemuse --showuse --json) # Representative cards: card0 (idle) and card7 (100% HCU load) HY_SMI_FULL = { "card0": { "Average Graphics Package Power (W)": "157.0", "Temperature (Sensor edge) (C)": "31.0", "Temperature (Sensor junction) (C)": "34.0", "Temperature (Sensor mem) (C)": "28.0", "Temperature (Sensor core) (C)": "30.0", "HCU use (%)": "0.0", "HCU memory use (%)": "89", "sclk clock level": "10", "sclk clock speed": "1500Mhz", }, "card7": { "Average Graphics Package Power (W)": "141.0", "Temperature (Sensor edge) (C)": "28.0", "Temperature (Sensor junction) (C)": "33.0", "Temperature (Sensor mem) (C)": "25.0", "Temperature (Sensor core) (C)": "25.0", "HCU use (%)": "100.0", "HCU memory use (%)": "89", "sclk clock level": "10", "sclk clock speed": "1500Mhz", }, } # Temp-only output (--showtemp --json): extra sensor keys should be ignored HY_SMI_TEMP_ONLY = { "card0": { "Temperature (Sensor edge) (C)": "28.0", "Temperature (Sensor junction) (C)": "31.0", "Temperature (Sensor mem) (C)": "25.0", "Temperature (Sensor core) (C)": "26.0", }, } # --------------------------------------------------------------------------- # strip_ansi # --------------------------------------------------------------------------- class TestStripAnsi: def test_plain_text_unchanged(self): assert strip_ansi("hello") == "hello" def test_color_codes_removed(self): assert strip_ansi("\x1b[31mred\x1b[0m") == "red" def test_empty_string(self): assert strip_ansi("") == "" def test_multiple_codes(self): assert strip_ansi("\x1b[1m\x1b[4mbold\x1b[0m") == "bold" # --------------------------------------------------------------------------- # parse_number # --------------------------------------------------------------------------- class TestParseNumber: def test_integer_string(self): assert parse_number("89") == pytest.approx(89.0) def test_float_string(self): assert parse_number("157.0") == pytest.approx(157.0) def test_value_with_unit_suffix(self): # "1500Mhz" — real sclk clock speed format from hy-smi assert parse_number("1500Mhz") == pytest.approx(1500.0) def test_no_number_raises(self): with pytest.raises(ValueError, match="cannot parse"): parse_number("N/A") def test_negative_number(self): assert parse_number("-5.5") == pytest.approx(-5.5) # --------------------------------------------------------------------------- # parse_hy_smi_output — with real fixture data # --------------------------------------------------------------------------- class TestParseHySmiOutput: def test_full_output_card_count(self): raw = json.dumps(HY_SMI_FULL) result = parse_hy_smi_output(raw, sample_ts=1.0) assert set(result.keys()) == {0, 7} def test_full_output_card0_metrics(self): raw = json.dumps(HY_SMI_FULL) result = parse_hy_smi_output(raw, sample_ts=1.0) s = result[0] assert s.temp_c == pytest.approx(30.0) assert s.avg_pwr_w == pytest.approx(157.0) assert s.gpu_pct == pytest.approx(0.0) assert s.vram_pct == pytest.approx(89.0) # integer string "89" → 89.0 assert s.sclk_mhz == pytest.approx(1500.0) # "1500Mhz" → 1500.0 def test_full_output_card7_hcu_load(self): raw = json.dumps(HY_SMI_FULL) result = parse_hy_smi_output(raw, sample_ts=1.0) assert result[7].gpu_pct == pytest.approx(100.0) def test_temp_only_output(self): raw = json.dumps(HY_SMI_TEMP_ONLY) result = parse_hy_smi_output(raw, sample_ts=1.0) s = result[0] assert s.temp_c == pytest.approx(26.0) # Unrelated sensor keys must not populate fields assert s.avg_pwr_w is None assert s.gpu_pct is None def test_sample_ts_propagated(self): raw = json.dumps(HY_SMI_FULL) result = parse_hy_smi_output(raw, sample_ts=42.5) assert result[0].ts == pytest.approx(42.5) def test_unknown_card_keys_ignored(self): payload = {"sys_info": {"foo": "bar"}, "card0": HY_SMI_FULL["card0"]} result = parse_hy_smi_output(json.dumps(payload), sample_ts=1.0) assert list(result.keys()) == [0] def test_empty_string_returns_empty(self): assert parse_hy_smi_output("", sample_ts=1.0) == {} def test_invalid_json_returns_empty(self): assert parse_hy_smi_output("not json", sample_ts=1.0) == {} def test_ansi_stripped_before_parse(self): # Some hy-smi versions emit ANSI colors; parser must strip them first raw_with_ansi = "\x1b[0m" + json.dumps(HY_SMI_TEMP_ONLY) + "\x1b[0m" result = parse_hy_smi_output(raw_with_ansi, sample_ts=1.0) assert 0 in result