Skip to content

Commit ae626cd

Browse files
AnHeuermannclaude
andauthored
Test MSL and publish results to GitHub pages (#10)
* Test MSL 4.1.0 with latest BaseModelica.jl and publish results to GitHub pages Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6d30f0e commit ae626cd

File tree

4 files changed

+347
-2
lines changed

4 files changed

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

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# BaseModelicaLibraryTesting.jl
22

33
[![Build Status][build-badge-svg]][build-action-url]
4+
[![MSL Test Reports][msl-badge-svg]][msl-pages-url]
45

56
Experimental Base Modelica library testing based on Julia.
67

@@ -68,6 +69,8 @@ file for details.
6869

6970
[build-badge-svg]: https://github.com/OpenModelica/BaseModelicaLibraryTesting.jl/actions/workflows/CI.yml/badge.svg?branch=main
7071
[build-action-url]: https://github.com/OpenModelica/BaseModelicaLibraryTesting.jl/actions/workflows/CI.yml?query=branch%3Amain
72+
[msl-badge-svg]: https://github.com/OpenModelica/BaseModelicaLibraryTesting.jl/actions/workflows/msl-test.yml/badge.svg?branch=main
73+
[msl-pages-url]: https://openmodelica.github.io/BaseModelicaLibraryTesting.jl/
7174
[openmodelica-url]: https://openmodelica.org/
7275
[basemodelicajl-url]: https://github.com/SciML/BaseModelica.jl
7376
[modelingtoolkitjl-url]: https://github.com/SciML/ModelingToolkit.jl

0 commit comments

Comments
 (0)