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
69 changes: 69 additions & 0 deletions .github/actions/mtest/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: mtest
description: >
The mtest engine the conformance suite reports to: the release the workflow pins (MTEST_VERSION), an
xlings package (xim:mtest) that the index verifies, else the release bundle checked against MTEST_SHA256;
MTEST names its launcher. A job that has no xlings yet gets the pinned one first (XLINGS_VERSION).
runs:
using: composite
steps:
- name: xlings, where the job has none
if: runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
command -v xlings > /dev/null 2>&1 && exit 0
curl -fsSL --retry 3 --retry-all-errors --retry-delay 2 \
https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \
| bash -s "$XLINGS_VERSION"
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
- name: xlings, where the job has none (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
if (Get-Command xlings -ErrorAction SilentlyContinue) { exit 0 }
irm https://d2learn.org/xlings-install.ps1.txt | iex
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# xlings by the configured mirror (GLOBAL), then by CN (GitCode first), each with a refreshed index
# (a cached index can predate the release, and xlings keeps it when an anonymous refresh fails); else
# the release bundle itself, checked against MTEST_SHA256: the web address, then the API's, which
# answered 200 while the web one answered 504 (GitHub's 2026-11-09 incident).
launcher=""
for mirror in GLOBAL CN; do
xlings config --mirror "$mirror" > /dev/null 2>&1 || true
xlings update > /dev/null 2>&1 || true
if xlings install "mtest@$MTEST_VERSION" -y -g; then launcher=$(command -v mtest); break; fi
done
# The package's own launcher, not the xvm shim: on Windows the shim runs the package's alias through
# cmd, which reads `&` in an argument (`--tag "on:win32-x64&release"`) as a second command.
exe=''; [ "$RUNNER_OS" != Windows ] || exe='.exe'
own="${XLINGS_HOME:-$HOME/.xlings}/data/xpkgs/xim-x-mtest/$MTEST_VERSION/bin/mtest$exe"
if [ -n "$launcher" ] && [ -f "$own" ]; then launcher="$own"; fi
xlings config --mirror GLOBAL > /dev/null 2>&1 || true
if [ -z "$launcher" ]; then
echo "::warning::xlings could not install mtest $MTEST_VERSION; taking the release itself"
archive="mtest-$MTEST_VERSION-openkal-bundle-all.tar.gz"
temp="$RUNNER_TEMP"; python=python3
if [ "$RUNNER_OS" = Windows ]; then temp=$(cygpath -u "$RUNNER_TEMP"); python=python; fi
if ! curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -o "$temp/$archive" \
"https://github.com/mcpp-community/mtest/releases/download/v$MTEST_VERSION/$archive"; then
id=$(curl -fsSL --retry 3 --retry-all-errors -H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/mcpp-community/mtest/releases/tags/v$MTEST_VERSION" \
| "$python" -c 'import json,sys; print(next(str(a["id"]) for a in json.load(sys.stdin)["assets"] if a["name"] == sys.argv[1]))' "$archive")
curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/octet-stream" -o "$temp/$archive" \
"https://api.github.com/repos/mcpp-community/mtest/releases/assets/$id"
fi
actual=$("$python" -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$temp/$archive")
[ "$actual" = "$MTEST_SHA256" ] || { echo "::error::$archive: sha256 $actual, the workflow pins $MTEST_SHA256"; exit 1; }
mkdir -p "$temp/mtest" && tar xzf "$temp/$archive" -C "$temp/mtest"
launcher="$temp/mtest/mtest-$MTEST_VERSION-openkal-bundle-all/bin/mtest"
[ "$RUNNER_OS" = Windows ] && launcher="$launcher.exe"
fi
"$launcher" --version
"$launcher" --version | grep -qF "$MTEST_VERSION" || { echo "::error::the mtest at $launcher is not $MTEST_VERSION"; exit 1; }
echo "MTEST=$launcher" >> "$GITHUB_ENV"
61 changes: 59 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ env:
# fail.
MCPP_VERSION: 2026.11.5.1
XLINGS_VERSION: v2026.10.26.1
# The mtest engine the conformance suite reports to (tools/run-conformance.sh
# with MTEST set): an xlings package, else the release bundle with this hash.
MTEST_VERSION: 0.5.2
MTEST_SHA256: be443f8c546b3f4628dcd3f1eb6f45b4d698a7f805232f966349faf8250863c6
XLINGS_NON_INTERACTIVE: '1'

jobs:
Expand Down Expand Up @@ -865,9 +869,31 @@ jobs:
# verdict: 0 when every observation held, 1 when one did not, and 2 when
# nothing was observed --- the last being the outcome a run that selected
# no interface would otherwise pass silently.
- uses: ./.github/actions/mtest

# THE SUITE REPORTS BY THE TEST PROTOCOL HERE (MTEST is set): each
# interface a case of kind `cts', each observation a case within it, kept
# in the engine's store under conformance/.mtest. A failure is explained
# in the log, since the engine prints a summary and not the suite's text.
- name: Every interface, every kind of examination
run: |
bash tools/run-conformance.sh '${{ matrix.implementation }}' .impl '${{ matrix.features }}'
bash tools/run-conformance.sh '${{ matrix.implementation }}' .impl '${{ matrix.features }}' \
|| { ( cd conformance && "$MTEST" explain ) || true; exit 1; }

- name: The run, reported
if: always()
run: |
[ -f conformance/.mtest/index.ndjson ] || { echo "the suite kept no run"; exit 0; }
( cd conformance && "$MTEST" report --format md --group "$GITHUB_RUN_ID" ) >> "$GITHUB_STEP_SUMMARY"

- uses: actions/upload-artifact@v4
if: always()
with:
name: mtest-cts-${{ matrix.implementation }}-${{ strategy.job-index }}
path: conformance/.mtest
include-hidden-files: true
if-no-files-found: ignore
retention-days: 7

# The suite is composable because openkal is: an implementation provides
# an interface in whole or not at all, and a suite that examined all eight
Expand Down Expand Up @@ -992,7 +1018,8 @@ jobs:
- name: A selection of three interfaces is examined, not refused
run: |
rm -rf conformance/target
bash tools/run-conformance.sh '${{ matrix.implementation }}' .impl core,fs,task \
# The suite's own text is what this step reads, so no runner is named.
MTEST= bash tools/run-conformance.sh '${{ matrix.implementation }}' .impl core,fs,task \
| tee selected.log

# A report was produced, and nothing in it failed.
Expand All @@ -1006,3 +1033,33 @@ jobs:

# And an interface that was selected was examined.
grep -qE 'held +\[behaviour\].*' selected.log

# ---------------------------------------------------------------------------
# EVERY IMPLEMENTATION'S RUN AS ONE REPORT: each conformance job's store,
# taken into one, and reported together as the workflow run's group
# (`mtest ci report`): the cases of each interface on each implementation,
# the failures, and the measurements of cost.
conformance-report:
name: conformance (report)
needs: conformance
if: always()
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/mtest
- uses: actions/download-artifact@v4
with:
pattern: mtest-cts-*
path: ${{ runner.temp }}/stores
- name: Every implementation's run, reported together
run: |
set -euo pipefail
found=0
for store in "$RUNNER_TEMP"/stores/*/; do
[ -f "$store/index.ndjson" ] || continue
"$MTEST" store import --from "$store"
found=1
done
if [ "$found" = 0 ]; then echo "no conformance job kept a run" >> "$GITHUB_STEP_SUMMARY"; exit 0; fi
"$MTEST" ci report --out "$RUNNER_TEMP/conformance.md"
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ admission and permits a helper to finish its bookkeeping after the unit's
external members have ended. The caller requirements are stated in
[SPEC clause 7.18](SPEC.md#718-observable-process-units) and `openkal/process.h`.

## Version 0.25

A file may be given a second name with `kal_fs_hard_link`, answered per volume by
`KAL_FS_PROP_HARD_LINKS` (clause 11 entry 59). The word `kal_process_stop_requested`
returns counts the requests to end that reach the program, so that each one is
observed (entry 60). The conformance suite reports to a test runner by the mtest
protocol, and checks that an implementation's manifest, `kal_interfaces()` and its
exported names state the same interfaces.

## How a program uses openkal

A program declares two dependencies. The first fixes the version of the contract
Expand All @@ -87,16 +96,16 @@ conditional on the target.

```toml
[dependencies]
openkal = "0.24.0"
openkal = "0.25.0"

[target.'cfg(os = "linux")'.dependencies]
openkal-linux = "0.25.0"
openkal-linux = "0.26.0"

[target.'cfg(os = "macos")'.dependencies]
openkal-macos = "0.22.0"
openkal-macos = "0.23.0"

[target.'cfg(windows)'.dependencies]
openkal-windows = "0.20.0"
openkal-windows = "0.21.0"
```

The program imports the interface and names no implementation.
Expand Down
26 changes: 25 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# openkal Specification, version 0.24
# openkal Specification, version 0.25

## 1. Scope

Expand Down Expand Up @@ -2110,6 +2110,30 @@ The following are recorded so that they are not mistaken for oversights.
continuation names the surviving program, not a remembered numeric group.
The existing spawn form and immutable structure layouts remain unchanged.

59. **A second name for a file.** **Specified in 0.25**, as `kal_fs_hard_link`
and `KAL_FS_PROP_HARD_LINKS` in `openkal.fs`. A C library's `link` and
`linkat`, and a C++ library's `std::filesystem::create_hard_link`, could not
be written above this interface: no operation gave an existing file a
second name, and copying the file is a different file with a different
identity. Every environment this specification targets has the operation
natively where the volume's format records several names for one node
(`linkat`, `CreateHardLinkW`), so it is answered per volume by
`kal_fs_props`, as the operations upon links are (entry 7), and a volume
that does not claim it refuses the request rather than copying. The name
being given another is not resolved, for the reason `kal_fs_remove` and
`kal_fs_rename` act upon a name: a program that names a link means the
link.

60. **The requests to end, counted.** **Specified in 0.25**, in
`kal_process_stop_requested`. The word was set once and never cleared, so a
program that took the requests over and went on after the first could not
learn of a second: an interactive program to which each interrupt keystroke
is a request to abandon the current command observed the first keystroke
and none after it. The word now counts the requests, skipping zero when the
count wraps, so the earlier statement --- zero until a request, non-zero
afterwards, never cleared --- remains true of every implementation of 0.25,
and a program written against 0.24 reads it as before.

## 12. Images

A program written against this specification is built for an environment: its
Expand Down
3 changes: 2 additions & 1 deletion SURFACE.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# The C surface of openkal 0.24.0, one name per line.
# The C surface of openkal 0.25.0, one name per line.
#
# This file is normative and is the single source consulted by clause 9. A
# conforming implementation exports the names of the interfaces it provides
Expand Down Expand Up @@ -60,6 +60,7 @@ kal_fs_close_dir
kal_fs_close_file
kal_fs_dir_attenuate
kal_fs_file_info
kal_fs_hard_link
kal_fs_info
kal_fs_link_create
kal_fs_link_read
Expand Down
35 changes: 35 additions & 0 deletions conformance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,41 @@ would be a surprising thing for `[dependencies]` to name — and it is reached b
checking out the specification, which is the package it is the conformance
procedure for.

## A runner's view: the mtest test protocol

The suite speaks the [mtest test protocol](https://github.com/mcpp-community/mtest/blob/main/spec/protocol.md),
version 1, at level 2, written without a library as the rest of the suite is. Each
interface is a case of kind `cts` that names the interface as the clause it covers;
each observation is a case within it, reported as it runs (protocol P3.4): `pass`,
`fail`, or `skip` with the reason an observation was not made; each measurement of
cost is a metric in nanoseconds. A selection may name an interface or an
observation within one; the second runs its interface. Every case holds the
resource `serial:openkal-conformance`, because the sections create fixed names in
the working directory.

The protocol needs `openkal.env` and `openkal.fs`; without them, or without a
runner, the suite is judged by its exit status alone, as before. With an engine
named in `MTEST`, `tools/run-conformance.sh` runs the suite through it in one job
and the run is kept in `.mtest`:

```bash
MTEST=mtest bash .spec/tools/run-conformance.sh openkal-linux . full,optional
(cd .spec/conformance && mtest report --format md)
```

The protocol's variables are not passed to the copies the suite starts: a copy is
started with the environment the suite gives it, which is empty (P2.2).

## The three statements of an interface set

An implementation states the interfaces it provides three times: its manifest's
`provides-interfaces` (what a build system checks a program's requirements
against), `kal_interfaces()` (what a loader checks an image's against), and the
names it exports. `tools/run-conformance.sh` reads the first and names it to the
suite as `OPENKAL_CONFORMANCE_PROVIDES`, which compares the second with it; the
script compares the third. A manifest that states no list is reported and not
compared.

## It is composable, because openkal is

An interface is the unit of provision. An implementation provides one in whole
Expand Down
4 changes: 2 additions & 2 deletions conformance/mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-conformance"
version = "0.8.0"
version = "0.9.0"
description = "The behavioural half of clause 9: a suite an implementation of openkal runs against itself, selectable to the interfaces it provides."
license = "Apache-2.0"
authors = ["mcpplibs"]
Expand All @@ -18,7 +18,7 @@ repo = "https://github.com/mcpplibs/openkal"
# What it depends upon is therefore openkal and the language. The formatting in
# okc.report is the price of that, and it is sixty lines.
[dependencies]
openkal = "0.24.0"
openkal = "0.25.0"

# The implementation under examination is not named here.
#
Expand Down
1 change: 1 addition & 0 deletions conformance/src/declarations.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ void okc_declarations_c(void)
(void)sizeof(&kal_fs_close_file);
(void)sizeof(&kal_fs_dir_attenuate);
(void)sizeof(&kal_fs_file_info);
(void)sizeof(&kal_fs_hard_link);
(void)sizeof(&kal_fs_info);
(void)sizeof(&kal_fs_link_create);
(void)sizeof(&kal_fs_link_read);
Expand Down
Loading
Loading