68 lines
8.6 KiB
Python
68 lines
8.6 KiB
Python
#!/usr/bin/python3
|
|
"""Create a portable HTML report from reviewed screenshots and recorded results."""
|
|
import base64
|
|
from datetime import datetime, timezone
|
|
import html
|
|
import json
|
|
from pathlib import Path
|
|
|
|
root = Path(__file__).resolve().parent.parent
|
|
report = root / 'test_report/ide-config'
|
|
data = json.loads((report / 'results.json').read_text())
|
|
e = html.escape
|
|
|
|
def photo(name, caption):
|
|
content = base64.b64encode((report / name).read_bytes()).decode()
|
|
url = 'data:image/png;base64,' + content
|
|
return f'<figure><button type="button" class="enlarge" aria-label="Enlarge {e(caption)}"><img src="{url}" loading="lazy" alt="{e(caption)}"></button><figcaption>{e(caption)} · <a href="{url}" download="{e(name)}">PNG</a></figcaption></figure>'
|
|
|
|
cases = data['cases']
|
|
total = len(cases)
|
|
passed = sum(c['status'] == 'pass' for c in cases)
|
|
rows = []
|
|
cards = []
|
|
for case in cases:
|
|
ident = case['id']
|
|
rows.append(f'<tr><td><a href="#{e(ident)}">{e(case["ide"])}</a></td><td>{e(case["language"])}</td><td class="{e(case["status"])}">{e(case["status"].upper())}</td><td>{e(case["action"])}</td></tr>')
|
|
pictures = ''.join(photo(p['file'], p['caption']) for p in case['screenshots'])
|
|
cards.append(f'<article id="{e(ident)}" data-ide="{e(case["ide"])}"><h2>{e(case["ide"])} · {e(case["language"])} <span class="badge {e(case["status"])}">{e(case["status"].upper())}</span></h2><p><strong>Action:</strong> {e(case["action"])}</p><p>{e(case["evidence"])}</p>{pictures}</article>')
|
|
|
|
logs = []
|
|
for name, title in [('integration-check.txt', 'Compiler and configuration integration checks'),
|
|
('feature-check.txt', 'Image feature checks'), ('technotes-check.txt', 'Technical notes checks'),
|
|
('idempotence.txt', 'Configuration reapplied without changes'),
|
|
('reset-check.txt', 'Team-home reset and restored configuration'),
|
|
('reset-integration-check.txt', 'Compiler integration checks after the team-home reset'),
|
|
('build-summary.txt', 'Final image build and installed file checksums'),
|
|
('gui-artifacts.txt', 'Artifacts created by graphical runs')]:
|
|
path = report / name
|
|
if path.exists():
|
|
logs.append(f'<details><summary>{e(title)}</summary><pre>{e(path.read_text())}</pre></details>')
|
|
sources = ''.join(f'<li><a href="{e(s["url"])}">{e(s["title"])}</a></li>' for s in data['sources'])
|
|
fixtures = ''.join(f'<details><summary>{e(p.name)} — test source</summary><pre>{e(p.read_text())}</pre></details>' for p in sorted((root / 'tests/ide_samples').iterdir()) if p.is_file())
|
|
options = ''.join(f'<option>{e(ide)}</option>' for ide in dict.fromkeys(c['ide'] for c in cases))
|
|
notes = ''.join(f'<li>{e(note)}</li>' for note in data['notes'])
|
|
build = e(data['build'])
|
|
notes_page = report / 'technotes-final-3.png'
|
|
instructions = ('<section class="panel"><h2>Instructions supplied to teams</h2><p>Page 3 of the updated TechNotes explains the tested action for every IDE and the first-launch dialogs. The PDF installed in the final image was checked against this copy.</p>' + photo(notes_page.name, 'TechNotes · IDE instructions, page 3') + '</section>') if notes_page.exists() else ''
|
|
page = f'''<!doctype html>
|
|
<html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>ACPC 2026 — IDE compiler verification</title>
|
|
<style>
|
|
:root{{font-family:system-ui,sans-serif;color:#172434;background:#f1f4f7;line-height:1.55}}body{{margin:0}}main{{max-width:1100px;margin:auto;padding:32px 22px 70px}}header,article,.panel{{background:white;border:1px solid #d7e0e8;border-radius:12px;padding:24px;margin:0 0 24px}}h1{{font-size:2rem;line-height:1.2;margin:.2em 0 .6em}}h2{{font-size:1.35rem}}.eyebrow{{color:#526579;font-weight:650;letter-spacing:.07em;text-transform:uppercase;font-size:.8rem}}a{{color:#155b96}}table{{width:100%;border-collapse:collapse}}th,td{{text-align:left;border-bottom:1px solid #dce4ec;padding:9px 8px}}th{{background:#edf3f8}}.pass{{color:#11603d}}.fail{{color:#a12121}}.pending{{color:#835800}}.badge{{border:1px solid currentColor;border-radius:5px;padding:2px 7px;font-size:.75rem;vertical-align:middle;margin-left:8px}}img{{max-width:100%;height:auto;display:block;border:1px solid #ced9e3;border-radius:6px}}figure{{margin:20px 0 0}}figcaption{{font-size:.9rem;margin:7px 0;color:#42576a}}pre{{white-space:pre-wrap;overflow-wrap:anywhere;background:#edf2f6;padding:16px;border-radius:6px;font-size:.85rem}}summary{{cursor:pointer;font-weight:600;padding:10px 0}}select{{font:inherit;padding:6px 12px;margin-left:10px}}.sub{{color:#516378}}.grid{{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}}.metric{{background:#edf6f0;padding:13px;border-radius:7px}}.metric b{{display:block;font-size:1.6rem}}article{{scroll-margin-top:20px}}.tablewrap{{overflow-x:auto}}@media(max-width:650px){{.grid{{grid-template-columns:1fr}}main{{padding:15px 10px}}header,article,.panel{{padding:16px}}h1{{font-size:1.6rem}}}}@media print{{details{{display:block}}select{{display:none}}article{{break-inside:avoid}}body{{background:white}}}}
|
|
</style><main>
|
|
<style>.enlarge{{display:block;padding:0;border:0;background:none;cursor:zoom-in;width:100%;text-align:left}}dialog{{max-width:96vw;max-height:94vh;padding:14px;border:1px solid #98aabd;border-radius:9px}}dialog::backdrop{{background:#102333cc}}dialog img{{max-width:none;max-height:none}}dialog form{{position:sticky;top:0;text-align:right;margin-bottom:10px;background:white}}dialog button{{font:inherit;padding:5px 15px}}</style>
|
|
<header><div class="eyebrow">ACPC 2026 · contest USB image</div><h1>IDE compiler verification</h1>
|
|
<p>{e(data['verdict'])}</p><div class="grid"><div class="metric"><b>{passed}/{total}</b>language workflows passed</div><div class="metric"><b>7</b>IDEs on the real VM desktop</div><div class="metric"><b>3000000004</b>expected result for 3000000000 + 4</div></div>
|
|
<p class="sub">{build}</p><p class="sub">Screenshots are embedded in this file. Click any image to inspect it, or download its PNG. No external service is needed to view this report.</p></header>
|
|
<section class="panel"><h2>What was tested</h2><p>Each recorded workflow was launched through the IDE interface, built or interpreted the supplied source, accepted input typed on the VM keyboard and produced the expected result. The screenshots were opened and visually inspected. Configuration was installed by Ansible; compiler settings were not repaired manually during the final GUI pass.</p><p>C rejects accidental C++ compilation and requires C23. C++ requires C++23 and uses <code>std::println</code>. Java asserts the exact runtime version and the required VM flags. Python asserts PyPy and its version. Supplementary checks inspect static C/C++ executables and Java class versions.</p><ul>{notes}</ul></section>
|
|
<section class="panel"><h2>Results</h2><div class="tablewrap"><table><thead><tr><th>IDE</th><th>Language</th><th>Result</th><th>Action tested</th></tr></thead><tbody>{''.join(rows)}</tbody></table></div></section>
|
|
<p><label>Show screenshots for <select id="filter"><option value="all">All IDEs</option>{options}</select></label></p>
|
|
{''.join(cards)}
|
|
{instructions}
|
|
<section class="panel"><h2>Supporting checks</h2>{''.join(logs)}{fixtures}</section>
|
|
<section class="panel"><h2>Configuration references</h2><p>The installed IDE files and their serialized settings were also inspected. These primary references explain the supported configuration mechanisms.</p><ul>{sources}</ul><p class="sub">Report generated {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}.</p></section>
|
|
</main><dialog id="viewer"><form method="dialog"><button>Close</button></form><img alt=""></dialog><script>const filter=document.getElementById('filter');function applyFilter(){{document.querySelectorAll('article[data-ide]').forEach(a=>a.hidden=filter.value!=='all'&&a.dataset.ide!==filter.value)}}filter.addEventListener('change',applyFilter);window.addEventListener('pageshow',applyFilter);document.querySelectorAll('a[href^="#"]').forEach(a=>a.addEventListener('click',()=>{{document.getElementById('filter').value='all';document.querySelectorAll('article[data-ide]').forEach(a=>a.hidden=false)}}));const viewer=document.getElementById('viewer');document.querySelectorAll('.enlarge').forEach(button=>button.addEventListener('click',()=>{{const source=button.querySelector('img'),target=viewer.querySelector('img');target.src=source.src;target.alt=source.alt;viewer.showModal()}}));viewer.addEventListener('click',event=>{{if(event.target===viewer)viewer.close()}});</script></html>'''
|
|
(report / 'index.html').write_text(page)
|
|
print(f'{report / "index.html"}: {passed}/{total} passed; {len(page):,} bytes')
|