From cef26ac2a634b2ca0720821cfade634a5be1a33f Mon Sep 17 00:00:00 2001 From: Paul Fournillon Date: Wed, 1 Jul 2026 10:17:05 +0200 Subject: [PATCH 1/2] feat: add matrix tests --- README.md | 25 +- utils/README.md | 26 ++- utils/run-containers-tests.sh | 416 +++++++++++++++++++++++++++++++++- 3 files changed, 454 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 3715202ba..edf5b868e 100644 --- a/README.md +++ b/README.md @@ -169,15 +169,27 @@ By default, the script clones the repository at the current commit for clean bui # Force rebuild of base image only (useful after Alpine/Ubuntu updates) ./utils/run-containers-tests.sh --libc=musl --rebuild-base +# Preview the full supported local test matrix without running containers +./utils/run-containers-tests.sh --matrix + +# Run all supported musl cells (sanitizer configs are skipped, matching CI policy) +./utils/run-containers-tests.sh --matrix --libc=musl --run + +# Run all aarch64 debug cells +./utils/run-containers-tests.sh --matrix --arch=aarch64 --config=debug --run + +# Run all OpenJ9 cells +./utils/run-containers-tests.sh --matrix --jdk=j9 --run + # Show options ./utils/run-containers-tests.sh --help ``` Supported options: -- `--libc=glibc|musl` (default: glibc) -- `--jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal` (default: 21) -- `--arch=x64|aarch64` (default: auto-detect) -- `--config=debug|release|asan|tsan` (default: debug) +- `--libc=glibc|musl|all` (default: glibc; `all` only with `--matrix`) +- `--jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|j9|graal|regular|all` (default: 21; groups/`all` only with `--matrix`) +- `--arch=x64|aarch64|all` (default: auto-detect; `all` only with `--matrix`) +- `--config=debug|release|asan|tsan|all` (default: debug; `all` only with `--matrix`) - `--container=podman|docker` (default: podman) - `--tests="TestPattern"` - `--gtest` (enable C++ gtests, disabled by default for faster runs) @@ -186,6 +198,11 @@ Supported options: - `--mount` (mount local repo instead of cloning - faster but may have stale artifacts) - `--rebuild` (force rebuild of all container images) - `--rebuild-base` (force rebuild of base image only) +- `--matrix` (preview a test matrix; unset dimensions expand to `all`) +- `--run` (execute matrix mode; invalid without `--matrix`) +- `--fail-fast` (stop matrix execution on first failure) + +Without `--matrix`, the script keeps its single-cell behavior and runs one configuration immediately. With `--matrix`, the script previews the expanded matrix by default and only runs containers when `--run` is present. Matrix execution writes summaries to `build/reports/container-matrix/summary.md` and `build/reports/container-matrix/summary.json`. ## Unwinding Validation Tool diff --git a/utils/README.md b/utils/README.md index 605adbdcc..d4d19cc7f 100644 --- a/utils/README.md +++ b/utils/README.md @@ -84,10 +84,11 @@ Runs tests in containers across various OS/libc/JDK combinations, mirroring the **Usage:** ```bash ./utils/run-containers-tests.sh [options] - --libc=glibc|musl (default: glibc) - --jdk=8|11|17|21|25|8-j9|... (default: 21) - --arch=x64|aarch64 (default: auto-detect) - --config=debug|release|asan|tsan (default: debug) + --libc=glibc|musl|all (default: glibc; all only with --matrix) + --jdk=8|11|17|21|25|8-j9|...|j9|graal|regular|all + (default: 21; groups/all only with --matrix) + --arch=x64|aarch64|all (default: auto-detect; all only with --matrix) + --config=debug|release|asan|tsan|all (default: debug; all only with --matrix) --container=podman|docker (default: podman) --tests="TestPattern" (optional) --gtest (enable C++ gtests) @@ -95,6 +96,9 @@ Runs tests in containers across various OS/libc/JDK combinations, mirroring the --shell (drop to shell instead of running tests) --mount (mount local repo instead of cloning) --rebuild (force rebuild of container images) + --matrix (preview a test matrix) + --run (execute matrix mode) + --fail-fast (stop matrix execution on first failure) ``` Examples: @@ -104,8 +108,22 @@ Examples: # Use Docker instead of the default Podman runtime ./utils/run-containers-tests.sh --container=docker --libc=glibc --jdk=21 + +# Preview the full supported local matrix without running containers +./utils/run-containers-tests.sh --matrix + +# Run all supported musl cells (sanitizer configs are skipped, matching CI policy) +./utils/run-containers-tests.sh --matrix --libc=musl --run + +# Run all aarch64 debug cells +./utils/run-containers-tests.sh --matrix --arch=aarch64 --config=debug --run + +# Run all OpenJ9 cells +./utils/run-containers-tests.sh --matrix --jdk=j9 --run ``` +Without `--matrix`, the script keeps its single-cell behavior and runs one container test configuration immediately. With `--matrix`, unset dimensions expand to `all`; the command previews the expanded matrix by default and only executes when `--run` is also provided. Matrix execution writes summaries to `build/reports/container-matrix/summary.md` and `build/reports/container-matrix/summary.json`. + ### `patch-dd-java-agent.sh` Patches a `dd-java-agent.jar` with a locally-built ddprof library for quick local testing without a full dd-trace-java rebuild. diff --git a/utils/run-containers-tests.sh b/utils/run-containers-tests.sh index 4d43c6ad8..82e2880a6 100755 --- a/utils/run-containers-tests.sh +++ b/utils/run-containers-tests.sh @@ -8,10 +8,11 @@ # 2. JDK-specific image on top (java-profiler-test:-jdk-) # # Usage: ./utils/run-containers-tests.sh [options] -# --libc=glibc|musl (default: glibc) -# --jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal (default: 21) -# --arch=x64|aarch64 (default: auto-detect) -# --config=debug|release|asan|tsan (default: debug) +# --libc=glibc|musl|all (default: glibc; all only with --matrix) +# --jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|j9|graal|regular|all +# (default: 21; groups/all only with --matrix) +# --arch=x64|aarch64|all (default: auto-detect; all only with --matrix) +# --config=debug|release|asan|tsan|all (default: debug; all only with --matrix) # --container=podman|docker (default: podman) # --tests="TestPattern" (optional, specific test to run) # --gtest (enable C++ gtests, disabled by default) @@ -20,11 +21,15 @@ # --mount (mount local repo instead of cloning - faster but may have stale artifacts) # --rebuild (force rebuild of container images) # --rebuild-base (force rebuild of base image only) +# --matrix (preview a test matrix; add --run to execute) +# --run (execute matrix mode; invalid without --matrix) +# --fail-fast (stop matrix execution on first failure) # --help (show this help) set -e # Defaults +ORIGINAL_ARGS=("$@") CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-podman}" LIBC="glibc" JDK_VERSION="21" @@ -37,6 +42,14 @@ MOUNT_MODE=false GTEST_ENABLED=false REBUILD=false REBUILD_BASE=false +MATRIX_MODE=false +RUN_MATRIX=false +FAIL_FAST=false +LIBC_SET=false +JDK_SET=false +ARCH_SET=false +CONFIG_SET=false +PASS_THROUGH_ARGS=() SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" BASE_IMAGE_PREFIX="java-profiler-base" @@ -146,8 +159,336 @@ get_j9_jdk_url() { esac } +join_by() { + local delimiter=$1 + shift + local first=true + local item + for item in "$@"; do + if $first; then + printf "%s" "$item" + first=false + else + printf "%s%s" "$delimiter" "$item" + fi + done +} + +json_escape() { + local value=$1 + value=${value//\\/\\\\} + value=${value//\"/\\\"} + value=${value//$'\n'/\\n} + value=${value//$'\r'/\\r} + value=${value//$'\t'/\\t} + printf "%s" "$value" +} + +original_command() { + if [[ ${#ORIGINAL_ARGS[@]} -eq 0 ]]; then + echo "./utils/run-containers-tests.sh" + else + echo "./utils/run-containers-tests.sh $(join_by " " "${ORIGINAL_ARGS[@]}")" + fi +} + +contains_value() { + local needle=$1 + shift + local value + for value in "$@"; do + if [[ "$value" == "$needle" ]]; then + return 0 + fi + done + return 1 +} + +read_lines_into_array() { + local array_name=$1 + local line + eval "$array_name=()" + while IFS= read -r line; do + eval "$array_name+=(\"\$line\")" + done +} + +expand_dimension() { + local value=$1 + local name=$2 + shift 2 + local all_values=("$@") + + if [[ "$value" == "all" ]]; then + printf "%s\n" "${all_values[@]}" + elif contains_value "$value" "${all_values[@]}"; then + printf "%s\n" "$value" + else + echo "Error: --$name must be one of: $(join_by "|" "${all_values[@]}")" >&2 + exit 1 + fi +} + +expand_jdk_dimension() { + local value=$1 + local regular_jdks=(8 11 17 21 25) + local j9_jdks=(8-j9 11-j9 17-j9 21-j9) + local graal_jdks=(17-graal 21-graal 25-graal) + local all_jdks=("${regular_jdks[@]}" "${j9_jdks[@]}" "${graal_jdks[@]}") + + case "$value" in + all) + printf "%s\n" "${all_jdks[@]}" + ;; + regular) + printf "%s\n" "${regular_jdks[@]}" + ;; + j9) + printf "%s\n" "${j9_jdks[@]}" + ;; + graal) + printf "%s\n" "${graal_jdks[@]}" + ;; + *) + if contains_value "$value" "${all_jdks[@]}"; then + printf "%s\n" "$value" + else + echo "Error: --jdk must be one of: 8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|j9|graal|regular|all" >&2 + exit 1 + fi + ;; + esac +} + +skip_reason() { + local libc=$1 + local jdk=$2 + local config=$3 + + if [[ "$libc" == "musl" && "$jdk" == *-j9 ]]; then + echo "J9/OpenJ9 is not available for musl libc" + elif [[ "$libc" == "musl" && "$jdk" == *-graal ]]; then + echo "GraalVM is not available for musl libc" + elif [[ "$libc" == "musl" && ( "$config" == "asan" || "$config" == "tsan" ) ]]; then + echo "sanitizer configs are filtered out for musl in CI" + fi +} + +matrix_command() { + local libc=$1 + local arch=$2 + local jdk=$3 + local config=$4 + local cmd=("./utils/run-containers-tests.sh" "--libc=$libc" "--arch=$arch" "--jdk=$jdk" "--config=$config") + + if [[ ${#PASS_THROUGH_ARGS[@]} -gt 0 ]]; then + cmd+=("${PASS_THROUGH_ARGS[@]}") + fi + + printf "%q " "${cmd[@]}" +} + +write_matrix_reports() { + local start_time=$1 + local end_time=$2 + local total=$3 + local passed=$4 + local failed=$5 + local skipped=$6 + local report_dir="$PROJECT_ROOT/build/reports/container-matrix" + local markdown_report="$report_dir/summary.md" + local json_report="$report_dir/summary.json" + local i + + mkdir -p "$report_dir" + + { + echo "# Container Matrix Summary" + echo + echo "- Command: \`$(original_command)\`" + echo "- Started: $start_time" + echo "- Finished: $end_time" + echo "- Filters: libc=$LIBC, arch=$ARCH, jdk=$JDK_VERSION, config=$CONFIG" + echo "- Totals: total=$total, passed=$passed, failed=$failed, skipped=$skipped" + echo + echo "## Cells" + for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do + echo "- libc=${MATRIX_CELL_LIBCS[$i]}, arch=${MATRIX_CELL_ARCHES[$i]}, jdk=${MATRIX_CELL_JDKS[$i]}, config=${MATRIX_CELL_CONFIGS[$i]}, status=${MATRIX_CELL_STATUSES[$i]}, exit_code=${MATRIX_CELL_EXIT_CODES[$i]}, reason=${MATRIX_CELL_REASONS[$i]:-}" + done + } > "$markdown_report" + + { + echo "{" + printf ' "command": "%s",\n' "$(json_escape "$(original_command)")" + printf ' "started": "%s",\n' "$(json_escape "$start_time")" + printf ' "finished": "%s",\n' "$(json_escape "$end_time")" + printf ' "filters": {"libc": "%s", "arch": "%s", "jdk": "%s", "config": "%s"},\n' \ + "$(json_escape "$LIBC")" "$(json_escape "$ARCH")" "$(json_escape "$JDK_VERSION")" "$(json_escape "$CONFIG")" + printf ' "totals": {"total": %d, "passed": %d, "failed": %d, "skipped": %d},\n' \ + "$total" "$passed" "$failed" "$skipped" + echo ' "cells": [' + for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do + printf ' {"libc": "%s", "arch": "%s", "jdk": "%s", "config": "%s", "status": "%s", "exit_code": %s, "reason": "%s"}' \ + "$(json_escape "${MATRIX_CELL_LIBCS[$i]}")" \ + "$(json_escape "${MATRIX_CELL_ARCHES[$i]}")" \ + "$(json_escape "${MATRIX_CELL_JDKS[$i]}")" \ + "$(json_escape "${MATRIX_CELL_CONFIGS[$i]}")" \ + "$(json_escape "${MATRIX_CELL_STATUSES[$i]}")" \ + "${MATRIX_CELL_EXIT_CODES[$i]}" \ + "$(json_escape "${MATRIX_CELL_REASONS[$i]:-}")" + if (( i < ${#MATRIX_CELL_LIBCS[@]} - 1 )); then + echo "," + else + echo + fi + done + echo " ]" + echo "}" + } > "$json_report" + + echo ">>> Matrix reports written:" + echo ">>> $markdown_report" + echo ">>> $json_report" +} + +run_matrix() { + local matrix_libcs matrix_arches matrix_jdks matrix_configs + local libc arch jdk config reason command + local total=0 runnable=0 skipped=0 passed=0 failed=0 exit_code=0 overall_exit=0 + local start_time end_time + local cell_cmd + + read_lines_into_array matrix_libcs < <(expand_dimension "$LIBC" "libc" glibc musl) + read_lines_into_array matrix_arches < <(expand_dimension "$ARCH" "arch" x64 aarch64) + read_lines_into_array matrix_jdks < <(expand_jdk_dimension "$JDK_VERSION") + read_lines_into_array matrix_configs < <(expand_dimension "$CONFIG" "config" debug release asan tsan) + + MATRIX_CELL_LIBCS=() + MATRIX_CELL_ARCHES=() + MATRIX_CELL_JDKS=() + MATRIX_CELL_CONFIGS=() + MATRIX_CELL_STATUSES=() + MATRIX_CELL_EXIT_CODES=() + MATRIX_CELL_REASONS=() + + echo "=== Container Test Matrix ===" + echo "LIBC filter: $LIBC" + echo "Arch filter: $ARCH" + echo "JDK filter: $JDK_VERSION" + echo "Config filter: $CONFIG" + echo "Runtime: $CONTAINER_RUNTIME" + echo "Mode: $(if $RUN_MATRIX; then echo 'run'; else echo 'preview'; fi)" + echo "=============================" + + for libc in "${matrix_libcs[@]}"; do + for arch in "${matrix_arches[@]}"; do + for jdk in "${matrix_jdks[@]}"; do + for config in "${matrix_configs[@]}"; do + ((total += 1)) + reason=$(skip_reason "$libc" "$jdk" "$config") + if [[ -n "$reason" ]]; then + ((skipped += 1)) + MATRIX_CELL_LIBCS+=("$libc") + MATRIX_CELL_ARCHES+=("$arch") + MATRIX_CELL_JDKS+=("$jdk") + MATRIX_CELL_CONFIGS+=("$config") + MATRIX_CELL_STATUSES+=("skipped") + MATRIX_CELL_EXIT_CODES+=("null") + MATRIX_CELL_REASONS+=("$reason") + continue + fi + + ((runnable += 1)) + MATRIX_CELL_LIBCS+=("$libc") + MATRIX_CELL_ARCHES+=("$arch") + MATRIX_CELL_JDKS+=("$jdk") + MATRIX_CELL_CONFIGS+=("$config") + MATRIX_CELL_STATUSES+=("pending") + MATRIX_CELL_EXIT_CODES+=("null") + MATRIX_CELL_REASONS+=("") + done + done + done + done + + echo "Generated cells: $total" + echo "Runnable cells: $runnable" + echo "Skipped cells: $skipped" + echo + + for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do + if [[ "${MATRIX_CELL_STATUSES[$i]}" == "skipped" ]]; then + echo "SKIP libc=${MATRIX_CELL_LIBCS[$i]} arch=${MATRIX_CELL_ARCHES[$i]} jdk=${MATRIX_CELL_JDKS[$i]} config=${MATRIX_CELL_CONFIGS[$i]} reason=${MATRIX_CELL_REASONS[$i]}" + else + cell_cmd=$(matrix_command "${MATRIX_CELL_LIBCS[$i]}" "${MATRIX_CELL_ARCHES[$i]}" "${MATRIX_CELL_JDKS[$i]}" "${MATRIX_CELL_CONFIGS[$i]}") + echo "RUN $cell_cmd" + fi + done + + if ! $RUN_MATRIX; then + echo + echo "Preview only. Add --run to execute." + exit 0 + fi + + start_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + set +e + for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do + if [[ "${MATRIX_CELL_STATUSES[$i]}" == "skipped" ]]; then + continue + fi + + echo + echo ">>> Matrix cell $((i + 1))/${#MATRIX_CELL_LIBCS[@]}: libc=${MATRIX_CELL_LIBCS[$i]} arch=${MATRIX_CELL_ARCHES[$i]} jdk=${MATRIX_CELL_JDKS[$i]} config=${MATRIX_CELL_CONFIGS[$i]}" + command=("$SCRIPT_DIR/run-containers-tests.sh" + "--libc=${MATRIX_CELL_LIBCS[$i]}" + "--arch=${MATRIX_CELL_ARCHES[$i]}" + "--jdk=${MATRIX_CELL_JDKS[$i]}" + "--config=${MATRIX_CELL_CONFIGS[$i]}") + if [[ ${#PASS_THROUGH_ARGS[@]} -gt 0 ]]; then + command+=("${PASS_THROUGH_ARGS[@]}") + fi + "${command[@]}" + exit_code=$? + + MATRIX_CELL_EXIT_CODES[$i]=$exit_code + if [[ $exit_code -eq 0 ]]; then + MATRIX_CELL_STATUSES[$i]="passed" + ((passed += 1)) + else + MATRIX_CELL_STATUSES[$i]="failed" + ((failed += 1)) + overall_exit=1 + if $FAIL_FAST; then + echo ">>> Stopping after first failure because --fail-fast is set" + for ((j = i + 1; j < ${#MATRIX_CELL_LIBCS[@]}; j++)); do + if [[ "${MATRIX_CELL_STATUSES[$j]}" == "pending" ]]; then + MATRIX_CELL_STATUSES[$j]="skipped" + MATRIX_CELL_REASONS[$j]="not run because --fail-fast stopped after an earlier failure" + ((skipped += 1)) + fi + done + break + fi + fi + done + set -e + + end_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + write_matrix_reports "$start_time" "$end_time" "$total" "$passed" "$failed" "$skipped" + + echo + echo "=== Matrix Result ===" + echo "Passed: $passed" + echo "Failed: $failed" + echo "Skipped: $skipped" + echo "=====================" + + exit "$overall_exit" +} + usage() { - head -n 23 "$0" | tail -n 20 + awk 'NR >= 4 && NR <= 27 { sub(/^# ?/, ""); print }' "$0" exit 0 } @@ -156,26 +497,32 @@ while [[ $# -gt 0 ]]; do case $1 in --libc=*) LIBC="${1#*=}" + LIBC_SET=true shift ;; --jdk=*) JDK_VERSION="${1#*=}" + JDK_SET=true shift ;; --arch=*) ARCH="${1#*=}" + ARCH_SET=true shift ;; --config=*) CONFIG="${1#*=}" + CONFIG_SET=true shift ;; --container=*) CONTAINER_RUNTIME="${1#*=}" + PASS_THROUGH_ARGS+=("$1") shift ;; --tests=*) TESTS="${1#*=}" + PASS_THROUGH_ARGS+=("$1") shift ;; --shell) @@ -184,23 +531,40 @@ while [[ $# -gt 0 ]]; do ;; --mount) MOUNT_MODE=true + PASS_THROUGH_ARGS+=("$1") shift ;; --gtest) GTEST_ENABLED=true + PASS_THROUGH_ARGS+=("$1") shift ;; --gtest-task=*) GTEST_TASK="${1#*=}" GTEST_ENABLED=true + PASS_THROUGH_ARGS+=("$1") shift ;; --rebuild) REBUILD=true + PASS_THROUGH_ARGS+=("$1") shift ;; --rebuild-base) REBUILD_BASE=true + PASS_THROUGH_ARGS+=("$1") + shift + ;; + --matrix) + MATRIX_MODE=true + shift + ;; + --run) + RUN_MATRIX=true + shift + ;; + --fail-fast) + FAIL_FAST=true shift ;; --help|-h) @@ -213,6 +577,48 @@ while [[ $# -gt 0 ]]; do esac done +if $MATRIX_MODE; then + if ! $LIBC_SET; then + LIBC="all" + fi + if ! $JDK_SET; then + JDK_VERSION="all" + fi + if ! $ARCH_SET; then + ARCH="all" + fi + if ! $CONFIG_SET; then + CONFIG="all" + fi + + if $SHELL_MODE; then + echo "Error: --shell is interactive and is not supported with --matrix. Run a single cell without --matrix instead." + exit 1 + fi + + if [[ -n "$GTEST_TASK" && -n "$TESTS" ]]; then + echo "Error: --tests cannot be combined with --gtest-task" + exit 1 + fi + + if [[ "$CONTAINER_RUNTIME" != "podman" && "$CONTAINER_RUNTIME" != "docker" ]]; then + echo "Error: --container must be 'podman' or 'docker'" + exit 1 + fi + + run_matrix +fi + +if $RUN_MATRIX; then + echo "Error: --run is only valid with --matrix" + exit 1 +fi + +if $FAIL_FAST; then + echo "Error: --fail-fast is only valid with --matrix" + exit 1 +fi + # Auto-detect architecture if not specified if [[ -z "$ARCH" ]]; then ARCH=$(detect_arch) From d10ab7fac720df3f26df2cf42694dd7e9626c2b1 Mon Sep 17 00:00:00 2001 From: Paul Fournillon Date: Tue, 7 Jul 2026 18:35:29 +0200 Subject: [PATCH 2/2] fix: apply PR review --- README.md | 28 +- utils/README.md | 29 +- utils/run-containers-tests.sh | 565 ++++++++++++++++++++++++---------- 3 files changed, 437 insertions(+), 185 deletions(-) diff --git a/README.md b/README.md index edf5b868e..3a90941fd 100644 --- a/README.md +++ b/README.md @@ -169,27 +169,27 @@ By default, the script clones the repository at the current commit for clean bui # Force rebuild of base image only (useful after Alpine/Ubuntu updates) ./utils/run-containers-tests.sh --libc=musl --rebuild-base -# Preview the full supported local test matrix without running containers -./utils/run-containers-tests.sh --matrix +# Preview all supported musl cells (sanitizer configs are skipped, matching CI policy) +./utils/run-containers-tests.sh --libc=musl --jdk=all --arch=all -# Run all supported musl cells (sanitizer configs are skipped, matching CI policy) -./utils/run-containers-tests.sh --matrix --libc=musl --run +# Run all supported musl cells without an interactive prompt +./utils/run-containers-tests.sh --libc=musl --jdk=all --arch=all --run -# Run all aarch64 debug cells -./utils/run-containers-tests.sh --matrix --arch=aarch64 --config=debug --run +# Preview selected JDKs across every supported libc/architecture pair +./utils/run-containers-tests.sh --libc=all --jdk=8,17,21 --arch=all # Run all OpenJ9 cells -./utils/run-containers-tests.sh --matrix --jdk=j9 --run +./utils/run-containers-tests.sh --jdk=j9 --arch=all --run # Show options ./utils/run-containers-tests.sh --help ``` Supported options: -- `--libc=glibc|musl|all` (default: glibc; `all` only with `--matrix`) -- `--jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|j9|graal|regular|all` (default: 21; groups/`all` only with `--matrix`) -- `--arch=x64|aarch64|all` (default: auto-detect; `all` only with `--matrix`) -- `--config=debug|release|asan|tsan|all` (default: debug; `all` only with `--matrix`) +- `--libc=glibc|musl|all[,..]` (default: glibc) +- `--jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|regular|j9|graal|all[,..]` (default: 21) +- `--arch=x64|aarch64|all[,..]` (default: auto-detect) +- `--config=debug|release|asan|tsan|all[,..]` (default: debug) - `--container=podman|docker` (default: podman) - `--tests="TestPattern"` - `--gtest` (enable C++ gtests, disabled by default for faster runs) @@ -198,11 +198,11 @@ Supported options: - `--mount` (mount local repo instead of cloning - faster but may have stale artifacts) - `--rebuild` (force rebuild of all container images) - `--rebuild-base` (force rebuild of base image only) -- `--matrix` (preview a test matrix; unset dimensions expand to `all`) -- `--run` (execute matrix mode; invalid without `--matrix`) +- `--matrix` (preview a full test matrix; unset dimensions expand to `all`) +- `--run` (execute an inferred matrix without prompting) - `--fail-fast` (stop matrix execution on first failure) -Without `--matrix`, the script keeps its single-cell behavior and runs one configuration immediately. With `--matrix`, the script previews the expanded matrix by default and only runs containers when `--run` is present. Matrix execution writes summaries to `build/reports/container-matrix/summary.md` and `build/reports/container-matrix/summary.json`. +Single-value commands run one configuration immediately. When any dimension expands to multiple cells, the script prints a compact status table first; interactive terminals ask for confirmation, while non-interactive runs require `--run` to execute. Matrix execution prints the status table again after all cells finish and writes summaries to `build/reports/container-matrix/summary.md` and `build/reports/container-matrix/summary.json`. ## Unwinding Validation Tool diff --git a/utils/README.md b/utils/README.md index d4d19cc7f..334648f8a 100644 --- a/utils/README.md +++ b/utils/README.md @@ -84,11 +84,11 @@ Runs tests in containers across various OS/libc/JDK combinations, mirroring the **Usage:** ```bash ./utils/run-containers-tests.sh [options] - --libc=glibc|musl|all (default: glibc; all only with --matrix) - --jdk=8|11|17|21|25|8-j9|...|j9|graal|regular|all - (default: 21; groups/all only with --matrix) - --arch=x64|aarch64|all (default: auto-detect; all only with --matrix) - --config=debug|release|asan|tsan|all (default: debug; all only with --matrix) + --libc=glibc|musl|all[,..] (default: glibc) + --jdk=8|11|17|21|25|8-j9|...|regular|j9|graal|all[,..] + (default: 21) + --arch=x64|aarch64|all[,..] (default: auto-detect) + --config=debug|release|asan|tsan|all[,..] (default: debug) --container=podman|docker (default: podman) --tests="TestPattern" (optional) --gtest (enable C++ gtests) @@ -96,8 +96,8 @@ Runs tests in containers across various OS/libc/JDK combinations, mirroring the --shell (drop to shell instead of running tests) --mount (mount local repo instead of cloning) --rebuild (force rebuild of container images) - --matrix (preview a test matrix) - --run (execute matrix mode) + --matrix (preview a full matrix) + --run (execute an inferred matrix without prompting) --fail-fast (stop matrix execution on first failure) ``` @@ -109,20 +109,17 @@ Examples: # Use Docker instead of the default Podman runtime ./utils/run-containers-tests.sh --container=docker --libc=glibc --jdk=21 -# Preview the full supported local matrix without running containers -./utils/run-containers-tests.sh --matrix +# Preview selected JDKs across every supported libc/architecture pair +./utils/run-containers-tests.sh --libc=all --jdk=8,17,21 --arch=all -# Run all supported musl cells (sanitizer configs are skipped, matching CI policy) -./utils/run-containers-tests.sh --matrix --libc=musl --run - -# Run all aarch64 debug cells -./utils/run-containers-tests.sh --matrix --arch=aarch64 --config=debug --run +# Run all supported musl cells without an interactive prompt +./utils/run-containers-tests.sh --libc=musl --jdk=all --arch=all --run # Run all OpenJ9 cells -./utils/run-containers-tests.sh --matrix --jdk=j9 --run +./utils/run-containers-tests.sh --jdk=j9 --arch=all --run ``` -Without `--matrix`, the script keeps its single-cell behavior and runs one container test configuration immediately. With `--matrix`, unset dimensions expand to `all`; the command previews the expanded matrix by default and only executes when `--run` is also provided. Matrix execution writes summaries to `build/reports/container-matrix/summary.md` and `build/reports/container-matrix/summary.json`. +Single-value commands run one container test configuration immediately. When any dimension expands to multiple cells, the script prints a compact status table first; interactive terminals ask for confirmation, while non-interactive runs require `--run` to execute. Matrix execution prints the status table again after all cells finish and writes summaries to `build/reports/container-matrix/summary.md` and `build/reports/container-matrix/summary.json`. ### `patch-dd-java-agent.sh` diff --git a/utils/run-containers-tests.sh b/utils/run-containers-tests.sh index 82e2880a6..f120210ad 100755 --- a/utils/run-containers-tests.sh +++ b/utils/run-containers-tests.sh @@ -8,11 +8,11 @@ # 2. JDK-specific image on top (java-profiler-test:-jdk-) # # Usage: ./utils/run-containers-tests.sh [options] -# --libc=glibc|musl|all (default: glibc; all only with --matrix) -# --jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|j9|graal|regular|all -# (default: 21; groups/all only with --matrix) -# --arch=x64|aarch64|all (default: auto-detect; all only with --matrix) -# --config=debug|release|asan|tsan|all (default: debug; all only with --matrix) +# --libc=glibc|musl|all[,..] (default: glibc) +# --jdk=8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|regular|j9|graal|all[,..] +# (default: 21) +# --arch=x64|aarch64|all[,..] (default: auto-detect) +# --config=debug|release|asan|tsan|all[,..] (default: debug) # --container=podman|docker (default: podman) # --tests="TestPattern" (optional, specific test to run) # --gtest (enable C++ gtests, disabled by default) @@ -21,8 +21,8 @@ # --mount (mount local repo instead of cloning - faster but may have stale artifacts) # --rebuild (force rebuild of container images) # --rebuild-base (force rebuild of base image only) -# --matrix (preview a test matrix; add --run to execute) -# --run (execute matrix mode; invalid without --matrix) +# --matrix (preview a full matrix; unset dimensions expand to all) +# --run (execute an inferred matrix without prompting) # --fail-fast (stop matrix execution on first failure) # --help (show this help) @@ -204,6 +204,14 @@ contains_value() { return 1 } +append_unique_line() { + local array_name=$1 + local value=$2 + + eval "contains_value \"\$value\" \"\${${array_name}[@]}\"" && return + eval "$array_name+=(\"\$value\")" +} + read_lines_into_array() { local array_name=$1 local line @@ -213,20 +221,43 @@ read_lines_into_array() { done } +read_string_into_array() { + local array_name=$1 + local values=$2 + local line + eval "$array_name=()" + while IFS= read -r line; do + eval "$array_name+=(\"\$line\")" + done <<< "$values" +} + expand_dimension() { local value=$1 local name=$2 shift 2 local all_values=("$@") + local expanded=() + local token + + IFS=',' read -ra tokens <<< "$value" + for token in "${tokens[@]}"; do + if [[ -z "$token" ]]; then + echo "Error: --$name contains an empty value" >&2 + exit 1 + elif [[ "$token" == "all" ]]; then + local all_value + for all_value in "${all_values[@]}"; do + append_unique_line expanded "$all_value" + done + elif contains_value "$token" "${all_values[@]}"; then + append_unique_line expanded "$token" + else + echo "Error: --$name must be one or more of: $(join_by "|" "${all_values[@]}")|all" >&2 + exit 1 + fi + done - if [[ "$value" == "all" ]]; then - printf "%s\n" "${all_values[@]}" - elif contains_value "$value" "${all_values[@]}"; then - printf "%s\n" "$value" - else - echo "Error: --$name must be one of: $(join_by "|" "${all_values[@]}")" >&2 - exit 1 - fi + printf "%s\n" "${expanded[@]}" } expand_jdk_dimension() { @@ -235,29 +266,52 @@ expand_jdk_dimension() { local j9_jdks=(8-j9 11-j9 17-j9 21-j9) local graal_jdks=(17-graal 21-graal 25-graal) local all_jdks=("${regular_jdks[@]}" "${j9_jdks[@]}" "${graal_jdks[@]}") - - case "$value" in - all) - printf "%s\n" "${all_jdks[@]}" - ;; - regular) - printf "%s\n" "${regular_jdks[@]}" - ;; - j9) - printf "%s\n" "${j9_jdks[@]}" - ;; - graal) - printf "%s\n" "${graal_jdks[@]}" - ;; - *) - if contains_value "$value" "${all_jdks[@]}"; then - printf "%s\n" "$value" - else - echo "Error: --jdk must be one of: 8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|j9|graal|regular|all" >&2 + local expanded=() + local token + + IFS=',' read -ra tokens <<< "$value" + for token in "${tokens[@]}"; do + case "$token" in + "") + echo "Error: --jdk contains an empty value" >&2 exit 1 - fi - ;; - esac + ;; + all) + local all_jdk + for all_jdk in "${all_jdks[@]}"; do + append_unique_line expanded "$all_jdk" + done + ;; + regular) + local regular_jdk + for regular_jdk in "${regular_jdks[@]}"; do + append_unique_line expanded "$regular_jdk" + done + ;; + j9) + local j9_jdk + for j9_jdk in "${j9_jdks[@]}"; do + append_unique_line expanded "$j9_jdk" + done + ;; + graal) + local graal_jdk + for graal_jdk in "${graal_jdks[@]}"; do + append_unique_line expanded "$graal_jdk" + done + ;; + *) + if contains_value "$token" "${all_jdks[@]}"; then + append_unique_line expanded "$token" + else + echo "Error: --jdk must be one or more of: 8|11|17|21|25|8-j9|11-j9|17-j9|21-j9|17-graal|21-graal|25-graal|regular|j9|graal|all" >&2 + exit 1 + fi + ;; + esac + done + + printf "%s\n" "${expanded[@]}" } skip_reason() { @@ -288,13 +342,252 @@ matrix_command() { printf "%q " "${cmd[@]}" } +cell_status_symbol() { + local status=$1 + local style=${2:-text} + + case "$status" in + pending) + printf "TODO" + ;; + passed) + if [[ "$style" == "emoji" ]]; then + printf "✅" + else + printf "PASS" + fi + ;; + failed) + if [[ "$style" == "emoji" ]]; then + printf "❌" + else + printf "FAIL" + fi + ;; + skipped) + printf "-" + ;; + cancelled) + if [[ "$style" == "emoji" ]]; then + printf "🚫" + else + printf "CANCEL" + fi + ;; + *) + printf "%s" "$status" + ;; + esac +} + +matrix_column_name() { + local libc=$1 + local arch=$2 + local config=$3 + local arch_label=$arch + + if [[ "$arch_label" == "x64" ]]; then + arch_label="amd64" + fi + + printf "%s-%s/%s" "$libc" "$arch_label" "$config" +} + +repeat_char() { + local char=$1 + local count=$2 + local i + + for ((i = 0; i < count; i++)); do + printf "%s" "$char" + done +} + +display_width() { + local value=$1 + + case "$value" in + "✅"|"❌"|"🚫") + printf "2" + ;; + *) + printf "%d" "${#value}" + ;; + esac +} + +print_padded() { + local value=$1 + local width=$2 + local visible_width + local padding + + visible_width=$(display_width "$value") + padding=$((width - visible_width)) + printf "%s" "$value" + if (( padding > 0 )); then + repeat_char " " "$padding" + fi +} + +print_table_separator() { + local jdk_width=$1 + local status_width=$2 + local columns=$3 + local i + + printf "+" + repeat_char "-" "$((jdk_width + 2))" + printf "+" + for ((i = 0; i < columns; i++)); do + repeat_char "-" "$((status_width + 2))" + printf "+" + done + printf "\n" +} + +print_matrix_status_table() { + local style=${1:-text} + local i row col cell_value + local columns=() + local rows=() + local jdk_width=3 + local status_width=4 + + for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do + append_unique_line columns "$(matrix_column_name "${MATRIX_CELL_LIBCS[$i]}" "${MATRIX_CELL_ARCHES[$i]}" "${MATRIX_CELL_CONFIGS[$i]}")" + append_unique_line rows "${MATRIX_CELL_JDKS[$i]}" + done + + for row in "${rows[@]}"; do + if (( ${#row} > jdk_width )); then + jdk_width=${#row} + fi + done + for col in "${columns[@]}"; do + if (( ${#col} > status_width )); then + status_width=${#col} + fi + done + + echo "Status overview" + echo + print_table_separator "$jdk_width" "$status_width" "${#columns[@]}" + printf "| " + print_padded "JDK" "$jdk_width" + printf " |" + for col in "${columns[@]}"; do + printf " " + print_padded "$col" "$status_width" + printf " |" + done + printf "\n" + print_table_separator "$jdk_width" "$status_width" "${#columns[@]}" + + for row in "${rows[@]}"; do + printf "| " + print_padded "$row" "$jdk_width" + printf " |" + for ((j = 0; j < ${#columns[@]}; j++)); do + col="${columns[$j]}" + cell_value="" + for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do + if [[ "${MATRIX_CELL_JDKS[$i]}" == "$row" ]] && [[ "$(matrix_column_name "${MATRIX_CELL_LIBCS[$i]}" "${MATRIX_CELL_ARCHES[$i]}" "${MATRIX_CELL_CONFIGS[$i]}")" == "$col" ]]; then + cell_value=$(cell_status_symbol "${MATRIX_CELL_STATUSES[$i]}" "$style") + break + fi + done + printf " " + print_padded "${cell_value:--}" "$status_width" + printf " |" + done + printf "\n" + done + print_table_separator "$jdk_width" "$status_width" "${#columns[@]}" +} + +print_matrix_legend() { + echo "Legend: ✅ passed | ❌ failed | - skipped | 🚫 cancelled" +} + +print_failed_cells() { + local i has_failures=false + + for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do + if [[ "${MATRIX_CELL_STATUSES[$i]}" == "failed" ]]; then + if ! $has_failures; then + echo + echo "Failed cells:" + has_failures=true + fi + echo "- jdk=${MATRIX_CELL_JDKS[$i]}, libc=${MATRIX_CELL_LIBCS[$i]}, arch=${MATRIX_CELL_ARCHES[$i]}, config=${MATRIX_CELL_CONFIGS[$i]}, exit_code=${MATRIX_CELL_EXIT_CODES[$i]}" + fi + done +} + +prepare_matrix_cells() { + local libc arch jdk config reason + + MATRIX_TOTAL=0 + MATRIX_RUNNABLE=0 + MATRIX_SKIPPED=0 + MATRIX_CELL_LIBCS=() + MATRIX_CELL_ARCHES=() + MATRIX_CELL_JDKS=() + MATRIX_CELL_CONFIGS=() + MATRIX_CELL_STATUSES=() + MATRIX_CELL_EXIT_CODES=() + MATRIX_CELL_REASONS=() + + for libc in "${MATRIX_LIBCS[@]}"; do + for arch in "${MATRIX_ARCHES[@]}"; do + for jdk in "${MATRIX_JDKS[@]}"; do + for config in "${MATRIX_CONFIGS[@]}"; do + ((MATRIX_TOTAL += 1)) + reason=$(skip_reason "$libc" "$jdk" "$config") + if [[ -n "$reason" ]]; then + ((MATRIX_SKIPPED += 1)) + MATRIX_CELL_STATUSES+=("skipped") + MATRIX_CELL_EXIT_CODES+=("null") + MATRIX_CELL_REASONS+=("$reason") + else + ((MATRIX_RUNNABLE += 1)) + MATRIX_CELL_STATUSES+=("pending") + MATRIX_CELL_EXIT_CODES+=("null") + MATRIX_CELL_REASONS+=("") + fi + MATRIX_CELL_LIBCS+=("$libc") + MATRIX_CELL_ARCHES+=("$arch") + MATRIX_CELL_JDKS+=("$jdk") + MATRIX_CELL_CONFIGS+=("$config") + done + done + done + done +} + +print_matrix_preview() { + echo "=== Container Test Matrix ===" + echo "LIBC filter: $LIBC" + echo "Arch filter: $ARCH" + echo "JDK filter: $JDK_VERSION" + echo "Config filter: $CONFIG" + echo "Runtime: $CONTAINER_RUNTIME" + echo "Mode: $(if $RUN_MATRIX; then echo 'run'; else echo 'preview'; fi)" + echo "=============================" + echo "Generated cells: $MATRIX_TOTAL" + echo "Runnable cells: $MATRIX_RUNNABLE" + echo "Skipped cells: $MATRIX_SKIPPED" + echo + print_matrix_status_table emoji +} + write_matrix_reports() { local start_time=$1 local end_time=$2 - local total=$3 - local passed=$4 - local failed=$5 - local skipped=$6 + local passed=$3 + local failed=$4 + local skipped=$5 local report_dir="$PROJECT_ROOT/build/reports/container-matrix" local markdown_report="$report_dir/summary.md" local json_report="$report_dir/summary.json" @@ -309,7 +602,11 @@ write_matrix_reports() { echo "- Started: $start_time" echo "- Finished: $end_time" echo "- Filters: libc=$LIBC, arch=$ARCH, jdk=$JDK_VERSION, config=$CONFIG" - echo "- Totals: total=$total, passed=$passed, failed=$failed, skipped=$skipped" + echo "- Totals: total=$MATRIX_TOTAL, passed=$passed, failed=$failed, skipped=$skipped" + echo + print_matrix_status_table emoji + print_matrix_legend + print_failed_cells echo echo "## Cells" for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do @@ -325,7 +622,7 @@ write_matrix_reports() { printf ' "filters": {"libc": "%s", "arch": "%s", "jdk": "%s", "config": "%s"},\n' \ "$(json_escape "$LIBC")" "$(json_escape "$ARCH")" "$(json_escape "$JDK_VERSION")" "$(json_escape "$CONFIG")" printf ' "totals": {"total": %d, "passed": %d, "failed": %d, "skipped": %d},\n' \ - "$total" "$passed" "$failed" "$skipped" + "$MATRIX_TOTAL" "$passed" "$failed" "$skipped" echo ' "cells": [' for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do printf ' {"libc": "%s", "arch": "%s", "jdk": "%s", "config": "%s", "status": "%s", "exit_code": %s, "reason": "%s"}' \ @@ -351,84 +648,41 @@ write_matrix_reports() { echo ">>> $json_report" } -run_matrix() { - local matrix_libcs matrix_arches matrix_jdks matrix_configs - local libc arch jdk config reason command - local total=0 runnable=0 skipped=0 passed=0 failed=0 exit_code=0 overall_exit=0 - local start_time end_time - local cell_cmd - - read_lines_into_array matrix_libcs < <(expand_dimension "$LIBC" "libc" glibc musl) - read_lines_into_array matrix_arches < <(expand_dimension "$ARCH" "arch" x64 aarch64) - read_lines_into_array matrix_jdks < <(expand_jdk_dimension "$JDK_VERSION") - read_lines_into_array matrix_configs < <(expand_dimension "$CONFIG" "config" debug release asan tsan) +confirm_matrix_run() { + local answer - MATRIX_CELL_LIBCS=() - MATRIX_CELL_ARCHES=() - MATRIX_CELL_JDKS=() - MATRIX_CELL_CONFIGS=() - MATRIX_CELL_STATUSES=() - MATRIX_CELL_EXIT_CODES=() - MATRIX_CELL_REASONS=() - - echo "=== Container Test Matrix ===" - echo "LIBC filter: $LIBC" - echo "Arch filter: $ARCH" - echo "JDK filter: $JDK_VERSION" - echo "Config filter: $CONFIG" - echo "Runtime: $CONTAINER_RUNTIME" - echo "Mode: $(if $RUN_MATRIX; then echo 'run'; else echo 'preview'; fi)" - echo "=============================" - - for libc in "${matrix_libcs[@]}"; do - for arch in "${matrix_arches[@]}"; do - for jdk in "${matrix_jdks[@]}"; do - for config in "${matrix_configs[@]}"; do - ((total += 1)) - reason=$(skip_reason "$libc" "$jdk" "$config") - if [[ -n "$reason" ]]; then - ((skipped += 1)) - MATRIX_CELL_LIBCS+=("$libc") - MATRIX_CELL_ARCHES+=("$arch") - MATRIX_CELL_JDKS+=("$jdk") - MATRIX_CELL_CONFIGS+=("$config") - MATRIX_CELL_STATUSES+=("skipped") - MATRIX_CELL_EXIT_CODES+=("null") - MATRIX_CELL_REASONS+=("$reason") - continue - fi + if $RUN_MATRIX; then + return 0 + fi - ((runnable += 1)) - MATRIX_CELL_LIBCS+=("$libc") - MATRIX_CELL_ARCHES+=("$arch") - MATRIX_CELL_JDKS+=("$jdk") - MATRIX_CELL_CONFIGS+=("$config") - MATRIX_CELL_STATUSES+=("pending") - MATRIX_CELL_EXIT_CODES+=("null") - MATRIX_CELL_REASONS+=("") - done - done - done - done + if [[ -t 0 ]]; then + echo + read -r -p "Run this matrix? [y/N] " answer + case "$answer" in + y|Y|yes|YES) + return 0 + ;; + *) + echo "Preview only. Matrix was not run." + return 1 + ;; + esac + fi - echo "Generated cells: $total" - echo "Runnable cells: $runnable" - echo "Skipped cells: $skipped" echo + echo "Preview only. Add --run to execute non-interactively." + return 1 +} - for ((i = 0; i < ${#MATRIX_CELL_LIBCS[@]}; i++)); do - if [[ "${MATRIX_CELL_STATUSES[$i]}" == "skipped" ]]; then - echo "SKIP libc=${MATRIX_CELL_LIBCS[$i]} arch=${MATRIX_CELL_ARCHES[$i]} jdk=${MATRIX_CELL_JDKS[$i]} config=${MATRIX_CELL_CONFIGS[$i]} reason=${MATRIX_CELL_REASONS[$i]}" - else - cell_cmd=$(matrix_command "${MATRIX_CELL_LIBCS[$i]}" "${MATRIX_CELL_ARCHES[$i]}" "${MATRIX_CELL_JDKS[$i]}" "${MATRIX_CELL_CONFIGS[$i]}") - echo "RUN $cell_cmd" - fi - done +run_matrix() { + local i j command exit_code + local passed=0 failed=0 skipped=$MATRIX_SKIPPED overall_exit=0 + local start_time end_time - if ! $RUN_MATRIX; then - echo - echo "Preview only. Add --run to execute." - exit 0 + if ! command -v "$CONTAINER_RUNTIME" >/dev/null 2>&1; then + echo "Error: container runtime '$CONTAINER_RUNTIME' not found" + echo "Use --container to select the runtime, e.g. $0 --container=docker ..." + exit 1 fi start_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") @@ -475,7 +729,7 @@ run_matrix() { set -e end_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - write_matrix_reports "$start_time" "$end_time" "$total" "$passed" "$failed" "$skipped" + write_matrix_reports "$start_time" "$end_time" "$passed" "$failed" "$skipped" echo echo "=== Matrix Result ===" @@ -483,6 +737,11 @@ run_matrix() { echo "Failed: $failed" echo "Skipped: $skipped" echo "=====================" + echo + print_matrix_status_table emoji + echo + print_matrix_legend + print_failed_cells exit "$overall_exit" } @@ -590,33 +849,6 @@ if $MATRIX_MODE; then if ! $CONFIG_SET; then CONFIG="all" fi - - if $SHELL_MODE; then - echo "Error: --shell is interactive and is not supported with --matrix. Run a single cell without --matrix instead." - exit 1 - fi - - if [[ -n "$GTEST_TASK" && -n "$TESTS" ]]; then - echo "Error: --tests cannot be combined with --gtest-task" - exit 1 - fi - - if [[ "$CONTAINER_RUNTIME" != "podman" && "$CONTAINER_RUNTIME" != "docker" ]]; then - echo "Error: --container must be 'podman' or 'docker'" - exit 1 - fi - - run_matrix -fi - -if $RUN_MATRIX; then - echo "Error: --run is only valid with --matrix" - exit 1 -fi - -if $FAIL_FAST; then - echo "Error: --fail-fast is only valid with --matrix" - exit 1 fi # Auto-detect architecture if not specified @@ -624,29 +856,47 @@ if [[ -z "$ARCH" ]]; then ARCH=$(detect_arch) fi -# Validate arguments -if [[ "$LIBC" != "musl" && "$LIBC" != "glibc" ]]; then - echo "Error: --libc must be 'musl' or 'glibc'" +if [[ "$CONTAINER_RUNTIME" != "podman" && "$CONTAINER_RUNTIME" != "docker" ]]; then + echo "Error: --container must be 'podman' or 'docker'" exit 1 fi -if [[ "$ARCH" != "x64" && "$ARCH" != "aarch64" ]]; then - echo "Error: --arch must be 'x64' or 'aarch64'" +if [[ -n "$GTEST_TASK" && -n "$TESTS" ]]; then + echo "Error: --tests cannot be combined with --gtest-task" exit 1 fi -if [[ "$CONFIG" != "debug" && "$CONFIG" != "release" && "$CONFIG" != "asan" && "$CONFIG" != "tsan" ]]; then - echo "Error: --config must be 'debug', 'release', 'asan', or 'tsan'" +MATRIX_LIBCS_TEXT=$(expand_dimension "$LIBC" "libc" glibc musl) +MATRIX_ARCHES_TEXT=$(expand_dimension "$ARCH" "arch" x64 aarch64) +MATRIX_JDKS_TEXT=$(expand_jdk_dimension "$JDK_VERSION") +MATRIX_CONFIGS_TEXT=$(expand_dimension "$CONFIG" "config" debug release asan tsan) +read_string_into_array MATRIX_LIBCS "$MATRIX_LIBCS_TEXT" +read_string_into_array MATRIX_ARCHES "$MATRIX_ARCHES_TEXT" +read_string_into_array MATRIX_JDKS "$MATRIX_JDKS_TEXT" +read_string_into_array MATRIX_CONFIGS "$MATRIX_CONFIGS_TEXT" +prepare_matrix_cells + +if (( MATRIX_RUNNABLE == 0 )); then + print_matrix_preview + echo + echo "Error: matrix has no runnable cells" exit 1 fi -if [[ "$CONTAINER_RUNTIME" != "podman" && "$CONTAINER_RUNTIME" != "docker" ]]; then - echo "Error: --container must be 'podman' or 'docker'" - exit 1 +if (( MATRIX_TOTAL > 1 || MATRIX_RUNNABLE > 1 || MATRIX_MODE )); then + if $SHELL_MODE; then + echo "Error: --shell is interactive and is not supported with matrix runs. Run a single cell without matrix dimensions instead." + exit 1 + fi + print_matrix_preview + if confirm_matrix_run; then + run_matrix + fi + exit 0 fi -if [[ -n "$GTEST_TASK" && -n "$TESTS" ]]; then - echo "Error: --tests cannot be combined with --gtest-task" +if $FAIL_FAST; then + echo "Error: --fail-fast is only valid when the command expands to a matrix" exit 1 fi @@ -656,6 +906,11 @@ if ! command -v "$CONTAINER_RUNTIME" >/dev/null 2>&1; then exit 1 fi +LIBC="${MATRIX_CELL_LIBCS[0]}" +ARCH="${MATRIX_CELL_ARCHES[0]}" +JDK_VERSION="${MATRIX_CELL_JDKS[0]}" +CONFIG="${MATRIX_CELL_CONFIGS[0]}" + # Parse JDK version and variant (e.g., "21-j9" -> version="21", variant="j9") JDK_BASE_VERSION="${JDK_VERSION%%-*}" JDK_VARIANT="${JDK_VERSION#*-}"