1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
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-22.04
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
# Broken intra-doc links compile fine and only show up as dead links on
# docs.rs, so nothing here caught them until this step existed. Denying
# rustdoc warnings also covers links from public docs into private items,
# which promise the reader a page that will never be rendered.
- name: Checking documentation
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps --no-default-features --features tokio-runtime,tokio-rustls,pool,json,client-cache
# Compile with the exact toolchain `rust-version` promises, so the promise is
# verified rather than asserted. The version is read from `Cargo.toml` and not
# repeated here: one source of truth, and raising the floor stays a one-line
# manifest change.
#
# `cargo check` rather than `--all-targets`: the MSRV covers the library a
# downstream crate compiles, not the dev-dependencies of our own test suite,
# which are free to require a newer compiler.
#
# Cargo's v3 resolver — the default in edition 2024 — reads `rust-version`
# when picking dependency versions, so the committed `Cargo.lock` is
# MSRV-compatible by construction: a dependency that raises its own MSRV above
# 1.88 is resolved away when the lock is written, not discovered here.
msrv:
name: MSRV
runs-on: ubuntu-22.04
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
# Enforce the panic policy declared in `src/lib.rs`: the explicit-panic family
# and `clippy::arithmetic_side_effects` crate-wide, plus
# `clippy::indexing_slicing` in `network/` and `resp/`. Those
# are `deny`, so they already fail without `-D warnings`; the flag is what
# makes an *unfulfilled* `#[expect(...)]` fail too, so a justification that no
# longer applies gets removed instead of quietly outliving its reason.
clippy:
name: Clippy (panic policy)
runs-on: ubuntu-22.04
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
# Check that the version bump in `Cargo.toml` matches what actually changed in
# the public API. In 0.x, cargo treats the minor field as the major one, so a
# breaking change requires 0.(x+1).0 and a compatible one 0.x.(y+1) — this job
# fails when the bump is smaller than the changes justify.
#
# It compares against the previous tag rather than crates.io: no dependency on
# the registry, and it works on a branch. Pull requests only, because it builds
# the crate twice (baseline and current).
#
# Its blind spot is worth stating: it reads the *shape* of the public API from
# rustdoc, so it cannot see a behavior change. A function that keeps its
# signature but returns a different value passes this job silently. Those
# belong in `CHANGELOG.md`, written by hand. It also misses some shape changes
# — the 0.20.0 run did not flag `Command::name()` switching its return type
# from `Bytes` to `&[u8]`.
#
# One-time caveat: tags up to 0.19.3 carry the placeholder `version = "0.1.0"`
# in their manifest, so against those the tool reads a 0.1.0 -> 0.20.0 jump,
# calls it a major release and skips every lint. Until 0.20.0 is tagged this
# job passes vacuously; a real check needs `--release-type minor` locally. From
# 0.20.0 onwards the baseline manifest is correct and inference works.
semver:
name: Semver compatibility
runs-on: ubuntu-22.04
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:
# Tags are needed to resolve the baseline revision.
fetch-depth: 0
- name: Install cargo-semver-checks
uses: taiki-e/install-action@v2
with:
tool: cargo-semver-checks
# The feature set is the one docs.rs builds, not the default one. Only the
# features named here have their public surface compared, so `--default-
# features` — tokio-runtime alone — left `pool`, `json`, `client-cache` and
# the TLS surface unchecked: three real breaks in 0.20.0 (`Cache::
# zremrangebyscore`, `TlsConfig`, `resp::JsonRef`) were invisible to this
# job and were caught by hand.
- name: cargo semver-checks
run: |
BASELINE=$(git describe --tags --abbrev=0 --match '[0-9]*.[0-9]*.[0-9]*')
echo "baseline: $BASELINE"
cargo semver-checks --baseline-rev "$BASELINE" \
--only-explicit-features \
--features tokio-runtime,tokio-rustls,pool,json,client-cache
# Compile a curated set of feature combinations (compile-only, no server
# needed). `--no-default-features` is used so each entry compiles exactly the
# features it names: the default set would otherwise mask a combination that
# only builds because something else pulled a dependency in.
check-features:
name: Check features (${{ matrix.features }})
runs-on: ubuntu-22.04
timeout-minutes: 20
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
strategy:
fail-fast: false
matrix:
features:
- tokio-runtime
- tokio-runtime,pool,json,client-cache
- 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 }}
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
# A second invocation, after the suite has filled the probe dump: the
# harness gives no ordering guarantee that would let one run both collect
# the observations and judge them. This is what keeps a Redis upgrade that
# changes a reply shape from passing unnoticed.
- name: Report response shapes
env:
RUSTIS_RESPONSE_SHAPE_REPORT: "1"
run: |
cargo test --release --features pool,tokio-rustls,json,client-cache \
response_shape -- --test-threads=1
# `rustls` and `native-tls` are mutually exclusive — `src/lib.rs` rejects the
# pair with a `compile_error!`, and `src/tests/tls.rs` defines one `tls()` per
# backend — so the native-tls backend needs a job of its own rather than an
# extra feature on the one above. Without it the backend is only ever
# `cargo check`ed, and a compiled path nothing runs is an untested path.
#
# The whole suite runs, not just `tls::`: the `native-tls` cfg sites reach
# into the config, network and error modules that every test goes through.
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."
# `--no-default-features` is required here: the default set pulls in
# `tokio-rustls`, which cannot coexist with `tokio-native-tls`.
- name: Run cargo test
run: |
cargo test --release --no-default-features \
--features tokio-runtime,tokio-native-tls,pool,json,client-cache \
-- --test-threads=1
# A second invocation, after the suite has filled the probe dump: the
# harness gives no ordering guarantee that would let one run both collect
# the observations and judge them. This is what keeps a Redis upgrade that
# changes a reply shape from passing unnoticed.
- name: Report response shapes
env:
RUSTIS_RESPONSE_SHAPE_REPORT: "1"
run: |
cargo test --release --no-default-features \
--features tokio-runtime,tokio-native-tls,pool,json,client-cache \
response_shape -- --test-threads=1