mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-20 00:01:44 +02:00
- transit.position(): Analyze/Patch/Sign bring the cover up to the portal (fixed at PORTAL_AT = 0.5), Upload carries it across under the portal (drawn on top), Install/Test on to the headset; steps row weighted so Upload sits under the portal - menus.quick_menu: Play · Open game page · Select · Install/Update · Game settings… · More actions… · Uninstall · Remove; job: Show progress · Cancel · Open game page - More actions… opens the game page and its full '…' menu (a ContextMenu around the button, opened from code: app.open_game_menu) - tests for the position mapping and the short menu; help text and CLAUDE.md updated
148 lines
6.5 KiB
Python
148 lines
6.5 KiB
Python
"""Install transit: job stage names → step + overall progress (ui/transit.py, no Flet)."""
|
|
import pytest
|
|
|
|
from frameport.ui.transit import STEPS, step_of, transit_state
|
|
|
|
QUEST = ["Patching the game", "OVRPort (primary)", "Frame fixes (primary)", "sign (primary)", "validate (primary)"]
|
|
ALT = ["OVRPort (alt)", "Frame fixes (alt)", "sign (alt)", "validate (alt)"]
|
|
INSTALL = ["Prepare Frame", "Upload APK", "Upload data (12 files)", "Artwork for the Steam library",
|
|
"Finalize install", "Add to Steam library"]
|
|
|
|
|
|
def run(stages, **kw):
|
|
return transit_state(stage=stages[-1] if stages else "", stages=stages, **kw)
|
|
|
|
|
|
@pytest.mark.parametrize("stage,step", [
|
|
("Patching the game", 0), ("Checking the game (installing it as it is)", 0), ("Check game files", 0),
|
|
("OVRPort (primary)", 1), ("Frame fixes (alt)", 1), ("Revive", 1), ("sign (primary)", 2), ("validate (alt)", 2),
|
|
("Prepare Frame", 3), ("Upload APK", 3), ("Upload data (3 files)", 3), ("Upload game (40 files, 3.1 GiB)", 3),
|
|
("Artwork for the Steam library", 4), ("Finalize install", 4), ("Add to Steam library", 4),
|
|
("Launch test (headless)", 5), ("Launch test: passed", 5), ("Installed", len(STEPS)), ("Something new", None)])
|
|
def test_step_of(stage, step):
|
|
assert step_of(stage) == step
|
|
|
|
|
|
def test_quest_primary_and_alt_never_go_back():
|
|
s = run(QUEST)
|
|
assert (s.step, s.label) == (2, "sign")
|
|
s = run(QUEST + ALT[:1]) # the alternate build's OVRPort after the primary's validate
|
|
assert s.step == 2
|
|
assert s.fraction == pytest.approx(2 / 6)
|
|
|
|
|
|
def test_upload_fraction_and_detail():
|
|
s = run(QUEST + ALT + INSTALL[:2], fraction=0.5, speed="36.4 MB/s · ~1 min left")
|
|
assert s.step == 3 and s.label == "upload"
|
|
assert s.fraction == pytest.approx(3.5 / 6)
|
|
assert s.detail == "50% · 36.4 MB/s · ~1 min left"
|
|
assert s.target == "frame" and not s.waiting and not s.failed
|
|
|
|
|
|
def test_install_and_test_steps():
|
|
assert run(QUEST + INSTALL).step == 4
|
|
s = run(QUEST + INSTALL + ["Launch test (headless)"])
|
|
assert (s.step, s.label) == (5, "test")
|
|
tested = QUEST + INSTALL + ["Launch test (headless)", "Launch test: passed"]
|
|
s = transit_state("done", "Launch test: passed", tested)
|
|
assert s.fraction == 1.0 and s.label == "done"
|
|
|
|
|
|
def test_launch_test_off_ends_at_installed():
|
|
s = run(QUEST + INSTALL + ["Installed"])
|
|
assert s.fraction == 1.0 and s.label == "done"
|
|
|
|
|
|
def test_rift_and_as_is():
|
|
rift = ["Checking the game", "Check game files", "Revive", "Prepare Frame", "Upload game (40 files, 3.1 GiB)"]
|
|
s = run(rift, fraction=0.25)
|
|
assert s.step == 3 and s.fraction == pytest.approx(3.25 / 6)
|
|
s = run(["Checking the game", "Checking the game (installing it as it is)"])
|
|
assert s.step == 0 and s.fraction == 0.0
|
|
|
|
|
|
def test_test_only_job():
|
|
s = transit_state(stage="Launch test (headless)", stages=["Launch test (headless)"], kind="test")
|
|
assert s.step == 5
|
|
s = transit_state(stage="", stages=[], kind="test")
|
|
assert s.step == 5 # starting: nothing before the test to do
|
|
|
|
|
|
def test_pc_install():
|
|
s = run(["Checking the game", "Check game files", "Revive", "Prepare this PC", "Add to Steam library (this PC)"],
|
|
to="pc")
|
|
assert s.target == "pc" and s.step == 4
|
|
s = transit_state("done", "Installed", ["Checking the game", "Prepare this PC", "Installed"], to="pc")
|
|
assert s.fraction == 1.0
|
|
|
|
|
|
def test_waiting_keeps_the_step():
|
|
stages = QUEST + ["Prepare Frame", "Upload APK"]
|
|
s = transit_state("queued", "Waiting for the Frame", stages, kind="install")
|
|
assert s.waiting and s.step == 3 and s.fraction == pytest.approx(3 / 6)
|
|
# resumed: the job runs again from the start
|
|
s = run(stages + ["Patching the game"])
|
|
assert s.step == 0 and not s.waiting
|
|
|
|
|
|
def test_failed_keeps_the_step():
|
|
s = transit_state("failed", "Upload APK", QUEST + ["Prepare Frame", "Upload APK"], fraction=0.3)
|
|
assert s.failed and s.step == 3 and s.fraction < 1.0
|
|
|
|
|
|
def test_unknown_stage_keeps_the_previous_step():
|
|
s = run(QUEST + INSTALL[:1] + ["Ensuring Proton"])
|
|
assert s.step == 3
|
|
|
|
|
|
def test_upload_never_goes_back_between_apk_and_data():
|
|
from frameport.ui.transit import Monotonic
|
|
|
|
mono = Monotonic()
|
|
base = QUEST + INSTALL[:2]
|
|
seen = [mono.follow("j", run(base, fraction=f)).fraction for f in (0.0, 0.1, 0.2)]
|
|
# "Upload data" starts: the job's fraction is cleared until the first tick, then continues over all files
|
|
seen += [mono.follow("j", run(QUEST + INSTALL[:3], fraction=f)).fraction for f in (None, 0.2, 0.6, 1.0)]
|
|
seen.append(mono.follow("j", run(QUEST + INSTALL[:4])).fraction)
|
|
assert seen == sorted(seen)
|
|
assert seen[3] == pytest.approx(3.2 / 6) and seen[-2] == pytest.approx(4 / 6)
|
|
|
|
|
|
def test_monotonic_resets_for_another_job_or_a_new_run():
|
|
from frameport.ui.transit import Monotonic
|
|
|
|
mono = Monotonic()
|
|
assert mono.follow("a", run(QUEST + INSTALL[:3], fraction=0.9)).fraction == pytest.approx(3.9 / 6)
|
|
assert mono.follow("a", run(["Patching the game"])).fraction == 0.0 # started over
|
|
mono.follow("a", run(QUEST + INSTALL[:3], fraction=0.9))
|
|
assert mono.follow("b", run(QUEST[:1])).fraction == 0.0
|
|
|
|
|
|
def test_position_portal_at_the_pc_frame_boundary():
|
|
from frameport.ui.transit import PORTAL_AT, position
|
|
|
|
assert PORTAL_AT == 0.5
|
|
# Analyze/Patch/Sign (also halfway through Sign): before the portal
|
|
for stages, f in ((QUEST[:1], None), (QUEST[:2], None), (QUEST, None), (QUEST + ALT, None)):
|
|
assert position(run(stages, fraction=f).fraction) < 0.5
|
|
assert position(0.0) == 0.0
|
|
# the upload crosses the portal: just before it at 0 %, at it halfway, just after it at 100 %
|
|
up = [position(run(QUEST + INSTALL[:2], fraction=f).fraction) for f in (0.0, 0.5, 1.0)]
|
|
assert up[0] < 0.5 < up[2]
|
|
assert up[1] == pytest.approx(0.5)
|
|
# Install/Test on the Frame: after the portal; done: at the headset
|
|
assert position(run(QUEST + INSTALL).fraction) > 0.5
|
|
assert 0.5 < position(run(QUEST + INSTALL + ["Launch test (headless)"]).fraction) < 1.0
|
|
assert position(transit_state("done", "Installed", QUEST + INSTALL + ["Installed"]).fraction) == 1.0
|
|
# a PC install: same track (Prepare this PC = the crossing's start)
|
|
pc = run(["Checking the game", "Revive", "Prepare this PC", "Add to Steam library (this PC)"], to="pc")
|
|
assert position(pc.fraction) > 0.5
|
|
|
|
|
|
def test_position_is_monotonic():
|
|
from frameport.ui.transit import position
|
|
|
|
xs = [i / 600 for i in range(601)]
|
|
ps = [position(x) for x in xs]
|
|
assert ps == sorted(ps) and ps[-1] == 1.0
|