Skip to content

Fix first-run onboarding: README/wiki quickstart + StatusWidget MonitorTag crash #672

Fix first-run onboarding: README/wiki quickstart + StatusWidget MonitorTag crash

Fix first-run onboarding: README/wiki quickstart + StatusWidget MonitorTag crash #672

Workflow file for this run

name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Nightly full-suite run. PRs use path-filtered subsets (see `changes` job);
# nightly runs everything so cross-library regressions surface within 24h.
- cron: '0 6 * * *'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Two-tier CI: PRs run only the jobs whose inputs changed, push-to-main and
# the nightly schedule run the full suite. Within MATLAB/Octave jobs the
# `test_pattern` output narrows the test set further when only downstream
# libs (Dashboard, EventDetection, WebBridge) changed. Foundational libs
# (FastSense, SensorThreshold), test infrastructure, and `core` paths
# (install.m, MEX C sources, workflows themselves, lint config, the test
# runner, the coverage script) all force a full run on PRs as a safety net.
changes:
name: Detect changed paths
runs-on: ubuntu-latest
outputs:
core: ${{ steps.filter.outputs.core }}
matlab: ${{ steps.filter.outputs.matlab }}
mex: ${{ steps.filter.outputs.mex }}
concurrency: ${{ steps.filter.outputs.concurrency }}
test_pattern: ${{ steps.test_pattern.outputs.pattern }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
core:
- 'install.m'
- 'libs/FastSense/private/mex_src/**'
- 'libs/FastSense/private/.mex-version'
- '.github/workflows/**'
- 'miss_hit.cfg'
- 'tests/run_all_tests.m'
- 'scripts/run_tests_with_coverage.m'
matlab:
- 'libs/**'
- 'tests/**'
- 'examples/**'
- 'scripts/**.m'
- 'install.m'
- 'run_profile.m'
mex:
- 'libs/**/mex_src/**'
- 'libs/**/build_mex.m'
- 'libs/FastSense/mksqlite.c'
- 'libs/FastSense/private/.mex-version'
fastsense:
- 'libs/FastSense/**'
sensorthreshold:
- 'libs/SensorThreshold/**'
eventdetection:
- 'libs/EventDetection/**'
dashboard:
- 'libs/Dashboard/**'
webbridge:
- 'libs/WebBridge/**'
tests:
- 'tests/**'
examples:
- 'examples/**'
# v4.0 Multi-User LAN Concurrency — files whose behaviour is
# platform-divergent at the kernel / filesystem level. Triggers
# the cross-OS smoke job (matlab-concurrency-smoke) which runs
# only on this subset to catch regressions on Linux (OFD locks),
# macOS (F_SETLK fallback), and Windows (LockFileEx). The full
# MATLAB test suite still runs Linux-only via the `matlab:` job.
concurrency:
- 'libs/Concurrency/**'
- 'libs/EventDetection/**'
- 'libs/SensorThreshold/MonitorTag.m'
- 'libs/SensorThreshold/LiveTagPipeline.m'
- 'libs/FastSenseCompanion/FastSenseCompanion.m'
- 'libs/FastSenseCompanion/private/companionDiscoverEventStore.m'
- 'tests/suite/TestFileLock*.m'
- 'tests/suite/TestLockfileMex.m'
- 'tests/suite/TestAtomicWriter.m'
- 'tests/suite/TestCluster*.m'
- 'tests/suite/TestTagWriteCoordinator.m'
- 'tests/suite/TestEventLog*.m'
- 'tests/suite/TestConcurrencyIntegration.m'
- 'tests/suite/TestListenerCannotAcquireLock.m'
- 'tests/suite/TestMonitorTagSingleSource.m'
- 'tests/suite/TestLiveTagPipelineCluster.m'
- 'tests/suite/TestShareLossRecovery.m'
- 'tests/suite/TestEventAcknowledgement.m'
- 'tests/test_ndjson_decode.m'
- 'tests/test_event_log_concurrent.m'
- 'tests/test_user_identity.m'
- 'tests/test_no_raw_save_to_shared.m'
- 'tests/test_mksqlite_extended_codes_probe.m'
- name: Compute test pattern
id: test_pattern
env:
EVENT_NAME: ${{ github.event_name }}
F_CORE: ${{ steps.filter.outputs.core }}
F_MEX: ${{ steps.filter.outputs.mex }}
F_FASTSENSE: ${{ steps.filter.outputs.fastsense }}
F_SENSORTHRESHOLD: ${{ steps.filter.outputs.sensorthreshold }}
F_EVENTDETECTION: ${{ steps.filter.outputs.eventdetection }}
F_DASHBOARD: ${{ steps.filter.outputs.dashboard }}
F_WEBBRIDGE: ${{ steps.filter.outputs.webbridge }}
F_TESTS: ${{ steps.filter.outputs.tests }}
F_EXAMPLES: ${{ steps.filter.outputs.examples }}
run: |
# Non-PR events (push, schedule, manual) always run the full suite.
# On PRs, foundational lib changes or any safety-net trigger also
# force the full suite. Only when the change is confined to the
# downstream libs (Dashboard/EventDetection/WebBridge) do we narrow.
if [ "$EVENT_NAME" != "pull_request" ] \
|| [ "$F_CORE" = "true" ] || [ "$F_MEX" = "true" ] \
|| [ "$F_FASTSENSE" = "true" ] || [ "$F_SENSORTHRESHOLD" = "true" ] \
|| [ "$F_TESTS" = "true" ] || [ "$F_EXAMPLES" = "true" ]; then
echo "pattern=" >> "$GITHUB_OUTPUT"
echo "Full suite (no narrowing)."
exit 0
fi
# Conservative downstream bundles per the dependency graph:
# WebBridge ← only WebBridge tests
# Dashboard ← Dashboard + WebBridge tests
# EventDetection ← EventDetection + Dashboard + WebBridge tests
patterns=()
if [ "$F_EVENTDETECTION" = "true" ]; then
patterns+=("Event" "Notification" "DataSource" "MatFile" "Mock" "Live.*Pipeline" "Incremental")
fi
if [ "$F_EVENTDETECTION" = "true" ] || [ "$F_DASHBOARD" = "true" ]; then
patterns+=("Dashboard" "Widget" "Toolbar" "InfoTooltip" "MarkdownRenderer")
fi
if [ "$F_EVENTDETECTION" = "true" ] || [ "$F_DASHBOARD" = "true" ] || [ "$F_WEBBRIDGE" = "true" ]; then
patterns+=("WebBridge")
fi
if [ "${#patterns[@]}" -eq 0 ]; then
echo "pattern=" >> "$GITHUB_OUTPUT"
echo "No matlab paths matched; full suite (jobs may still skip via job-level if:)."
exit 0
fi
IFS='|'
pattern="${patterns[*]}"
echo "pattern=${pattern}" >> "$GITHUB_OUTPUT"
echo "Narrowed test pattern: ${pattern}"
lint:
name: MATLAB Lint
needs: changes
if: github.event_name != 'pull_request' || needs.changes.outputs.matlab == 'true' || needs.changes.outputs.core == 'true'
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install MISS_HIT
run: pip install miss_hit
- name: Run style checker
run: mh_style libs/ tests/ examples/
- name: Run linter
run: mh_lint libs/ tests/ examples/
- name: Run complexity metrics
run: mh_metric --ci libs/ tests/ examples/
build-mex:
name: Build MEX (Linux)
needs: changes
if: github.event_name != 'pull_request' || needs.changes.outputs.matlab == 'true' || needs.changes.outputs.core == 'true' || needs.changes.outputs.mex == 'true'
uses: ./.github/workflows/_build-mex-octave.yml
with:
artifact-name: mex-linux
build-mex-matlab:
name: Build MEX (MATLAB Linux)
needs: changes
timeout-minutes: 30
if: github.event_name != 'pull_request' || needs.changes.outputs.matlab == 'true' || needs.changes.outputs.core == 'true' || needs.changes.outputs.mex == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup MATLAB
uses: matlab-actions/setup-matlab@v3
with:
# Bumped from R2020b in PR #109 — R2020b's libmex.so chronically
# segfaults on the GitHub xvfb runner during widget-heavy tests
# (TestDashboardInfo web(), TestDemoIndustrialPlant*,
# TestFastSenseWidgetUpdate). Codebase already targets R2021a+
# (uifigure features, BorderColor, Placeholder, WordWrap).
release: R2021b
cache: true
- name: Cache MATLAB MEX binaries
id: cache-mex-matlab
uses: actions/cache@v5
with:
path: |
libs/FastSense/private/*.mexa64
libs/SensorThreshold/private/*.mexa64
libs/FastSense/mksqlite.mexa64
# v4.0 Phase 1029: lockfile_mex (MEX) lives in libs/Concurrency/ root
# (NOT private/ — MATLAB classdef files can't access sibling private/).
libs/Concurrency/*.mexa64
# Cache key includes Concurrency MEX sources + build helper so cache
# invalidates when those change, not just FastSense MEX inputs.
key: mex-matlab-linux-r2021b-${{ hashFiles('libs/FastSense/private/mex_src/**', 'libs/FastSense/build_mex.m', 'libs/FastSense/private/.mex-version', 'libs/Concurrency/private/mex_src/**', 'libs/Concurrency/build_concurrency_mex.m') }}
- name: Compile MEX files (MATLAB)
if: steps.cache-mex-matlab.outputs.cache-hit != 'true'
uses: matlab-actions/run-command@v3
with:
command: "install();"
- name: Diagnose mksqlite build output
if: always()
continue-on-error: true
run: |
echo "=== build-mex-matlab: post-compile mksqlite diagnostic ==="
echo "--- libs/FastSense/mksqlite.* ---"
ls -la libs/FastSense/mksqlite.* 2>&1 || echo "(no mksqlite files in libs/FastSense/)"
echo "--- libs/FastSense/private/*.mexa64 ---"
ls -la libs/FastSense/private/*.mexa64 2>&1 || echo "(no mexa64 in private)"
echo "--- mksqlite source ---"
ls -la libs/FastSense/mksqlite.c 2>&1 || echo "(no mksqlite.c)"
- name: Upload MATLAB MEX artifacts
uses: actions/upload-artifact@v7
with:
name: mex-matlab-linux
path: |
libs/FastSense/private/*.mexa64
libs/SensorThreshold/private/*.mexa64
libs/FastSense/mksqlite.mexa64
# v4.0 Phase 1029: lockfile_mex.mexa64 lives in libs/Concurrency/ root
# (the mksqlite pattern: external callers can't reach private/).
# Without this, the matlab: batches fail with "lockfile_mex not on
# path after install()" -> cascading test failures in
# TestConcurrencyIntegration, TestFileLock, TestLockfileMex, etc.
libs/Concurrency/*.mexa64
retention-days: 1
octave:
name: Octave Tests
timeout-minutes: 45
needs: [changes, build-mex]
if: github.event_name != 'pull_request' || needs.changes.outputs.matlab == 'true' || needs.changes.outputs.core == 'true' || needs.changes.outputs.mex == 'true'
runs-on: ubuntu-latest
container: gnuoctave/octave:11.1.0
env:
FASTSENSE_SKIP_BUILD: "1"
FASTSENSE_RESULTS_FILE: /tmp/test-results.txt
steps:
- uses: actions/checkout@v6
- name: Download MEX binaries
uses: actions/download-artifact@v8
with:
name: mex-linux
- name: Run tests
env:
TEST_PATTERN: ${{ needs.changes.outputs.test_pattern }}
run: |
# Writes /tmp/test-results.txt for the downstream "Write test summary" step.
# Octave 11.1.0 fixed the break_closure_cycles GC crash (upstream bug #67749)
# that plagued 8.x-10.x, so the old `|| true` workaround dance is gone.
# TEST_PATTERN is empty for full runs (push/schedule/foundational PR
# changes) and a regex for narrowed downstream-only PR changes.
if [ -n "$TEST_PATTERN" ]; then
echo "Running narrowed Octave suite: $TEST_PATTERN"
fi
xvfb-run octave --eval "
cd('tests');
r = run_all_tests('${TEST_PATTERN}');
fid = fopen('/tmp/test-results.txt', 'w');
fprintf(fid, '%d %d\n', r.passed, r.failed);
fclose(fid);
exit(double(r.failed > 0));
"
- name: Write test summary
if: always()
shell: bash
run: |
if [ -f /tmp/test-results.txt ]; then
read PASSED FAILED < /tmp/test-results.txt
{
echo "### Octave Tests"
echo ""
echo "- Passed: ${PASSED:-0}"
echo "- Failed: ${FAILED:-0}"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "### Octave Tests" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "_Results file not produced (test job likely crashed before completion)._" >> "$GITHUB_STEP_SUMMARY"
fi
# Phase 1028 D-06: smoke-run the 1000-tag harness on every push so
# syntax/regressions in the harness itself surface within the test job
# (the gated full bench runs separately in benchmark.yml).
- name: Phase 1028 harness smoke
run: |
xvfb-run octave --eval "addpath(pwd); install(); bench_tag_pipeline_1k('--smoke');"
# Smoke test: ensure Octave MEX sources still compile on macOS.
# Authoritative prebuilt binaries for releases come from refresh-mex-binaries.yml,
# which commits platform-specific binaries into libs/.../octave-<platform>/.
# This job catches PRs that break the compile path without shipping artifacts.
mex-build-macos:
name: MEX Build (macOS ${{ matrix.os == 'macos-latest' && 'ARM64' || 'x64' }})
needs: changes
timeout-minutes: 20
if: github.event_name != 'pull_request' || needs.changes.outputs.mex == 'true' || needs.changes.outputs.core == 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest]
steps:
- uses: actions/checkout@v6
- name: Install Octave
run: brew install octave
- name: Compile MEX files
run: octave --eval "install(); fprintf('All MEX files compiled successfully on %s (%s)\n', computer(), getenv('RUNNER_OS'));"
- name: List compiled MEX binaries
run: find libs -name '*.mex*' -type f | sort
# Smoke test: ensure Octave MEX sources still compile on Windows.
# Authoritative prebuilt binaries for releases come from refresh-mex-binaries.yml,
# which commits platform-specific binaries into libs/.../octave-<platform>/.
# This job catches PRs that break the compile path without shipping artifacts.
mex-build-windows:
name: MEX Build (Windows ${{ matrix.os == 'windows-latest' && 'latest' || '2022' }})
needs: changes
timeout-minutes: 30
if: github.event_name != 'pull_request' || needs.changes.outputs.mex == 'true' || needs.changes.outputs.core == 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-2022, windows-latest]
steps:
- name: Allow paths with colons (wiki files)
run: git config --global core.protectNTFS false
- uses: actions/checkout@v6
with:
sparse-checkout: |
libs
tests
benchmarks
examples
install.m
sparse-checkout-cone-mode: false
- name: Install Octave
shell: pwsh
run: |
# Try Chocolatey first
choco install octave.portable --yes --version=9.2.0 --no-progress 2>$null
$octExe = Get-ChildItem -Path "C:\ProgramData\chocolatey\lib\octave.portable\tools" -Recurse -Filter "octave-cli.exe" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($octExe) {
Write-Host "Octave installed via Chocolatey at: $($octExe.FullName)"
echo "OCTAVE_EXE=$($octExe.FullName)" >> $env:GITHUB_ENV
exit 0
}
# Fallback: direct download from mirror
$LASTEXITCODE = 0
$global:LASTEXITCODE = 0
Write-Host "::warning::Chocolatey install failed (ftp.gnu.org may be down). Downloading from mirror..."
$url = "https://mirrors.kernel.org/gnu/octave/windows/octave-9.2.0-w64.zip"
$zip = "$env:TEMP\octave.zip"
$dest = "C:\octave"
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
Expand-Archive -Path $zip -DestinationPath $dest -Force
$octExe = Get-ChildItem -Path $dest -Recurse -Filter "octave-cli.exe" | Select-Object -First 1
if (-not $octExe) {
Write-Error "Failed to find octave-cli.exe after direct download"
exit 1
}
Write-Host "Octave installed via direct download at: $($octExe.FullName)"
echo "OCTAVE_EXE=$($octExe.FullName)" >> $env:GITHUB_ENV
timeout-minutes: 15
- name: Compile MEX files
shell: pwsh
run: |
$octExe = $env:OCTAVE_EXE
if (-not $octExe -or -not (Test-Path $octExe)) {
Write-Error "OCTAVE_EXE not set or not found: $octExe"
exit 1
}
Write-Host "Using Octave at: $octExe"
& $octExe --version
& $octExe --no-window-system --eval "install(); fprintf('All MEX files compiled successfully on %s\n', computer());"
- name: List compiled MEX binaries
shell: pwsh
run: Get-ChildItem -Recurse libs -Filter '*.mex' | ForEach-Object { $_.FullName }
matlab:
# Split into 4 alphabetical batches so each MATLAB process sees a
# bounded test load. Background: R2021b headless Linux suffers
# cumulative state corruption (libmwm_dispatcher / libmwlxeindexing
# internals) after the long test suite runs in one process, causing
# segfaults in unrelated tests late in the run. Running each batch
# in a fresh MATLAB process resets the state between groups.
# Batches are matrix-parallel so wall-clock time is similar to the
# old single-process job.
name: MATLAB Tests (${{ matrix.batch.name }})
needs: [changes, build-mex-matlab]
timeout-minutes: 25
if: github.event_name != 'pull_request' || needs.changes.outputs.matlab == 'true' || needs.changes.outputs.core == 'true' || needs.changes.outputs.mex == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Batch budget: empirically ~30 classes per fresh MATLAB process
# on R2021b headless Linux before the dispatcher state corrupts.
# TestDashboard* alone is 23 classes (heaviest cluster), so it
# gets its own batch; the rest of D folds into batch 1.
batch:
# A-D batch: excludes TestConcurrencyIntegration (moved to batch 6) —
# it triggers an R2021b cumulative-state segfault when run after the
# ~17 widget/render tests that precede it in this alphabetical range.
- { id: 1, name: "A-D", pattern: "^Test[AB]|^TestC(?!oncurrencyIntegration)|^TestD(?!ashboard)" }
- { id: 2, name: "Dashboard", pattern: "^TestDashboard" }
- { id: 3, name: "E-I", pattern: "^Test[E-I]" }
# J-P batch: excludes TestMonitorTagSingleSource (moved to batch 6) —
# it triggers the same R2021b cumulative-state segfault as
# TestConcurrencyIntegration when run after ~20 preceding tests in
# the same MATLAB process.
- { id: 4, name: "J-P", pattern: "^Test[J-LN-P]|^TestM(?!onitorTagSingleSource)" }
# Batch 5 also picks up digit-prefixed v4.0 cluster acceptance harness
# (Test50CompanionAcceptance). The test self-gates via FASTSENSE_RUN_ACCEPTANCE
# so it will report as skipped/incomplete in CI rather than running, but
# including it in a batch keeps it discoverable and statused.
- { id: 5, name: "Q-Z", pattern: "^Test([Q-Z]|[0-9])" }
# Batch 6: v4.0 cluster tests that load `lockfile_mex` run alone in a
# fresh MATLAB process. Background: R2021b headless Linux accumulates
# state corruption across the long earlier batches; loading the MEX
# late in the run triggers a segfault inside MATLAB's MEX dispatcher.
# Running in isolation gives the MEX a clean dispatcher state.
- { id: 6, name: "v4-Cluster-Tests", pattern: "^Test(ConcurrencyIntegration|MonitorTagSingleSource)" }
env:
FASTSENSE_SKIP_BUILD: "1"
# v4.0 Phase 1032 SC1: 4-node simulated-cluster smoke. Spawns 4 child
# `matlab -batch` workers that race to emit events through a shared
# FileLock + EventStore, asserting exactly-N events for N rising edges
# (single-source ACK-04 guarantee). Test gates on isunix() && ~ismac()
# so it runs on Ubuntu CI and skips cleanly on macOS / Windows.
# Cost: ~30 s per CI run on top of the standard batch.
FASTSENSE_STRESS_4: "1"
steps:
- uses: actions/checkout@v6
- name: Setup MATLAB
uses: matlab-actions/setup-matlab@v3
with:
# Matches build-mex-matlab job (PR #109): R2020b libmex segfaults.
release: R2021b
cache: true
- name: Download MATLAB MEX binaries
uses: actions/download-artifact@v8
with:
name: mex-matlab-linux
- name: Diagnose mksqlite availability for tests
if: always()
continue-on-error: true
run: |
echo "=== matlab job batch ${{ matrix.batch.name }}: pre-test mksqlite diagnostic ==="
echo "--- files on disk after artifact download ---"
ls -la libs/FastSense/mksqlite.* 2>&1 || echo "(no mksqlite files on disk)"
echo "--- libs/Concurrency/ contents (v4.0 lockfile_mex check) ---"
ls -la libs/Concurrency/ 2>&1 || echo "(no libs/Concurrency/ dir)"
echo "--- repo-rooted alternative paths (if upload-artifact stripped libs/) ---"
ls -la Concurrency/ 2>&1 | head -5 || echo "(no Concurrency/ at workspace root)"
ls -la FastSense/mksqlite.* 2>&1 | head -5 || echo "(no FastSense/ at workspace root)"
- name: Rebuild Concurrency MEX inline
# actions/upload-artifact@v7 strips the LCA `libs/` from paths, and
# actions/download-artifact@v8 doesn't reliably restore libs/Concurrency/
# contents the way it does libs/FastSense/private/. Unlike mksqlite, the
# lockfile_mex binary isn't committed to the repo, so the test job has
# no copy without rebuild. Compiling lockfile_mex.c is ~5s — cheap.
# FastSense/SensorThreshold MEX binaries continue to come from the
# cached artifact (they're slower to build and have stable inputs).
uses: matlab-actions/run-command@v3
with:
command: |
addpath('libs/Concurrency');
build_concurrency_mex();
fprintf('lockfile_mex on path: %s\n', which('lockfile_mex'));
- name: MATLAB which-mksqlite check
if: always()
continue-on-error: true
uses: matlab-actions/run-command@v3
with:
command: |
addpath('.');
install();
fprintf('which mksqlite: %s\n', which('mksqlite'));
fprintf('exist mksqlite: %d (expect 3 if MEX loadable)\n', exist('mksqlite'));
fprintf('which lockfile_mex: %s\n', which('lockfile_mex'));
try
mksqlite('version');
fprintf('mksqlite call: OK\n');
catch e
fprintf('mksqlite call FAILED: %s\n', e.message);
end
- name: Run tests with coverage (batch ${{ matrix.batch.name }})
id: matlab-tests
continue-on-error: true
uses: matlab-actions/run-command@v3
with:
# Second arg is the batch regex; first arg remains the
# path-filter pattern. Empty batch regex disables the filter
# (used for non-batched single-job invocations).
command: "addpath('scripts'); run_tests_with_coverage('${{ needs.changes.outputs.test_pattern }}', '${{ matrix.batch.pattern }}');"
- name: Verify MATLAB test pass via sentinel file
# MATLAB R2021b reproducibly segfaults during shutdown on the
# headless Linux runner, even when every test passes. The
# matlab-actions step above is allowed to fail; truth comes from
# the .matlab-tests-passed sentinel that run_tests_with_coverage
# writes only after results.Failed == 0. If the sentinel is
# missing, this step fails the job.
shell: bash
run: |
if [[ -f .matlab-tests-passed ]]; then
echo "MATLAB tests passed in batch ${{ matrix.batch.name }} (sentinel present); ignoring shutdown segfault."
rm -f .matlab-tests-passed
else
echo "MATLAB tests failed in batch ${{ matrix.batch.name }}: .matlab-tests-passed sentinel not written."
echo "matlab-actions step outcome: ${{ steps.matlab-tests.outcome }}"
exit 1
fi
- name: Write MATLAB test summary
if: always()
shell: bash
run: |
{
echo "### MATLAB Tests (batch ${{ matrix.batch.name }})"
echo ""
echo "Pattern: \`${{ matrix.batch.pattern }}\`"
echo "MATLAB test run completed — see job log for details."
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v6
with:
files: coverage.xml
# Codecov merges multiple uploads sharing the `matlab` flag
# by SHA. Batch flag is informational so per-batch coverage
# is visible in the Codecov UI.
flags: matlab,batch${{ matrix.batch.id }}
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# v4.0 Multi-User LAN Concurrency cross-platform smoke.
#
# Why a separate job: lockfile_mex.c has three kernel branches (LockFileEx on
# Win32, F_OFD_SETLK on Linux ≥ 3.15, F_SETLK fallback on macOS). The full
# MATLAB test suite only runs on Linux (the `matlab:` job above), so a bad
# #ifdef change could break Windows or macOS silently. This job runs a focused
# subset of concurrency tests on all three OSes to catch platform-specific
# regressions within 24 h.
#
# Scope: ~10 test files, all in libs/Concurrency/ + the cluster-mode paths in
# libs/EventDetection and libs/SensorThreshold. The Dashboard / FastSense /
# WebBridge / UI tests stay Linux-only — they're MATLAB-level functionality
# without kernel divergence.
#
# Cost: ~5 min wall-clock per platform on top of the existing matrix.
# Gated by path-filter so PRs touching unrelated areas don't pay this cost.
matlab-concurrency-smoke:
name: Concurrency Smoke (${{ matrix.os }})
needs: [changes]
timeout-minutes: 30
if: github.event_name != 'pull_request' || needs.changes.outputs.concurrency == 'true' || needs.changes.outputs.core == 'true' || needs.changes.outputs.mex == 'true'
strategy:
fail-fast: false
matrix:
# ubuntu-latest: kernel 5.x → F_OFD_SETLK branch of lockfile_mex.c
# macos-14: Apple Silicon (ARM64), Darwin → F_SETLK fallback branch
# windows-latest: Windows Server 2022, NTFS → LockFileEx branch
os: [ubuntu-latest, macos-14, windows-latest]
runs-on: ${{ matrix.os }}
env:
# Phase 1032 SC1: enable the 4-node simulated-cluster smoke. Test self-gates
# additionally on isunix() && ~ismac() so it runs on Linux only; macOS and
# Windows skip cleanly. Cost ~30s on the Linux job; ~0s on the others.
FASTSENSE_STRESS_4: "1"
steps:
# Windows-only: wiki/ contains filenames with colons (e.g.
# `API-Reference:-Dashboard.md`) which NTFS rejects under git's default
# protectNTFS. Same pattern as mex-build-windows. macOS / Linux are no-ops.
- name: Allow paths with colons (Windows / wiki files)
if: runner.os == 'Windows'
run: git config --global core.protectNTFS false
# Sparse-checkout skips wiki/ entirely on all OSes — fixes Windows checkout
# by avoiding the colon-filename files, and trims clone size on the others.
# Tests need libs/, tests/, scripts/, install.m. Nothing else.
- uses: actions/checkout@v6
with:
sparse-checkout: |
libs
tests
scripts
install.m
run_profile.m
sparse-checkout-cone-mode: false
- name: Setup MATLAB
uses: matlab-actions/setup-matlab@v3
with:
# Match the main matlab: job. R2021b has best matlab-actions support
# across all three OSes. On macos-14 ARM64 this runs under Rosetta;
# if Rosetta proves flaky we can bump just this platform later.
release: R2021b
cache: true
- name: Build MEX + run concurrency smoke
id: smoke
# matlab-actions occasionally reports nonzero exit on R2021b shutdown
# segfault even when tests pass; the sentinel-file step below is the
# source of truth (same pattern as the main matlab: job).
continue-on-error: true
uses: matlab-actions/run-command@v3
with:
# install() compiles lockfile_mex on the current platform (LockFileEx
# / F_OFD_SETLK / F_SETLK branch selected by #ifdef in lockfile_mex.c
# + -D_GNU_SOURCE on Linux from build_concurrency_mex.m).
#
# Second arg of run_tests_with_coverage is the batch regex; first arg
# (path-filter pattern) is empty so the batch regex is the sole filter.
# The regex matches the v4.0 concurrency test surface:
# - TestFileLock / TestFileLockStress50 (FASTSENSE_STRESS_50-gated)
# - TestLockfileMex (platform-branch probe)
# - TestAtomicWriter (movefile / rename semantics per FS)
# - TestCluster{Identity,Config,ConfigOplocks,ConfigNfsv3}
# - TestTagWriteCoordinator
# - TestEventLog{,Consolidator,Reader}
# - TestConcurrencyIntegration
# - TestListenerCannotAcquireLock
# - TestMonitorTagSingleSource (FASTSENSE_STRESS_4-gated)
# - TestLiveTagPipelineCluster
# - TestShareLossRecovery
# - TestEventAcknowledgement
command: |
addpath('.');
install();
addpath('scripts');
run_tests_with_coverage('', '^Test(FileLock|LockfileMex|AtomicWriter|Cluster|TagWriteCoordinator|EventLog|Concurrency|ListenerCannotAcquireLock|MonitorTagSingleSource|LiveTagPipelineCluster|ShareLossRecovery|EventAcknowledgement)');
- name: Verify smoke pass via sentinel file
shell: bash
run: |
if [[ -f .matlab-tests-passed ]]; then
echo "Concurrency smoke passed on ${{ matrix.os }} (sentinel present); ignoring any shutdown-time noise."
rm -f .matlab-tests-passed
else
echo "Concurrency smoke FAILED on ${{ matrix.os }}: .matlab-tests-passed sentinel not written."
echo "matlab-actions step outcome: ${{ steps.smoke.outcome }}"
exit 1
fi
- name: Write concurrency smoke summary
if: always()
shell: bash
run: |
{
echo "### Concurrency Smoke (${{ matrix.os }})"
echo ""
echo "Validates v4.0 platform-divergent concurrency code paths:"
echo "- **ubuntu-latest** → \`F_OFD_SETLK\` branch (kernel ≥ 3.15, OFD locks)"
echo "- **macos-14** → \`F_SETLK\` fallback branch (Darwin, no OFD support)"
echo "- **windows-latest** → \`LockFileEx\` branch (NTFS, process-scoped advisory locks)"
echo ""
echo "Tests cover: \`FileLock\`, \`AtomicWriter\`, \`ClusterIdentity\`, \`ClusterConfig\`,"
echo "\`TagWriteCoordinator\`, \`EventLog\`/\`EventLogReader\`, ack workflow, and"
echo "share-loss recovery. Gated multi-process smokes:"
echo "- \`testTwoProcessMutualExclusion\` / \`testTwoProcessWriteRace\` — Linux only (gate: ~ispc() && ~ismac())"
echo "- \`testFourNodeRisingEdges\` — Linux only (gates: isunix() && ~ismac() + FASTSENSE_STRESS_4=1)"
echo "- \`TestFileLockStress50\` / \`Test50CompanionAcceptance\` — require real SMB share (operator-run; not in CI)"
} >> "$GITHUB_STEP_SUMMARY"