chore: checkpoint app work before firmware sync

This commit is contained in:
Adolfo Reyna
2026-09-07 23:01:26 -04:00
parent e42036a044
commit 59612020bb
20 changed files with 2111 additions and 1218 deletions
+9
View File
@@ -30,6 +30,15 @@ class LiveCaptionsContractTests(unittest.TestCase):
self.assertIn("const char* stop", source)
self.assertNotIn("i2s_controller_", source)
def test_ui_autostarts_and_keeps_only_the_latest_thirty_words(self):
source = (APP / "main" / "Source" / "main.c").read_text()
self.assertIn("#define DISPLAY_WORD_LIMIT 50", source)
self.assertIn("copy_recent_words", source)
self.assertIn("tt_lvgl_toolbar_create_for_app", source)
self.assertNotIn("lv_btn_create", source)
self.assertNotIn("start_button", source)
self.assertIn("xTaskCreate(worker_task", source)
def test_connection_failure_stays_failed_until_user_starts_again(self):
source = (APP / "main" / "Source" / "main.c").read_text()
self.assertIn("CAPTION_FAILED", source)
+82
View File
@@ -0,0 +1,82 @@
from pathlib import Path
MP3_SOURCE = Path(__file__).parents[1] / "Apps" / "Mp3Player" / "main" / "Source" / "main.c"
def test_mp3_player_uses_the_firmware_audio_stream_device_name():
source = MP3_SOURCE.read_text(encoding="utf-8")
assert 'device_find_by_name("audio-stream0")' in source
assert 'device_find_by_name("audio-stream")' not in source
assert "device_get_first_by_type" not in source
assert "device_find_first_by_type" not in source
def test_mp3_player_restores_a_visible_persisted_volume_control_above_the_six_button_row():
source = MP3_SOURCE.read_text(encoding="utf-8")
assert "lv_obj_t* card = lv_obj_create(parent);" not in source
assert "lv_label_set_text(icon, LV_SYMBOL_AUDIO);" not in source
assert "lv_obj_t* vol_box = lv_obj_create(parent);" in source
assert "g_ctx.slider_volume = lv_slider_create(vol_box);" in source
assert "lv_slider_set_value(g_ctx.slider_volume, g_ctx.volume, LV_ANIM_OFF);" in source
assert "on_volume_slider_changed" in source
assert "save_volume(ctx);" in source
assert "lv_obj_set_size(bottom_box, lv_pct(100), 52);" in source
assert "lv_obj_t* btn_close = lv_btn_create(ctrl_box);" in source
assert "lv_obj_t* btn_hist = lv_btn_create(ctrl_box);" in source
assert "lv_label_set_text(lbl_close, LV_SYMBOL_CLOSE);" in source
assert "lv_label_set_text(lbl_hist_btn, LV_SYMBOL_DIRECTORY);" in source
assert "lv_label_set_text(lbl_back, \"-15s\");" in source
assert "lv_label_set_text(lbl_fwd, \"+15s\");" in source
def test_mp3_player_folder_button_returns_to_the_files_list():
source = MP3_SOURCE.read_text(encoding="utf-8")
library_callback = source.split("static void on_library_click", 1)[1].split("/* ─── App Lifecycle", 1)[0]
assert "tt_app_stop();" not in library_callback
assert "show_history_screen(&g_ctx);" in library_callback
assert "load_first_sd_mp3" not in library_callback
def test_mp3_player_history_view_and_fifteen_second_seek_are_present():
source = MP3_SOURCE.read_text(encoding="utf-8")
assert "#define SEEK_SECONDS 15" in source
assert 'tt_app_get_user_data_child_path(app, "play_history.txt"' in source
assert "static void show_history_screen" in source
assert "static void on_history_selected" in source
assert "char label[640];" in source
assert "request_seek(ctx, -SEEK_SECONDS);" in source
assert "request_seek(ctx, SEEK_SECONDS);" in source
def test_mp3_player_persists_and_resumes_short_tracks_too():
source = MP3_SOURCE.read_text(encoding="utf-8")
history_selector = source.split("static void on_history_selected", 1)[1].split("static void refresh_history_list", 1)[0]
assert "Skip history: audio too short" not in source
assert "Resuming last 10min+ play" not in source
assert "Resuming last play %s at %d sec from history" in source
assert "if (he->total_sec >= 600)" not in history_selector
assert "resume_pos = he->pos_sec;" in history_selector
def test_mp3_player_close_waits_for_playback_before_stopping_the_app():
source = MP3_SOURCE.read_text(encoding="utf-8")
close_callback = source.split("static void on_close_click", 1)[1].split("static void on_library_click", 1)[0]
assert "wait_for_playback_task_to_exit(&g_ctx);" in close_callback
assert close_callback.index("wait_for_playback_task_to_exit(&g_ctx);") < close_callback.index("tt_app_stop();")
assert "tt_lvgl_unlock();" in source
assert "tt_lvgl_lock(portMAX_DELAY);" in source
def test_mp3_player_has_a_consumed_dev_autoclose_hook_for_device_tests():
source = MP3_SOURCE.read_text(encoding="utf-8")
assert '"mp3player_dev_autoclose_ms"' in source
assert "on_close_click(NULL);" in source
assert "unlink(marker_path);" in source
+69
View File
@@ -0,0 +1,69 @@
import importlib.util
from pathlib import Path
RUNNER_PATH = Path(__file__).parents[1] / "scripts" / "mp3_player_device_runner.py"
def load_runner():
spec = importlib.util.spec_from_file_location("mp3_player_device_runner", RUNNER_PATH)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_resume_trace_requires_playback_persistence_and_same_path_resume():
runner = load_runner()
fixture = "/sdcard/download/test.mp3"
trace = "\n".join(
[
"I Mp3Player: Starting MP3 playback: /sdcard/download/test.mp3 (size: 480000 bytes) resume=0",
"I Mp3Player: Audio stream opened: 16000 Hz, 1 channels",
"I Mp3Player: History saved on hide: /sdcard/download/test.mp3 pos 9 total 30",
"I Mp3Player: Resuming last play /sdcard/download/test.mp3 at 9 sec from history",
"I Mp3Player: Starting MP3 playback: /sdcard/download/test.mp3 (size: 480000 bytes) resume=9",
]
)
result = runner.evaluate_resume_trace(trace, fixture, minimum_position=6)
assert result == {"persisted_position": 9, "resumed_position": 9}
def test_resume_trace_rejects_a_different_file_or_no_playback_evidence():
runner = load_runner()
fixture = "/sdcard/download/test.mp3"
try:
runner.evaluate_resume_trace(
"I Mp3Player: Starting MP3 playback: /sdcard/other.mp3 (size: 1 bytes) resume=0",
fixture,
minimum_position=6,
)
except RuntimeError as exc:
assert "playback" in str(exc).lower()
else:
raise AssertionError("missing playback evidence must fail")
def test_resume_trace_rejects_forced_playback_task_termination():
runner = load_runner()
fixture = "/sdcard/download/test.mp3"
trace = "\n".join(
[
f"I Mp3Player: Starting MP3 playback: {fixture} (size: 480000 bytes) resume=0",
"I Mp3Player: Audio stream opened: 16000 Hz, 1 channels",
f"I Mp3Player: History saved on hide: {fixture} pos 9 total 180",
"W Mp3Player: Playback task stuck, force deleting",
f"I Mp3Player: Resuming last play {fixture} at 9 sec from history",
f"I Mp3Player: Starting MP3 playback: {fixture} (size: 480000 bytes) resume=9",
]
)
try:
runner.evaluate_resume_trace(trace, fixture, minimum_position=6)
except RuntimeError as exc:
assert "forced" in str(exc).lower()
else:
raise AssertionError("forced task termination must fail the device test")