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
90 changes: 80 additions & 10 deletions .github/workflows/compat-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ name: Compatibility Matrix
# * weekly drift -> re-runs the informational tier against upstream HEADs,
# purely to surface upstream divergence; never blocks
#
# Each tier fans out one job per pair (strategy.matrix from generate-matrix), so
# the independent pairs run in parallel rather than as one long sequential job.
# Per-tier gate jobs ("Blocking tier" / "Informational tier") aggregate the legs
# back into a single, stable merge-gating status check.
#
# amd64 only — bonded UDP interop is exercised on a single ubuntu-latest host.

on:
Expand Down Expand Up @@ -91,13 +96,19 @@ jobs:
} >> "$GITHUB_STEP_SUMMARY"

# --------------------------------------------------------------------- #
# Blocking tier — a failing pair fails the workflow. #
# Blocking tier — one job per pair, fanned out from matrix.yaml so the #
# independent pairs run in parallel instead of one sequential job. #
# fail-fast: false keeps every pair reporting; the compat-blocking-gate #
# job below folds the legs into the single merge-gating status. #
# --------------------------------------------------------------------- #
compat-blocking:
name: Blocking tier
name: "blocking: ${{ matrix.sender }} -> ${{ matrix.receiver }}"
needs: generate-matrix
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.generate-matrix.outputs.blocking) }}
steps:
- name: Checkout code
uses: actions/checkout@v7
Expand All @@ -115,25 +126,55 @@ jobs:
with:
tier: blocking

- name: Run blocking compatibility tier
run: tests/compat/run-matrix.sh --tier blocking --duration 20
- name: Run compatibility pair
run: >-
tests/compat/run-matrix.sh
--sender "${{ matrix.sender }}"
--receiver "${{ matrix.receiver }}"
--duration 20

- name: Upload results
if: always()
uses: actions/upload-artifact@v7
with:
name: compat-results-blocking
name: compat-results-blocking-${{ matrix.sender }}-x-${{ matrix.receiver }}
path: tests/compat/results/**/result.json
if-no-files-found: ignore

# --------------------------------------------------------------------- #
# Informational tier — gates PRs (pinned SHAs; a failure is a regression). #
# Blocking gate — one stable status that fails if any blocking leg #
# failed, so "a failing blocking pair fails this run" holds under one #
# check name no matter how many pairs the matrix fans into. #
# --------------------------------------------------------------------- #
compat-blocking-gate:
name: Blocking tier
needs: compat-blocking
if: always() && github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- name: Require all blocking pairs to pass
env:
RESULT: ${{ needs.compat-blocking.result }}
run: |
echo "blocking matrix result: $RESULT"
[ "$RESULT" = "success" ] || {
echo "::error title=Blocking tier::one or more blocking compat pairs failed ($RESULT)"
exit 1
}

# --------------------------------------------------------------------- #
# Informational tier — same per-pair fan-out (pinned SHAs; a failure is #
# a regression). Only materializes when matrix.yaml has informational #
# pairs (has_informational guard). #
# --------------------------------------------------------------------- #
compat-informational:
name: Informational tier
name: "informational: ${{ matrix.sender }} -> ${{ matrix.receiver }}"
needs: generate-matrix
if: github.event_name != 'schedule' && needs.generate-matrix.outputs.has_informational == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.generate-matrix.outputs.informational) }}
steps:
- name: Checkout code
uses: actions/checkout@v7
Expand All @@ -151,17 +192,46 @@ jobs:
with:
tier: informational

- name: Run informational compatibility tier
run: tests/compat/run-matrix.sh --tier informational --duration 20
- name: Run compatibility pair
run: >-
tests/compat/run-matrix.sh
--sender "${{ matrix.sender }}"
--receiver "${{ matrix.receiver }}"
--duration 20

- name: Upload results
if: always()
uses: actions/upload-artifact@v7
with:
name: compat-results-informational
name: compat-results-informational-${{ matrix.sender }}-x-${{ matrix.receiver }}
path: tests/compat/results/**/result.json
if-no-files-found: ignore

# --------------------------------------------------------------------- #
# Informational gate — stable status mirroring the blocking gate; passes #
# vacuously when no informational pairs are registered. #
# --------------------------------------------------------------------- #
compat-informational-gate:
name: Informational tier
needs: [generate-matrix, compat-informational]
if: always() && github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- name: Require all informational pairs to pass
env:
RESULT: ${{ needs.compat-informational.result }}
HAS_INFO: ${{ needs.generate-matrix.outputs.has_informational }}
run: |
echo "informational matrix result: $RESULT (has_informational=$HAS_INFO)"
if [ "$HAS_INFO" != "true" ]; then
echo "No informational pairs registered; nothing to gate."
exit 0
fi
[ "$RESULT" = "success" ] || {
echo "::error title=Informational tier::one or more informational compat pairs failed ($RESULT)"
exit 1
}

# --------------------------------------------------------------------- #
# pcap replay — runs only when an LFS fixture is present. Missing #
# fixture (or replay tool not yet wired) is a graceful skip, mirroring #
Expand Down
36 changes: 31 additions & 5 deletions tests/compat/run-matrix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,18 @@
#
# Usage:
# run-matrix.sh --pair <sender>x<receiver> [options]
# run-matrix.sh --sender <name> --receiver <name> [options]
# run-matrix.sh --tier blocking|informational|all [options]
#
# Options:
# --pair <s>x<r> Run a single pair by token (e.g. belabox-senderxours, oursxours).
# --pair <s>x<r> Run a single pair by harness token (e.g. belabox-senderxours,
# oursxours).
# --sender <name> With --receiver: run a single pair addressed by its
# --receiver <name> matrix.yaml names (e.g. --sender ceralive-srtla-send-rs
# --receiver ours). This is the CI-matrix fan-out entry point —
# the names map to harness tokens via the same table the --tier
# path uses, so no token aliasing has to leak into the workflow.
# Tier is resolved from matrix.yaml.
# --tier <t> Run every matrix.yaml pair of tier blocking|informational|all.
# --scenario <name> stream (default, healthy) | port-mismatch (negative/broken).
# --duration <sec> Measurement window length (default 20).
Expand Down Expand Up @@ -62,6 +70,8 @@ now_ms() { date +%s%3N; }
# --------------------------------------------------------------------------- #
TIER=""
PAIR=""
SENDER_NAME=""
RECEIVER_NAME=""
SCENARIO="stream"
DURATION=20
KEEP_LOGS=0
Expand All @@ -71,17 +81,27 @@ while [[ $# -gt 0 ]]; do
case "$1" in
--tier) TIER="${2:?--tier needs a value}"; shift 2 ;;
--pair) PAIR="${2:?--pair needs a value}"; shift 2 ;;
--sender) SENDER_NAME="${2:?--sender needs a value}"; shift 2 ;;
--receiver) RECEIVER_NAME="${2:?--receiver needs a value}"; shift 2 ;;
--scenario) SCENARIO="${2:?--scenario needs a value}"; shift 2 ;;
--duration) DURATION="${2:?--duration needs a value}"; shift 2 ;;
--keep-logs) KEEP_LOGS=1; shift ;;
--build-dir) BUILD_DIR="${2:?--build-dir needs a value}"; shift 2 ;;
-h|--help) sed -n '2,40p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
-h|--help) sed -n '2,46p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) die "unknown argument '$1' (try --help)" ;;
esac
done

[[ -n "$TIER" || -n "$PAIR" ]] || die "specify --pair <s>x<r> or --tier <t>"
[[ -z "$TIER" || -z "$PAIR" ]] || die "--pair and --tier are mutually exclusive"
SELECT_BY_NAME=0
[[ -n "$SENDER_NAME" || -n "$RECEIVER_NAME" ]] && SELECT_BY_NAME=1
[[ "$SELECT_BY_NAME" -eq 0 || ( -n "$SENDER_NAME" && -n "$RECEIVER_NAME" ) ]] \
|| die "--sender and --receiver must be given together"
_modes=0
[[ -n "$PAIR" ]] && _modes=$((_modes + 1))
[[ "$SELECT_BY_NAME" -eq 1 ]] && _modes=$((_modes + 1))
[[ -n "$TIER" ]] && _modes=$((_modes + 1))
[[ "$_modes" -eq 1 ]] \
|| die "specify exactly one of: --pair <s>x<r>, --sender <name> --receiver <name>, or --tier <t>"
case "$SCENARIO" in stream|port-mismatch) ;; *) die "unknown --scenario '$SCENARIO'";; esac
[[ "$DURATION" =~ ^[0-9]+$ && "$DURATION" -ge 1 ]] || die "--duration must be a positive integer"

Expand Down Expand Up @@ -456,7 +476,13 @@ run_pair() {
declare -a JOBS
add_job() { JOBS+=("$1|$2|$3"); }

if [[ -n "$PAIR" ]]; then
if [[ "$SELECT_BY_NAME" -eq 1 ]]; then
s="$(matrix_to_token "$SENDER_NAME")" \
|| die "unknown matrix sender name '$SENDER_NAME' (see matrix.yaml senders:)"
r="$(matrix_to_token "$RECEIVER_NAME")" \
|| die "unknown matrix receiver name '$RECEIVER_NAME' (see matrix.yaml receivers:)"
add_job "$s" "$r" "${PAIR_TIER["$s:$r"]:-blocking}"
elif [[ -n "$PAIR" ]]; then
read -r s r < <(split_pair "$PAIR") \
|| die "could not parse --pair '$PAIR' into known sender x receiver tokens"
add_job "$s" "$r" "${PAIR_TIER["$s:$r"]:-blocking}"
Expand Down
Loading