From 20c3b426766ea7da27c9c694f8b080c66d0b5f50 Mon Sep 17 00:00:00 2001 From: jecacs Date: Sun, 20 Sep 2026 17:14:23 +0200 Subject: [PATCH] Update tests and workflows: align CI/Release with offline-only validation, document manual integration test options, and simplify release gates. --- .github/workflows/release.yml | 87 ++++------------------------------- AGENTS.md | 6 +-- CHANGELOG.md | 7 +++ CONTRIBUTING.md | 10 ++-- README.md | 30 ++++++++---- 5 files changed, 48 insertions(+), 92 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d4fcda9..1c14c46 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,102 +77,35 @@ jobs: - name: Verify formatting and analyzer style run: dotnet format --no-restore --verify-no-changes --severity info - - name: Require private integration endpoints - shell: bash - env: - SOLSHARP_RPC_URL: ${{ secrets.SOLSHARP_RPC_URL }} - SOLSHARP_WS_URL: ${{ secrets.SOLSHARP_WS_URL }} - SOLSHARP_DEVNET_RPC_URL: ${{ secrets.SOLSHARP_DEVNET_RPC_URL }} - run: | - for variable in SOLSHARP_RPC_URL SOLSHARP_WS_URL SOLSHARP_DEVNET_RPC_URL; do - value="${!variable:-}" - if [[ -z "${value}" ]]; then - echo "Required release integration secret ${variable} is not configured." - exit 1 - fi - - parsed_endpoint="$(python3 -c ' - import sys - from urllib.parse import urlsplit - - try: - endpoint = urlsplit(sys.argv[1]) - _ = endpoint.port - except ValueError as error: - raise SystemExit(f"Invalid endpoint URI: {error}") from error - - if not endpoint.scheme or not endpoint.hostname: - raise SystemExit("Endpoint URI must include a scheme and hostname.") - - print(endpoint.scheme.lower(), endpoint.hostname.rstrip(".").lower()) - ' "${value}")" || exit 1 - read -r endpoint_scheme endpoint_host <<< "${parsed_endpoint}" - case "${variable}:${endpoint_scheme}" in - SOLSHARP_RPC_URL:http|SOLSHARP_RPC_URL:https|SOLSHARP_DEVNET_RPC_URL:http|SOLSHARP_DEVNET_RPC_URL:https|SOLSHARP_WS_URL:ws|SOLSHARP_WS_URL:wss) - ;; - *) - echo "Release integration secret ${variable} has an unsupported URI scheme." - exit 1 - ;; - esac - case "${endpoint_host}" in - api.mainnet-beta.solana.com|api.devnet.solana.com|api.testnet.solana.com) - echo "Release integration secret ${variable} must use a private endpoint, not a public fallback." - exit 1 - ;; - esac - done - - # Unit tests plus live read/streaming integration form the blocking release gate. Unlike ordinary local - # runs, this pass is strict: transport failures and every inconclusive/skipped result fail the job. - - name: Test (strict suite and live reads) + # Release validation must not depend on external RPC providers or devnet faucets. + - name: Test (offline) run: >- dotnet test --no-build + --no-restore --configuration Release - --filter "TestCategory!=DevnetWrite" + --filter "TestCategory!=Integration" --logger "console;verbosity=normal" --logger trx - --results-directory "${{ runner.temp }}/solsharp-strict-test-results" - env: - SOLSHARP_RPC_URL: ${{ secrets.SOLSHARP_RPC_URL }} - SOLSHARP_WS_URL: ${{ secrets.SOLSHARP_WS_URL }} - SOLSHARP_INTEGRATION_STRICT: 'true' + --results-directory "${{ runner.temp }}/solsharp-offline-test-results" - - name: Reject inconclusive or skipped strict tests + - name: Reject inconclusive or skipped offline tests shell: bash env: - STRICT_RESULTS_DIR: ${{ runner.temp }}/solsharp-strict-test-results + OFFLINE_RESULTS_DIR: ${{ runner.temp }}/solsharp-offline-test-results run: | - mapfile -t result_files < <(find "${STRICT_RESULTS_DIR}" -type f -name '*.trx') + mapfile -t result_files < <(find "${OFFLINE_RESULTS_DIR}" -type f -name '*.trx') if (( ${#result_files[@]} == 0 )); then - echo "No strict-suite TRX test results were produced." + echo "No offline-suite TRX test results were produced." exit 1 fi if grep --with-filename --extended-regexp \ 'outcome="(NotExecuted|Skipped|Inconclusive|NotRunnable|Warning)"' "${result_files[@]}"; then - echo "Strict release tests contained an inconclusive or skipped result." + echo "Offline release tests contained an inconclusive or skipped result." exit 1 fi - # requestAirdrop is deliberately not retried because it is not idempotent, and the shared devnet faucet - # can return 429/-32603 even through a healthy private RPC provider. Attempt the real write paths on every - # release, but let only classified faucet/transport failures become inconclusive; deterministic client, - # wire, assertion, or cluster-safety failures still fail this step. - - name: Probe devnet writes (faucet-limited) - run: >- - dotnet test tests/SolSharp.IntegrationTests/SolSharp.IntegrationTests.csproj - --no-build - --configuration Release - --filter "TestCategory=DevnetWrite" - --logger "console;verbosity=normal" - --logger trx - --results-directory "${{ runner.temp }}/solsharp-devnet-write-results" - env: - SOLSHARP_DEVNET_RPC_URL: ${{ secrets.SOLSHARP_DEVNET_RPC_URL }} - SOLSHARP_INTEGRATION_STRICT: 'false' - # No --no-build: packing rebuilds so the facade's bundling target (which folds the four # assemblies into the single SolSharp package) runs against fully resolved references. - name: Pack diff --git a/AGENTS.md b/AGENTS.md index 2396c47..470c377 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,14 +9,14 @@ against immutable Anza Solana SDK, Agave, and SPL source revisions; exact pins, coverage, verification criteria, and deliberate node/runtime exclusions live in `docs/RUST_PARITY.md`. All JSON used by the library is source-generated and all four functional assemblies are Native AOT compatible; the package also contains a minimal facade. The live integration suite exercises read, streaming, -and devnet write paths against real nodes. +and devnet write paths against real nodes when run manually; CI and release test gates run offline only. ## Commands Run from the repo root (where `SolSharp.sln` lives): - `dotnet build` — Roslyn and StyleCop code style is enforced on build (`EnforceCodeStyleInBuild`), so actionable style violations surface as warnings. Repository-specific StyleCop severities live in `.editorconfig`; the member-order precedence is explicit in `stylecop.json`. -- `dotnet test` — NUnit suite. Add `--filter "TestCategory!=Integration"` for a fast offline run. +- `dotnet test --filter "TestCategory!=Integration"` — offline NUnit suite used by CI and release. An unfiltered `dotnet test` also runs the optional live integration tests. - `dotnet format` — applies supported style fixes. It cannot reorder members (SA1201/SA1202/SA1203/SA1204/SA1214) or fix naming (IDE1006); move or rename those by hand. **Before pushing, run the core developer gates with the same flags CI uses.** `.githooks/pre-push` runs @@ -136,7 +136,7 @@ SolSharp/ - `IDE1006` is disabled for `tests/**` so `Method_Scenario_Expectation` names are allowed. - For constructor-throws-only tests use an explicit discard: `Action act = () => _ = new T(...);`. - **Arrange / Act / Assert comments.** Mark the three phases with `// Arrange`, `// Act`, `// Assert`. When the call under test and its check are a single fluent statement (exception delegates, `(await …).Should()…`), use one `// Act & Assert`. Skip the labels on expression-bodied or single-statement `[TestCase]` tests where there is nothing to separate — never restructure a test body just to fit them. -- **Integration tests** live in `SolSharp.IntegrationTests`, hit a real cluster, and run as part of `dotnet test`. They are tagged `[Category("Integration")]`; read/streaming tests default to public mainnet (`SOLSHARP_RPC_URL` / `SOLSHARP_WS_URL` override), and the write suite (airdrop, transfer, durable nonce) always targets devnet (`SOLSHARP_DEVNET_RPC_URL` override) — never mainnet. HTTP read and write harnesses use shared two-request-per-second token buckets; WebSocket probes are serialized and their starts are paced at 500 ms. Write fixtures additionally carry `[Category("DevnetWrite")]` and are non-parallel. No key is ever committed. Ordinary runs report transient endpoint/faucet failures as inconclusive. The release gate is strict for unit/read/streaming tests, while it attempts the faucet-dependent write probe separately so a shared-faucet 429 cannot block publication; deterministic write-path failures still fail. Skip all live tests for a fast offline run with `dotnet test --filter "TestCategory!=Integration"`. +- **Integration tests** live in `SolSharp.IntegrationTests` and remain available for explicit manual runs. CI and release exclude all `[Category("Integration")]` tests with `TestCategory!=Integration`; deterministic fixtures in the same project still run, and the release gate rejects skipped or inconclusive offline results. An unfiltered `dotnet test` also includes live tests. Read/streaming tests default to public mainnet (`SOLSHARP_RPC_URL` / `SOLSHARP_WS_URL` override), and the write suite (airdrop, transfer, durable nonce) always targets devnet (`SOLSHARP_DEVNET_RPC_URL` override) — never mainnet. HTTP read and write harnesses use shared two-request-per-second token buckets; WebSocket probes are serialized and their starts are paced at 500 ms. Write fixtures additionally carry `[Category("DevnetWrite")]` and are non-parallel. No key is ever committed. Ordinary manual runs report transient endpoint/faucet failures as inconclusive; `SOLSHARP_INTEGRATION_STRICT=1` makes those failures and missing V1 discovery data fail a manual run. Deterministic client failures always fail. Release validation does not require private endpoint secrets or run a devnet write probe. ## Security (money-critical) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37b7ea9..d523420 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ version (on the earlier 0.x releases, minor versions could carry them). ## [Unreleased] +### Changed + +- Release validation now runs offline tests only, matching CI, and no longer requires private RPC/WebSocket + endpoint secrets or runs a devnet write probe. Live HTTP, WebSocket, and devnet tests remain available for + explicit manual runs, including optional strict mode. Offline test validation, dependency/security checks, + package/API validation, packed Native AOT smoke checks, and release provenance remain in place. + ## [4.0.0] - 2026-09-20 ### Breaking changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17f232b..2da5e8c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,9 +47,13 @@ dotnet test --no-build --configuration Release --filter "TestCategory!=Integrati dotnet format --no-restore --verify-no-changes --severity info ``` -The offline test command excludes live mainnet/devnet probes. See [README.md](README.md#build--test) for -the integration-test endpoints and filters. The benchmark project is intentionally outside the solution; -changes to performance-sensitive code should also build and format it explicitly. +CI and release use the offline test selection above, including deterministic fixtures in the integration +test project. Release validation rejects skipped or inconclusive offline results. Live HTTP, WebSocket, +and devnet write tests remain available for explicit manual runs; they are excluded from both pipelines, +and release validation does not require private endpoint secrets. See [README.md](README.md#build--test) +for manual endpoints, filters, and optional `SOLSHARP_INTEGRATION_STRICT=1` mode. The benchmark project is +intentionally outside the solution; changes to performance-sensitive code should also build and format it +explicitly. ## Change requirements diff --git a/README.md b/README.md index a269b5a..d450843 100644 --- a/README.md +++ b/README.md @@ -354,22 +354,23 @@ Contributions are welcome; read the [contribution guide](CONTRIBUTING.md) before ```bash dotnet build -dotnet test +dotnet test --filter "TestCategory!=Integration" dotnet format # apply the enforced code style ``` +CI and release run offline tests only, including the deterministic fixtures in `SolSharp.IntegrationTests`. +Release validation rejects skipped or inconclusive offline results. Live HTTP, WebSocket, and devnet +checks remain available for explicit manual runs; release validation does not require private endpoint secrets. +An unfiltered `dotnet test` still includes the live tests. + The suite includes a `SolSharp.IntegrationTests` project that exercises the read and streaming paths against a live cluster, plus a write suite (airdrop, transfer, durable nonce) that always targets **devnet**. Reads default to the public mainnet endpoint (`SOLSHARP_RPC_URL` / `SOLSHARP_WS_URL` override); the write suite uses the public devnet endpoint (`SOLSHARP_DEVNET_RPC_URL` override); no credentials are committed. These tests hit -the network, so they tolerate rate limits by reporting inconclusive rather than failing, and are tagged +the network, so ordinary manual runs report transient endpoint/faucet failures as inconclusive, and are tagged `Integration`. Live HTTP reads and devnet writes use two-request-per-second test-only limiters; WebSocket probes run serially with starts spaced by 500 ms. The shared faucet can still reject `requestAirdrop` -independently of RPC traffic. For a fast, offline-only run, exclude them: - -```bash -dotnet test --filter "TestCategory!=Integration" -``` +independently of RPC traffic. Deterministic client failures always fail the test. Micro-benchmarks live in a standalone BenchmarkDotNet harness: @@ -377,13 +378,24 @@ Micro-benchmarks live in a standalone BenchmarkDotNet harness: dotnet run -c Release --project benchmarks/SolSharp.Benchmarks ``` -To point the integration tests at your own node, set the endpoints (the key stays in your shell, never the repo): +To run the live read and streaming tests manually against your own node, set the endpoints (the key stays +in your shell, never the repo): ```bash SOLSHARP_RPC_URL=https://your-node SOLSHARP_WS_URL=wss://your-node \ - dotnet test --filter "TestCategory=Integration" + dotnet test --filter "TestCategory=Integration&TestCategory!=DevnetWrite" ``` +Run the devnet write tests separately when needed: + +```bash +SOLSHARP_DEVNET_RPC_URL=https://your-devnet-node \ + dotnet test --filter "TestCategory=DevnetWrite" +``` + +Set `SOLSHARP_INTEGRATION_STRICT=1` for a manual run that fails on transient endpoint/faucet errors and +missing V1 discovery data instead of reporting those conditions as inconclusive. + The read-only V1 regression test finds a V1 transaction in at most three recent finalized blocks, then checks default raw/parsed reads, wire round-tripping, signatures, and execution configuration. Set `SOLSHARP_V1_TRANSACTION_SIGNATURE` to use a known V1 transaction on the configured cluster instead.