95 lines
3.1 KiB
Python
95 lines
3.1 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from tests.dashboard_bundle import dashboard
|
|
|
|
|
|
READINESS = Path(__file__).parents[1] / "frontend" / "plan-today-readiness.js"
|
|
|
|
|
|
def run_node(script: str) -> dict:
|
|
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def test_plan_readiness_bounds_concurrency_and_preserves_ranked_order():
|
|
script = f"""
|
|
const createReadiness = require({json.dumps(str(READINESS))});
|
|
let active = 0;
|
|
let peak = 0;
|
|
const completed = [];
|
|
const delays = {{1:40, 2:5, 3:20, 4:1, 5:10, 6:2}};
|
|
const readiness = createReadiness({{
|
|
concurrency:3,
|
|
identity:item => 'issue:' + item.number,
|
|
inspect:async item => {{
|
|
active += 1;
|
|
peak = Math.max(peak, active);
|
|
await new Promise(resolve => setTimeout(resolve, delays[item.number]));
|
|
completed.push(item.number);
|
|
active -= 1;
|
|
return item.number === 4 ? {{status:'blocked', reason:'Waiting'}} : {{status:'ready'}};
|
|
}},
|
|
}});
|
|
readiness.run([1,2,3,4,5,6].map(number => ({{kind:'issue', number}}))).then(result => {{
|
|
process.stdout.write(JSON.stringify({{peak, completed, entries:Object.entries(result)}}));
|
|
}});
|
|
"""
|
|
result = run_node(script)
|
|
assert result["peak"] == 3
|
|
assert result["completed"] != [1, 2, 3, 4, 5, 6]
|
|
assert result["entries"] == [
|
|
["issue:1", {"status": "ready"}],
|
|
["issue:2", {"status": "ready"}],
|
|
["issue:3", {"status": "ready"}],
|
|
["issue:4", {"status": "blocked", "reason": "Waiting"}],
|
|
["issue:5", {"status": "ready"}],
|
|
["issue:6", {"status": "ready"}],
|
|
]
|
|
|
|
|
|
def test_plan_readiness_supersedes_stale_builds_and_normalizes_real_failures():
|
|
script = f"""
|
|
const createReadiness = require({json.dumps(str(READINESS))});
|
|
let releaseFirst;
|
|
const firstGate = new Promise(resolve => {{ releaseFirst = resolve; }});
|
|
const readiness = createReadiness({{
|
|
concurrency:2,
|
|
identity:item => 'issue:' + item.number,
|
|
inspect:async item => {{
|
|
if (item.number === 1) await firstGate;
|
|
if (item.number === 3) throw new Error('upstream details');
|
|
return {{status:'ready'}};
|
|
}},
|
|
}});
|
|
const stale = readiness.run([{{number:1}}, {{number:2}}]);
|
|
const current = readiness.run([{{number:3}}, {{number:4}}]);
|
|
current.then(currentResult => {{
|
|
releaseFirst();
|
|
stale.then(staleResult => process.stdout.write(JSON.stringify({{staleResult, currentResult}})));
|
|
}});
|
|
"""
|
|
assert run_node(script) == {
|
|
"staleResult": None,
|
|
"currentResult": {
|
|
"issue:3": {"status": "unverified"},
|
|
"issue:4": {"status": "ready"},
|
|
},
|
|
}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_dashboard_uses_bounded_supersedable_readiness_for_build_my_today():
|
|
html = await dashboard()
|
|
|
|
assert '<script src="static/plan-today-readiness.js"></script>' in html
|
|
assert "const planTodayReadiness = createPlanTodayReadiness({" in html
|
|
assert "concurrency:3" in html
|
|
assert "const checks = await planTodayReadiness.run(candidates);" in html
|
|
assert "if (!checks) return;" in html
|
|
assert "planTodayReadiness.cancel();" in html
|
|
assert "Promise.all(candidates.map" not in html
|