diff --git a/.claude/skills/build-compilation-dependencies/SKILL.md b/.claude/skills/build-compilation-dependencies/SKILL.md index 82e68615b..d7cc4202f 100644 --- a/.claude/skills/build-compilation-dependencies/SKILL.md +++ b/.claude/skills/build-compilation-dependencies/SKILL.md @@ -42,7 +42,7 @@ react-native-audio-api/ │ │ └── include_ffmpeg/ # Headers for FFmpeg │ ├── common/cpp/test/ │ │ ├── CMakeLists.txt # Standalone test build (no Android/iOS) -│ │ ├── RunTests.sh # Test runner script +│ │ ├── run-tests.sh / filters.sh # smoke|extended|full (+ categories, CI path filters) │ │ └── src/ # Google Test files │ ├── RNAudioAPI.podspec # CocoaPods spec for iOS │ └── scripts/ @@ -233,10 +233,11 @@ CI intentionally skips native Android/iOS builds (expensive). Use the tiered loc ```bash yarn validate:fast # CI parity (format, lint, typecheck, enum sync, build, C++ + JS tests) -yarn validate:graph # graph tests + ASan (optional; graph path changes) +yarn validate:cpp # C++ smoke +yarn validate:cpp-extended # C++ extended (all categories) yarn validate:android # yarn workspace … build:android yarn validate:ios # yarn workspace … build:ios (macOS only) -yarn validate:full # --fast + --android + --ios +yarn validate:full # --fast + C++ extended + --android + --ios ``` Script: [`scripts/validate.sh`](../../../scripts/validate.sh) at monorepo root. @@ -246,10 +247,10 @@ Script: [`scripts/validate.sh`](../../../scripts/validate.sh) at monorepo root. | Layer | CI (`ci.yml` + `tests.yml`) | Local tiers | |---|---|---| | TS build (`bob build`) | Yes | `--fast` | -| C++ test subset (`RunTests.sh`) | Yes | `--fast` | -| C++ coverage (`RunCoverage.sh`, Clang) | Yes (`cpp-coverage` artifact) | `yarn test:cpp:coverage` | +| C++ smoke (`run-tests.sh`) | Yes | `--fast` | +| C++ coverage (`run-coverage.sh`, smoke, Clang) | Yes (`cpp-coverage` artifact) | `yarn test:cpp:coverage` | | Jest | Yes | `--fast` | -| Graph tests | No, path-filtered in `graph-tests.yml` | `--graph` | +| Extended C++ by category (e.g. graph) | Path change or manual dispatch in `tests.yml` | `--cpp-extended` | | HostObjects (26 JSI `.cpp` files) | **No** | `--android` + `--ios` | | Android JNI C++ + Kotlin | **No** | `--android` | | iOS ObjC++ | **No** | `--ios` | @@ -272,7 +273,7 @@ Android (NDK) and iOS (Clang) cannot share object files — reuse is at the preb - `--ios` on Linux → skip with message (exit 0) - `--android` without `ANDROID_HOME` → fail on explicit `--android`; skip with warning inside `--full` -- Graph tests are separate from `--full` (slow; CI path-filters them) +- `--full` includes C++ extended (all categories) after `--fast`’s smoke, so local full covers C++ full + native builds ### Which tier to run @@ -287,18 +288,12 @@ See the decision table in [post-work-checks](../post-work-checks/SKILL.md). ### How to run ```bash -yarn test # from monorepo root — runs RunTests.sh +yarn test # Jest + C++ smoke +yarn workspace react-native-audio-api test:cpp:smoke|extended|full +yarn workspace react-native-audio-api test:cpp:extended -- graph ``` -`RunTests.sh` does: -```bash -cd packages/react-native-audio-api/common/cpp/test -cmake -S . -B build -Wno-dev -cd build && make -j10 -./tests --gtest_print_time=1 -``` - -The `build/` directory is deleted after each run. +`run-tests.sh [smoke|extended|full] [category…] [--ubasan|--tsan|--no-ubasan]` uses filters from `filters.sh`. Docs: `common/cpp/test/TESTING.md`. Shell scripts in this repo use kebab-case plus `.sh` (`run-tests.sh`, not `RunTests.sh`). ### Coverage (Clang / llvm-cov) @@ -307,9 +302,9 @@ yarn workspace react-native-audio-api test:cpp:coverage # open packages/react-native-audio-api/common/cpp/test/coverage-html/index.html ``` -`RunCoverage.sh` configures a separate `build-coverage/` tree with `-DENABLE_COVERAGE=ON` (Clang-only LLVM source-based coverage: `-fprofile-instr-generate -fcoverage-mapping`), defaults `CC`/`CXX` to `clang`/`clang++` when unset, runs the same gtest filter as `RunTests.sh`, then prints `llvm-cov report` and writes HTML via `llvm-cov show -format=html`. When `GITHUB_STEP_SUMMARY` is set, the report is also appended there. Sanitizer targets are skipped when coverage is enabled. Requires Apple Clang / `xcrun llvm-profdata` and `xcrun llvm-cov` on macOS (or the same tools on PATH for Linux). +`run-coverage.sh` configures a separate `build-coverage/` tree with `-DENABLE_COVERAGE=ON` (Clang-only LLVM source-based coverage: `-fprofile-instr-generate -fcoverage-mapping`), defaults `CC`/`CXX` to `clang`/`clang++` when unset, runs the **smoke** filter from `filters.sh`, then prints `llvm-cov report` and writes HTML via `llvm-cov show -format=html`. When `GITHUB_STEP_SUMMARY` is set, the report is also appended there. Sanitizer targets are skipped when coverage is enabled. Requires Apple Clang / `xcrun llvm-profdata` and `xcrun llvm-cov` on macOS (or the same tools on PATH for Linux). -CI runs a parallel `cpp-coverage` job via `.github/workflows/cpp-coverage-job.yml` (called from `tests.yml` on pull requests; Clang + LLVM apt packages, separate from the GCC `cpp-tests` job). It uploads the HTML tree as the `cpp-coverage-html` artifact (14-day retention); download the zip from the Actions run and open `index.html`. Manual `workflow_dispatch` on `tests.yml` accepts booleans `run_cpp_tests` / `run_cpp_coverage` / `run_js_tests` (default true); PRs always run all three. +CI runs a parallel `cpp-coverage` job via `.github/workflows/cpp-coverage-job.yml` (called from `tests.yml` on pull requests; Clang + LLVM apt packages, separate from the GCC `cpp-smoke-tests` job). It uploads the HTML tree as the `cpp-coverage-html` artifact (14-day retention); download the zip from the Actions run and open `index.html`. Manual `workflow_dispatch` on `tests.yml` accepts booleans `run_cpp_smoke_tests` / `run_cpp_coverage` / `run_js_tests` (default true); non-draft PRs always run all three, including when a draft is marked ready for review (`ready_for_review` is listed explicitly because it is not a default `pull_request` type). Extended categories share `cpp-job.yml` and pass `categories`; path-filter YAML is generated by `filters.sh path-filters`. > **Generated build trees must be named `build*`.** The C++ linters walk the filesystem with `find` and never consult git, so a `.gitignore` entry does not keep generated sources out of them. Exclusion happens by directory name in two places that must stay in sync: `**/build*/**` in `.clang-format-ignore` (used by `format:check:common`) and `-type d -name 'build*' -prune` in `scripts/cpplint.sh`. A CMake binary directory outside that prefix makes the pre-commit hook fail on generated files such as `CMakeFiles/*/CompilerIdCXX/CMakeCXXCompilerId.cpp`. CI never hits this because it checks out a clean tree. diff --git a/.claude/skills/build-compilation-dependencies/maintenance.md b/.claude/skills/build-compilation-dependencies/maintenance.md index a7e3bf44d..885df2da2 100644 --- a/.claude/skills/build-compilation-dependencies/maintenance.md +++ b/.claude/skills/build-compilation-dependencies/maintenance.md @@ -12,10 +12,12 @@ Review this skill when `pre-push-update` reports changes in: | `RNAudioAPI.podspec` | Subspecs table, `miniaudio_impl` workaround, `-force_load` list, xcframeworks list, `rnaa_utils.rb` dynamic paths | | `apps/fabric-example/ios/Podfile` | New Architecture enablement, minimum iOS version helper | | `common/cpp/test/CMakeLists.txt` | Excluded sources list, compile definitions, GoogleTest fetch URL, include paths, `ENABLE_COVERAGE` | -| `common/cpp/test/RunCoverage.sh` | Coverage build dir, Clang CC/CXX defaults, llvm-profdata/llvm-cov report+HTML, `GITHUB_STEP_SUMMARY`, ignore regexes | +| `common/cpp/test/run-coverage.sh` | Coverage build dir, Clang CC/CXX defaults, llvm-profdata/llvm-cov report+HTML, `GITHUB_STEP_SUMMARY`, ignore regexes, smoke filter via `filters.sh` | +| `common/cpp/test/filters.sh` / `run-tests.sh` | Mode/category gtest filters (smoke/extended/full), sanitizer flags, CI path-filter YAML (`path-filters`) | | `.github/workflows/cpp-coverage-job.yml` | Reusable coverage job (Clang/LLVM install, artifact `cpp-coverage-html`) | | `.github/workflows/js-job.yml` | Reusable JS integration tests job (draft-PR skip) | -| `.github/workflows/tests.yml` | Calls cpp/js/coverage jobs; `workflow_dispatch` booleans select jobs | +| `.github/workflows/ci.yml`, `tests.yml`, `wpt-tests.yml` | PR entry workflows that skip drafts; `ready_for_review` is listed (not a default type). Reusable jobs inherit it. | +| `.github/workflows/cpp-job.yml` | Shared C++ smoke + extended runner; path filters come from `filters.sh path-filters` | | `.clang-format-ignore` / `scripts/cpplint.sh` | Autogenerated files exclusions | | `common/cpp/test/src/MockAudioEventHandlerRegistry.h` | Mock interface — update fixture boilerplate in `build-details.md` if signature changes | | `scripts/download-prebuilt-binaries.sh` | New download artifacts, new TAG version | diff --git a/.claude/skills/post-work-checks/SKILL.md b/.claude/skills/post-work-checks/SKILL.md index 951f994fc..7433eda7e 100644 --- a/.claude/skills/post-work-checks/SKILL.md +++ b/.claude/skills/post-work-checks/SKILL.md @@ -19,14 +19,15 @@ Run these checks after any code change and before opening a PR. ## Quick Reference — Local Validation Tiers -CI always runs the **fast** tier (format, lint, typecheck, enum sync, TS build, C++ + JS tests). Graph tests run in a separate path-filtered workflow (`graph-tests.yml`). CI does **not** build Android Gradle, iOS pods, or example apps. Use `validate.sh` locally to close that gap: +CI always runs the **fast** tier (format, lint, typecheck, enum sync, TS build, C++ smoke + JS tests). Extended C++ categories (e.g. graph) run from `tests.yml` when matching paths change, or via `workflow_dispatch` booleans. CI does **not** build Android Gradle, iOS pods, or example apps. Use `validate.sh` locally to close that gap: ```bash -yarn validate:fast # CI parity — always run before opening a PR -yarn validate:graph # graph tests (when graph/audio-thread code changes) -yarn validate:android # Android native build (requires ANDROID_HOME) -yarn validate:ios # iOS native build (macOS only) -yarn validate:full # --fast + --android + --ios (skips unavailable platforms) +yarn validate:fast # CI parity — always run before opening a PR +yarn validate:cpp # C++ smoke +yarn validate:cpp-extended # C++ extended (all categories) +yarn validate:android # Android native build (requires ANDROID_HOME) +yarn validate:ios # iOS native build (macOS only) +yarn validate:full # --fast + C++ extended + --android + --ios ``` Equivalent: `./scripts/validate.sh --fast` (etc.) @@ -42,7 +43,7 @@ Equivalent: `./scripts/validate.sh --fast` (etc.) | `ios/audioapi/` | `--fast` + `--ios` | | `CMakeLists.txt`, `build.gradle`, `podspec` | `--full` | -Graph changes under `common/cpp/audioapi/core/utils/graph/` → also run `yarn validate:graph`. +Graph changes under `common/cpp/audioapi/core/utils/graph/` → also run `yarn validate:cpp-extended` or `yarn workspace react-native-audio-api test:cpp:extended -- graph`. --- @@ -73,7 +74,7 @@ Hooks run when lefthook is installed (`lefthook install`). **If a hook fails, the commit is aborted.** Fix the issue and re-commit — do NOT use `--no-verify`. -There is no pre-push hook — `yarn validate:fast` (and native/graph tiers) are run manually before opening a PR. Native builds (`validate:android`, `validate:ios`, `validate:full`) and graph tests (`validate:graph`) are never run by lefthook. +There is no pre-push hook — `yarn validate:fast` (and native/C++ extended tiers) are run manually before opening a PR. Native builds (`validate:android`, `validate:ios`, `validate:full`) and extended C++ (`validate:cpp-extended`) are never run by lefthook. --- @@ -92,24 +93,27 @@ yarn validate:fast ```bash yarn validate:android # yarn workspace … build:android (~3–4 min) yarn validate:ios # yarn workspace … build:ios (macOS only) -yarn validate:full # --fast + --android + --ios +yarn validate:full # --fast + C++ extended + --android + --ios ``` The Gradle project resolves through the `node_modules/react-native-audio-api` workspace symlink, so local edits in `packages/react-native-audio-api/` are picked up. -### Graph tests (when graph / audio-thread code changes) +### Extended graph (when graph / audio-thread code changes) ```bash -yarn validate:graph +yarn validate:cpp-extended # all extended categories +# or: yarn workspace react-native-audio-api test:cpp:extended -- graph ``` ### C++ tests only ```bash -yarn workspace react-native-audio-api run test:cpp +yarn validate:cpp # smoke via validate.sh +yarn workspace react-native-audio-api run test:cpp # smoke +yarn workspace react-native-audio-api run test:cpp:full # smoke + all extended ``` -**When**: after any change to `common/cpp/audioapi/core/`, `dsp/`, or `utils/` C++ files. Prefer this for a fast C++-only loop without running Jest; run `yarn validate:fast` before opening a PR. +**When**: after any change to `common/cpp/audioapi/core/`, `dsp/`, or `utils/` C++ files. Prefer smoke for a fast C++-only loop; see `common/cpp/test/TESTING.md`. Run `yarn validate:fast` before opening a PR. ### Library unit tests (JS + C++) @@ -162,7 +166,7 @@ Later steps may surface issues caused by earlier ones — run in this order: 2. `yarn lint` — catch remaining code issues 3. `yarn typecheck` — catch TypeScript errors 4. `yarn validate:fast` — full CI-parity gate (or `yarn test` / `test:cpp` for a quick local loop; always run `--fast` before opening a PR) -5. `yarn validate:graph` — when graph / audio-thread code changed +5. `yarn validate:cpp-extended` — when graph / audio-thread code changed 6. `yarn validate:android` / `yarn validate:ios` / `yarn validate:full` — when native code or build files changed (see decision table above) --- diff --git a/.claude/skills/post-work-checks/maintenance.md b/.claude/skills/post-work-checks/maintenance.md index 1073cb524..b4863c5e2 100644 --- a/.claude/skills/post-work-checks/maintenance.md +++ b/.claude/skills/post-work-checks/maintenance.md @@ -9,6 +9,6 @@ Review this skill when `pre-push-update` reports changes in: | Root `package.json` scripts | New or renamed lint/format/test/`validate:*` commands | | `packages/react-native-audio-api/package.json` scripts | Package-level command changes (including per-language lint/format) | | `lefthook.yml` | Pre-commit / commit-msg hook changes | -| `scripts/validate.sh` | Tier behavior (`--fast` / `--graph` / `--android` / `--ios` / `--full`), skip rules | +| `scripts/validate.sh` | Tier behavior (`--fast` / `--cpp-extended` / `--android` / `--ios` / `--full`), skip rules | | `scripts/check-audio-enum-sync*` or `packages/react-native-audio-api/scripts/check-audio-events-sync.sh` | Enum sync check details | -| `.github/workflows/ci.yml`, `tests.yml`, `graph-tests.yml` | What CI covers vs local validation tiers | +| `.github/workflows/ci.yml`, `tests.yml`, `cpp-job.yml` | What CI covers vs local validation tiers | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3051bc4ff..1fa0a3fb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ on: pull_request: branches: - main + types: [opened, synchronize, reopened, ready_for_review] merge_group: types: - checks_requested diff --git a/.github/workflows/cpp-job.yml b/.github/workflows/cpp-job.yml index 02be0f811..3bb3e9eda 100644 --- a/.github/workflows/cpp-job.yml +++ b/.github/workflows/cpp-job.yml @@ -12,13 +12,28 @@ on: working-directory: required: false type: string - default: '.' + default: packages/react-native-audio-api + force: + description: Skip path filter and always run (manual workflow_dispatch) + required: false + type: boolean + default: false + categories: + description: > + Space-separated extended categories. When set, path filters come from + filters.sh. Empty means always run (smoke). + required: false + type: string + default: '' jobs: run: name: ${{ inputs.name }} if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read defaults: run: working-directory: ${{ inputs.working-directory }} @@ -26,14 +41,55 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Compose path filters + if: ${{ inputs.force == false && inputs.categories != '' }} + env: + CATEGORIES: ${{ inputs.categories }} + working-directory: ${{ github.workspace }} + run: > + bash packages/react-native-audio-api/common/cpp/test/filters.sh + path-filters ${CATEGORIES} + > "$RUNNER_TEMP/cpp-path-filters.yml" + + - name: Path filter + if: ${{ inputs.force == false && inputs.categories != '' }} + uses: dorny/paths-filter@v3 + id: filter + with: + filters: ${{ runner.temp }}/cpp-path-filters.yml + + - name: Decide whether to run + id: decide + env: + FORCE: ${{ inputs.force }} + CATEGORIES: ${{ inputs.categories }} + FILTER_RUN: ${{ steps.filter.outputs.run }} + JOB_NAME: ${{ inputs.name }} + working-directory: ${{ github.workspace }} + run: | + if [[ "$FORCE" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + elif [[ -z "$CATEGORIES" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + elif [[ "$FILTER_RUN" == "true" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + echo "No matching path changes; skipping $JOB_NAME." + fi + - name: Checkout with Node + if: steps.decide.outputs.run == 'true' uses: ./.github/actions/checkout-with-node - name: Setup Yarn + if: steps.decide.outputs.run == 'true' uses: ./.github/actions/setup-yarn - name: Setup CMake + if: steps.decide.outputs.run == 'true' uses: ./.github/actions/setup-cmake - name: ${{ inputs.name }} + if: steps.decide.outputs.run == 'true' run: ${{ inputs.run }} diff --git a/.github/workflows/graph-tests.yml b/.github/workflows/graph-tests.yml deleted file mode 100644 index 011cb0f74..000000000 --- a/.github/workflows/graph-tests.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Graph Tests - -on: - pull_request: - paths: - - 'packages/react-native-audio-api/common/cpp/audioapi/core/utils/graph/**' - - 'packages/react-native-audio-api/common/cpp/test/src/graph/**' - -jobs: - graph-test: - uses: ./.github/workflows/cpp-job.yml - with: - name: Graph tests - working-directory: packages/react-native-audio-api/common/cpp/test - run: bash RunTestsGraph.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 599ee43e9..10b8296c5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,14 +2,19 @@ name: Tests on: pull_request: + types: [opened, synchronize, reopened, ready_for_review] workflow_dispatch: inputs: - run_cpp_tests: - description: Run C++ tests (GCC) + run_cpp_smoke_tests: + description: Run C++ smoke tests (GCC) type: boolean default: true + run_cpp_extended_graph: + description: Run C++ extended category graph + type: boolean + default: false run_cpp_coverage: - description: Run C++ coverage (Clang / llvm-cov) + description: Run C++ coverage (Clang / llvm-cov, smoke) type: boolean default: true run_js_tests: @@ -18,13 +23,21 @@ on: default: true jobs: - cpp-tests: - if: github.event_name == 'pull_request' || inputs.run_cpp_tests + cpp-smoke-tests: + if: github.event_name == 'pull_request' || inputs.run_cpp_smoke_tests + uses: ./.github/workflows/cpp-job.yml + with: + name: C++ smoke + run: yarn test:cpp:smoke + + cpp-extended-graph: + if: github.event_name == 'pull_request' || inputs.run_cpp_extended_graph uses: ./.github/workflows/cpp-job.yml with: - name: C++ tests - working-directory: packages/react-native-audio-api - run: yarn test:cpp + name: C++ extended (graph) + run: yarn test:cpp:extended -- graph + categories: graph + force: ${{ github.event_name == 'workflow_dispatch' }} cpp-coverage: if: github.event_name == 'pull_request' || inputs.run_cpp_coverage diff --git a/.github/workflows/wpt-tests.yml b/.github/workflows/wpt-tests.yml index 334eb5f06..a71e4ef7f 100644 --- a/.github/workflows/wpt-tests.yml +++ b/.github/workflows/wpt-tests.yml @@ -2,8 +2,6 @@ name: WPT Tests on: pull_request: - # ready_for_review is not a default type, but drafts are skipped below, so without it a - # pull request opened as a draft would never get a report until its next push. types: [opened, synchronize, reopened, ready_for_review] workflow_dispatch: diff --git a/package.json b/package.json index 8deec2876..80999f185 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "test": "yarn workspace react-native-audio-api run test", "check-audio-enum-sync": "bash packages/react-native-audio-api/scripts/check-audio-events-sync.sh", "validate:fast": "bash scripts/validate.sh --fast", - "validate:graph": "bash scripts/validate.sh --graph", + "validate:cpp": "bash scripts/validate.sh --cpp", + "validate:cpp-extended": "bash scripts/validate.sh --cpp-extended", + "validate:cpp-full": "bash scripts/validate.sh --cpp-full", "validate:android": "bash scripts/validate.sh --android", "validate:ios": "bash scripts/validate.sh --ios", "validate:full": "bash scripts/validate.sh --full", diff --git a/packages/react-native-audio-api/common/cpp/test/RunTests.sh b/packages/react-native-audio-api/common/cpp/test/RunTests.sh deleted file mode 100755 index b6e33a639..000000000 --- a/packages/react-native-audio-api/common/cpp/test/RunTests.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e - -cd common/cpp/test - -cmake -S . -B build -Wno-dev - -cd build -make -j10 - -GRAPH_FILTER="AudioGraphTest.*:AudioGraphFuzzTest.*:GraphTest.*:GraphFuzzTest.*:GraphCycleDebugTest.*:HostGraphTest.*:Seeds/*" -./tests --gtest_print_time=1 --gtest_filter="-${GRAPH_FILTER}" \ No newline at end of file diff --git a/packages/react-native-audio-api/common/cpp/test/RunTestsGraph.sh b/packages/react-native-audio-api/common/cpp/test/RunTestsGraph.sh deleted file mode 100755 index 658fc72c9..000000000 --- a/packages/react-native-audio-api/common/cpp/test/RunTestsGraph.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -set -e - -cleanup() { - echo "Cleaning up..." - rm -rf build/ -} - -trap cleanup EXIT - - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -cd "$SCRIPT_DIR" - -# Allow override of GRAPH_FILTER via environment variable -GRAPH_FILTER="${GRAPH_FILTER:-AudioGraphTest.*:AudioGraphFuzzTest.*:GraphTest.*:GraphFuzzTest.*:GraphCycleDebugTest.*:HostGraphTest.*:GraphNodeGrowthTest.*:Seeds/*}" - -cmake -S . -B build -Wno-dev - -cd build -make tests_asan tests -j10 - -echo "" -echo "=== Running graph tests with AddressSanitizer + UndefinedBehaviorSanitizer ===" -echo "" -./tests_asan --gtest_print_time=1 --gtest_filter="${GRAPH_FILTER}" "$@" - -echo "" -echo "=== Running graph tests (normal) ===" -echo "" -./tests --gtest_print_time=1 --gtest_filter="${GRAPH_FILTER}" "$@" - -cd .. - -rm -rf build/ diff --git a/packages/react-native-audio-api/common/cpp/test/RunTestsGraphDocker.sh b/packages/react-native-audio-api/common/cpp/test/RunTestsGraphDocker.sh deleted file mode 100755 index 3f341a7d3..000000000 --- a/packages/react-native-audio-api/common/cpp/test/RunTestsGraphDocker.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# This script builds and runs the AddressSanitizer-enabled tests in a Linux Docker container from macOS. -# Usage: ./run_graph_tests_in_docker.sh -# Make sure to run from the root of your repo or adjust paths accordingly. - -set -e - -# Absolute path to the repo root on the host (macOS) -REPO_ROOT=$(cd "$(dirname "$0")/../../../../.." && pwd) - -# Name for the Docker image/container -IMAGE_NAME=asan-graph-test -CONTAINER_NAME=asan-graph-test-container - -# Build the Docker image (Dockerfile must be in the test dir) -docker build -t $IMAGE_NAME "${REPO_ROOT}/packages/react-native-audio-api/common/cpp/test" - -# Run the container, mounting the entire repo for source access -docker run --rm -it \ - --name $CONTAINER_NAME \ - -v "$REPO_ROOT:/workspace" \ - -w /workspace/packages/react-native-audio-api/common/cpp/test \ - -e ASAN_OPTIONS=detect_leaks=1:verbosity=2 \ - $IMAGE_NAME \ - bash RunTestsGraph.sh diff --git a/packages/react-native-audio-api/common/cpp/test/TESTING.md b/packages/react-native-audio-api/common/cpp/test/TESTING.md new file mode 100644 index 000000000..b3fbdcd49 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/test/TESTING.md @@ -0,0 +1,39 @@ +# C++ tests + +`common/cpp/test/` — Google Test, modes by **runtime cost**. + +| Mode | What | Default binary | +| --- | --- | --- | +| **smoke** | Fast suites (PR + coverage). Disjoint from extended. | `tests` | +| **extended** | Slow suites by **category** | `tests_asan` (ASan+UBSan) | +| **full** | smoke, then all extended categories | each mode’s default | + +```bash +yarn test:cpp # smoke +yarn test:cpp:extended # all categories +yarn test:cpp:extended -- graph +yarn test:cpp:full +yarn test:cpp:coverage # smoke + llvm-cov HTML +yarn test:cpp:smoke --ubasan # Address + UndefinedBehavior sanitizers +yarn test:cpp:extended -- graph --tsan +yarn test:cpp:extended -- graph --no-ubasan +bash common/cpp/test/run-tests.sh --help +``` + +`--ubasan` = AddressSanitizer + UndefinedBehaviorSanitizer. Incompatible with `--tsan` (ASan and TSan cannot run together). + +Filters: [`filters.sh`](filters.sh). Override: `GTEST_FILTER=...`. + +### Categories + +| Category | Contents | CI | +| --- | --- | --- | +| `graph` | Slow graph (`AudioGraph*`, `Graph*`, `HostGraph*`, `Seeds/*`, …) | path filter + manual dispatch in `tests.yml` | + +**`GraphNodeGrowthTest` is smoke**, not `graph`: it is short (~100–200 ms) and needs unsanitized `AudioThreadGuard` (asserts `GTEST_SKIP` under ASan/TSan). + +**Add a category:** (1) gtest filter and CI path globs in `filters.sh`, (2) append the name to `CPP_TEST_EXTENDED_CATEGORIES`, (3) in `tests.yml` add a `workflow_dispatch` boolean and one `cpp-extended-*` job that calls `cpp-job.yml` with `run: yarn test:cpp:extended -- `, `categories: `, and `force`. + +### Docker (Linux leak / ASan) + +`yarn test:cpp:docker` runs `run-tests.sh` in Docker (default `extended`). Extra args are forwarded. Use this from macOS when you need Linux ASan leak detection. diff --git a/packages/react-native-audio-api/common/cpp/test/filters.sh b/packages/react-native-audio-api/common/cpp/test/filters.sh new file mode 100755 index 000000000..1cdb1b9d9 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/test/filters.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# Gtest filters and CI path filters for C++ test modes +# (smoke / extended-by-category / full). +# Sourced by run-tests.sh and run-coverage.sh. Also runnable: +# bash filters.sh path-filters … +# +# Invariant: smoke and extended are disjoint. Extended lists only slow suites. +# Override any computed filter with GTEST_FILTER=... + +# Slow graph suites only. GraphNodeGrowthTest stays in smoke (short; needs +# unsanitized AudioThreadGuard — see TESTING.md). +CPP_TEST_EXTENDED_GRAPH_FILTER="AudioGraphTest.*:AudioGraphFuzzTest.*:GraphTest.*:GraphFuzzTest.*:GraphCycleDebugTest.*:HostGraphTest.*:Seeds/*" + +# Space-separated registered extended categories (add new names here). +CPP_TEST_EXTENDED_CATEGORIES="graph" + +# Repo-root-relative prefix for dorny/paths-filter globs in CI. +CPP_TEST_CI_PATH_PREFIX="packages/react-native-audio-api/common/cpp" + +cpp_test_extended_filter_for_category() { + local category="$1" + case "$category" in + graph) + printf '%s' "${CPP_TEST_EXTENDED_GRAPH_FILTER}" + ;; + *) + echo "error: unknown extended category '${category}' (registered: ${CPP_TEST_EXTENDED_CATEGORIES})" >&2 + return 1 + ;; + esac +} + +# Join category filters with ':'. Args = category names (default: all registered). +cpp_test_extended_filter() { + local categories=("$@") + if [[ ${#categories[@]} -eq 0 ]]; then + # shellcheck disable=SC2206 + categories=(${CPP_TEST_EXTENDED_CATEGORIES}) + fi + + local parts=() + local category filter + for category in "${categories[@]}"; do + filter="$(cpp_test_extended_filter_for_category "$category")" || return 1 + parts+=("$filter") + done + + local IFS=':' + printf '%s' "${parts[*]}" +} + +cpp_test_smoke_filter() { + local extended + extended="$(cpp_test_extended_filter)" || return 1 + printf '%s' "-${extended}" +} + +cpp_test_ci_shared_path_filters() { + printf '%s\n' \ + "${CPP_TEST_CI_PATH_PREFIX}/test/filters.sh" \ + "${CPP_TEST_CI_PATH_PREFIX}/test/run-tests.sh" \ + "${CPP_TEST_CI_PATH_PREFIX}/test/CMakeLists.txt" +} + +cpp_test_ci_path_filters_for_category() { + local category="$1" + case "$category" in + graph) + printf '%s\n' \ + "${CPP_TEST_CI_PATH_PREFIX}/audioapi/core/utils/graph/**" \ + "${CPP_TEST_CI_PATH_PREFIX}/test/src/graph/**" + ;; + *) + echo "error: unknown extended category '${category}' (registered: ${CPP_TEST_EXTENDED_CATEGORIES})" >&2 + return 1 + ;; + esac +} + +# Prints a dorny/paths-filter YAML document (top-level key `run`). +# Args = category names (at least one). +cpp_test_ci_path_filters_yaml() { + if [[ $# -eq 0 ]]; then + echo "error: cpp_test_ci_path_filters_yaml requires at least one category" >&2 + return 1 + fi + + local category path globs + local category_globs="" + for category in "$@"; do + globs="$(cpp_test_ci_path_filters_for_category "$category")" || return 1 + category_globs+="${globs}"$'\n' + done + + echo 'run:' + while IFS= read -r path; do + [[ -n "$path" ]] && echo " - '${path}'" + done < <(cpp_test_ci_shared_path_filters; printf '%s' "$category_globs") +} + +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + set -euo pipefail + command="${1:-}" + shift || true + case "$command" in + path-filters) + cpp_test_ci_path_filters_yaml "$@" + ;; + *) + echo "usage: filters.sh path-filters ..." >&2 + exit 1 + ;; + esac +fi diff --git a/packages/react-native-audio-api/common/cpp/test/graph_tests_docs.md b/packages/react-native-audio-api/common/cpp/test/graph_tests_docs.md deleted file mode 100644 index ea10ebdbc..000000000 --- a/packages/react-native-audio-api/common/cpp/test/graph_tests_docs.md +++ /dev/null @@ -1,33 +0,0 @@ - -Here is the guide on how to run the graph tests locally with some tips and tricks to make it easier. - - -## Running it normally -``` -yarn test:graph -``` -> This is most common way to run the tests, but it has subtle difference on macos - -## Running on docker -``` -yarn test:graph:docker -``` -> This way we ensure that the test enviroment is simmilar to the one used in CI, it is much slower and requires docker to be installed - - -## Tips and tricks -As running docker tests takes forevewer it is recommended to relly on CI/CD. Tests without docker does not have ASAN and may not catch memory leaks or address realated issues. So if any of these issues occur in CI/CD here is how you can run single test with docker to debug it locally: -add the following line to the `RunTestsGraphDocker.sh` file in docker command: - -```bash -docker run --rm -it \ - --name $CONTAINER_NAME \ - -v "$REPO_ROOT:/workspace" \ - -w /workspace/packages/react-native-audio-api/common/cpp/test \ - -e ASAN_OPTIONS=detect_leaks=1:verbosity=2 \ - -e GRAPH_FILTER="" \ # Add this line to set the filter for the tests - $IMAGE_NAME \ - bash RunTestsGraph.sh -``` - -This will add enviromental variable `GRAPH_FILTER` which will limit the tests cases to only ones matching the filter. This way you can run single test case or a group instead of running all the tests all the time. diff --git a/packages/react-native-audio-api/common/cpp/test/RunCoverage.sh b/packages/react-native-audio-api/common/cpp/test/run-coverage.sh similarity index 76% rename from packages/react-native-audio-api/common/cpp/test/RunCoverage.sh rename to packages/react-native-audio-api/common/cpp/test/run-coverage.sh index cd9da290c..ed7110f89 100755 --- a/packages/react-native-audio-api/common/cpp/test/RunCoverage.sh +++ b/packages/react-native-audio-api/common/cpp/test/run-coverage.sh @@ -1,21 +1,47 @@ #!/bin/bash -# Build the gtest suite with Clang LLVM source-based coverage and emit an -# llvm-cov HTML report. Uses Xcode llvm-profdata/llvm-cov on macOS (no gcovr). -# -# From packages/react-native-audio-api: -# yarn test:cpp:coverage +print_help() { + cat <<'EOF' +Usage: run-coverage.sh + +Build the C++ gtest suite with Clang LLVM source-based coverage and emit an +llvm-cov HTML report under coverage-html/. Always uses the smoke filter +(unsanitized). Override with GTEST_FILTER if needed. + + yarn test:cpp:coverage + +Options: + --help, -h Show this help. +EOF +} set -euo pipefail +if [[ $# -gt 0 ]]; then + case "$1" in + --help|-h) + print_help + exit 0 + ;; + *) + echo "error: unexpected argument '$1'" >&2 + print_help >&2 + exit 1 + ;; + esac +fi + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$SCRIPT_DIR" +# shellcheck source=filters.sh +source "${SCRIPT_DIR}/filters.sh" + readonly BUILD_DIR="${SCRIPT_DIR}/build-coverage" readonly COVERAGE_HTML_DIR="${SCRIPT_DIR}/coverage-html" readonly PROFDATA_FILE="${BUILD_DIR}/coverage.profdata" -readonly GRAPH_FILTER="AudioGraphTest.*:AudioGraphFuzzTest.*:GraphTest.*:GraphFuzzTest.*:GraphCycleDebugTest.*:HostGraphTest.*:Seeds/*" readonly IGNORE_FILENAME_REGEX='(/common/cpp/test/|/_deps/|/googletest|/gmock|/audioapi/libs/|/r8brain/|/jsi/|/HostObjects/)' +readonly FILTER="${GTEST_FILTER:-$(cpp_test_smoke_filter)}" resolve_llvm_tool() { local tool_name="$1" @@ -57,7 +83,8 @@ rm -rf "$COVERAGE_HTML_DIR" ( cd "$BUILD_DIR" export LLVM_PROFILE_FILE="${BUILD_DIR}/default-%p.profraw" - ./tests --gtest_print_time=1 --gtest_filter="-${GRAPH_FILTER}" + echo "=== Coverage run (smoke filter=${FILTER}) ===" + ./tests --gtest_print_time=1 --gtest_filter="${FILTER}" ) shopt -s nullglob diff --git a/packages/react-native-audio-api/common/cpp/test/run-tests-docker.sh b/packages/react-native-audio-api/common/cpp/test/run-tests-docker.sh new file mode 100755 index 000000000..5d510ebfd --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/test/run-tests-docker.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +print_help() { + cat <<'EOF' +Usage: run-tests-docker.sh [run-tests.sh args…] + +Thin Docker wrapper around run-tests.sh (Linux leak/ASan parity from macOS). +Forwards all arguments into the container. If none are given, runs: + extended + +Examples: + run-tests-docker.sh + run-tests-docker.sh extended --tsan + run-tests-docker.sh extended graph --tsan + run-tests-docker.sh --help # this help (container not started) + GTEST_FILTER='GraphTest.*' run-tests-docker.sh extended graph + +See run-tests.sh --help and TESTING.md. +EOF +} + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../../.." && pwd)" + +IMAGE_NAME=cpp-tests +CONTAINER_NAME=cpp-tests-container + +if [[ $# -eq 1 && ( "$1" == "--help" || "$1" == "-h" ) ]]; then + print_help + exit 0 +fi + +if [[ $# -eq 0 ]]; then + set -- extended +fi + +docker build -t "$IMAGE_NAME" "${SCRIPT_DIR}" + +docker run --rm -it \ + --name "$CONTAINER_NAME" \ + -v "$REPO_ROOT:/workspace" \ + -w /workspace/packages/react-native-audio-api/common/cpp/test \ + -e ASAN_OPTIONS=detect_leaks=1:verbosity=2 \ + ${GTEST_FILTER:+-e GTEST_FILTER="$GTEST_FILTER"} \ + "$IMAGE_NAME" \ + bash run-tests.sh "$@" diff --git a/packages/react-native-audio-api/common/cpp/test/run-tests.sh b/packages/react-native-audio-api/common/cpp/test/run-tests.sh new file mode 100755 index 000000000..715330760 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/test/run-tests.sh @@ -0,0 +1,222 @@ +#!/bin/bash + +print_help() { + cat <<'EOF' +Usage: run-tests.sh [smoke|extended|full] [category…] [options] + +Modes (default: smoke): + smoke Fast suites — PR default and coverage. Disjoint from extended. + extended Slow suites for the given categories (default: all registered). + full smoke, then extended for all categories (each mode’s defaults). + +Categories (extended only): + graph Slow graph suites (see filters.sh). GraphNodeGrowthTest is smoke. + +Sanitizer options (choose at most one; --ubasan and --tsan are incompatible): + --ubasan AddressSanitizer + UndefinedBehaviorSanitizer (tests_asan). + Default for extended. + --no-ubasan Plain tests binary (no sanitizers). Default for smoke. + --tsan ThreadSanitizer (tests_tsan). + Incompatible with --ubasan (ASan and TSan cannot run together). + +Other: + --help, -h Show this help. + --gtest_* Forwarded to the gtest binary. + GTEST_FILTER If set, overrides the mode/category gtest filter. + +Examples: + run-tests.sh + run-tests.sh smoke --ubasan + run-tests.sh extended graph + run-tests.sh extended graph --no-ubasan + run-tests.sh extended graph --tsan + run-tests.sh full + +See TESTING.md. +EOF +} + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +# shellcheck source=filters.sh +source "${SCRIPT_DIR}/filters.sh" + +MODE="" +SANITIZER="" # empty = mode default; none|ubasan|tsan +CATEGORIES=() +PASSTHROUGH=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --help|-h) + print_help + exit 0 + ;; + smoke|extended|full) + if [[ -n "$MODE" ]]; then + echo "error: mode already set to '${MODE}', got '$1'" >&2 + exit 1 + fi + MODE="$1" + shift + ;; + --ubasan) + if [[ "$SANITIZER" == "tsan" ]]; then + echo "error: --ubasan is incompatible with --tsan (AddressSanitizer and ThreadSanitizer cannot be combined)" >&2 + exit 1 + fi + SANITIZER=ubasan + shift + ;; + --tsan) + if [[ "$SANITIZER" == "ubasan" ]]; then + echo "error: --tsan is incompatible with --ubasan (AddressSanitizer and ThreadSanitizer cannot be combined)" >&2 + exit 1 + fi + SANITIZER=tsan + shift + ;; + --no-ubasan) + if [[ "$SANITIZER" == "ubasan" || "$SANITIZER" == "tsan" ]]; then + echo "error: --no-ubasan conflicts with a sanitizer flag already set (${SANITIZER})" >&2 + exit 1 + fi + SANITIZER=none + shift + ;; + # Yarn forwards a literal "--" before script args (`yarn cmd -- graph`). + --) + shift + ;; + --gtest_*) + PASSTHROUGH+=("$1") + shift + ;; + -*) + echo "error: unknown option '$1'" >&2 + print_help >&2 + exit 1 + ;; + *) + CATEGORIES+=("$1") + shift + ;; + esac +done + +MODE="${MODE:-smoke}" + +if [[ "$MODE" != "extended" && "$MODE" != "full" && ${#CATEGORIES[@]} -gt 0 ]]; then + echo "error: categories are only valid with extended (or full); got mode=${MODE}" >&2 + exit 1 +fi + +if [[ "$MODE" == "full" && ${#CATEGORIES[@]} -gt 0 ]]; then + echo "error: full always runs all extended categories; omit category args" >&2 + exit 1 +fi + +resolve_sanitizer_for_mode() { + local mode="$1" + if [[ -n "$SANITIZER" ]]; then + printf '%s' "$SANITIZER" + return + fi + case "$mode" in + smoke) printf 'none' ;; + extended) printf 'ubasan' ;; + *) + echo "error: internal: no sanitizer default for mode '${mode}'" >&2 + return 1 + ;; + esac +} + +binary_for_sanitizer() { + case "$1" in + none) printf 'tests' ;; + ubasan) printf 'tests_asan' ;; + tsan) printf 'tests_tsan' ;; + *) + echo "error: unknown sanitizer '$1'" >&2 + return 1 + ;; + esac +} + +label_for_sanitizer() { + case "$1" in + none) printf 'tests (normal)' ;; + ubasan) printf 'tests with AddressSanitizer + UndefinedBehaviorSanitizer' ;; + tsan) printf 'tests with ThreadSanitizer' ;; + esac +} + +parallel_job_count() { + sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 10 +} + +filter_for_mode() { + local mode="$1" + shift + if [[ -n "${GTEST_FILTER:-}" ]]; then + printf '%s' "$GTEST_FILTER" + return + fi + case "$mode" in + smoke) + cpp_test_smoke_filter + ;; + extended) + cpp_test_extended_filter "$@" + ;; + *) + echo "error: internal: no filter for mode '${mode}'" >&2 + return 1 + ;; + esac +} + +run_mode() { + local mode="$1" + shift + local categories=("$@") + local sanitizer binary filter label + sanitizer="$(resolve_sanitizer_for_mode "$mode")" + binary="$(binary_for_sanitizer "$sanitizer")" + label="$(label_for_sanitizer "$sanitizer")" + filter="$(filter_for_mode "$mode" "${categories[@]+"${categories[@]}"}")" + + cmake --build build --target "$binary" -j "$(parallel_job_count)" + + echo "" + echo "=== ${label} (mode=${mode}, filter=${filter}) ===" + echo "" + "./build/${binary}" --gtest_print_time=1 --gtest_filter="${filter}" \ + ${PASSTHROUGH[@]+"${PASSTHROUGH[@]}"} +} + +cmake -S . -B build -Wno-dev + +case "$MODE" in + smoke) + run_mode smoke + ;; + extended) + run_mode extended "${CATEGORIES[@]+"${CATEGORIES[@]}"}" + ;; + full) + # Each sub-mode uses its own default sanitizer (ignore flags for the pair). + SANITIZER="" + run_mode smoke + SANITIZER="" + run_mode extended + ;; + *) + echo "error: unknown mode '${MODE}'" >&2 + exit 1 + ;; +esac diff --git a/packages/react-native-audio-api/package.json b/packages/react-native-audio-api/package.json index abfda5d1d..8f45c52c5 100644 --- a/packages/react-native-audio-api/package.json +++ b/packages/react-native-audio-api/package.json @@ -91,11 +91,13 @@ "scripts": { "test": "yarn test:js && yarn test:cpp", "test:js": "jest", - "test:cpp": "bash common/cpp/test/RunTests.sh", - "test:cpp:coverage": "bash common/cpp/test/RunCoverage.sh", - "test:full": "yarn test && yarn test:graph", - "test:graph": "bash common/cpp/test/RunTestsGraph.sh", - "test:graph:docker": "bash common/cpp/test/RunTestsGraphDocker.sh", + "test:cpp": "bash common/cpp/test/run-tests.sh", + "test:cpp:smoke": "bash common/cpp/test/run-tests.sh smoke", + "test:cpp:extended": "bash common/cpp/test/run-tests.sh extended", + "test:cpp:full": "bash common/cpp/test/run-tests.sh full", + "test:cpp:coverage": "bash common/cpp/test/run-coverage.sh", + "test:full": "yarn test:js && yarn test:cpp:full", + "test:cpp:docker": "bash common/cpp/test/run-tests-docker.sh", "build:android": "cd ../../apps/fabric-example/android && ./gradlew :react-native-audio-api:assembleDebug", "build:ios": "cd ../../apps/fabric-example/ios && pod install && xcodebuild build -workspace FabricExample.xcworkspace -scheme FabricExample -destination 'generic/platform=iOS Simulator' -quiet", "typecheck": "tsc --noEmit", diff --git a/scripts/validate.sh b/scripts/validate.sh index 391e59ba3..2b080a905 100755 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -1,14 +1,31 @@ #!/usr/bin/env bash -# validate.sh — tiered local validation for react-native-audio-api -# -# Usage: -# ./scripts/validate.sh --fast # CI parity (format, lint, typecheck, tests, build) -# ./scripts/validate.sh --graph # graph tests (optional, graph changes) -# ./scripts/validate.sh --android # Android native build (requires Android SDK) -# ./scripts/validate.sh --ios # iOS native build (macOS only) -# ./scripts/validate.sh --full # --fast + --android + --ios (with graceful skips) -# -# Local-only — not wired into CI. + +print_help() { + cat <<'EOF' +Usage: ./scripts/validate.sh [tiers…] + +Local-only validation (not run in CI). Combine flags as needed. + +Tiers: + --fast CI parity: format, lint, typecheck, enum sync, build, C++ smoke + JS + --cpp C++ smoke only + --cpp-extended C++ extended (all registered categories) + --cpp-full C++ full (smoke, then all extended categories) + --android Android native build (requires ANDROID_HOME) + --ios iOS native build (macOS only) + --full --fast + C++ extended (all categories) + --android + --ios + (C++ full = smoke from --fast + extended; skips unavailable platforms) + +Examples: + yarn validate:fast + yarn validate:cpp + yarn validate:cpp-extended + yarn validate:full + +Options: + --help, -h Show this help. +EOF +} set -euo pipefail @@ -18,7 +35,9 @@ LIBRARY_DIR="$REPO_ROOT/packages/react-native-audio-api" SCRIPTS_DIR="$LIBRARY_DIR/scripts" RUN_FAST=false -RUN_GRAPH=false +RUN_CPP=false +RUN_CPP_EXTENDED=false +RUN_CPP_FULL=false RUN_ANDROID=false RUN_IOS=false RUN_FULL=false @@ -59,7 +78,7 @@ run_prebuild_core() { log_step "Prebuild: yarn build" (cd "$REPO_ROOT" && yarn build) - log_step "Prebuild: C++ tests (shared layer compile check)" + log_step "Prebuild: C++ smoke (shared layer compile check)" (cd "$REPO_ROOT" && yarn workspace react-native-audio-api test:cpp) PREBUILD_CORE_DONE=true @@ -90,7 +109,7 @@ EOF } run_fast() { - log_step "Tier 0 (--fast): CI parity checks" + log_step "Tier (--fast): CI parity checks" log_step "yarn install --immutable" (cd "$REPO_ROOT" && yarn install --immutable) @@ -119,10 +138,22 @@ run_fast() { PREBUILD_CORE_DONE=true } -run_graph() { - log_step "Tier 1 (--graph): graph tests" +run_cpp() { + log_step "C++ smoke (--cpp)" + run_prebuild_core + (cd "$REPO_ROOT" && yarn workspace react-native-audio-api test:cpp:smoke) +} + +run_cpp_extended() { + log_step "C++ extended all categories (--cpp-extended)" run_prebuild_core - (cd "$REPO_ROOT" && yarn workspace react-native-audio-api test:graph) + (cd "$REPO_ROOT" && yarn workspace react-native-audio-api test:cpp:extended) +} + +run_cpp_full() { + log_step "C++ full (--cpp-full)" + run_prebuild_core + (cd "$REPO_ROOT" && yarn workspace react-native-audio-api test:cpp:full) } run_android() { @@ -141,7 +172,7 @@ run_android() { exit 1 fi - log_step "Tier 2 (--android): Android native build" + log_step "Tier (--android): Android native build" run_prebuild_for_platform android @@ -169,7 +200,7 @@ run_ios() { exit 1 fi - log_step "Tier 3 (--ios): iOS native build" + log_step "Tier (--ios): iOS native build" run_prebuild_for_platform ios @@ -180,37 +211,26 @@ run_ios() { run_full() { log_step "Full local validation (--full)" run_fast + # --fast already ran C++ smoke; extended completes C++ full without re-running smoke. + log_step "C++ extended all categories (remainder of C++ full)" + (cd "$REPO_ROOT" && yarn workspace react-native-audio-api test:cpp:extended) run_android true run_ios true } -usage() { - cat <<'EOF' -Usage: ./scripts/validate.sh [--fast] [--graph] [--android] [--ios] [--full] - -Tiers: - --fast CI parity: format, lint, typecheck, enum sync, build, C++ + JS tests - --graph Graph tests (optional; run when graph/audio-thread code changes) - --android Android native build via yarn workspace … build:android (requires ANDROID_HOME) - --ios iOS native build via yarn workspace … build:ios (macOS only) - --full --fast + --android + --ios (skips unavailable platforms with a warning) - -Examples: - yarn validate:fast - yarn validate:android - yarn validate:full - -Local-only — not run in CI. -EOF -} - while [[ $# -gt 0 ]]; do case "$1" in --fast) RUN_FAST=true ;; - --graph) - RUN_GRAPH=true + --cpp) + RUN_CPP=true + ;; + --cpp-extended) + RUN_CPP_EXTENDED=true + ;; + --cpp-full) + RUN_CPP_FULL=true ;; --android) RUN_ANDROID=true @@ -219,28 +239,29 @@ while [[ $# -gt 0 ]]; do RUN_IOS=true ;; --full) - RUN_FULL=true + RUN_FULL=true ;; --help|-h) - usage + print_help exit 0 ;; *) echo "Unknown option: $1" >&2 - usage >&2 + print_help >&2 exit 1 ;; esac shift done -if [[ "$RUN_FAST" == false && "$RUN_GRAPH" == false && "$RUN_ANDROID" == false && "$RUN_IOS" == false && "$RUN_FULL" == false ]]; then - usage >&2 +if [[ "$RUN_FAST" == false && "$RUN_CPP" == false && "$RUN_CPP_EXTENDED" == false && "$RUN_CPP_FULL" == false && "$RUN_ANDROID" == false && "$RUN_IOS" == false && "$RUN_FULL" == false ]]; then + print_help >&2 exit 1 fi cd "$REPO_ROOT" +enable_ccache_if_available if [[ "$RUN_FULL" == true ]]; then run_full @@ -248,8 +269,14 @@ fi if [[ "$RUN_FAST" == true && "$RUN_FULL" == false ]]; then run_fast fi -if [[ "$RUN_GRAPH" == true ]]; then - run_graph +if [[ "$RUN_CPP" == true ]]; then + run_cpp +fi +if [[ "$RUN_CPP_EXTENDED" == true ]]; then + run_cpp_extended +fi +if [[ "$RUN_CPP_FULL" == true ]]; then + run_cpp_full fi if [[ "$RUN_ANDROID" == true && "$RUN_FULL" == false ]]; then run_android false