name: Build and Test
on:
push:
paths:
- "src/**"
- "redis/**"
- ".github/workflows/**"
- "Cargo.toml"
- "Cargo.lock"
branches:
- "**"
tags-ignore:
- "*.*.*"
pull_request:
paths:
- "src/**"
- ".github/workflows/**"
- "Cargo.toml"
- "Cargo.lock"
branches:
- "**"
jobs:
check:
name: Check
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
steps:
- name: Checkout sources
uses: actions/checkout@v5
- name: Run cargo check
run: cargo check
- name: Run cargo check --release
run: cargo check --release
- name: Checking style
run: cargo fmt --all -- --check
- name: Checking documentation
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps --no-default-features --features tokio-runtime,tokio-rustls,pool,json,client-cache
msrv:
name: MSRV
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
steps:
- name: Checkout sources
uses: actions/checkout@v5
- name: Read the MSRV from Cargo.toml
id: msrv
run: |
VERSION=$(sed -n 's/^rust-version *= *"\(.*\)"/\1/p' Cargo.toml)
test -n "$VERSION" || { echo "no rust-version in Cargo.toml"; exit 1; }
echo "declared MSRV: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Install the declared toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ steps.msrv.outputs.version }}
- name: cargo check
run: cargo check --no-default-features --features tokio-runtime,tokio-rustls,pool,json,client-cache
clippy:
name: Clippy (panic policy)
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
steps:
- name: Checkout sources
uses: actions/checkout@v5
- name: cargo clippy --all-targets
run: cargo clippy --all-targets --no-default-features --features tokio-runtime,tokio-rustls,pool,json,client-cache -- -D warnings
semver:
name: Semver report
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.event_name == 'pull_request'
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
steps:
- name: Checkout sources
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install cargo-semver-checks
uses: taiki-e/install-action@v2
with:
tool: cargo-semver-checks
- name: cargo semver-checks
env:
BASELINE: ${{ github.event.pull_request.base.sha }}
run: |
# Never fails the job: `set -e` is off for the run, and the exit status
# is turned into a summary instead of a failure.
set +e
echo "baseline: $BASELINE"
output=$(cargo semver-checks --baseline-rev "$BASELINE" \
--only-explicit-features \
--features tokio-runtime,tokio-rustls,pool,json,client-cache 2>&1)
status=$?
echo "$output"
{
if [ $status -eq 0 ]; then
echo "### Semver report: no public-API break against the base"
else
echo "### Semver report: public-API breaks against the base"
echo
echo "Each one belongs in \`CHANGELOG.md\` under \`[Unreleased]\`."
echo "\`publish.yml\` is what enforces that the version bump covers them."
echo
echo '```'
echo "$output"
echo '```'
fi
} >> "$GITHUB_STEP_SUMMARY"
if [ $status -ne 0 ]; then
echo "::notice title=Semver::public-API breaks reported, see the job summary"
fi
exit 0
check-features:
name: Check features (${{ matrix.features }})
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
strategy:
fail-fast: false
matrix:
features:
- tokio-runtime
- tokio-runtime,pool
- tokio-runtime,json
- tokio-runtime,client-cache
- tokio-runtime,pool,json,client-cache
- tokio-runtime,tokio-rustls
- tokio-runtime,tokio-native-tls
- tokio-runtime,tokio-rustls,pool,json,client-cache
- tokio-runtime,tokio-native-tls,pool,json,client-cache
steps:
- name: Checkout sources
uses: actions/checkout@v5
- name: cargo check --no-default-features --features ${{ matrix.features }}
run: cargo check --no-default-features --features ${{ matrix.features }}
check-rejected-features:
name: Check rejected features (${{ matrix.name }})
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
strategy:
fail-fast: false
matrix:
include:
- name: no runtime
features: ""
expected: "rustis needs an async runtime feature"
- name: both TLS runtimes
features: tokio-runtime,tokio-rustls,tokio-native-tls
expected: "cannot be enabled at the same time"
- name: rustls alone
features: tokio-runtime,rustls
expected: "Feature `rustls` cannot be enabled on its own"
- name: native-tls alone
features: tokio-runtime,native-tls
expected: "Feature `native-tls` cannot be enabled on its own"
steps:
- name: Checkout sources
uses: actions/checkout@v5
- name: cargo check --no-default-features --features ${{ matrix.features }} must fail
run: |
set +e
output=$(cargo check --no-default-features --features "${{ matrix.features }}" 2>&1)
status=$?
echo "$output"
if [ $status -eq 0 ]; then
echo "::error::expected the build to fail, but it succeeded"
exit 1
fi
if ! echo "$output" | grep -qF '${{ matrix.expected }}'; then
echo "::error::build failed without the expected guard: ${{ matrix.expected }}"
exit 1
fi
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
steps:
- name: Checkout sources
uses: actions/checkout@v5
- name: Create Redis containers
run: |
cd /home/runner/work/rustis/rustis/redis/
sh ./docker_up.sh
- name: Wait for Redis to be ready
timeout-minutes: 3
run: |
# `docker compose up -d` returns as soon as the containers start,
# not when Redis is serving or the cluster has finished forming.
# Launching the tests too early makes cluster clients loop on
# reconnection forever, so gate the test step on readiness.
echo "Waiting for standalone node..."
until docker exec redis-standalone redis-cli ping 2>/dev/null | grep -q PONG; do
sleep 1
done
echo "Waiting for cluster to reach cluster_state:ok..."
until docker exec redis-node1 redis-cli -p 7000 cluster info 2>/dev/null | grep -q "cluster_state:ok"; do
sleep 1
done
# The spare servers carry the topology mutators and the Sentinel
# failover commands, which cannot be sent to the shared deployments
# above. They belong to no cluster, so there is no `cluster_state:ok`
# to wait for — answering a ping is the whole readiness condition.
echo "Waiting for spare cluster nodes..."
until docker exec redis-spare-node1 redis-cli -p 7006 ping 2>/dev/null | grep -q PONG; do
sleep 1
done
until docker exec redis-spare-node2 redis-cli -p 7007 ping 2>/dev/null | grep -q PONG; do
sleep 1
done
echo "Waiting for the spare Sentinel to see its replica..."
until docker exec redis-spare-sentinel redis-cli -p 26382 \
sentinel replicas spareservice 2>/dev/null | grep -q "master-link-status"; do
sleep 1
done
echo "Redis is ready."
- name: Run cargo test
run: cargo test --release --features pool,tokio-rustls,json,client-cache -- --test-threads=1
- name: Report response shapes
env:
RUSTIS_RESPONSE_SHAPE_REPORT: "1"
run: |
# `--nocapture` because the report is printed by a test that passes, and
# the harness swallows the stdout of a passing test. The step cannot
# fail on an unexplained row -- see the module documentation -- so what
# it does instead is put the rows in the job summary.
output=$(cargo test --release --features pool,tokio-rustls,json,client-cache \
response_shape -- --test-threads=1 --nocapture 2>&1)
echo "$output"
count=$(echo "$output" | sed -n 's/.*, \([0-9][0-9]*\) unexplained.*/\1/p' | tail -1)
if [ -n "$count" ] && [ "$count" != "0" ]; then
{
echo "### Response shape report: $count unexplained observation(s)"
echo
echo "A declared response type the server's reply contradicts, or a shape"
echo "nobody has triaged yet. Fix the type, or add the row to"
echo "\`src/tests/response_shape_baseline.tsv\` with its reason."
echo
echo '```'
echo "$output" | grep 'answered'
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
echo "::notice title=Response shapes::$count unexplained observation(s), see the job summary"
fi
test-native-tls:
name: Test (native-tls)
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
steps:
- name: Checkout sources
uses: actions/checkout@v5
- name: Create Redis containers
run: |
cd /home/runner/work/rustis/rustis/redis/
sh ./docker_up.sh
- name: Wait for Redis to be ready
timeout-minutes: 3
run: |
echo "Waiting for standalone node..."
until docker exec redis-standalone redis-cli ping 2>/dev/null | grep -q PONG; do
sleep 1
done
echo "Waiting for cluster to reach cluster_state:ok..."
until docker exec redis-node1 redis-cli -p 7000 cluster info 2>/dev/null | grep -q "cluster_state:ok"; do
sleep 1
done
# The spare servers carry the topology mutators and the Sentinel
# failover commands, which cannot be sent to the shared deployments
# above. They belong to no cluster, so there is no `cluster_state:ok`
# to wait for — answering a ping is the whole readiness condition.
echo "Waiting for spare cluster nodes..."
until docker exec redis-spare-node1 redis-cli -p 7006 ping 2>/dev/null | grep -q PONG; do
sleep 1
done
until docker exec redis-spare-node2 redis-cli -p 7007 ping 2>/dev/null | grep -q PONG; do
sleep 1
done
echo "Waiting for the spare Sentinel to see its replica..."
until docker exec redis-spare-sentinel redis-cli -p 26382 \
sentinel replicas spareservice 2>/dev/null | grep -q "master-link-status"; do
sleep 1
done
echo "Redis is ready."
- name: Run cargo test
run: |
cargo test --release --no-default-features \
--features tokio-runtime,tokio-native-tls,pool,json,client-cache \
-- --test-threads=1
- name: Report response shapes
env:
RUSTIS_RESPONSE_SHAPE_REPORT: "1"
run: |
# See the same step in `test`: `--nocapture` for a passing test's output,
# and an unexplained row goes to the job summary instead of failing.
output=$(cargo test --release --no-default-features \
--features tokio-runtime,tokio-native-tls,pool,json,client-cache \
response_shape -- --test-threads=1 --nocapture 2>&1)
echo "$output"
count=$(echo "$output" | sed -n 's/.*, \([0-9][0-9]*\) unexplained.*/\1/p' | tail -1)
if [ -n "$count" ] && [ "$count" != "0" ]; then
{
echo "### Response shape report (native-tls): $count unexplained observation(s)"
echo
echo "A declared response type the server's reply contradicts, or a shape"
echo "nobody has triaged yet. Fix the type, or add the row to"
echo "\`src/tests/response_shape_baseline.tsv\` with its reason."
echo
echo '```'
echo "$output" | grep 'answered'
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
echo "::notice title=Response shapes (native-tls)::$count unexplained observation(s), see the job summary"
fi