Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,5 @@
.CondaPkg/

# Generated files and example results
*.log
*.html
!asserts/*.html
*.csv
*.bmo
results/
MAP-LIB_ReferenceResults/
main/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ main(
version = "4.1.0",
filter = "Modelica.Electrical.Analog.Examples.ChuaCircuit",
omc_exe = "omc",
results_root = "main/Modelica/4.1.0/",
results_root = "results/main/Modelica/4.1.0/",
ref_root = "MAP-LIB_ReferenceResults"
)
```

Preview the generated HTML report at `main/Modelica/4.1.0/report.html`.

```bash
python -m http.server -d main/Modelica/4.1.0/
python -m http.server -d results/main/Modelica/4.1.0/
```

## License
Expand Down
6 changes: 5 additions & 1 deletion src/BaseModelicaLibraryTesting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ include("export.jl")
include("parse_bm.jl")
include("simulate.jl")
include("report.jl")
include("summary.jl")
include("pipeline.jl")

# ── Public API ─────────────────────────────────────────────────────────────────

# Shared types and constants
export ModelResult, CompareSettings
export ModelResult, CompareSettings, RunInfo
export LIBRARY, LIBRARY_VERSION, CMP_REL_TOL, CMP_ABS_TOL

# Comparison configuration
Expand All @@ -36,6 +37,9 @@ export compare_with_reference, write_diff_html
# HTML report
export generate_report

# Summary JSON
export RunSummary, write_summary, load_summary

# Top-level orchestration
export test_model, main

Expand Down
22 changes: 21 additions & 1 deletion src/pipeline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ function main(;
results_root :: String = "",
ref_root :: String = get(ENV, "MAPLIB_REF", ""),
)
t0 = time()

if isempty(results_root)
results_root = joinpath(library, version)
end
Expand All @@ -76,6 +78,7 @@ function main(;
@info "Starting OMC session ($(omc_exe))..."
omc = OMJulia.OMCSession(omc_exe)

omc_version = "unknown"
results = ModelResult[]
try
omc_version = sendExpression(omc, "getVersion()")
Expand Down Expand Up @@ -136,6 +139,23 @@ function main(;
OMJulia.quit(omc)
end

generate_report(results, results_root, library, version)
cpu_info = Sys.cpu_info()
info = RunInfo(
library,
version,
something(filter, ""),
omc_exe,
results_root,
ref_root,
omc_version,
string(pkgversion(BaseModelica)),
isempty(cpu_info) ? "unknown" : strip(cpu_info[1].model),
length(cpu_info),
Sys.total_memory() / 1024^3,
time() - t0,
)

generate_report(results, results_root, info)
write_summary(results, results_root, info)
return results
end
31 changes: 22 additions & 9 deletions src/report.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import Dates: now
import Printf: @sprintf
import BaseModelica

function _status_cell(ok::Bool, t::Float64, logFile::Union{String,Nothing})
link = isnothing(logFile) ? "" : """ <a href="$(logFile)">(log)</a>"""
Expand Down Expand Up @@ -34,13 +33,22 @@ function rel_log_file_or_nothing(results_root::String, model::String,
isfile(path) ? joinpath("files", model, "$(model)_$(phase).log") : nothing
end

function _format_duration(t::Float64)::String
t < 60 && return @sprintf("%.1f s", t)
m = div(floor(Int, t), 60)
s = floor(Int, t) % 60
m < 60 && return @sprintf("%d min %d s", m, s)
h = div(m, 60)
return @sprintf("%d h %d min %d s", h, m % 60, s)
end

"""
generate_report(results, results_root, library, version) → report_path
generate_report(results, results_root, info) → report_path

Write an `index.html` overview report to `results_root` and return its path.
"""
function generate_report(results::Vector{ModelResult}, results_root::String,
library::String, version::String)
info::RunInfo)
n = length(results)
n_exp = count(r -> r.export_success, results)
n_par = count(r -> r.parse_success, results)
Expand All @@ -64,14 +72,16 @@ function generate_report(results::Vector{ModelResult}, results_root::String,
$(_cmp_cell(r, results_root))
</tr>""" for r in results], "\n")

omc_ver = try sendExpression(OMJulia.OMCSession("omc"), "getVersion()") catch; "unknown" end
bm_ver = string(pkgversion(BaseModelica))
filter_row = isempty(info.filter) ? "" : "<br>Filter: $(info.filter)"
ref_row = isempty(info.ref_root) ? "" : "<br>Reference results: $(info.ref_root)"
ram_str = @sprintf("%.1f", info.ram_gb)
time_str = _format_duration(info.total_time_s)

html = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>$library $version — Base Modelica / MTK Results</title>
<title>$(info.library) $(info.lib_version) — Base Modelica / MTK Results</title>
<style>
body { font-family: sans-serif; margin: 2em; font-size: 14px; }
h1 { font-size: 1.4em; }
Expand All @@ -86,10 +96,13 @@ function generate_report(results::Vector{ModelResult}, results_root::String,
</style>
</head>
<body>
<h1>$library $version — Base Modelica / MTK Pipeline Test Results</h1>
<h1>$(info.library) $(info.lib_version) — Base Modelica / MTK Pipeline Test Results</h1>
<p>Generated: $(now())<br>
OpenModelica: $omc_ver<br>
BaseModelica.jl: $bm_ver</p>
OpenModelica: $(info.omc_version)<br>
BaseModelica.jl: $(info.bm_version)$(filter_row)$(ref_row)</p>
<p>CPU: $(info.cpu_model) ($(info.cpu_threads) threads)<br>
RAM: $(ram_str) GiB<br>
Total run time: $(time_str)</p>

<table style="width:auto; margin-bottom:1.5em;">
<tr><th>Stage</th><th>Passed</th><th>Total</th><th>Rate</th></tr>
Expand Down
141 changes: 141 additions & 0 deletions src/summary.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# ── Summary JSON serialization ─────────────────────────────────────────────────

function _esc_json(s::String)::String
replace(s, "\\" => "\\\\", "\"" => "\\\"", "\n" => "\\n")
end

"""
write_summary(results, results_root, info)

Write a `summary.json` to `results_root` encoding run settings, tool versions,
machine info, and per-model pipeline pass/fail data.
Called automatically by `main()` at the end of each run.
"""
function write_summary(
results :: Vector{ModelResult},
results_root :: String,
info :: RunInfo,
)
path = joinpath(results_root, "summary.json")
open(path, "w") do io
print(io, "{\n")
print(io, " \"library\": \"$(_esc_json(info.library))\",\n")
print(io, " \"lib_version\": \"$(_esc_json(info.lib_version))\",\n")
print(io, " \"filter\": \"$(_esc_json(info.filter))\",\n")
print(io, " \"omc_exe\": \"$(_esc_json(info.omc_exe))\",\n")
print(io, " \"results_root\": \"$(_esc_json(info.results_root))\",\n")
print(io, " \"ref_root\": \"$(_esc_json(info.ref_root))\",\n")
print(io, " \"omc_version\": \"$(_esc_json(info.omc_version))\",\n")
print(io, " \"bm_version\": \"$(_esc_json(info.bm_version))\",\n")
print(io, " \"cpu_model\": \"$(_esc_json(info.cpu_model))\",\n")
print(io, " \"cpu_threads\": $(info.cpu_threads),\n")
print(io, " \"ram_gb\": $(@sprintf "%.2f" info.ram_gb),\n")
print(io, " \"total_time_s\": $(@sprintf "%.2f" info.total_time_s),\n")
print(io, " \"models\": [\n")
for (i, r) in enumerate(results)
sep = i < length(results) ? "," : ""
print(io,
" {\"name\":\"$(_esc_json(r.name))\"," *
"\"export\":$(r.export_success)," *
"\"parse\":$(r.parse_success)," *
"\"sim\":$(r.sim_success)," *
"\"cmp_total\":$(r.cmp_total)," *
"\"cmp_pass\":$(r.cmp_pass)}$sep\n")
end
print(io, " ]\n}\n")
end
@info "summary.json written to $results_root"
end

# ── Summary type and JSON loading ──────────────────────────────────────────────

"""
RunSummary

Parsed contents of a single `summary.json` file.

# Fields
- `library` — Modelica library name (e.g. `"Modelica"`)
- `lib_version` — library version (e.g. `"4.1.0"`)
- `filter` — model name filter regex, or `""` when none was given
- `omc_exe` — path / command used to launch OMC
- `results_root` — absolute path where results were written
- `ref_root` — absolute path to reference results, or `""` when unused
- `omc_version` — OMC version string
- `bm_version` — BaseModelica.jl version string (e.g. `"1.6.0"`)
- `cpu_model` — CPU model name
- `cpu_threads` — number of logical CPU threads
- `ram_gb` — total system RAM in GiB
- `total_time_s` — wall-clock duration of the full test run in seconds
- `models` — vector of per-model dicts; each has keys
`"name"`, `"export"`, `"parse"`, `"sim"`, `"cmp_total"`, `"cmp_pass"`
"""
struct RunSummary
library :: String
lib_version :: String
filter :: String
omc_exe :: String
results_root :: String
ref_root :: String
omc_version :: String
bm_version :: String
cpu_model :: String
cpu_threads :: Int
ram_gb :: Float64
total_time_s :: Float64
models :: Vector{Dict{String,Any}}
end

"""
load_summary(results_root) → RunSummary or nothing

Read and parse the `summary.json` written by `write_summary` from `results_root`.
Returns `nothing` if the file does not exist or cannot be parsed.
"""
function load_summary(results_root::String)::Union{RunSummary,Nothing}
path = joinpath(results_root, "summary.json")
isfile(path) || return nothing
txt = read(path, String)

_str(key) = begin
m = match(Regex("\"$(key)\"\\s*:\\s*\"([^\"]*)\""), txt)
m === nothing ? "" : string(m.captures[1])
end
_int(key) = begin
m = match(Regex("\"$(key)\"\\s*:\\s*(\\d+)"), txt)
m === nothing ? 0 : parse(Int, m.captures[1])
end
_float(key) = begin
m = match(Regex("\"$(key)\"\\s*:\\s*([\\d.]+)"), txt)
m === nothing ? 0.0 : parse(Float64, m.captures[1])
end

models = Dict{String,Any}[]
for m in eachmatch(
r"\{\"name\":\"([^\"]*)\",\"export\":(true|false),\"parse\":(true|false),\"sim\":(true|false),\"cmp_total\":(\d+),\"cmp_pass\":(\d+)\}",
txt)
push!(models, Dict{String,Any}(
"name" => string(m.captures[1]),
"export" => m.captures[2] == "true",
"parse" => m.captures[3] == "true",
"sim" => m.captures[4] == "true",
"cmp_total" => parse(Int, m.captures[5]),
"cmp_pass" => parse(Int, m.captures[6]),
))
end
return RunSummary(
_str("library"),
_str("lib_version"),
_str("filter"),
_str("omc_exe"),
_str("results_root"),
_str("ref_root"),
_str("omc_version"),
_str("bm_version"),
_str("cpu_model"),
_int("cpu_threads"),
_float("ram_gb"),
_float("total_time_s"),
models,
)
end
37 changes: 37 additions & 0 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,43 @@ Base.@kwdef mutable struct CompareSettings
error_fn :: Symbol = :mixed
end

# ── Run metadata ───────────────────────────────────────────────────────────────

"""
RunInfo

Metadata about a single test run, collected by `main()` and written into both
`index.html` and `summary.json`.

# Fields
- `library` — Modelica library name (e.g. `"Modelica"`)
- `lib_version` — library version (e.g. `"4.1.0"`)
- `filter` — model name filter regex, or `""` when none was given
- `omc_exe` — path / command used to launch OMC
- `results_root` — absolute path where results are written
- `ref_root` — absolute path to reference results, or `""` when unused
- `omc_version` — version string returned by `getVersion()`, e.g. `"v1.23.0"`
- `bm_version` — BaseModelica.jl version string, e.g. `"1.6.0"`
- `cpu_model` — CPU model name from `Sys.cpu_info()`
- `cpu_threads` — number of logical CPU threads
- `ram_gb` — total system RAM in GiB
- `total_time_s` — wall-clock duration of the full test run in seconds
"""
struct RunInfo
library :: String
lib_version :: String
filter :: String # "" when no filter was given
omc_exe :: String
results_root :: String
ref_root :: String # "" when no reference root was given
omc_version :: String
bm_version :: String
cpu_model :: String
cpu_threads :: Int
ram_gb :: Float64
total_time_s :: Float64
end

# ── Result type ────────────────────────────────────────────────────────────────

struct ModelResult
Expand Down