Skip to content

Commit 067f8b3

Browse files
committed
Test MSL and publish results to GitHub pages
1 parent c541cb9 commit 067f8b3

File tree

3 files changed

+335
-2
lines changed

3 files changed

+335
-2
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate the root index.html landing page for the gh-pages site.
4+
5+
Scans results/<bm_version>/<library>/<lib_version>/summary.json
6+
and produces a table linking to each run's individual report.
7+
8+
Usage:
9+
python3 gen_landing_page.py <site_root>
10+
11+
<site_root> is the root of the gh-pages checkout (i.e. the directory that
12+
contains the 'results/' sub-tree and will receive the generated index.html).
13+
"""
14+
15+
import json
16+
import os
17+
import subprocess
18+
import sys
19+
from datetime import datetime, timezone
20+
from pathlib import Path
21+
22+
23+
# ── Helpers ───────────────────────────────────────────────────────────────────
24+
25+
def pct_str(num: int, den: int) -> str:
26+
if den == 0:
27+
return "—"
28+
return f"{100 * num / den:.1f} %"
29+
30+
31+
def format_duration(seconds: float) -> str:
32+
t = int(seconds)
33+
if t < 60:
34+
return f"{t} s"
35+
m, s = divmod(t, 60)
36+
if m < 60:
37+
return f"{m} min {s} s"
38+
h, m = divmod(m, 60)
39+
return f"{h} h {m} min"
40+
41+
42+
def pct_class(num: int, den: int) -> str:
43+
"""CSS class for a pass-rate cell."""
44+
if den == 0:
45+
return "na"
46+
r = num / den
47+
if r >= 0.90:
48+
return "ok"
49+
if r >= 0.70:
50+
return "warn"
51+
return "fail"
52+
53+
54+
def git_date(summary_path: Path, site_root: Path) -> str:
55+
"""Return the YYYY-MM-DD of the git commit that last touched summary_path."""
56+
try:
57+
rel = summary_path.relative_to(site_root)
58+
result = subprocess.run(
59+
["git", "log", "-1", "--format=%as", "--", str(rel)],
60+
capture_output=True, text=True, cwd=site_root,
61+
)
62+
return result.stdout.strip()
63+
except Exception:
64+
return ""
65+
66+
67+
# ── Data loading ──────────────────────────────────────────────────────────────
68+
69+
def load_runs(site_root: Path) -> list[dict]:
70+
runs = []
71+
results_dir = site_root / "results"
72+
if not results_dir.exists():
73+
return runs
74+
75+
for summary_path in sorted(results_dir.glob("*/*/*/summary.json"), reverse=True):
76+
try:
77+
with open(summary_path, encoding="utf-8") as f:
78+
data = json.load(f)
79+
except Exception:
80+
continue
81+
82+
models = data.get("models", [])
83+
n = len(models)
84+
n_exp = sum(1 for m in models if m.get("export", False))
85+
n_par = sum(1 for m in models if m.get("parse", False))
86+
n_sim = sum(1 for m in models if m.get("sim", False))
87+
88+
cmp_models = [m for m in models if m.get("cmp_total", 0) > 0]
89+
n_cmp = len(cmp_models)
90+
n_cmp_pass = sum(1 for m in cmp_models if m["cmp_pass"] == m["cmp_total"])
91+
92+
run_dir = summary_path.parent
93+
index_url = str((run_dir / "index.html").relative_to(site_root)).replace("\\", "/")
94+
95+
runs.append({
96+
"bm_version": data.get("bm_version", "?"),
97+
"library": data.get("library", "?"),
98+
"lib_version": data.get("lib_version", "?"),
99+
"omc_version": data.get("omc_version", "?"),
100+
"total": n,
101+
"n_exp": n_exp,
102+
"n_par": n_par,
103+
"n_sim": n_sim,
104+
"n_cmp": n_cmp,
105+
"n_cmp_pass": n_cmp_pass,
106+
"duration": format_duration(data.get("total_time_s", 0)),
107+
"date": git_date(summary_path, site_root),
108+
"index_url": index_url,
109+
})
110+
111+
return runs
112+
113+
114+
# ── HTML rendering ────────────────────────────────────────────────────────────
115+
116+
def _pct_cell(num: int, den: int) -> str:
117+
css = pct_class(num, den)
118+
label = f"{num}/{den} ({pct_str(num, den)})" if den > 0 else "—"
119+
return f'<td class="{css}">{label}</td>'
120+
121+
122+
def render(runs: list[dict]) -> str:
123+
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
124+
125+
if runs:
126+
rows = []
127+
for r in runs:
128+
cmp_cell = (
129+
_pct_cell(r["n_cmp_pass"], r["n_cmp"])
130+
if r["n_cmp"] > 0
131+
else '<td class="na">—</td>'
132+
)
133+
rows.append(f"""\
134+
<tr>
135+
<td><a href="{r['index_url']}">{r['bm_version']}</a></td>
136+
<td>{r['library']}</td>
137+
<td>{r['lib_version']}</td>
138+
<td>{r['omc_version']}</td>
139+
<td>{r['date']}</td>
140+
<td>{r['duration']}</td>
141+
{_pct_cell(r['n_exp'], r['total'])}
142+
{_pct_cell(r['n_par'], r['n_exp'])}
143+
{_pct_cell(r['n_sim'], r['n_par'])}
144+
{cmp_cell}
145+
</tr>""")
146+
rows_html = "\n".join(rows)
147+
else:
148+
rows_html = ' <tr><td colspan="10" class="na" style="text-align:center">No results yet.</td></tr>'
149+
150+
return f"""\
151+
<!DOCTYPE html>
152+
<html lang="en">
153+
<head>
154+
<meta charset="UTF-8"/>
155+
<title>BaseModelicaLibraryTesting — Test Results</title>
156+
<style>
157+
body {{ font-family: sans-serif; margin: 2em; font-size: 14px; }}
158+
h1 {{ font-size: 1.4em; }}
159+
table {{ border-collapse: collapse; }}
160+
th, td {{ border: 1px solid #ccc; padding: 4px 12px; text-align: left; white-space: nowrap; }}
161+
th {{ background: #eee; }}
162+
td.ok {{ background: #d4edda; color: #155724; }}
163+
td.warn {{ background: #fff3cd; color: #856404; }}
164+
td.fail {{ background: #f8d7da; color: #721c24; }}
165+
td.na {{ color: #888; }}
166+
a {{ color: #0366d6; text-decoration: none; }}
167+
a:hover {{ text-decoration: underline; }}
168+
</style>
169+
</head>
170+
<body>
171+
<h1>BaseModelicaLibraryTesting — Test Results</h1>
172+
<p>Generated: {now}</p>
173+
<table>
174+
<tr>
175+
<th>BaseModelica.jl</th>
176+
<th>Library</th>
177+
<th>Version</th>
178+
<th>OpenModelica</th>
179+
<th>Date</th>
180+
<th>Duration</th>
181+
<th>BM Export</th>
182+
<th>BM Parse</th>
183+
<th>MTK Sim</th>
184+
<th>Ref Cmp</th>
185+
</tr>
186+
{rows_html}
187+
</table>
188+
</body>
189+
</html>
190+
"""
191+
192+
193+
# ── Entry point ───────────────────────────────────────────────────────────────
194+
195+
def main() -> None:
196+
if len(sys.argv) != 2:
197+
print(f"Usage: {sys.argv[0]} <site_root>", file=sys.stderr)
198+
sys.exit(1)
199+
200+
site_root = Path(sys.argv[1]).resolve()
201+
runs = load_runs(site_root)
202+
html = render(runs)
203+
204+
# Disable Jekyll so GitHub Pages serves files as-is
205+
(site_root / ".nojekyll").touch()
206+
207+
out = site_root / "index.html"
208+
out.write_text(html, encoding="utf-8")
209+
print(f"Landing page written to {out} ({len(runs)} run(s) listed)")
210+
211+
212+
if __name__ == "__main__":
213+
main()

.github/workflows/CI.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,13 @@ jobs:
9292
library = "Modelica",
9393
version = "4.1.0",
9494
filter = "Modelica.Electrical.Analog.Examples.ChuaCircuit",
95-
results_root = "main/Modelica/4.1.0/"
95+
results_root = "results/main/Modelica/4.1.0/"
9696
)
9797
'
9898
9999
- name: Upload test results
100100
uses: actions/upload-artifact@v6
101101
with:
102102
name: sanity-results-${{ matrix.os }}
103-
path: main/
103+
path: results/
104104
if-no-files-found: error

.github/workflows/msl-test.yml

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
name: MSL Test & GitHub Pages
2+
3+
# Run the full Modelica Standard Library pipeline and publish results to
4+
# GitHub Pages at results/<bm_version>/Modelica/<lib_version>/
5+
#
6+
# Prerequisites (one-time repo setup):
7+
# Settings → Pages → Source: Deploy from a branch → Branch: gh-pages / (root)
8+
9+
on:
10+
schedule:
11+
- cron: '0 3 * * *' # every day 03:00 UTC
12+
pull_request:
13+
branches: [main] # only for testing
14+
workflow_dispatch:
15+
inputs:
16+
lib_version:
17+
description: 'Modelica Standard Library version'
18+
required: false
19+
default: '4.1.0'
20+
type: string
21+
22+
concurrency:
23+
group: pages
24+
cancel-in-progress: false # never abort a Pages deployment mid-flight
25+
26+
permissions:
27+
contents: write # needed to push to gh-pages
28+
29+
jobs:
30+
test-and-deploy:
31+
name: Test MSL & Deploy Pages
32+
runs-on: ubuntu-latest
33+
timeout-minutes: 480
34+
35+
env:
36+
LIB_VERSION: ${{ inputs.lib_version || '4.1.0' }}
37+
38+
steps:
39+
- name: Checkout source
40+
uses: actions/checkout@v6
41+
42+
- name: Set up OpenModelica (nightly)
43+
uses: OpenModelica/setup-openmodelica@v1.0
44+
with:
45+
version: nightly
46+
packages: |
47+
omc
48+
libraries: |
49+
'Modelica ${{ env.LIB_VERSION }}'
50+
51+
- name: Set up Julia
52+
uses: julia-actions/setup-julia@v2
53+
with:
54+
version: '1'
55+
arch: x64
56+
57+
- name: Restore Julia package cache
58+
uses: julia-actions/cache@v2
59+
60+
- name: Build package
61+
uses: julia-actions/julia-buildpkg@v1
62+
63+
# ── Resolve versions ──────────────────────────────────────────────────────
64+
- name: Resolve BaseModelica.jl version
65+
id: versions
66+
run: |
67+
julia --project=. -e '
68+
import BaseModelica
69+
println("bm_version=" * string(pkgversion(BaseModelica)))
70+
' >> "$GITHUB_OUTPUT"
71+
72+
# ── Run the pipeline ──────────────────────────────────────────────────────
73+
- name: Run MSL pipeline
74+
env:
75+
BM_VERSION: ${{ steps.versions.outputs.bm_version }}
76+
run: |
77+
julia --project=. -e '
78+
using BaseModelicaLibraryTesting
79+
main(
80+
version = ENV["LIB_VERSION"],
81+
results_root = "results/$(ENV["BM_VERSION"])/Modelica/$(ENV["LIB_VERSION"])",
82+
)
83+
'
84+
85+
- name: Upload test results
86+
uses: actions/upload-artifact@v6
87+
with:
88+
name: sanity-results-${{ matrix.os }}
89+
path: results/
90+
if-no-files-found: error
91+
92+
93+
# ── Deploy to gh-pages ────────────────────────────────────────────────────
94+
- name: Prepare gh-pages worktree
95+
run: |
96+
git fetch origin gh-pages 2>/dev/null || true
97+
if git show-ref --verify refs/remotes/origin/gh-pages 2>/dev/null; then
98+
git worktree add site origin/gh-pages
99+
else
100+
# First ever run: create an orphan branch
101+
git worktree add --orphan -b gh-pages site
102+
fi
103+
104+
- name: Copy new results into gh-pages tree
105+
run: rsync -a results/ site/results/
106+
107+
- name: Generate landing page
108+
run: python3 .github/scripts/gen_landing_page.py site
109+
110+
- name: Commit and push to gh-pages
111+
env:
112+
BM_VERSION: ${{ steps.versions.outputs.bm_version }}
113+
run: |
114+
cd site
115+
git config user.name "github-actions[bot]"
116+
git config user.email "github-actions[bot]@users.noreply.github.com"
117+
git add -A
118+
git diff --cached --quiet && { echo "Nothing to commit."; exit 0; }
119+
git commit -m "results: ${BM_VERSION}/Modelica/${LIB_VERSION} [$(date -u '+%Y-%m-%d')]"
120+
git push origin HEAD:gh-pages

0 commit comments

Comments
 (0)