test: add firmware capture fixtures and stdlib-level unit tests for parsers, intents, running apps
This commit is contained in:
1 parent
f829ef7a40
commit
a2f1948a88
7 files changed
+198
No files matched your search
@@ -0,0 +1,12 @@
|
|||||||
|
"""Test bootstrap.
|
||||||
|
|
||||||
|
Puts the vendored ha_adb_common package on sys.path so helpers can be imported
|
||||||
|
directly (their code is stdlib-only) without installing Home Assistant or
|
||||||
|
executing the integration's __init__ module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(REPO_ROOT / "custom_components" / "nvidia_shield"))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mResumedActivity: ActivityRecord{4f0d030 u0 ar.tvplayer.tv/.ui.MainActivity t8152}
|
||||||
|
state=RESUMED stopped=false delayedResume=false finishing=false
|
||||||
|
Resumed activities in task display areas (from top to bottom):
|
||||||
|
Resumed: ActivityRecord{4f0d030 u0 ar.tvplayer.tv/.ui.MainActivity t8152}
|
||||||
|
ResumedActivity: ActivityRecord{4f0d030 u0 ar.tvplayer.tv/.ui.MainActivity t8152}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
MEDIA SESSION SERVICE (dumpsys media_session)
|
||||||
|
|
||||||
|
9 sessions listeners.
|
||||||
|
Global priority session is null
|
||||||
|
User Records:
|
||||||
|
Record for full_user=0
|
||||||
|
Volume key long-press listener: null
|
||||||
|
Volume key long-press listener package:
|
||||||
|
Media key listener: null
|
||||||
|
Media key listener package:
|
||||||
|
OnMediaKeyEventDispatchedListener: added 0 listener(s)
|
||||||
|
OnMediaKeyEventSessionChangedListener: added 1 listener(s)
|
||||||
|
from com.android.bluetooth
|
||||||
|
Last MediaButtonReceiver: null
|
||||||
|
Media button session is null
|
||||||
|
Sessions Stack - have 1 sessions:
|
||||||
|
Netflix media session com.netflix.ninja/Netflix media session (userId=0)
|
||||||
|
ownerPid=5833, ownerUid=10080, userId=0
|
||||||
|
package=com.netflix.ninja
|
||||||
|
launchIntent=null
|
||||||
|
mediaButtonReceiver=null
|
||||||
|
active=false
|
||||||
|
flags=3
|
||||||
|
rating type=0
|
||||||
|
controllers: 0
|
||||||
|
state=null
|
||||||
|
audioAttrs=AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_UNKNOWN flags=0x800 tags= bundle=null
|
||||||
|
volumeType=1, controlType=2, max=0, current=0
|
||||||
|
metadata: null
|
||||||
|
queueTitle=null, size=0
|
||||||
|
Audio playback (lastly played comes first)
|
||||||
|
uid=10117 packages=ar.tvplayer.tv
|
||||||
|
uid=10006 packages=com.google.android.apps.mediashell
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
MEDIA SESSION SERVICE (dumpsys media_session)
|
||||||
|
|
||||||
|
9 sessions listeners.
|
||||||
|
User Records:
|
||||||
|
Record for full_user=0
|
||||||
|
Media button session is null
|
||||||
|
Sessions Stack - have 1 sessions:
|
||||||
|
Wholphin media session com.github.damontecres.wholphin/Wholphin media session (userId=0)
|
||||||
|
ownerPid=6117, ownerUid=10117, userId=0
|
||||||
|
package=com.github.damontecres.wholphin
|
||||||
|
launchIntent=null
|
||||||
|
mediaButtonReceiver=null
|
||||||
|
active=true
|
||||||
|
flags=3
|
||||||
|
rating type=0
|
||||||
|
controllers: 1
|
||||||
|
state=PlaybackState {state=3, position=17237, buffered position=70070, speed=1.0, updated=1408392, actions=7339995, custom actions=[], active item id=0, error=null}
|
||||||
|
audioAttrs=AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MOVIE flags=0x800 tags= bundle=null
|
||||||
|
volumeType=1, controlType=2, max=0, current=0
|
||||||
|
metadata: size=11, description=Twirlywoos, null, null
|
||||||
|
queueTitle=null, size=1
|
||||||
|
Audio playback (lastly played comes first)
|
||||||
|
uid=10117 packages=com.github.damontecres.wholphin
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""Tests for the PlaybackIntent dataclass (optimistic state tracking)."""
|
||||||
|
|
||||||
|
from ha_adb_common.intent import PlaybackIntent
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_captures_fields():
|
||||||
|
intent = PlaybackIntent.create(
|
||||||
|
target_state="paused", command="media_pause", prior_state="playing"
|
||||||
|
)
|
||||||
|
assert intent.target_state == "paused"
|
||||||
|
assert intent.command == "media_pause"
|
||||||
|
assert intent.prior_state == "playing"
|
||||||
|
assert intent.retry_count == 0
|
||||||
|
assert intent.created_at > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_debug_dict_shape():
|
||||||
|
intent = PlaybackIntent.create(target_state="playing", command="media_play")
|
||||||
|
debug = intent.to_debug_dict()
|
||||||
|
assert debug["target"] == "playing"
|
||||||
|
assert debug["command"] == "media_play"
|
||||||
|
assert debug["retries"] == 0
|
||||||
|
assert "age_s" in debug
|
||||||
|
|
||||||
|
|
||||||
|
def test_age_increases():
|
||||||
|
import time
|
||||||
|
|
||||||
|
intent = PlaybackIntent.create(target_state="playing", command="media_play")
|
||||||
|
before = intent.age_seconds
|
||||||
|
time.sleep(0.01)
|
||||||
|
assert intent.age_seconds >= before
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""Parser tests driven by real Shield firmware captures (Android 11, RQ1A.210105.003).
|
||||||
|
|
||||||
|
Fixtures live in tests/fixtures — see docs/RESEARCH_SHIELD.md for how each
|
||||||
|
capture was produced and which expectations it pins.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ha_adb_common.media_session import parse_all_media_sessions, parse_media_session
|
||||||
|
|
||||||
|
FIXTURES = Path(__file__).parent / "fixtures"
|
||||||
|
|
||||||
|
|
||||||
|
def _fixture(name: str) -> str:
|
||||||
|
return (FIXTURES / name).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
class TestTiviMateHasNoSession:
|
||||||
|
"""TiviMate (ar.tvplayer.tv) registers no MediaSession on this firmware."""
|
||||||
|
|
||||||
|
def test_no_sessions_parsed(self):
|
||||||
|
assert parse_all_media_sessions(_fixture("media_session_tivimate.txt")) == []
|
||||||
|
|
||||||
|
def test_session_query_returns_none(self):
|
||||||
|
dump = _fixture("media_session_tivimate.txt")
|
||||||
|
assert parse_media_session(dump, "ar.tvplayer.tv") is None
|
||||||
|
|
||||||
|
def test_inactive_stale_session_is_not_surfaced(self):
|
||||||
|
"""Netflix registers an inactive, state-less session; the parser must
|
||||||
|
ignore it — an active=false block never appears in the results."""
|
||||||
|
dump = _fixture("media_session_tivimate.txt")
|
||||||
|
assert parse_media_session(dump, "com.netflix.ninja") is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestWholphinSession:
|
||||||
|
"""Wholphin (com.github.damontecres.wholphin) publishes a proper session."""
|
||||||
|
|
||||||
|
def test_playing_session_parsed(self):
|
||||||
|
sessions = parse_all_media_sessions(_fixture("media_session_wholphin.txt"))
|
||||||
|
assert len(sessions) == 1
|
||||||
|
session = sessions[0]
|
||||||
|
assert session.package == "com.github.damontecres.wholphin"
|
||||||
|
assert session.playing is True
|
||||||
|
assert session.paused is False
|
||||||
|
assert session.title == "Twirlywoos"
|
||||||
|
assert session.artist is None
|
||||||
|
|
||||||
|
def test_foreground_package_is_preferred(self):
|
||||||
|
dump = _fixture("media_session_wholphin.txt")
|
||||||
|
best = parse_media_session(dump, "com.github.damontecres.wholphin")
|
||||||
|
assert best is not None
|
||||||
|
assert best.package == "com.github.damontecres.wholphin"
|
||||||
|
assert best.playing is True
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"""Tests for the running-apps list builder (recent tasks + media sessions)."""
|
||||||
|
|
||||||
|
from ha_adb_common.running_apps import running_apps_from_tasks
|
||||||
|
|
||||||
|
|
||||||
|
def _session(package: str, playing: bool = False, title: str | None = None):
|
||||||
|
from ha_adb_common.media_session import MediaSessionInfo
|
||||||
|
|
||||||
|
s = MediaSessionInfo(package=package)
|
||||||
|
s.playing = playing
|
||||||
|
s.paused = not playing
|
||||||
|
s.title = title
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def test_returns_none_for_fewer_than_two_tasks():
|
||||||
|
assert running_apps_from_tasks([]) is None
|
||||||
|
assert running_apps_from_tasks([{"package": "app.a"}]) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_marks_foreground_and_defaults_to_running():
|
||||||
|
tasks = [{"package": "app.a"}, {"package": "app.b"}]
|
||||||
|
apps = running_apps_from_tasks(tasks, [], foreground_package="app.b")
|
||||||
|
assert apps is not None
|
||||||
|
assert len(apps) == 2
|
||||||
|
by_pkg = {a["package"]: a for a in apps}
|
||||||
|
assert "foreground" not in by_pkg["app.a"]
|
||||||
|
assert by_pkg["app.b"]["foreground"] is True
|
||||||
|
assert by_pkg["app.a"]["state"] == "running"
|
||||||
|
|
||||||
|
|
||||||
|
def test_enriches_with_media_session_state():
|
||||||
|
tasks = [{"package": "app.a"}, {"package": "app.b"}]
|
||||||
|
sessions = [_session("app.a", playing=True, title="Song")]
|
||||||
|
apps = running_apps_from_tasks(tasks, sessions)
|
||||||
|
assert apps is not None
|
||||||
|
by_pkg = {a["package"]: a for a in apps}
|
||||||
|
assert by_pkg["app.a"]["state"] == "playing"
|
||||||
|
assert by_pkg["app.a"]["title"] == "Song"
|
||||||
|
assert by_pkg["app.b"]["state"] == "running"
|
||||||
Reference in new issue
Block a user