Skip to content

Commit 7f4d3ee

Browse files
test: use exact string matching to kill string mutation survivors
Tighten assertions in widget format and CLI display tests to use exact string equality instead of substring checks. This kills mutants that prepend/append "XX" to string literals (e.g., label names, separator characters, color names). Also adds boundary tests for duration=1 to kill `> 0` vs `> 1` mutations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0be24d1 commit 7f4d3ee

2 files changed

Lines changed: 192 additions & 169 deletions

File tree

tests/test_cli_commands.py

Lines changed: 91 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -209,28 +209,28 @@ def test_standby_no_temp_unit(self, capsys):
209209
param = _make_mode_param(mode=FireMode.STANDBY, target_temperature=20.0)
210210
_display_mode(param)
211211
out = capsys.readouterr().out
212-
assert "[321] Mode" in out
213-
assert "Standby" in out
214-
assert "20.0" in out
215-
# No unit suffix when temp_unit is None
216-
assert "\u00b0" in out
212+
lines = out.strip().split("\n")
213+
assert lines[0].strip() == "[321] Mode"
214+
assert "─" * 40 in lines[1]
215+
assert "Mode: Standby" in out
216+
assert "Target Temp: 20.0\u00b0" in out
217+
# No unit suffix when temp_unit is None (empty string)
218+
assert "20.0\u00b0\n" in out or out.endswith("20.0\u00b0")
217219

218220
def test_manual_with_celsius(self, capsys):
219221
param = _make_mode_param(mode=FireMode.MANUAL, target_temperature=22.0)
220222
tu = TempUnitParam(unit=TempUnit.CELSIUS)
221223
_display_mode(param, tu)
222224
out = capsys.readouterr().out
223-
assert "On" in out
224-
assert "22.0" in out
225-
assert "\u00b0C" in out
225+
assert "Mode: On" in out
226+
assert "Target Temp: 22.0\u00b0C" in out
226227

227228
def test_manual_with_fahrenheit(self, capsys):
228229
param = _make_mode_param(mode=FireMode.MANUAL, target_temperature=22.0)
229230
tu = TempUnitParam(unit=TempUnit.FAHRENHEIT)
230231
_display_mode(param, tu)
231232
out = capsys.readouterr().out
232-
assert "71.6" in out
233-
assert "\u00b0F" in out
233+
assert "Target Temp: 71.6\u00b0F" in out
234234

235235

236236
class TestDisplayFlameEffect:
@@ -251,23 +251,24 @@ def test_all_fields_displayed(self, capsys):
251251
)
252252
_display_flame_effect(param)
253253
out = capsys.readouterr().out
254-
assert "[322] Flame Effect" in out
255-
assert "Flame: On" in out
256-
assert "4 / 5" in out
257-
assert "Low" in out
258-
assert "Overhead Pulsating: On" in out
259-
assert "Blue" in out
260-
assert "Prism" in out
261-
assert "RGBW(10, 20, 30, 40)" in out
262-
assert "Overhead Light: On" in out
263-
assert "RGBW(50, 60, 70, 80)" in out
264-
assert "Ambient Sensor: On" in out
254+
lines = out.strip().split("\n")
255+
assert lines[0].strip() == "[322] Flame Effect"
256+
assert "─" * 40 in lines[1]
257+
assert " Flame: On" in out
258+
assert " Flame Speed: 4 / 5" in out
259+
assert " Brightness: Low" in out
260+
assert " Flame Color: Blue" in out
261+
assert " Media Light: Prism | RGBW(10, 20, 30, 40)" in out
262+
assert " Overhead Light: On" in out
263+
assert " Overhead Pulsating: On" in out
264+
assert " Overhead Color: RGBW(50, 60, 70, 80)" in out
265+
assert " Ambient Sensor: On" in out
265266

266267
def test_flame_off(self, capsys):
267268
param = _make_flame_effect_param(flame_effect=FlameEffect.OFF)
268269
_display_flame_effect(param)
269270
out = capsys.readouterr().out
270-
assert "Flame: Off" in out
271+
assert " Flame: Off" in out
271272

272273

273274
class TestDisplayHeat:
@@ -282,31 +283,33 @@ def test_heat_on_no_temp_unit(self, capsys):
282283
)
283284
_display_heat(param)
284285
out = capsys.readouterr().out
285-
assert "[323] Heat Settings" in out
286-
assert "On" in out
287-
assert "Boost" in out
288-
assert "25.0" in out
289-
assert "15" in out
286+
lines = out.strip().split("\n")
287+
assert lines[0].strip() == "[323] Heat Settings"
288+
assert "─" * 40 in lines[1]
289+
assert " Heat: On" in out
290+
assert " Heat Mode: Boost" in out
291+
assert " Setpoint Temp: 25.0\u00b0" in out
292+
assert " Boost Duration: 15" in out
290293

291294
def test_heat_with_celsius(self, capsys):
292295
param = _make_heat_param(setpoint_temperature=22.0)
293296
tu = TempUnitParam(unit=TempUnit.CELSIUS)
294297
_display_heat(param, tu)
295298
out = capsys.readouterr().out
296-
assert "22.0\u00b0C" in out
299+
assert " Setpoint Temp: 22.0\u00b0C" in out
297300

298301
def test_heat_with_fahrenheit(self, capsys):
299302
param = _make_heat_param(setpoint_temperature=22.0)
300303
tu = TempUnitParam(unit=TempUnit.FAHRENHEIT)
301304
_display_heat(param, tu)
302305
out = capsys.readouterr().out
303-
assert "71.6\u00b0F" in out
306+
assert " Setpoint Temp: 71.6\u00b0F" in out
304307

305308
def test_heat_off(self, capsys):
306309
param = _make_heat_param(heat_status=HeatStatus.OFF)
307310
_display_heat(param)
308311
out = capsys.readouterr().out
309-
assert "Off" in out
312+
assert " Heat: Off" in out
310313

311314

312315
class TestDisplayHeatMode:
@@ -316,20 +319,22 @@ def test_enabled(self, capsys):
316319
param = HeatModeParam(heat_control=HeatControl.ENABLED)
317320
_display_heat_mode(param)
318321
out = capsys.readouterr().out
319-
assert "[325] Heat Mode" in out
320-
assert "Enabled" in out
322+
lines = out.strip().split("\n")
323+
assert lines[0].strip() == "[325] Heat Mode"
324+
assert "─" * 40 in lines[1]
325+
assert " Heat Control: Enabled" in out
321326

322327
def test_software_disabled(self, capsys):
323328
param = HeatModeParam(heat_control=HeatControl.SOFTWARE_DISABLED)
324329
_display_heat_mode(param)
325330
out = capsys.readouterr().out
326-
assert "Software Disabled" in out
331+
assert " Heat Control: Software Disabled" in out
327332

328333
def test_hardware_disabled(self, capsys):
329334
param = HeatModeParam(heat_control=HeatControl.HARDWARE_DISABLED)
330335
_display_heat_mode(param)
331336
out = capsys.readouterr().out
332-
assert "Hardware Disabled" in out
337+
assert " Heat Control: Hardware Disabled" in out
333338

334339

335340
class TestDisplayTimer:
@@ -339,28 +344,36 @@ def test_timer_disabled(self, capsys):
339344
param = TimerParam(timer_status=TimerStatus.DISABLED, duration=0)
340345
_display_timer(param)
341346
out = capsys.readouterr().out
342-
assert "[326] Timer Mode" in out
343-
assert "Disabled" in out
344-
assert "0 min (0h 0m)" in out
345-
# Should NOT show "Off at:" when disabled
347+
lines = out.strip().split("\n")
348+
assert lines[0].strip() == "[326] Timer Mode"
349+
assert "─" * 40 in lines[1]
350+
assert " Timer: Disabled" in out
351+
assert " Duration: 0 min (0h 0m)" in out
346352
assert "Off at:" not in out
347353

348354
def test_timer_enabled_with_duration(self, capsys):
349355
param = TimerParam(timer_status=TimerStatus.ENABLED, duration=90)
350356
_display_timer(param)
351357
out = capsys.readouterr().out
352-
assert "Enabled" in out
353-
assert "90 min (1h 30m)" in out
354-
assert "Off at:" in out
358+
assert " Timer: Enabled" in out
359+
assert " Duration: 90 min (1h 30m)" in out
360+
assert " Off at:" in out
355361

356362
def test_timer_enabled_zero_duration(self, capsys):
357-
# Enabled but 0 duration: no "Off at:" line
358363
param = TimerParam(timer_status=TimerStatus.ENABLED, duration=0)
359364
_display_timer(param)
360365
out = capsys.readouterr().out
361-
assert "Enabled" in out
366+
assert " Timer: Enabled" in out
367+
assert " Duration: 0 min (0h 0m)" in out
362368
assert "Off at:" not in out
363369

370+
def test_timer_enabled_duration_1(self, capsys):
371+
"""Boundary: duration=1 should show off-at (kills > 0 vs > 1)."""
372+
param = TimerParam(timer_status=TimerStatus.ENABLED, duration=1)
373+
_display_timer(param)
374+
out = capsys.readouterr().out
375+
assert " Off at:" in out
376+
364377

365378
class TestDisplaySoftwareVersion:
366379
"""Tests for _display_software_version()."""
@@ -379,10 +392,12 @@ def test_version_display(self, capsys):
379392
)
380393
_display_software_version(param)
381394
out = capsys.readouterr().out
382-
assert "[327] Software Version" in out
383-
assert "1.2.3" in out
384-
assert "4.5.6" in out
385-
assert "7.8.9" in out
395+
lines = out.strip().split("\n")
396+
assert lines[0].strip() == "[327] Software Version"
397+
assert "─" * 40 in lines[1]
398+
assert " UI Version: 1.2.3" in out
399+
assert " Control Version: 4.5.6" in out
400+
assert " Relay Version: 7.8.9" in out
386401

387402

388403
class TestDisplayError:
@@ -392,28 +407,30 @@ def test_no_errors(self, capsys):
392407
param = ErrorParam(error_byte1=0, error_byte2=0, error_byte3=0, error_byte4=0)
393408
_display_error(param)
394409
out = capsys.readouterr().out
395-
assert "[329] Error" in out
396-
assert "None" in out
397-
assert "Active Faults: None" in out
410+
lines = out.strip().split("\n")
411+
assert lines[0].strip() == "[329] Error"
412+
assert "─" * 40 in lines[1]
413+
assert " Error Byte 1: 0x00 (00000000)" in out
414+
assert " Active Faults: None" in out
398415

399416
def test_with_errors(self, capsys):
400417
param = ErrorParam(
401418
error_byte1=0xFF, error_byte2=0, error_byte3=0, error_byte4=0
402419
)
403420
_display_error(param)
404421
out = capsys.readouterr().out
405-
assert "Active Faults: Yes" in out
406-
assert "0xFF" in out
422+
assert " Active Faults: Yes" in out
423+
assert " Error Byte 1: 0xFF (11111111)" in out
407424

408425
def test_error_bytes_formatted(self, capsys):
409426
param = ErrorParam(error_byte1=1, error_byte2=2, error_byte3=4, error_byte4=8)
410427
_display_error(param)
411428
out = capsys.readouterr().out
412-
assert "Error Byte 1:" in out
413-
assert "Error Byte 2:" in out
414-
assert "Error Byte 3:" in out
415-
assert "Error Byte 4:" in out
416-
assert "Active Faults: Yes" in out
429+
assert " Error Byte 1: 0x01 (00000001)" in out
430+
assert " Error Byte 2: 0x02 (00000010)" in out
431+
assert " Error Byte 3: 0x04 (00000100)" in out
432+
assert " Error Byte 4: 0x08 (00001000)" in out
433+
assert " Active Faults: Yes" in out
417434

418435

419436
class TestDisplayTempUnit:
@@ -423,14 +440,16 @@ def test_celsius(self, capsys):
423440
param = TempUnitParam(unit=TempUnit.CELSIUS)
424441
_display_temp_unit(param)
425442
out = capsys.readouterr().out
426-
assert "[236] Temperature Unit" in out
427-
assert "Celsius" in out
443+
lines = out.strip().split("\n")
444+
assert lines[0].strip() == "[236] Temperature Unit"
445+
assert "─" * 40 in lines[1]
446+
assert " Unit: Celsius" in out
428447

429448
def test_fahrenheit(self, capsys):
430449
param = TempUnitParam(unit=TempUnit.FAHRENHEIT)
431450
_display_temp_unit(param)
432451
out = capsys.readouterr().out
433-
assert "Fahrenheit" in out
452+
assert " Unit: Fahrenheit" in out
434453

435454

436455
class TestDisplaySound:
@@ -440,9 +459,11 @@ def test_display(self, capsys):
440459
param = SoundParam(volume=128, sound_file=3)
441460
_display_sound(param)
442461
out = capsys.readouterr().out
443-
assert "[369] Sound" in out
444-
assert "128 / 255" in out
445-
assert "3" in out
462+
lines = out.strip().split("\n")
463+
assert lines[0].strip() == "[369] Sound"
464+
assert "─" * 40 in lines[1]
465+
assert " Volume: 128 / 255" in out
466+
assert " Sound File: 3" in out
446467

447468

448469
class TestDisplayLogEffect:
@@ -456,10 +477,12 @@ def test_on(self, capsys):
456477
)
457478
_display_log_effect(param)
458479
out = capsys.readouterr().out
459-
assert "[370] Log Effect" in out
460-
assert "On" in out
461-
assert "RGBW(1, 0, 255, 128)" in out
462-
assert "5" in out
480+
lines = out.strip().split("\n")
481+
assert lines[0].strip() == "[370] Log Effect"
482+
assert "─" * 40 in lines[1]
483+
assert " Log Effect: On" in out
484+
assert " Colors: RGBW(1, 0, 255, 128)" in out
485+
assert " Pattern: 5" in out
463486

464487
def test_off(self, capsys):
465488
param = LogEffectParam(
@@ -469,7 +492,7 @@ def test_off(self, capsys):
469492
)
470493
_display_log_effect(param)
471494
out = capsys.readouterr().out
472-
assert "Off" in out
495+
assert " Log Effect: Off" in out
473496

474497

475498
# ===================================================================

0 commit comments

Comments
 (0)