Skip to content
Open
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
103 changes: 103 additions & 0 deletions .github/workflows/mdsplus-contract.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: MDSplus contract suite (non-blocking)

# Task 2 issue #18 (parent #12): a dedicated, informational CI leg for the
# MDSplus tier (tests/contract/test_mdsplus*.cpp, TRACEABILITY.md Part 4),
# mirroring the D7 sanitizer leg (sanitizers.yml). continue-on-error: true —
# informational, not a merge gate, until the characterization baseline
# (issues #13-17) is triaged. Promotion to a blocking gate is deferred to
# Task 3.
#
# Runs on a GitHub-hosted arm64 runner: MDSplus publishes no arm64 packages
# for Ubuntu 22, only 24.04+ (docker/mdsplus/README.md's deviation note), and
# the characterization image itself is aarch64.
#
# Two-layer cache, so an ordinary push doesn't pay the full toolchain-install
# + DD-download cost every time:
# 1. the toolchain image (docker/mdsplus/Dockerfile) — keyed on its hash.
# A miss means re-running the MDSplus/Java/DD-toolchain package install.
# 2. the baked DD model tree (build-mdsplus/) — keyed on the DD version
# plus the model-build inputs (ALBuildDataDictionary.cmake,
# models/mdsplus/CMakeLists.txt). A hit restores the whole configured
# build directory (including the generated ids.xml and the model-tree
# files under build-mdsplus/models/mdsplus), so the configure step is
# skipped entirely on a hit — CMake's own dependency tracking then only
# rebuilds C++ sources that actually changed.

on:
push:
pull_request:

env:
DD_VERSION: "4.1.1"
IMAGE_TAG: imas-core-mdsplus:ci

jobs:
mdsplus-contract:
name: MDSplus contract suite (aarch64, non-blocking)
runs-on: ubuntu-24.04-arm
continue-on-error: true # informational leg, not a gate — for now

steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0 # git describe needs full history for the version macro

- name: Cache toolchain image (layer 1 — keyed on Dockerfile)
id: cache-image
uses: actions/cache@v4
with:
path: /tmp/mdsplus-image.tar
key: mdsplus-image-${{ hashFiles('docker/mdsplus/Dockerfile') }}

- name: Build toolchain image
if: steps.cache-image.outputs.cache-hit != 'true'
run: |
docker build -f docker/mdsplus/Dockerfile -t "${IMAGE_TAG}" docker/mdsplus
docker save "${IMAGE_TAG}" -o /tmp/mdsplus-image.tar

- name: Load cached toolchain image
if: steps.cache-image.outputs.cache-hit == 'true'
run: docker load -i /tmp/mdsplus-image.tar

- name: Cache baked DD model tree (layer 2 — keyed on DD version + model-build inputs)
id: cache-model
uses: actions/cache@v4
with:
path: build-mdsplus
key: mdsplus-model-${{ env.DD_VERSION }}-${{ hashFiles('common/cmake/ALBuildDataDictionary.cmake', 'models/mdsplus/CMakeLists.txt') }}

- name: Configure (bakes the DD model tree on a cache miss)
if: steps.cache-model.outputs.cache-hit != 'true'
run: >
docker run --rm --platform linux/arm64
-v "$PWD":/workspace -w /workspace
"${IMAGE_TAG}" bash -c "
cmake -B build-mdsplus -G Ninja
-DAL_BACKEND_MDSPLUS=ON
-DAL_BUILD_MDSPLUS_MODELS=ON
-DAL_BUILD_TESTS=ON
-DDD_VERSION=${DD_VERSION}
-DCMAKE_BUILD_TYPE=Release
"

- name: Build contract suite
run: >
docker run --rm --platform linux/arm64
-v "$PWD":/workspace -w /workspace
"${IMAGE_TAG}" bash -c "
cmake --build build-mdsplus --target contract_tests -j\$(nproc)
"

- name: Run MDSplus contract suite
run: >
docker run --rm --platform linux/arm64
-v "$PWD":/workspace -w /workspace
-e MDSPLUS_MODELS_PATH=/workspace/build-mdsplus/models/mdsplus
"${IMAGE_TAG}" bash -c "
cd build-mdsplus/tests/contract && ctest -L mdsplus --output-on-failure
"

- name: Note
if: always()
run: echo "MDSplus leg is informational (issue #12/#18). Red-by-design until the characterization baseline is triaged; promotion to a blocking gate is Task 3."
48 changes: 48 additions & 0 deletions docker/mdsplus/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# MDSplus characterization environment (issue #13, tracer bullet for #12).
#
# aarch64 (native on Apple Silicon) image that bakes a DD-4.1.1 MDSplus model
# tree and builds the contract suite with AL_BACKEND_MDSPLUS=ON. See
# docker/mdsplus/README.md for the characterization-discovered deviation from
# the issue's ubuntu:22.04 starting point (no arm64 MDSplus .debs exist for
# Ubuntu 22; Ubuntu 24 is the first LTS with an arm64 repo) and for the run
# recipe.
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

# --- MDSplus apt repo -------------------------------------------------------
# The repo key is an ASCII-armored PGP block; apt's signed-by wants a binary
# keyring, hence the dearmor step. Plain http (not https): www.mdsplus.org's
# TLS chain is missing the InCommon intermediate cert, so a fresh container's
# default trust store cannot verify it (characterization-discovered; a
# browser/host with a cached intermediate does not hit this). The repo
# content is still authenticated via the dearmored signed-by key below.
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates gnupg wget \
&& mkdir -p /etc/apt/keyrings \
&& wget -qO- http://www.mdsplus.org/dist/mdsplus.gpg.key \
| gpg --dearmor -o /etc/apt/keyrings/mdsplus.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/mdsplus.gpg] http://www.mdsplus.org/dist/Ubuntu24/repo MDSplus stable" \
> /etc/apt/sources.list.d/mdsplus.list \
&& apt-get update

# --- Toolchain + MDSplus + DD-bake dependencies -----------------------------
# mdsplus-devel: mdslib.h + link libs (common/cmake/FindMDSplus.cmake).
# mdsplus-java: jTraverser.jar (models/mdsplus/CMakeLists.txt CompileTree) +
# pulls a java-runtime.
# python3/venv: ALBuildDataDictionary.cmake's saxonche venv + pip-installs
# imas_data_dictionary==4.1.1 from PyPI (no DD git clone needed).
RUN apt-get install -y --no-install-recommends \
mdsplus mdsplus-devel mdsplus-java \
build-essential cmake git ninja-build pkg-config \
libboost-filesystem-dev libhdf5-dev \
python3 python3-venv python3-pip python3-dev \
openjdk-21-jdk-headless \
&& rm -rf /var/lib/apt/lists/*

ENV MDSPLUS_DIR=/usr/local/mdsplus
ENV PATH="${MDSPLUS_DIR}/bin:${PATH}"
ENV LD_LIBRARY_PATH="${MDSPLUS_DIR}/lib:${LD_LIBRARY_PATH}"
ENV CLASSPATH="${MDSPLUS_DIR}/java/classes/jTraverser.jar"

WORKDIR /workspace
59 changes: 59 additions & 0 deletions docker/mdsplus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# MDSplus characterization environment (issue #13)

aarch64 image standing up a real, runnable MDSplus for the contract suite's
MDSplus tier (`tests/contract/test_mdsplus.cpp`, `TRACEABILITY.md` Part 4).

## Deviation from the issue's starting point

Issue #12/#13 specified `FROM ubuntu:22.04`. Empirically, MDSplus does not
publish arm64 `.deb` packages for Ubuntu 22 (or 20): its
`https://www.mdsplus.org/dist/Ubuntu22/repo/dists/MDSplus/stable/binary-arm64/Packages`
is a 0-byte file. Ubuntu 24.04 is the first LTS with a populated arm64
repo, so this image uses `ubuntu:24.04` instead — still native aarch64 (no
emulation), which is what the issue's Apple-Silicon developer story (#12
user story 8) actually needs. The issue's stated amd64-emulation fallback is
unnecessary as a result.

## Build

```sh
docker build --platform linux/arm64 -f docker/mdsplus/Dockerfile \
-t imas-core-mdsplus:dev docker/mdsplus
```

## Bake the DD-4.1.1 model tree and build the contract suite

Run from the repository root, mounting it into the container:

```sh
docker run --rm --platform linux/arm64 \
-v "$PWD":/workspace -w /workspace \
imas-core-mdsplus:dev bash -c '
cmake -B build-mdsplus -G Ninja \
-DAL_BACKEND_MDSPLUS=ON \
-DAL_BUILD_MDSPLUS_MODELS=ON \
-DAL_BUILD_TESTS=ON \
-DDD_VERSION=4.1.1 \
-DCMAKE_BUILD_TYPE=Release &&
cmake --build build-mdsplus -j$(nproc)
'
```

`-DDD_VERSION=4.1.1` with the default `AL_DOWNLOAD_DEPENDENCIES=ON` makes
`common/cmake/ALBuildDataDictionary.cmake` `pip install
imas_data_dictionary==4.1.1` from PyPI — no data-dictionary git clone needed.
The model tree (`ids_model.tree`/`.datafile`/`.characteristics`) lands under
`build-mdsplus/models/mdsplus/`.

## Run the MDSplus tests

```sh
docker run --rm --platform linux/arm64 \
-v "$PWD":/workspace -w /workspace \
-e MDSPLUS_MODELS_PATH=/workspace/build-mdsplus/models/mdsplus \
imas-core-mdsplus:dev bash -c \
'cd build-mdsplus/tests/contract && ctest -L mdsplus --output-on-failure'
```

Omitting `MDSPLUS_MODELS_PATH` makes the same tests report `Skipped` rather
than failing (`GTEST_SKIP()`, TEST_STRATEGY.md D4).
62 changes: 61 additions & 1 deletion tests/contract/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,41 @@ add_executable(contract_tests
test_plugins.cpp
test_structured_data.cpp
test_pulse_lifecycle.cpp
test_mdsplus.cpp
test_mdsplus_real_paths.cpp
test_mdsplus_version_drift.cpp
test_mdsplus_unique_surface.cpp
)

target_link_libraries(contract_tests PRIVATE al GTest::gtest GTest::gtest_main)
add_dependencies(contract_tests al)
target_compile_features(contract_tests PRIVATE cxx_std_17)

########################################################################################################################
# MDSplus build gate (issue #13 / TEST_STRATEGY.md D4 "compile-guarded tier").
#
# test_mdsplus.cpp wraps its whole body in #ifdef AL_CONTRACT_HAVE_MDSPLUS, so
# it compiles out to nothing when the backend is off — a developer without
# MDSplus installed never pays for it, and CI legs that build with
# AL_BACKEND_MDSPLUS=OFF are unaffected. When the backend is on, the file's
# lone test still runtime-skips (GTEST_SKIP()) if MDSPLUS_MODELS_PATH is
# unset, so "on but unconfigured" never fails either.
if(AL_BACKEND_MDSPLUS)
target_compile_definitions(contract_tests PRIVATE AL_CONTRACT_HAVE_MDSPLUS)

# test_mdsplus_version_drift.cpp (issue #16) forces the version-mismatch
# case by writing directly into the pulse's VERSION:BACK_MAJOR tree node
# through the raw MDSplus C++ API (<mdsobjects.h>) -- something reachable
# only outside the public C ABI. `al`'s own src/mdsplus/CMakeLists.txt
# already links MDSplus, but PRIVATEly, so it doesn't propagate here;
# find_package(MDSplus) is repeated (idempotent) to wire the same
# include dir / libs into contract_tests, test-only and build-gated behind
# the same AL_BACKEND_MDSPLUS option as the backend itself.
find_package(MDSplus REQUIRED)
target_include_directories(contract_tests PRIVATE ${MDSPLUS_INCLUDES})
target_link_libraries(contract_tests PRIVATE ${MDSPLUS_LIBRARIES})
endif()

########################################################################################################################
# Test plugin fixture (issue #7, TEST_STRATEGY.md step 5).
#
Expand Down Expand Up @@ -157,13 +186,44 @@ endif()
# TEST_FILTER uses gtest's one-"-"-then-colon-separated negative syntax
# ("*:-a:b:c", NOT "*:-a:-b:-c") so the two passes stay disjoint — otherwise the
# second pass re-registers the unit cases and CTest sees duplicates.
# MdsplusTracerBullet* is carved out of the integration pass into its own
# third pass (issue #13: new CTest label "mdsplus") so `ctest -L mdsplus`
# selects it without also pulling in every other on-disk backend case.
# Issue #14 joins MDSplus onto four more fixtures (DataEntryModes,
# CapabilityMatrix, AosMatrix, Occurrences) as a "Mdsplus"-named parameter/
# case; "*/Mdsplus" and "Occurrences.Mdsplus*" carve those into the same
# mdsplus pass so `ctest -L mdsplus` covers the whole parity set, not just
# the tracer bullet. Issue #15 adds the real-DD-path datatype x rank breadth
# matrix (test_mdsplus_real_paths.cpp); "Mdsplus/MdsplusRealPathMatrix.*"
# carves its instances into the same mdsplus pass. Issue #16 adds the
# version-drift check (test_mdsplus_version_drift.cpp); "MdsplusVersionDrift*"
# carves its two cases into the same mdsplus pass. Issue #17 adds the
# MDSplus-unique surface (test_mdsplus_unique_surface.cpp):
# "MdsplusStructAwareSliceInterpolation*", "MdsplusTimerangeResampling*",
# "MdsplusSegmentBackedWrites*", "MdsplusSliceWriteKnownDefects*",
# "MdsplusSliceWriteKnownDefectsDeath*", and "MdsplusTimebaseCache*" carve its
# cases into the same mdsplus pass.
#
# Empirically discovered running this against a real build (issue #13,
# CMake 3.28 + Ninja): gtest_discover_tests' PROPERTIES forwarding does NOT
# actually preserve a multi-element LABELS list through its escaped-";"
# encoding -- it collapses to just the first token ("contract"), silently
# dropping "unit"/"integration" as bogus trailing property names with no
# value. That pre-existing defect (present in the two passes below
# unmodified by this issue) means `ctest -L unit`/`-L integration` currently
# select nothing; it is out of this issue's scope to fix. The new mdsplus
# pass sidesteps it by using a single-element LABELS list, which has no
# separator to lose.
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22")
gtest_discover_tests(contract_tests
TEST_FILTER "Introspection.*:*Memory*:KnownDefects*:Plugin*:ReadbackPlugins.*:UriOwnership.*"
PROPERTIES LABELS "contract\;unit")
gtest_discover_tests(contract_tests
TEST_FILTER "*:-Introspection.*:*Memory*:KnownDefects*:Plugin*:ReadbackPlugins.*:UriOwnership.*"
TEST_FILTER "*:-Introspection.*:*Memory*:KnownDefects*:Plugin*:ReadbackPlugins.*:UriOwnership.*:MdsplusTracerBullet*:*/Mdsplus:Occurrences.Mdsplus*:Mdsplus/MdsplusRealPathMatrix.*:MdsplusVersionDrift*:MdsplusStructAwareSliceInterpolation*:MdsplusTimerangeResampling*:MdsplusSegmentBackedWrites*:MdsplusSliceWriteKnownDefects*:MdsplusSliceWriteKnownDefectsDeath*:MdsplusTimebaseCache*"
PROPERTIES LABELS "contract\;integration")
gtest_discover_tests(contract_tests
TEST_FILTER "MdsplusTracerBullet*:*/Mdsplus:Occurrences.Mdsplus*:Mdsplus/MdsplusRealPathMatrix.*:MdsplusVersionDrift*:MdsplusStructAwareSliceInterpolation*:MdsplusTimerangeResampling*:MdsplusSegmentBackedWrites*:MdsplusSliceWriteKnownDefects*:MdsplusSliceWriteKnownDefectsDeath*:MdsplusTimebaseCache*"
PROPERTIES LABELS "mdsplus")
else()
gtest_discover_tests(contract_tests
PROPERTIES LABELS "contract")
Expand Down
Loading
Loading