Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 56 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ jobs:
- name: Run build
run: pnpm run build

test:
name: Test
test-no-cluster:
name: Test (no cluster)
runs-on: ubuntu-latest
needs: checks
steps:
Expand All @@ -45,5 +45,58 @@ jobs:
- name: Setup
uses: ./.github/actions/setup

# Redis-free projects only; every redis-backed project runs in the
# cluster-matrix job below (including against the newest image).
- name: Run tests
run: pnpm run test
run: pnpm run test:no-cluster

cluster-matrix:
name: Cluster (${{ matrix.image }})
runs-on: ubuntu-latest
needs: checks
strategy:
fail-fast: false
matrix:
image:
- redis:6-alpine
- redis:7-alpine
- valkey/valkey:8-alpine
- valkey/valkey:9-alpine
env:
REDIS_IMAGE: ${{ matrix.image }}
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup
uses: ./.github/actions/setup

# Only the redis-backed projects vary by server image: the two cluster
# projects plus the lib smoke test (which exercises the built bundle
# against redis). The local/memory/light projects are covered by `test`,
# which runs the full suite against the default (newest) matrix image.
- name: Run cluster tests
run: pnpm run test:cluster && pnpm run test:lib

test:
name: Test
runs-on: ubuntu-latest
needs: [test-no-cluster, cluster-matrix]
if: always()
steps:
- name: Require all test jobs to pass
run: |
test_no_cluster_result="${{ needs.test-no-cluster.result }}"
cluster_matrix_result="${{ needs.cluster-matrix.result }}"
failed=0
for r in "$test_no_cluster_result" "$cluster_matrix_result"; do
if [[ "$r" != "success" ]]; then
failed=1
fi
done
echo "test-no-cluster: $test_no_cluster_result"
echo "cluster-matrix: $cluster_matrix_result"
if [[ "$failed" == "1" ]]; then
echo "::error::One or more test jobs failed."
exit 1
fi
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1171,19 +1171,40 @@ Vitest projects:
You need a container runtime (Docker Desktop, Colima, OrbStack, …) for the Redis-backed projects.

```bash
pnpm run test # default CI-fast loop (no memory project)
pnpm run test # all projects against the default server image
pnpm run test:no-cluster # local + light-smoke + memory; no container needed
pnpm run test:memory # memory project only
pnpm run test:all # everything including memory (same as CI `pnpm run test:all`)
```

### Redis server matrix

The redis-backed projects run against any Redis-compatible server via the
`REDIS_IMAGE` env var. Local runs default to the newest supported image; CI
runs the whole matrix (the `cluster-matrix` job):

```bash
REDIS_IMAGE=redis:6-alpine pnpm run test:cluster
REDIS_IMAGE=redis:7-alpine pnpm run test:cluster
REDIS_IMAGE=valkey/valkey:8-alpine pnpm run test:cluster
pnpm run test:cluster # valkey/valkey:9-alpine (the default)
```

### Test conventions

- Every test file explicitly imports from `vitest`: `import { describe, it, expect, ... } from "vitest"`.
- New tests should use `makeLimiter()` from `test/helpers/limiter.js` for env-aware limiter construction with a separate `{ expectErrors }` meta argument.
- Framework primitives (`describe`, `expect`, `vi`) are imported directly from
`vitest`; the fixture-extended `test` (plus `waitForState`, `deferred`, and
`enqueued`) comes from `test/helpers/test-api.js`.
- Tests declare the fixtures they use: `harness` (job task fns + call log),
`makeLimiter` (env-aware, auto-disconnected), and `track` (teardown for
limiters/groups constructed directly).

### CI

CI is split into a `checks` job (format, lint, types, build) and a matrixed `test` job that runs each Vitest project independently. See [.github/workflows/ci.yaml](.github/workflows/ci.yaml); please make sure each step passes locally before opening a PR.
CI is split into a `checks` job (format, lint, types, build), a `test` job
running the redis-free projects, and a `cluster-matrix` job running the
redis-backed projects against every supported server image — each project
runs exactly once per relevant dimension. See [.github/workflows/ci.yaml](.github/workflows/ci.yaml);
please make sure each step passes locally before opening a PR.

All contributions are appreciated and will be considered.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"test:light": "pnpm exec vitest run --project light-smoke",
"test:local": "pnpm exec vitest run --project local",
"test:memory": "pnpm exec vitest run --project memory",
"test:no-cluster": "SKIP_REDIS_CONTAINER=1 pnpm exec vitest run --project local --project light-smoke --project memory",
"test:smoke": "pnpm exec vitest run --project light-smoke --project lib-smoke"
},
"devDependencies": {
Expand Down
12 changes: 11 additions & 1 deletion test/global-setup/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ let stop: (() => Promise<unknown>) | undefined;
const START_ATTEMPTS = 3;
const RETRY_DELAY_MS = 2_000;

// The redis-compatible server image under test. Local runs default to the
// newest entry in the supported matrix; CI runs every entry (see ci.yaml):
// redis:6-alpine, redis:7-alpine, valkey/valkey:8-alpine, valkey/valkey:9-alpine
const REDIS_IMAGE = process.env.REDIS_IMAGE ?? "valkey/valkey:9-alpine";

export async function setup(): Promise<void> {
// Redis-free runs (the CI test:no-cluster job) skip the container entirely.
if (process.env.SKIP_REDIS_CONTAINER) return;

const { RedisContainer } = await import("@testcontainers/redis");

// testcontainers hardcodes a 10s port-bind-inspection timeout that
Expand All @@ -33,7 +41,9 @@ export async function setup(): Promise<void> {
let container: StartedRedisContainer | undefined;
for (let attempt = 1; attempt <= START_ATTEMPTS; attempt++) {
try {
container = await new RedisContainer("redis:7-alpine")
// valkey images ship redis-* compatibility symlinks, so the command
// works across the whole image matrix.
container = await new RedisContainer(REDIS_IMAGE)
.withStartupTimeout(30_000)
.withCommand(["redis-server", "--save", "", "--appendonly", "no"])
.start();
Expand Down