mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 06:00:33 +02:00
Tests against a fake Frame, and a headset smoke test
tests/fakeframe: a container that stands in for the Frame (Arch Linux, or Arch Linux ARM on arm64) with sshd, rsync, Valve's steamos-devkit-service and hooks (vendored unmodified), a fake Steam client for the devkit pipe and the DevTools port (the app's JavaScript runs in Node against stand-in SteamClient/appStore objects), stubs for steam, wpctl, flatpak, podman, Lepton and friends, battery and thermal files under /sys, and fault switches (fakeframe-ctl): pairing mode, approve/deny/timeout, Steam not running, headset asleep, sshd off, disk full, runtimes missing. A second container is the computer running Frame Control. tests/e2e: 29 tests driving the real ui/server.py, frame_connect.py and frame_titles.py against it; skipped unless FRAME_E2E=1. scripts/e2e.sh builds, runs and tears down; CI runs it on ubuntu-24.04-arm. tests/smoke + scripts/frame-smoke.sh: the core cases against a real Frame, recorded with its BUILD_ID, cleaning up after itself; --pair to pair a throwaway key. docs/testing.md describes the layers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ca2a991f03
commit
0181071b83
43 files changed
+4747
No files matched your search
@@ -0,0 +1,111 @@
|
||||
"""Setting up the connection: ui/frame_connect.py against Valve's real
|
||||
steamos-devkit-service on the fake Frame, and its password fallback."""
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
import urllib.request
|
||||
|
||||
import harness
|
||||
from harness import FAKE_HOST, ROOT, ctl, state, wait_for
|
||||
|
||||
harness.require()
|
||||
|
||||
|
||||
class Pairing(harness.FrameTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
ctl('keys', 'none') # a computer the headset doesn't know yet
|
||||
|
||||
def connect(self, askpass=None):
|
||||
"""Start frame_connect.py FAKE_HOST with no terminal, as the app's setup window would."""
|
||||
env = {k: v for k, v in os.environ.items() if not k.startswith('SSH_ASKPASS')}
|
||||
if askpass:
|
||||
script = self.path('askpass')
|
||||
with open(script, 'w') as f:
|
||||
f.write(f'#!/bin/sh\necho {askpass}\n')
|
||||
os.chmod(script, stat.S_IRWXU)
|
||||
env.update(SSH_ASKPASS=script, SSH_ASKPASS_REQUIRE='force')
|
||||
return subprocess.Popen([sys.executable, str(ROOT / 'ui' / 'frame_connect.py'), FAKE_HOST],
|
||||
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
text=True, env=env, start_new_session=True)
|
||||
|
||||
def finish(self, proc, timeout=120):
|
||||
out, _ = proc.communicate(timeout=timeout)
|
||||
return proc.returncode, out
|
||||
|
||||
def authorized_keys(self):
|
||||
return ctl('authorized-keys')['text']
|
||||
|
||||
def test_service_properties_and_announcement(self):
|
||||
# docs/ssh.md: properties.json answers "login": "steamos" on the Frame.
|
||||
with urllib.request.urlopen(f'http://{FAKE_HOST}:32000/properties.json', timeout=10) as r:
|
||||
props = json.load(r)
|
||||
self.assertEqual(props['login'], 'steamos')
|
||||
self.assertEqual(props['devkit1'], ['devkit-1'])
|
||||
# At start the service announces _steamos-devkit._tcp under the Frame's hostname.
|
||||
ctl('devkit-service', 'off')
|
||||
ctl('devkit-service', 'on')
|
||||
reg = wait_for(lambda: [c for c in harness.calls('resolve1') if c['method'] == 'RegisterService'],
|
||||
15, 'the mDNS registration')
|
||||
self.assertEqual(reg[0]['args'][:3], ['frame', 'frame', '_steamos-devkit._tcp'])
|
||||
self.assertEqual(reg[0]['args'][3], 32000)
|
||||
|
||||
def test_pairing_mode_refusal_then_approval(self):
|
||||
proc = self.connect()
|
||||
# The first /register is refused: "Pair new host" isn't open.
|
||||
wait_for(lambda: any(r['answer'] == 'not in pairing mode' for r in state()['pairing_requests']),
|
||||
30, 'a refused pairing request')
|
||||
ctl('pairing', 'on') # the user opens Settings > Developer > Pair new host
|
||||
rc, out = self.finish(proc)
|
||||
self.assertEqual(rc, 0, out)
|
||||
self.assertIn('paired; key login OK', out)
|
||||
answers = [r['answer'] for r in state()['pairing_requests']]
|
||||
self.assertEqual(answers[-1], 'approve')
|
||||
self.assertIn('not in pairing mode', answers)
|
||||
self.assertIn('frame-control@', state()['pairing_requests'][-1]['request'])
|
||||
# The hook turned sshd on and installed the RSA key for steamos.
|
||||
self.assertTrue(harness.calls('steamos-enable-sshd'))
|
||||
keys = self.authorized_keys()
|
||||
self.assertRegex(keys, r'(?m)^ssh-rsa \S+ frame-control@\S+$')
|
||||
self.assertNotIn('900b919520e4cf601998a71eec318fec', keys) # the magic phrase isn't stored
|
||||
|
||||
def test_denied_request_falls_back_to_the_password(self):
|
||||
ctl('pairing', 'on')
|
||||
ctl('answer', 'deny')
|
||||
rc, out = self.finish(self.connect()) # no password to give: the fallback can't finish
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn('devkit pairing failed: the pairing request was denied', out)
|
||||
self.assertIn('falling back to the password', out)
|
||||
self.assertNotIn('ssh-rsa', self.authorized_keys())
|
||||
|
||||
def test_service_down_uses_the_password(self):
|
||||
ctl('devkit-service', 'off')
|
||||
rc, out = self.finish(self.connect(askpass='frame'))
|
||||
self.assertEqual(rc, 0, out)
|
||||
self.assertIn('devkit service not reachable on port 32000', out)
|
||||
self.assertIn('key login OK', out)
|
||||
with open(os.path.expanduser('~/.ssh/id_ed25519_frame.pub')) as f:
|
||||
ours = f.read().split()[1]
|
||||
self.assertIn(ours, self.authorized_keys())
|
||||
|
||||
def test_prompt_left_unanswered_times_out(self):
|
||||
# approve-ssh-key waits 30 s for Steam, then says so.
|
||||
ctl('pairing', 'on')
|
||||
ctl('answer', 'timeout')
|
||||
rc, out = self.finish(self.connect(), timeout=150)
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn('timeout - Steam did not respond to the pairing request', out)
|
||||
|
||||
def test_steam_not_running(self):
|
||||
ctl('pairing', 'on')
|
||||
ctl('steam', 'off')
|
||||
rc, out = self.finish(self.connect())
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn('devkit pairing failed: Steam is not running', out)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user