Upload 41 files
Browse files- tests/test_v52_ui_fixes.py +1 -1
- tests/test_v57_live_pi_traces.py +1 -1
- tests/test_v58_stable_refresh_pi_stdout.py +1 -1
- tests/test_v59_job_log_progress_clean.py +1 -1
- tests/test_v60_cancel_persist_delete_run.py +1 -1
- tests/test_v61_stabilize_buttons_runtime.py +1 -1
- tests/test_v62_progress_perf_clean_delete.py +2 -2
- tests/test_v63_delete_import_fix.py +1 -1
- tests/test_v64_delete_clean_events_timeline.py +1 -1
- tests/test_v65_floating_form_timeline.py +1 -1
- tests/test_v66_clean_qwen_coder_path.py +1 -1
- tests/test_v67_monotonic_progress_events.py +1 -1
- tests/test_v68_stable_events_timeline_centering.py +1 -1
- tests/test_v69_unified_progress_state.py +1 -1
- tests/test_v70_progress_resume_events_latency.py +1 -1
- tests/test_v71_compact_stable_progress.py +35 -0
- tests/test_v72_clear_modes_space_link_gating.py +36 -0
- tests/test_v73_toast_timer_clean_active_run.py +30 -0
- tests/test_v74_grouped_timeline_rings.py +43 -0
tests/test_v52_ui_fixes.py
CHANGED
|
@@ -35,6 +35,6 @@ def test_runs_explorer_is_limited_to_five_rows_with_pagination():
|
|
| 35 |
|
| 36 |
def test_progress_endpoint_uses_view_model_links_for_job_url():
|
| 37 |
app_py = Path("app.py").read_text(encoding="utf-8")
|
| 38 |
-
assert '"version": "
|
| 39 |
assert 'view = build_run_view_model(run_id, bundle, bucket_source=bucket_source)' in app_py
|
| 40 |
assert '"links": view.get("links")' in app_py
|
|
|
|
| 35 |
|
| 36 |
def test_progress_endpoint_uses_view_model_links_for_job_url():
|
| 37 |
app_py = Path("app.py").read_text(encoding="utf-8")
|
| 38 |
+
assert '"version": "v74-grouped-timeline-rings"' in app_py
|
| 39 |
assert 'view = build_run_view_model(run_id, bundle, bucket_source=bucket_source)' in app_py
|
| 40 |
assert '"links": view.get("links")' in app_py
|
tests/test_v57_live_pi_traces.py
CHANGED
|
@@ -32,4 +32,4 @@ def test_ui_has_pi_live_trace_panel_and_renderer():
|
|
| 32 |
|
| 33 |
|
| 34 |
def test_app_version_v57():
|
| 35 |
-
assert '"version": "
|
|
|
|
| 32 |
|
| 33 |
|
| 34 |
def test_app_version_v57():
|
| 35 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v58_stable_refresh_pi_stdout.py
CHANGED
|
@@ -23,4 +23,4 @@ def test_elapsed_ticks_client_side_each_second():
|
|
| 23 |
|
| 24 |
|
| 25 |
def test_app_version_v62():
|
| 26 |
-
assert '"version": "
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
def test_app_version_v62():
|
| 26 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v59_job_log_progress_clean.py
CHANGED
|
@@ -51,4 +51,4 @@ def test_progress_renderer_no_longer_sets_active_run_link_buttons():
|
|
| 51 |
|
| 52 |
|
| 53 |
def test_app_version_v59():
|
| 54 |
-
assert '"version": "
|
|
|
|
| 51 |
|
| 52 |
|
| 53 |
def test_app_version_v59():
|
| 54 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v60_cancel_persist_delete_run.py
CHANGED
|
@@ -44,4 +44,4 @@ def test_active_run_still_only_has_cancel_button():
|
|
| 44 |
|
| 45 |
|
| 46 |
def test_app_version_v60():
|
| 47 |
-
assert '"version": "
|
|
|
|
| 44 |
|
| 45 |
|
| 46 |
def test_app_version_v60():
|
| 47 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v61_stabilize_buttons_runtime.py
CHANGED
|
@@ -45,4 +45,4 @@ setTimeout(()=>process.exit(0), 25);
|
|
| 45 |
|
| 46 |
|
| 47 |
def test_app_version_v61():
|
| 48 |
-
assert '"version": "
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
def test_app_version_v61():
|
| 48 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v62_progress_perf_clean_delete.py
CHANGED
|
@@ -7,7 +7,7 @@ def test_progress_endpoint_uses_light_bucket_bundle_and_optional_job_logs():
|
|
| 7 |
assert "include_heavy: bool = True" in bucket
|
| 8 |
assert "include_heavy=False" in app
|
| 9 |
assert "include_job_logs: bool = False" in app
|
| 10 |
-
assert "if include_job_logs:" in app
|
| 11 |
assert '"job_logs_polled": bool(include_job_logs)' in app
|
| 12 |
|
| 13 |
|
|
@@ -40,4 +40,4 @@ def test_delete_selected_run_clears_ui_and_refreshes_runs():
|
|
| 40 |
|
| 41 |
|
| 42 |
def test_app_version_v62():
|
| 43 |
-
assert '"version": "
|
|
|
|
| 7 |
assert "include_heavy: bool = True" in bucket
|
| 8 |
assert "include_heavy=False" in app
|
| 9 |
assert "include_job_logs: bool = False" in app
|
| 10 |
+
assert "if job_id and include_job_logs:" in app
|
| 11 |
assert '"job_logs_polled": bool(include_job_logs)' in app
|
| 12 |
|
| 13 |
|
|
|
|
| 40 |
|
| 41 |
|
| 42 |
def test_app_version_v62():
|
| 43 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v63_delete_import_fix.py
CHANGED
|
@@ -14,4 +14,4 @@ def test_bucket_delete_helper_exists():
|
|
| 14 |
|
| 15 |
|
| 16 |
def test_app_version_v63():
|
| 17 |
-
assert '"version": "
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def test_app_version_v63():
|
| 17 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v64_delete_clean_events_timeline.py
CHANGED
|
@@ -50,4 +50,4 @@ def test_timeline_has_stopped_state_without_running_animation_confusion():
|
|
| 50 |
|
| 51 |
|
| 52 |
def test_app_version_v64():
|
| 53 |
-
assert '"version": "
|
|
|
|
| 50 |
|
| 51 |
|
| 52 |
def test_app_version_v64():
|
| 53 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v65_floating_form_timeline.py
CHANGED
|
@@ -57,4 +57,4 @@ def test_frontend_timeline_supports_stopped_status():
|
|
| 57 |
|
| 58 |
|
| 59 |
def test_app_version_v65():
|
| 60 |
-
assert '"version": "
|
|
|
|
| 57 |
|
| 58 |
|
| 59 |
def test_app_version_v65():
|
| 60 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v66_clean_qwen_coder_path.py
CHANGED
|
@@ -19,4 +19,4 @@ def test_preferred_gpu_stays_zerogpu_only_and_fallback_a10g_large_default():
|
|
| 19 |
|
| 20 |
|
| 21 |
def test_app_version_v66():
|
| 22 |
-
assert '"version": "
|
|
|
|
| 19 |
|
| 20 |
|
| 21 |
def test_app_version_v66():
|
| 22 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v67_monotonic_progress_events.py
CHANGED
|
@@ -36,4 +36,4 @@ def test_light_poll_cannot_reset_timeline_signature_to_earlier_steps():
|
|
| 36 |
|
| 37 |
|
| 38 |
def test_app_version_v67():
|
| 39 |
-
assert '"version": "
|
|
|
|
| 36 |
|
| 37 |
|
| 38 |
def test_app_version_v67():
|
| 39 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v68_stable_events_timeline_centering.py
CHANGED
|
@@ -31,4 +31,4 @@ def test_timeline_centers_even_when_signature_is_unchanged():
|
|
| 31 |
|
| 32 |
|
| 33 |
def test_app_version_v68():
|
| 34 |
-
assert '"version": "
|
|
|
|
| 31 |
|
| 32 |
|
| 33 |
def test_app_version_v68():
|
| 34 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v69_unified_progress_state.py
CHANGED
|
@@ -33,4 +33,4 @@ def test_timeline_centering_css_escape_is_safe():
|
|
| 33 |
|
| 34 |
|
| 35 |
def test_app_version_v69():
|
| 36 |
-
assert '"version": "
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
def test_app_version_v69():
|
| 36 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v70_progress_resume_events_latency.py
CHANGED
|
@@ -58,4 +58,4 @@ def test_resume_latest_falls_back_to_latest_run():
|
|
| 58 |
|
| 59 |
|
| 60 |
def test_app_version_v70():
|
| 61 |
-
assert '"version": "
|
|
|
|
| 58 |
|
| 59 |
|
| 60 |
def test_app_version_v70():
|
| 61 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v71_compact_stable_progress.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def test_compact_timeline_groups_are_used_instead_of_all_worker_steps():
|
| 5 |
+
js = Path("web/static/progress.js").read_text(encoding="utf-8")
|
| 6 |
+
css = Path("web/static/app.css").read_text(encoding="utf-8")
|
| 7 |
+
assert "COMPACT_TIMELINE_GROUPS" in js
|
| 8 |
+
assert "function compactTimelineItems" in js
|
| 9 |
+
assert "const list=compactTimelineItems(fullList)" in js
|
| 10 |
+
assert "repeat(11,minmax(92px,1fr))" in css
|
| 11 |
+
assert "failure" not in js.split("COMPACT_TIMELINE_GROUPS", 1)[1].split("];", 1)[0]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def test_run_selection_does_not_flash_stale_view_pipeline():
|
| 15 |
+
runs = Path("web/static/runs.js").read_text(encoding="utf-8")
|
| 16 |
+
assert "avoid rendering stale view.pipeline" in runs
|
| 17 |
+
render_detail = runs.split("function renderRunDetail", 1)[1].split("const homeBtn", 1)[0]
|
| 18 |
+
assert "renderRunViewModel(view" not in render_detail
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def test_light_progress_poll_does_not_call_hf_job_api_every_tick():
|
| 22 |
+
app = Path("app.py").read_text(encoding="utf-8")
|
| 23 |
+
assert "if job_id and include_job_logs:" in app
|
| 24 |
+
assert "inspect_job_safe(job_id" in app
|
| 25 |
+
assert "fetch_recent_logs_safe(job_id" in app
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_refresh_progress_prefers_canonical_status():
|
| 29 |
+
js = Path("web/static/app.js").read_text(encoding="utf-8")
|
| 30 |
+
assert "setText('detailJobStatus',p.status||p.state?.status" in js
|
| 31 |
+
assert "setText('homeRunDetailStatus',badgeLabel(p.status" in js
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def test_app_version_v71():
|
| 35 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v72_clear_modes_space_link_gating.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def test_implementation_modes_are_explicit_and_not_duplicated():
|
| 5 |
+
html = Path("web/index.html").read_text(encoding="utf-8")
|
| 6 |
+
select = html.split('id="implementationMode"', 1)[1].split("</select>", 1)[0]
|
| 7 |
+
assert "Gradio (Python)" not in select
|
| 8 |
+
assert select.count('value="full-inference-gated"') == 1
|
| 9 |
+
assert "Strict real inference — validated Gradio app" in select
|
| 10 |
+
assert "Best-effort inference — permissive prototype" in select
|
| 11 |
+
assert "Safe scaffold only — boots without inference guarantee" in select
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def test_space_and_settings_links_are_gated_until_space_creation_passed():
|
| 15 |
+
app_js = Path("web/static/app.js").read_text(encoding="utf-8")
|
| 16 |
+
runs_js = Path("web/static/runs.js").read_text(encoding="utf-8")
|
| 17 |
+
assert "function runPassedSpaceCreation" in app_js
|
| 18 |
+
assert "passedSteps=new Set" in app_js
|
| 19 |
+
assert "setGeneratedSpaceLinksEnabled" in app_js
|
| 20 |
+
assert "spaceReady=runPassedSpaceCreation" in app_js
|
| 21 |
+
assert "ready=typeof runPassedSpaceCreation" in runs_js
|
| 22 |
+
assert "setLink('selectedOpenSpace',ready?summary.target_space_url:'')" in runs_js
|
| 23 |
+
assert "setLink('selectedOpenSettings',ready&&summary.target_space_url" in runs_js
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def test_launch_result_gates_generated_space_buttons_but_not_job_or_artifacts():
|
| 27 |
+
app_js = Path("web/static/app.js").read_text(encoding="utf-8")
|
| 28 |
+
launch = app_js.split("function renderLaunchResult", 1)[1].split("function setActiveRun", 1)[0]
|
| 29 |
+
assert "setLink('launchOpenJob',links.job_url)" in launch
|
| 30 |
+
assert "spaceReady=runPassedSpaceCreation(result)" in launch
|
| 31 |
+
assert "setLink('launchOpenSpace',spaceReady?links.target_space_url:'')" in launch
|
| 32 |
+
assert "setLink('launchOpenArtifacts',links.artifacts_url)" in launch
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def test_app_version_v72():
|
| 36 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v73_toast_timer_clean_active_run.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def test_floating_toasts_have_timer_for_all_status_types():
|
| 5 |
+
js = Path("web/static/app.js").read_text(encoding="utf-8")
|
| 6 |
+
assert "function toastDuration" in js
|
| 7 |
+
assert "type==='loading'" in js
|
| 8 |
+
assert "type==='error'" in js
|
| 9 |
+
assert "operationTimer=setTimeout(()=>{setOperation('')}" in js
|
| 10 |
+
assert "if(!state.busy&&$('operationStatus')?.className.includes('loading'))setOperation('')" in js
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def test_active_run_internal_poll_warning_banner_removed():
|
| 14 |
+
html = Path("web/index.html").read_text(encoding="utf-8")
|
| 15 |
+
css = Path("web/static/app.css").read_text(encoding="utf-8")
|
| 16 |
+
js = Path("web/static/app.js").read_text(encoding="utf-8")
|
| 17 |
+
assert 'id="pollWarning"' not in html
|
| 18 |
+
assert ".poll-warning" not in css
|
| 19 |
+
assert "$('pollWarning')" not in js
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def test_progress_poll_errors_use_floating_toast_not_internal_banner():
|
| 23 |
+
js = Path("web/static/app.js").read_text(encoding="utf-8")
|
| 24 |
+
assert "Progress polling failed" in js
|
| 25 |
+
assert "Retrying automatically" in js
|
| 26 |
+
assert "setOperation(`Progress polling failed" in js
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def test_app_version_v73():
|
| 30 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|
tests/test_v74_grouped_timeline_rings.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def test_timeline_has_intermediate_groups_not_too_coarse():
|
| 5 |
+
js = Path("web/static/progress.js").read_text(encoding="utf-8")
|
| 6 |
+
groups = js.split("const COMPACT_TIMELINE_GROUPS=[", 1)[1].split("];", 1)[0]
|
| 7 |
+
for label in [
|
| 8 |
+
"Setup",
|
| 9 |
+
"Auth",
|
| 10 |
+
"Model analysis",
|
| 11 |
+
"Workspace",
|
| 12 |
+
"Pi setup",
|
| 13 |
+
"Pi generation",
|
| 14 |
+
"Sanitize",
|
| 15 |
+
"Space creation",
|
| 16 |
+
"Upload/runtime",
|
| 17 |
+
"Validation",
|
| 18 |
+
"Report",
|
| 19 |
+
]:
|
| 20 |
+
assert label in groups
|
| 21 |
+
assert groups.count("id:") == 11
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def test_grouped_timeline_computes_substep_progress():
|
| 25 |
+
js = Path("web/static/progress.js").read_text(encoding="utf-8")
|
| 26 |
+
assert "done_count:doneCount" in js
|
| 27 |
+
assert "total_count:group.steps.length" in js
|
| 28 |
+
assert "percent:" in js
|
| 29 |
+
assert "active_substep:activeSubstep" in js
|
| 30 |
+
assert "--step-progress:${percent}%" in js
|
| 31 |
+
assert "x.done_count||0" in js
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def test_timeline_dots_use_conic_gradient_ring():
|
| 35 |
+
css = Path("web/static/app.css").read_text(encoding="utf-8")
|
| 36 |
+
assert "v74: grouped timeline dots" in css
|
| 37 |
+
assert "conic-gradient(var(--blue)" in css
|
| 38 |
+
assert "var(--step-progress,0%)" in css
|
| 39 |
+
assert "repeat(11,minmax(92px,1fr))" in css
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def test_app_version_v74():
|
| 43 |
+
assert '"version": "v74-grouped-timeline-rings"' in Path("app.py").read_text(encoding="utf-8")
|