# Contributing to rustis The rules below are the ones CI enforces or the code assumes. Nothing here is a matter of taste, and where a rule exists because of a specific failure, the failure is named. ## Running the tests The suite has two halves, and which one you need depends on what you touched. **Without a server.** `./run_tests.sh --hermetic` runs everything that reaches neither a Redis nor the network: RESP parsing and deserialization, command encoding, configuration parsing, the message queue, cluster topology arithmetic, and the client stack over an in-memory pipe. About 470 tests in a second, with no Docker and no deployment. Use it as the inner loop. **With a server.** The rest needs a running deployment: 1. from `redis/`, run `docker_up.sh` (or `docker_up.cmd`) 2. run `./run_tests.sh` `run_tests.sh` refuses to start against a deployment that is up but unusable — a cluster whose nodes announce a stale address never forms, and the tests then hang instead of failing. `RUSTIS_SKIP_DEPLOYMENT_CHECK=1` steps past the gate. The server half **requires `--test-threads=1`**, which `run_tests.sh` passes: those tests share one Redis instance and flush it, so running them in parallel produces failures that belong to no test in particular. ## Where a new test goes The two halves are separated by the `server-tests` feature, which is on by default. The gate lives on the module list in `src/tests/mod.rs`, never on an individual test: * a module gated on `server-tests` needs a live Redis; * a `_server` module holds the server-bound tests of the module it is named after, whose own tests stay hermetic; * everything ungated must pass with no server and no network. Put a test that needs a Redis in the gated module. A test placed on the wrong side is not silent: it fails `./run_tests.sh --hermetic`. A hermetic test that still needs a server to talk to has one: `src/tests/fake_server.rs` answers RESP3 over an in-memory pipe, and `src/tests/fault_injection_proxy.rs` scripts a broken one. ## What CI checks * `cargo fmt --all -- --check`, and `cargo check` in debug and release. * `cargo clippy --all-targets -- -D warnings`, on the library feature set and again on the one that adds `bench` and `web-examples` — otherwise no job builds the benchmark targets or the gated examples, and they rot. * The MSRV job compiles with exactly the declared toolchain. * A feature matrix compiles each combination on its own, and a second matrix asserts that the rejected combinations still fail with the message they promise. * `cargo semver-checks` reports the public-API breaks a pull request introduces. It never fails the job: the report is there so each break becomes a deliberate `CHANGELOG.md` entry rather than a discovery made after publishing. * The four `cargo-fuzz` targets run weekly. They reach the parser through the `fuzzing` feature, which exposes the same kind of internal entry points as `bench` does through `resp::bench_support` and carries the same absence of a stability guarantee: `cd fuzz && cargo +nightly fuzz run `. **Never `--all-features`.** The two TLS runtimes are mutually exclusive and enabling both is a compile error; the CI feature sets are what to reproduce locally. ## Rules the code assumes **Panics.** `unwrap`, `expect`, `panic`, `unreachable`, `todo`, `unimplemented` and `arithmetic_side_effects` are denied crate-wide, and `indexing_slicing` is denied in `resp/` and `network/`. A surviving site carries `#[expect(…, reason = "…")]` naming the invariant that makes it unreachable — `expect` rather than `allow`, so a justification whose lint stops firing becomes a warning and gets deleted instead of rotting. Test code is exempt: a test that panics is a test that failed. **`#![forbid(unsafe_code)]`**, argued in `src/lib.rs` rather than assumed. **MSRV.** Declared as `rust-version` in `Cargo.toml`. Raising it is a breaking change and is announced in `CHANGELOG.md`. **`CHANGELOG.md`.** Every user-visible change gets an entry, in the section it belongs to. A breaking change also gets a line in the `BREAKING CHANGES` checklist at the top of `[Unreleased]`, stating what a caller has to do. ## Benchmarks From a running deployment, `cargo bench --features bench`. The feature is required: every benchmark target declares `required-features = ["bench"]`, so a plain `cargo bench` skips all of them and reports success having measured nothing. A measurement that compares two variants must alternate them inside each round and compare per-round ratios. Two sequential series measure the machine's mood as much as the code. ## Releasing `Cargo.toml` holds the version; the tag confirms it. Publishing runs from a GitHub release, and the workflow refuses a tag that disagrees with the manifest, checks the version bump against `cargo semver-checks`, and builds the docs.rs feature set and the native-tls backend before publishing — `cargo publish` builds with default features only, so neither is covered otherwise.