Skip to main content

yo_resp/dispatch/
mod.rs

1//! From a decoded command to a written reply.
2//!
3//! This is the layer Y23 exists to keep thin. The wire and the embedded API
4//! both have to reach the same code, or there are two implementations of `INCR`
5//! and one of them is wrong. So `yo-kv` holds one method per command taking
6//! ordinary Rust values, and everything here is about the part that is only
7//! true on a socket: which keyword goes where, which combinations a real server
8//! refuses, and which of the two protocols the answer is spelled in.
9//!
10//! # What runs a command
11//!
12//! [`Server`] holds the databases. [`Session`] holds what one connection has
13//! chosen: which database, which name it gave itself, what its id is. The
14//! protocol version lives in the [`Out`] because that is what needs it, and
15//! `HELLO` changes it there.
16//!
17//! ```
18//! use yo_resp::{Argv, Limits, Out, Proto};
19//! use yo_resp::dispatch::{Args, Flow, Server, Session, execute};
20//!
21//! let mut server = Server::new();
22//! let mut session = Session::new(1);
23//! let mut out = Out::new(Proto::Resp2);
24//!
25//! let wire = b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$1\r\nv\r\n";
26//! let mut argv = Argv::new();
27//! argv.decode(wire, &Limits::default())?;
28//! let flow = execute(&mut server, &mut session, Args::new(&argv, wire), &mut out);
29//!
30//! assert_eq!(flow, Flow::Continue);
31//! assert_eq!(out.as_slice(), b"+OK\r\n");
32//! # Ok::<(), yo_resp::ProtocolError>(())
33//! ```
34//!
35//! # Errors are values until the last moment
36//!
37//! A command body returns a [`Result`], and this module turns the error into
38//! the line that goes on the wire. That is what keeps the same body usable from
39//! the embedded API, where an error is a value with a [`Code`] on it and not a
40//! sentence to be parsed.
41//!
42//! The reply buffer is rolled back to where it was before a failing command
43//! wrote anything, so a body that checks its arguments halfway through cannot
44//! leave half a reply in front of the error.
45//!
46//! # Nothing here allocates
47//!
48//! Arguments are slices of the connection's read buffer, keywords are compared
49//! in place, numbers are written straight into the reply, and the pairs of
50//! `MSET` reach the store as an iterator rather than a `Vec`. The two places
51//! that do allocate, an error message and the text of `INFO`, say so and wrap
52//! it, because a shard thread that allocates aborts.
53
54mod acl;
55mod args;
56mod arrays;
57mod auth;
58mod backup;
59mod bits;
60mod blocking;
61mod bloom;
62mod client;
63mod clients;
64mod cluster;
65mod cms;
66mod cpu;
67mod cuckoo;
68mod debug;
69mod failover;
70mod follow;
71mod geo;
72mod graph;
73mod hashes;
74mod himport;
75mod hll;
76mod indexing;
77mod json;
78mod keyspace;
79pub mod keyspec;
80mod lists;
81mod load;
82mod lua;
83mod memory;
84mod migrate;
85mod misses;
86mod monitor;
87mod multi;
88mod notify;
89mod persist;
90mod pubsub;
91mod repl;
92mod scan;
93mod scripting;
94mod search;
95mod server;
96mod sets;
97mod streams;
98mod strings;
99mod suggest;
100pub mod table;
101mod tdigest;
102mod topk;
103mod ts;
104mod vectors;
105mod vfilter;
106mod zsets;
107
108pub use args::Args;
109pub use blocking::{Parked, Waiters};
110pub use clients::Client;
111pub use load::{Loaded, Refused};
112pub(crate) use pubsub::Envelope;
113pub use server::parse_memory;
114pub use table::{COMMANDS, Spec, arity_ok, lookup};
115
116use crate::reply::Out;
117use std::cell::Cell;
118use std::path::{Path, PathBuf};
119use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
120use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize};
121use std::sync::{Arc, Weak};
122use yo_common::lock::{Held, Lock};
123use yo_common::{Code, Error};
124use yo_kv::cold::Store;
125use yo_kv::lookups;
126use yo_kv::{Clock, Db, Keyspace};
127use yo_search::Registry;
128
129use multi::Watches;
130use search::cursor::Cursors;
131
132/// How many databases a server has.
133///
134/// Redis's default is sixteen and its `databases` setting can change it. Ours
135/// is sixteen and cannot, which is why `CONFIG GET databases` can answer with a
136/// constant. Nothing in the design needs the number to be fixed; nothing yet
137/// needs it not to be.
138pub const DATABASES: usize = 16;
139
140/// Every database's bit in [`Server::dirty`], which is what a fresh server
141/// starts on so that the first maintenance turn asks all of them.
142///
143/// A `u64` holds sixteen bits with room to spare, and the assertion below is
144/// what turns raising [`DATABASES`] past sixty four into a build failure rather
145/// than a shift that silently drops the databases past the end.
146const ALL_DATABASES: u64 = if DATABASES == 64 {
147    u64::MAX
148} else {
149    (1u64 << DATABASES) - 1
150};
151const _: () = assert!(DATABASES <= 64);
152
153/// How many keys one command throws away before it leaves the rest to the next.
154///
155/// A bound and not a loop to the end, because this runs in front of a client
156/// that is waiting for its reply, and a server a long way over its limit would
157/// otherwise hold that client for as long as it took to walk all the way back
158/// under. Sixty four is a batch's worth of commands, so a server that went over
159/// by what one batch allocated comes back under in one command, and a server
160/// whose limit was just cut in half works through it over the next few thousand
161/// rather than in one long stall. Redis bounds the same loop by a time slice
162/// instead of a count and hands the rest to a timer; there is no timer here, so
163/// the rest goes to the next command that runs.
164const EVICT_BUDGET: usize = 64;
165
166/// How many stripes one compaction turn looks at before it leaves the rest to
167/// the next turn.
168///
169/// The walk used to run from its cursor to the end of [`Server::slots`], which
170/// is [`DATABASES`] times the stripe width, and the width is derived from the
171/// thread count. It does not stop early on a server with nothing to collect,
172/// since nothing to collect is exactly the answer that does not end the walk, so
173/// an idle stripe still cost a lock taken and given back. Every worker paid that
174/// on every batch and the locks it took were the same stripe locks the commands
175/// wanted, which put the thread count into the price every thread pays. Measured
176/// on a ten core laptop at pipeline 50, one thread ran at 6417 Kops and four ran
177/// at 3283, and turning this walk off with `DEBUG DICT-RESIZING 0` took four
178/// threads to 4518.
179///
180/// Eight stripes is a bound with no thread count in it. The cursor moves on
181/// every turn rather than only when something was found, so a walk that stops
182/// after eight still comes round to the far end of the databases, and it comes
183/// round after the same number of turns however many threads are turning.
184///
185/// The active expiry walk is not bounded the same way. It is already gated to
186/// once a millisecond for the whole server rather than running once a batch per
187/// worker, and a bound there would mean a key with a deadline waiting several
188/// sweeps to be noticed rather than one.
189const COMPACT_LOOKS: usize = 8;
190
191/// The `maxstore` a server with no storage limit carries.
192///
193/// Sixteen exabytes, which is every disk there is and then some, so a server
194/// that set a limit this high and a server that set none behave the same way and
195/// the only difference is what `CONFIG GET maxstore` says. Zero cannot be the
196/// sentinel because zero is a limit with a meaning: nothing may live on the
197/// file.
198const NO_MAXSTORE: u64 = u64::MAX;
199
200/// What a server says to a command that would allocate when it has no room.
201///
202/// Redis's `shared.oomerr`, word for word including the full stop, because
203/// clients match on the `OOM` prefix and people match on the sentence.
204const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
205
206/// What the connection should do after a command.
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub enum Flow {
209    /// Read the next command.
210    Continue,
211    /// Write what is buffered and then close, which is what `QUIT` asks for.
212    Close,
213    /// Nothing was written and nothing is owed yet.
214    ///
215    /// The client is on the waiter list and its reply comes when a key it named
216    /// has something in it or when its deadline passes, whichever happens first.
217    /// Until then the connection stops reading commands, because a client that
218    /// is waiting for an answer is not a client that has sent another question.
219    Block,
220    /// Nothing was written and the command has not run at all.
221    ///
222    /// The server is paused, so the connection keeps the command it was about to
223    /// run and runs it again once the pause is over. Everything the client
224    /// pipelined behind it is kept in the order it arrived, the same way a
225    /// blocking command keeps it.
226    Hold,
227}
228
229/// A number one thread adds to and any thread may read.
230///
231/// The add is a load, an add and a store rather than a fetch and add, which on
232/// x86 is three ordinary instructions instead of one locked one. That is sound
233/// because every counter here has exactly one writer, which is what the slots
234/// below are for: two threads never hold the same counter, so nothing can be
235/// lost between the load and the store. A reader can be a command or two behind,
236/// and `INFO` on a running server is behind by the time the reply reaches the
237/// client anyway.
238#[derive(Debug, Default)]
239pub struct Counter(AtomicU64);
240
241impl Counter {
242    /// One more.
243    fn bump(&self) {
244        self.0.store(self.get().wrapping_add(1), Relaxed);
245    }
246
247    /// One fewer, stopping at zero.
248    ///
249    /// The floor is for the gauge, which is the number of open connections: a
250    /// close that arrives without its open, which nothing can do now and a
251    /// misplaced call could, is a number that stays at zero rather than one
252    /// that wraps to eighteen quintillion clients.
253    fn drop_one(&self) {
254        self.0.store(self.get().saturating_sub(1), Relaxed);
255    }
256
257    /// What it says.
258    fn get(&self) -> u64 {
259        self.0.load(Relaxed)
260    }
261
262    /// Back to zero, which is `CONFIG RESETSTAT`.
263    fn zero(&self) {
264        self.0.store(0, Relaxed);
265    }
266}
267
268/// The numbers `INFO` reports that this layer cannot see for itself.
269///
270/// The reactor owns the sockets, so the reactor is what knows how many clients
271/// there are. It counts them here and nothing else does anything with them
272/// except report them.
273#[derive(Debug, Default)]
274pub struct Stats {
275    /// Connections open right now.
276    clients: Counter,
277    /// Connections accepted since the server started.
278    connections: Counter,
279    /// Commands run since the server started, which this layer counts itself.
280    commands: Counter,
281}
282
283impl Stats {
284    /// A connection arrived.
285    pub fn opened(&self) {
286        self.clients.bump();
287        self.connections.bump();
288    }
289
290    /// A connection went away.
291    pub fn closed(&self) {
292        self.clients.drop_one();
293    }
294}
295
296/// Every thread's [`Stats`] added together, which is what `INFO` answers.
297#[derive(Debug, Clone, Copy, Default)]
298pub struct Totals {
299    /// Connections open right now.
300    pub clients: u64,
301    /// Connections accepted since the server started.
302    pub connections: u64,
303    /// Commands run since the server started.
304    pub commands: u64,
305}
306
307thread_local! {
308    /// Which set of counters the running thread writes into.
309    ///
310    /// Claimed the first time a thread counts anything and kept for as long as
311    /// the thread runs. It is a number rather than a pointer, so a thread that
312    /// has counted on one server and then counts on another lands in the same
313    /// place in both, and a process with two servers in it shares the numbering
314    /// between them. That is the tests and it is not `yodb`, which has one.
315    static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
316}
317
318/// What one thread keeps to itself.
319///
320/// One of these per thread and not one per server, because a number every
321/// thread writes to is a cache line every thread has to own to write to it, and
322/// at a few million commands a second that one line is the server. So each
323/// thread writes into its own and whoever needs the whole picture, which is
324/// `INFO` and the maintenance turn, puts the pieces together when it asks.
325///
326/// A cache line apart for the same reason, so that two threads writing at once
327/// are not two threads passing one line back and forth.
328#[derive(Debug)]
329#[repr(align(64))]
330struct Local {
331    /// What the reactor counts.
332    stats: Stats,
333    /// A counter per command, for `INFO commandstats`.
334    cmdstats: CommandStats,
335    /// Which databases this thread has run a command against since the
336    /// maintenance turn last took the mask.
337    ///
338    /// One bit per database. The thread ors into it and the turn takes the whole
339    /// of it with a swap, which is what keeps a mark that lands during the swap
340    /// from being lost: the worst that can happen is a bit the turn has already
341    /// taken being set again, and that costs one more look at a database with
342    /// nothing to collect.
343    dirty: AtomicU64,
344    /// The mask this thread's maintenance turn is working from.
345    ///
346    /// Its own and not a shared one, because a turn reads it in place and then
347    /// clears bits of it, and a shared mask cleared that way would lose whatever
348    /// another thread marked in between. Every thread turns a loop and every
349    /// loop maintains, so what stops the same work being done twice is not the
350    /// mask but the stripe lock underneath it: two threads that both look at
351    /// database nine take turns, and the second one finds nothing left to move.
352    ///
353    /// Starts with every database set, so a server that has just been built
354    /// looks at all of them once rather than waiting to be told about the ones
355    /// something was loaded into before any command ran.
356    turn: AtomicU64,
357    /// How many of this thread's clients are on the waiter list.
358    ///
359    /// The waiter list is one list behind one lock, and a thread can only answer
360    /// the waiters it parked itself, so a thread with none of its own has no
361    /// reason to take that lock at all. Without this the check is the server
362    /// wide count, and one client blocked anywhere puts every thread through the
363    /// shared lock after every command it runs and again on every disconnect.
364    ///
365    /// Only the thread this belongs to writes it, because parking, answering and
366    /// forgetting a waiter all happen on the thread that read the command, so
367    /// the load and the store either side of a change cannot lose one.
368    parked: AtomicUsize,
369    /// The millisecond this thread last took every thread's marks.
370    ///
371    /// One per thread rather than one for the server, which is the opposite of
372    /// [`Server::expire_ms`] and for a reason. Taking the marks moves them out
373    /// of the shared counters and into the mask of whoever took them, so a
374    /// thread that skips a collection is a thread that never hears about a
375    /// database somebody else wrote to. A server wide gate would leave every
376    /// thread but one with a stale mask.
377    ///
378    /// Only the thread this belongs to reads or writes it, so it is a plain
379    /// number in an atomic rather than anything that needs ordering.
380    collect_ms: AtomicU64,
381    /// Where this thread's maintenance turn starts looking for a segment to
382    /// hand back.
383    ///
384    /// One per thread rather than one for the server, for the same reason
385    /// [`Local::collect_ms`] is one per thread and for one more. The turn runs
386    /// after every batch on every thread and it moves the cursor whether or not
387    /// it found anything, so a shared cursor is a line every thread writes at
388    /// batch rate, and what that costs grows with the thread count rather than
389    /// staying still. It is the only shared write left on a maintenance turn
390    /// that has nothing to do, which is nearly every turn on a server that is
391    /// keeping up.
392    ///
393    /// Sharing bought one thing, which is two threads not looking at the same
394    /// database at the same time, and that was already worth very little: the
395    /// second one takes the stripe lock, finds the first has moved what was
396    /// there and goes on. Starting each thread at its own index keeps most of
397    /// that anyway.
398    ///
399    /// [`Server::next_db`] stays shared and stays where it is, because the path
400    /// that reads it runs when a server is over its memory limit and writes it
401    /// only when it moved something.
402    ///
403    /// Only the thread this belongs to reads or writes it, so it is a plain
404    /// number in an atomic rather than anything that needs ordering.
405    compact_db: AtomicUsize,
406    /// Which databases this thread has to weigh again before the total it
407    /// publishes means anything.
408    ///
409    /// One bit per database, the same shape as [`Local::dirty`] and set from the
410    /// same places, because the two questions have the same answer: a database
411    /// something ran against is a database whose memory may have moved. They are
412    /// two masks and not one because they are taken by different readers at
413    /// different rates, and a mask one of them cleared would be a mask the other
414    /// never saw.
415    ///
416    /// Starts with every database set, so the first reading a thread takes is a
417    /// walk over all of them rather than a sum of sixteen zeroes.
418    ///
419    /// Only the thread this belongs to reads or writes it. Another thread's
420    /// writes arrive through [`Server::collect_marks`], so a database that only
421    /// somebody else has written to is weighed again on the next collection
422    /// rather than on the next batch.
423    unmeasured: AtomicU64,
424    /// The millisecond this thread last took a memory reading.
425    ///
426    /// The gate that turns a reading a batch into a reading a millisecond. See
427    /// [`Server::refresh_memory_slice`] for why a reading that old is enough,
428    /// which comes down to the reading only having to be exact at the moment a
429    /// command is judged against the limit, and [`Server::make_room`] taking its
430    /// own at that moment.
431    ///
432    /// Only the thread this belongs to reads or writes it.
433    measure_ms: AtomicU64,
434    /// Which database this thread weighs again next whatever its mask says.
435    ///
436    /// One a reading, round robin, so a database that something changed without
437    /// marking it is out of date for at most sixteen readings rather than until
438    /// the next time a client happens to name it. What that costs is one
439    /// database's stripes on a reading that would otherwise have touched none.
440    ///
441    /// Only the thread this belongs to reads or writes it.
442    measure_db: AtomicUsize,
443}
444
445impl Local {
446    /// A thread's counters, starting its compaction cursor at `at`.
447    fn at(at: usize) -> Local {
448        Local {
449            stats: Stats::default(),
450            cmdstats: CommandStats::default(),
451            dirty: AtomicU64::new(0),
452            turn: AtomicU64::new(ALL_DATABASES),
453            parked: AtomicUsize::new(0),
454            collect_ms: AtomicU64::new(u64::MAX),
455            compact_db: AtomicUsize::new(at),
456            unmeasured: AtomicU64::new(ALL_DATABASES),
457            measure_ms: AtomicU64::new(u64::MAX),
458            measure_db: AtomicUsize::new(at),
459        }
460    }
461}
462
463impl Default for Local {
464    fn default() -> Local {
465        Local::at(0)
466    }
467}
468
469impl Local {
470    /// Note that a command has run against these databases.
471    fn mark(&self, dbs: u64) {
472        self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
473        self.unmeasure(dbs);
474    }
475
476    /// Note that what these databases hold may have changed since they were last
477    /// weighed.
478    ///
479    /// Every write goes through [`Local::mark`], which calls this. The places
480    /// that call it on their own are the ones that change what a database holds
481    /// without a command having asked: the expiry sweep, compaction and
482    /// eviction. A path that forgot would be out of date until
483    /// [`Local::measure_db`] came round to it rather than wrong for good.
484    fn unmeasure(&self, dbs: u64) {
485        self.unmeasured
486            .store(self.unmeasured.load(Relaxed) | dbs, Relaxed);
487    }
488
489    /// Take the mask of databases to weigh again, leaving it empty.
490    ///
491    /// A swap and not a read, because the reading that follows is what makes
492    /// them measured. A mark that lands during it is left set and is weighed on
493    /// the next reading, which is the same one batch of slack every other number
494    /// on this path already carries.
495    fn to_weigh(&self) -> u64 {
496        self.unmeasured.swap(0, Relaxed)
497    }
498
499    /// Which database to weigh again this reading whatever the mask says, moving
500    /// the cursor on for the next one.
501    fn measure_next(&self) -> usize {
502        let at = self.measure_db.load(Relaxed) % DATABASES;
503        self.measure_db.store((at + 1) % DATABASES, Relaxed);
504        at
505    }
506
507    /// Whether this thread has yet to take a memory reading on millisecond
508    /// `now`.
509    ///
510    /// The same shape as [`Local::collecting`] and for the same reason: the
511    /// caller asks on every batch and pays for it a thousand times a second.
512    fn measuring(&self, now: u64) -> bool {
513        if self.measure_ms.load(Relaxed) == now {
514            return false;
515        }
516        self.measure_ms.store(now, Relaxed);
517        true
518    }
519
520    /// Add `dbs` to what this thread's turn is going to look at.
521    fn note(&self, dbs: u64) {
522        self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
523    }
524
525    /// Take `at` off the list of databases this thread's turn will look at.
526    fn done(&self, at: usize) {
527        self.turn
528            .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
529    }
530
531    /// Whether this thread's turn still has database `at` to look at.
532    fn wanted(&self, at: usize) -> bool {
533        self.turn.load(Relaxed) & (1u64 << at) != 0
534    }
535
536    /// Whether this thread has yet to take the marks on millisecond `now`.
537    ///
538    /// Says yes once a millisecond and remembers that it did, so the caller can
539    /// ask on every batch and pay for it a thousand times a second.
540    fn collecting(&self, now: u64) -> bool {
541        if self.collect_ms.load(Relaxed) == now {
542            return false;
543        }
544        self.collect_ms.store(now, Relaxed);
545        true
546    }
547
548    /// Note that `n` more of this thread's clients are parked.
549    fn blocked(&self, n: usize) {
550        self.parked
551            .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
552    }
553
554    /// Note that `n` of them are not parked any more.
555    fn woke(&self, n: usize) {
556        self.parked
557            .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
558    }
559}
560
561/// Room for one thread, which is what a server starts with.
562fn one_thread() -> Box<[Local]> {
563    slots(1)
564}
565
566/// Room for `threads` of them.
567fn slots(threads: usize) -> Box<[Local]> {
568    // By index, so that the compaction cursors start spread out over the
569    // databases rather than every thread walking in on the same one.
570    (0..threads.max(1)).map(Local::at).collect()
571}
572
573/// Where the process was started, which is what `dir` defaults to.
574///
575/// A dot if the working directory cannot be read, which happens when it has
576/// been deleted out from under a running process. That is not a reason to
577/// refuse to start a server, and it leaves `BACKUP` to fail with the real error
578/// from the filesystem if anybody asks for one.
579fn working_dir() -> PathBuf {
580    std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
581}
582
583/// One command's counters, for `INFO commandstats`.
584///
585/// Three of Redis's five. `usec` and `usec_per_call` are not here because
586/// nothing times a command, and timing one means two clock reads around a call
587/// that takes tens of nanoseconds to begin with. Redis pays that because Redis
588/// has room for it; this does not, and a zero under a name that says microseconds
589/// is worse than an absent field, which is the same rule the rest of `INFO`
590/// follows.
591#[derive(Debug, Clone, Copy, Default)]
592pub struct CommandStat {
593    /// Times the command ran, whatever it answered.
594    pub calls: u64,
595    /// Times it was turned away before it ran, which is the wrong number of
596    /// arguments or no room under `maxmemory`.
597    pub rejected: u64,
598    /// Times it ran and answered with an error.
599    pub failed: u64,
600}
601
602impl CommandStat {
603    /// Whether this command has ever been seen.
604    ///
605    /// A row that has not is left out of the reply, which is what Redis does and
606    /// is why the section is a handful of lines on a working server rather than
607    /// one line per command in the table.
608    const fn seen(&self) -> bool {
609        self.calls != 0 || self.rejected != 0 || self.failed != 0
610    }
611}
612
613/// One command's counters as one thread keeps them.
614///
615/// The same three numbers as [`CommandStat`], which is what they add up to when
616/// `INFO` asks. This is the written form and that is the read one.
617#[derive(Debug, Default)]
618struct Row {
619    /// Times the command ran.
620    calls: Counter,
621    /// Times it was turned away before it ran.
622    rejected: Counter,
623    /// Times it ran and answered with an error.
624    failed: Counter,
625}
626
627/// A counter per command, indexed the way [`table::index_of`] says.
628///
629/// A flat array and not a map, because the dispatcher is already holding the
630/// spec and the spec's position in the table is two addresses subtracted. That
631/// makes the counting a load, an add and a store on a row the previous command
632/// of the same name has already pulled into cache.
633#[derive(Debug)]
634struct CommandStats(Box<[Row]>);
635
636impl Default for CommandStats {
637    fn default() -> CommandStats {
638        CommandStats((0..table::count()).map(|_| Row::default()).collect())
639    }
640}
641
642impl CommandStats {
643    /// The row for one command.
644    fn at(&self, spec: &'static Spec) -> &Row {
645        &self.0[table::index_of(spec)]
646    }
647}
648
649/// Where a database gets its store from, asked by database number.
650///
651/// `None` means that database cannot have one. The caller owns whatever the
652/// stores are cut out of, which for `yodb` is one `.yo` file with a log per
653/// database, and this crate never learns what any of that is.
654pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
655
656/// Every thread that runs commands here shares this server, so it has to be
657/// `Send` and `Sync`, and the check is here so that a type added to it that is
658/// neither is a compile error where it was added rather than an error in the
659/// code that starts the threads.
660const _: () = {
661    const fn shareable<T: Send + Sync>() {}
662    shareable::<Server>();
663};
664
665/// Everything a server holds.
666///
667/// One per process, however many threads are serving out of it. What is inside
668/// is either shared outright, which is the counters and the settings, or behind
669/// a lock, which is the stripes and the few pieces of state a command can
670/// change. What makes this a server rather than a shard is that it is the whole
671/// of what a connection can address.
672pub struct Server {
673    dbs: Vec<Db>,
674    /// How many stripes each database is cut into, the same for all of them.
675    ///
676    /// Kept here as well as in each database so that the flat slot arithmetic
677    /// below is a multiply and a divide against a field on the server rather
678    /// than a walk asking each database how wide it is.
679    width: usize,
680    clock: Clock,
681    started_ms: u64,
682    /// Where the next hard compaction starts looking, so that a database under
683    /// constant write load cannot hold the other fifteen's space.
684    ///
685    /// Shared, because the thing that asks for one is a command that went over
686    /// the memory limit and is trying to get back under it, and that is any
687    /// thread. Two threads that read the same cursor start on the same
688    /// database, and what that costs is one of them finding the other has
689    /// already moved what was there. It is only written when a segment did
690    /// move, so a server that is not over its limit never touches it at all.
691    ///
692    /// The maintenance turn has its own cursor per thread rather than sharing
693    /// this one. See [`Local::compact_db`] for why.
694    next_db: AtomicUsize,
695    /// One bit per database, set when a command ran against it.
696    ///
697    /// The maintenance turn after every batch used to ask all sixteen
698    /// databases whether they had anything to collect, and asking costs a load
699    /// and a store in each one. Fifteen of those are cold lines on a server
700    /// where every client is on database zero, which is every server, and the
701    /// answer is no every time. This is the cheap half of the question: a
702    /// database nobody has touched since it last said no cannot have started
703    /// saying yes.
704    ///
705    /// What the connections are holding, kept by the engine.
706    ///
707    /// Shared, because every thread has connections and the memory total is one
708    /// total. Each thread adds and subtracts its own change rather than storing
709    /// a figure it worked out, so two threads whose buffers grew in the same
710    /// moment both count.
711    conn_bytes: AtomicUsize,
712    /// The `maxmemory` limit in bytes, zero when there is not one.
713    ///
714    /// Zero is the default and it is the whole reason the check in front of
715    /// every write is one comparison against a field that is already warm. It
716    /// is read by every command on every thread and written by a client that
717    /// sends `CONFIG SET`, so it is a number the threads can share rather than
718    /// a field one of them owns.
719    maxmemory: AtomicU64,
720    /// Where a database gets a store from the first time it needs one.
721    ///
722    /// A closure and not a store, because there are sixteen databases and a
723    /// server that fills memory on database zero should not have opened
724    /// anything for the other fifteen. Nothing is asked of this until a memory
725    /// limit is actually reached, so a server that never fills memory never
726    /// opens a file, and a server that has no file never has one of these.
727    ///
728    /// `None` from the closure means that database cannot have one, which is
729    /// how the caller says the file it opened has no more room for logs.
730    ///
731    /// Behind a lock because it is a closure the caller gave us and there is no
732    /// saying it can be run by two threads at once. It is asked once per
733    /// database, the first time that database has to move something, so a
734    /// server that has reached its memory limit takes this lock sixteen times
735    /// in its life.
736    store: Lock<Option<Box<StoreSource>>>,
737    /// The `maxstore` limit in bytes, `None` when there is not one.
738    ///
739    /// The storage limit, and the other half of the inversion `14` section 4.1
740    /// describes. `maxmemory` is a limit on memory and the right answer to a
741    /// memory limit on a system with a file under it is to move data to the
742    /// file, not to delete it. Deleting is the right answer to a limit on the
743    /// file, and this is that limit.
744    ///
745    /// Zero is not "no limit" here, which is the one place this reads
746    /// differently from `maxmemory` and is the difference that makes a drop in
747    /// cache possible. A storage budget of zero bytes means nothing may live on
748    /// the file, so migration cannot make room and eviction is the only thing
749    /// left, which is Redis exactly. `None` is no limit and is the default,
750    /// which with `noeviction` means the database grows until the disk is full
751    /// and then writes fail, which is what a database does.
752    ///
753    /// Shared between the threads the same way `maxmemory` is, and no limit is
754    /// [`NO_MAXSTORE`] rather than a second field saying whether the first one
755    /// counts. Two fields cannot be read as one, and a limit that was on when
756    /// the bytes were read and off by the time the number was is a limit that
757    /// answers from a server that never existed.
758    maxstore: AtomicU64,
759    /// What [`Server::memory_bytes`] said at the last maintenance turn.
760    ///
761    /// The reading is a walk over every collection in every database and cannot
762    /// go on a command path, so the command path reads this instead and is at
763    /// most one batch behind. What that costs is overshoot: a server can end a
764    /// batch holding one batch's worth of allocation more than its limit before
765    /// anything notices. A batch is 64 commands, so that is bounded by what 64
766    /// commands can allocate and not by how long the server runs.
767    ///
768    /// Only kept up to date when there is a limit to judge it against. A server
769    /// with no `maxmemory` never reads it and never pays for it.
770    ///
771    /// Shared, because it is read in front of every write on every thread and
772    /// written by whichever thread last took a reading. A reader that catches it
773    /// mid write gets one of the two readings and both of them were true a
774    /// moment ago, which is all this number ever claims to be.
775    used: AtomicUsize,
776    /// What each database was holding the last time anything read it.
777    ///
778    /// [`Server::settled_memory`] adds these up rather than walking the stripes
779    /// of every database, and re-reads a database only when something has marked
780    /// it since the last reading. Fifteen of the sixteen are empty on nearly
781    /// every server there is, and walking them was locking every stripe of every
782    /// one of them once a batch to be told the same number again.
783    ///
784    /// Shared and not per thread, so a database one thread re-read is a database
785    /// every thread has the fresh number for.
786    db_bytes: [AtomicUsize; DATABASES],
787    /// What the server was holding before a client had written anything.
788    ///
789    /// `MEMORY STATS` reports it as `startup.allocated` and subtracts it from
790    /// the total to work out what a key costs on average, which only means
791    /// something if the baseline is a real reading rather than a guess. So it is
792    /// taken once, at the end of building the server, and never again.
793    startup: AtomicUsize,
794    /// The largest total anything has ever seen here.
795    ///
796    /// See [`Server::peak_bytes`] for what "ever seen" means, which is not the
797    /// same as the largest total there ever was.
798    peak: AtomicUsize,
799    /// Which database the next eviction draws from.
800    ///
801    /// Its own cursor and not [`Server::next_db`], because eviction and
802    /// compaction move at different rates and sharing one would make the
803    /// database that gets compacted depend on how many keys were evicted.
804    ///
805    /// Shared for the same reason [`Server::next_db`] is, and with the same
806    /// answer: two threads evicting at once may pick the same database, and one
807    /// of them finds the other got there first and moves on.
808    evict_db: AtomicUsize,
809    /// Which database the next active expiry sweep starts at.
810    ///
811    /// A third cursor for the same reason there is a second one. A sweep runs on
812    /// every turn of the loop and compaction runs when there is dead space, so
813    /// sharing a cursor would make which database gets swept depend on which one
814    /// was last collected.
815    expire_db: AtomicUsize,
816    /// The millisecond the last active expiry sweep ran on, so the next one on
817    /// the same millisecond does not bother.
818    ///
819    /// One for the server and not one per thread, so the sweeping a server does
820    /// is a function of how long it has been running and not of how many threads
821    /// it was started with. Two threads that read the same millisecond can both
822    /// decide to sweep, which costs one extra sweep of a budget that is already
823    /// small and cannot happen twice for the same millisecond more than once per
824    /// thread.
825    expire_ms: AtomicU64,
826    /// Clients parked on a blocking command.
827    ///
828    /// Behind a lock because a client parks on the thread that ran its command
829    /// and is woken by whichever thread later puts something under a key it
830    /// named, and those are not the same thread. The lock is only ever taken to
831    /// park somebody, to serve somebody or to forget a connection that has gone,
832    /// so a command that does not block never touches it.
833    waiters: Lock<Waiters>,
834    /// How many clients are parked.
835    ///
836    /// Beside the list rather than read out of it, because every command asks
837    /// whether anybody is waiting and nearly every answer is no. Taking a lock
838    /// to be told no would be a cache line every thread has to own to ask, which
839    /// is the cost the list was put behind a lock to avoid.
840    ///
841    /// Written under the lock, by whoever changed the list, so the number and
842    /// the list agree except while a change is in progress. A reader that asks
843    /// during one is told about the moment before it, and the worst that costs
844    /// is a walk of the list that serves nobody or one that has not started yet
845    /// and happens on the next command instead.
846    parked: AtomicUsize,
847    /// Sockets `MIGRATE` is holding open to the servers it has talked to.
848    ///
849    /// Empty on a server nobody has migrated a key out of, which is nearly all
850    /// of them, and it costs a vector's three words to be empty.
851    ///
852    /// Behind a lock because a socket cannot be written by two threads at once
853    /// and a cache of them cannot be searched by one while another is taking an
854    /// entry out. It is held for the whole of a migration, which is a round trip
855    /// to another server, so two threads migrating at the same time take turns.
856    /// That is the right way round: the alternative is a socket per thread per
857    /// peer, and a `MIGRATE` is not what a server spends its time on.
858    peers: Lock<migrate::Peers>,
859    /// What each thread that runs commands here keeps to itself.
860    ///
861    /// A fixed list, because a thread reading its own entry must not have the
862    /// list move under it, and how many threads there will be is known before
863    /// any of them starts. A server nobody told otherwise has one.
864    locals: Box<[Local]>,
865    /// How many entries have been handed out.
866    claimed: AtomicUsize,
867    /// The next client id, which is what `CLIENT ID` answers.
868    ///
869    /// On the server and not on a front, because CLIENT LIST and CLIENT KILL
870    /// name a client by this number across the whole server, and two threads
871    /// counting on their own would hand the same number to two clients. Starts
872    /// at one so that zero is never a client, which is what makes it usable as
873    /// the id of a command that came from nowhere.
874    next_client: AtomicU64,
875    /// Where `BACKUP` puts its files, and where `CONFIG GET dir` points.
876    ///
877    /// Absolute, and resolved once when the server is built rather than every
878    /// time somebody asks. `BACKUP LIST` answers absolute paths and a client is
879    /// entitled to hand one of them to a copy tool, so a relative path that
880    /// meant something different after a `chdir` would be a path that stops
881    /// working for reasons nobody could see.
882    dir: PathBuf,
883    /// What backup is running, if one is.
884    ///
885    /// On the server and not on a session, because a backup outlives the
886    /// connection that asked for it and any other connection can seal it.
887    ///
888    /// Behind a lock because there is one backup at a time and any thread can be
889    /// the one that starts, seals or abandons it. It is held while the base file
890    /// is written, which is what keeps two `BACKUP START` commands from writing
891    /// over each other's files.
892    backup: Lock<backup::State>,
893    /// Whether a sealed backup is sitting on disk.
894    ///
895    /// Beside the state rather than read out of it, because every batch of
896    /// commands asks whether there is a backup old enough to sweep away and on
897    /// nearly every server the answer is that there is no backup at all. A load
898    /// answers that. Written under the lock by whoever moved the phase, so a
899    /// reader that asks mid-change sees the moment before and sweeps one batch
900    /// later, which is a file staying on disk for a few microseconds longer than
901    /// it had to.
902    sealed: AtomicBool,
903    /// The search indexes and the names pointing at them.
904    ///
905    /// On the server and not on a database, which is the one collection in this
906    /// build that is. A real server keeps its indexes in the search module, the
907    /// module has one table, and `SELECT 1` followed by `FT._LIST` lists the
908    /// indexes made on database zero. `search.rs` has the rest of why.
909    ///
910    /// A server nobody has made an index on holds two empty vectors here, which
911    /// is six words and no allocation.
912    ///
913    /// Behind a lock because an index is made and dropped by whichever thread
914    /// ran the command, and the table it goes in is one table. Only the `FT`
915    /// commands take it, so nothing a working server spends its time on comes
916    /// through here.
917    search: Lock<Registry>,
918    /// The replies that came back in pieces and have pieces left.
919    ///
920    /// Beside the indexes rather than inside one, because a cursor is read
921    /// under its own number and a real server resolves the index name on a read
922    /// and then pays no attention to it, so a cursor made on one index reads
923    /// through the name of another. Behind a lock for the reason the registry is
924    /// behind one, and a server nobody has opened a cursor on holds an empty map
925    /// here.
926    cursors: Lock<Cursors>,
927    /// The script bodies `EVALSHA` runs, by their digests.
928    ///
929    /// On the server rather than on a connection, because that is the whole
930    /// point of the cache. A client loads its scripts once when it starts up,
931    /// on whichever connection it happened to open first, and then sends nothing
932    /// but digests forever after, from every connection in its pool.
933    ///
934    /// Behind a lock because loading is a write and every thread can be the one
935    /// doing it. Held only long enough to add a body or copy one out, never
936    /// across a run: a running script calls commands, and those take locks of
937    /// their own.
938    scripts: Lock<lua::Scripts>,
939    /// Every library `FUNCTION LOAD` has taken, and what each one registered.
940    ///
941    /// Data only. A callback is a Lua value and there is an interpreter per
942    /// thread, so what is here is the name, the code, the digest of the code and
943    /// one row per function, and every thread compiles the code for itself the
944    /// first time one of its clients calls into the library.
945    libraries: Lock<lua::library::Libraries>,
946    /// Set by `SHUTDOWN`, and read by whatever is turning the loop.
947    ///
948    /// A flag rather than an exit, because the command layer is not what owns
949    /// the process. It runs inside a batch that has other commands behind it
950    /// and inside a driver that has a socket file to take away and a file to
951    /// close, and a server that calls `exit` from a command handler skips all
952    /// of that. So the command says stop and the driver stops, on the same turn
953    /// and through the same door a signal uses.
954    stopping: AtomicBool,
955    /// Every key any connection is watching, with a stamp on each.
956    ///
957    /// Here and not on the connection, and that is the whole design of `WATCH`
958    /// rather than an implementation detail. A connection cannot see a write
959    /// another thread made, so what records the write has to sit beside the key.
960    /// See the `multi` module for the rest of it.
961    watches: Lock<Watches>,
962    /// How many watched keys there are, so the write path can ask without
963    /// taking the lock.
964    ///
965    /// Zero on every server nobody has sent `WATCH` to, which is very nearly all
966    /// of them, and that is what keeps the cost of watches on a server that has
967    /// none down to one relaxed load per write.
968    watched: AtomicUsize,
969    /// Who is listening on what, for pub/sub.
970    ///
971    /// Here and not on the connection for the reason the watches are: a publish
972    /// arrives on a connection that knows nothing about the subscribers, so what
973    /// finds them has to sit beside the name rather than beside the client. See
974    /// the `pubsub` module for the rest of it.
975    pubsub: Lock<pubsub::Registry>,
976    /// How many subscriptions there are, so a publish can ask without taking
977    /// the lock.
978    ///
979    /// Zero on every server nobody has subscribed on, which is what keeps
980    /// `PUBLISH` on a server with no listeners down to one relaxed load.
981    subs: AtomicUsize,
982    /// One inbox per thread, for messages published on another one.
983    ///
984    /// Its own array and not a field on [`Local`], which is a cache line per
985    /// thread precisely so that no other thread writes to it. A mailbox is a
986    /// line another thread is meant to write to, so it gets one of its own.
987    mail: Box<[pubsub::Mailbox]>,
988    /// Which classes of keyspace notification are turned on.
989    ///
990    /// Zero is off and is the default, so the read every write does costs one
991    /// relaxed load and a test. It is `notify-keyspace-events` and the bits are
992    /// Redis's own, kept in the `notify` module beside the two parsers that
993    /// turn them into the setting text and back.
994    notify: AtomicU32,
995    /// One row per open connection, which is what `CLIENT LIST` reads and what
996    /// `CLIENT KILL` writes to.
997    ///
998    /// Here and not on the front for the reason the watches and the
999    /// subscriptions are here: both commands are about connections the thread
1000    /// running them does not own and cannot borrow. See the `clients` module.
1001    clients: Lock<clients::Clients>,
1002    /// How many connections have been asked to close and not closed yet.
1003    ///
1004    /// Zero on every server nobody has run `CLIENT KILL` on, which is what keeps
1005    /// the check on the flush path down to one load.
1006    kills: AtomicUsize,
1007    /// When the pause `CLIENT PAUSE` armed runs out, and what it covers.
1008    ///
1009    /// One word rather than a deadline and a mode beside it, because every
1010    /// command on every thread reads this and a server that has never been
1011    /// paused should pay one load and one test for it. The low bit says whether
1012    /// everything is held or only the writes, and the rest is the deadline in
1013    /// milliseconds. Zero is no pause at all, which is why the deadline is
1014    /// shifted up rather than packed into the top bits: the whole word is zero
1015    /// exactly when nothing is armed.
1016    pause: AtomicU64,
1017    /// The connections `MONITOR` is feeding, and a count of them.
1018    ///
1019    /// Here for the third time and for the third version of the same reason:
1020    /// the command being reported is running on a thread that cannot reach the
1021    /// connection being told about it. See the `monitor` module.
1022    monitors: monitor::Monitors,
1023    /// Being a master: the identity, the stream and whoever is being fed it.
1024    ///
1025    /// Here for the fourth time and for the fourth version of the same reason:
1026    /// the write being copied is running on a thread that cannot reach the
1027    /// connection it has to be copied to. See the `repl` module.
1028    repl: repl::Replication,
1029    /// Being a replica: who this server follows and the link out to them.
1030    ///
1031    /// Beside [`Server::repl`] rather than inside it because the two are
1032    /// opposite halves of the same idea and a server is nearly always neither.
1033    /// See the `follow` module.
1034    follow: follow::Follower,
1035    /// Handing the master's job over on purpose, which is `FAILOVER`.
1036    ///
1037    /// Beside the other two because it is the one thing that reaches into both:
1038    /// it starts on a master, waits on a replica, and ends with this server
1039    /// being one. See the `failover` module.
1040    failover: failover::Failover,
1041    /// The sixteen thousand slots and who owns each of them, which is all of
1042    /// cluster mode and is idle on a server that was not started as a node.
1043    ///
1044    /// Beside the replication fields because it is the other half of the same
1045    /// subject: replication is how one server's keys reach a second, and this is
1046    /// how a keyspace too big for one server is cut up in the first place. See
1047    /// the `cluster` module.
1048    cluster: cluster::Cluster,
1049    /// A handle on this server, for the one thing that outlives the command
1050    /// that started it.
1051    ///
1052    /// The replica link is a thread, and a thread cannot borrow the server it
1053    /// runs against, so it has to hold a counted handle. Nothing inside a
1054    /// `Server` can make one of those out of a borrow, so the handle is put here
1055    /// by whoever wrapped the server up, which is `Wire::over` and is the one
1056    /// place that has both. Weak rather than strong, because a strong one would
1057    /// be a server holding itself alive forever.
1058    ///
1059    /// Empty on an embedded caller that never built an engine, and `REPLICAOF`
1060    /// says so rather than pretending to have started a link.
1061    myself: Lock<Weak<Server>>,
1062    /// What the saves have done, which is all `INFO persistence` has to report.
1063    persist: persist::Persistence,
1064    /// Who is allowed to run what, which is also where `requirepass` lives.
1065    acl: acl::Users,
1066    /// Every refusal the ACL has made, which is what `ACL LOG` reports.
1067    acllog: acl::Log,
1068    /// The file `ACL LOAD` reads and `ACL SAVE` writes, empty when there is
1069    /// none, which is the default and is every server nobody gave one to.
1070    ///
1071    /// Taken at startup and never changed, the same as on a real server, where
1072    /// `aclfile` is an immutable config: a server that could be pointed at a
1073    /// different ACL file while it was running would be a server an operator
1074    /// could not reason about.
1075    aclfile: PathBuf,
1076    /// The plain `requirepass`, kept only so `CONFIG GET` can report it.
1077    plain: acl::Plain,
1078    /// The knobs `DEBUG` turns, which is what a test suite reaches for.
1079    debug: debug::Knobs,
1080}
1081
1082impl Server {
1083    /// A server with [`DATABASES`] empty databases on the system clock.
1084    #[must_use]
1085    pub fn new() -> Server {
1086        let clock = Clock::system();
1087        let server = Server {
1088            dbs: (0..DATABASES)
1089                .map(|_| Db::with_clock(clock.clone(), 1))
1090                .collect(),
1091            width: 1,
1092            started_ms: clock.now_ms(),
1093            clock,
1094            next_db: AtomicUsize::new(0),
1095            conn_bytes: AtomicUsize::new(0),
1096            maxmemory: AtomicU64::new(0),
1097            store: Lock::new(None),
1098            maxstore: AtomicU64::new(NO_MAXSTORE),
1099            used: AtomicUsize::new(0),
1100            db_bytes: [const { AtomicUsize::new(0) }; DATABASES],
1101            startup: AtomicUsize::new(0),
1102            peak: AtomicUsize::new(0),
1103            evict_db: AtomicUsize::new(0),
1104            expire_db: AtomicUsize::new(0),
1105            expire_ms: AtomicU64::new(0),
1106            waiters: Lock::default(),
1107            parked: AtomicUsize::new(0),
1108            peers: Lock::default(),
1109            locals: one_thread(),
1110            claimed: AtomicUsize::new(0),
1111            next_client: AtomicU64::new(1),
1112            dir: working_dir(),
1113            backup: Lock::default(),
1114            sealed: AtomicBool::new(false),
1115            search: Lock::new(Registry::new()),
1116            cursors: Lock::default(),
1117            scripts: Lock::default(),
1118            libraries: Lock::default(),
1119            stopping: AtomicBool::new(false),
1120            watches: Lock::default(),
1121            watched: AtomicUsize::new(0),
1122            pubsub: Lock::default(),
1123            subs: AtomicUsize::new(0),
1124            notify: AtomicU32::new(0),
1125            clients: Lock::default(),
1126            kills: AtomicUsize::new(0),
1127            pause: AtomicU64::new(0),
1128            monitors: monitor::Monitors::default(),
1129            repl: repl::Replication::default(),
1130            follow: follow::Follower::default(),
1131            failover: failover::Failover::default(),
1132            cluster: cluster::Cluster::default(),
1133            myself: Lock::new(Weak::new()),
1134            persist: persist::Persistence::default(),
1135            acl: acl::Users::default(),
1136            acllog: acl::Log::default(),
1137            aclfile: PathBuf::new(),
1138            plain: acl::Plain::default(),
1139            debug: debug::Knobs::default(),
1140            mail: pubsub::boxes(1),
1141        };
1142        server.note_startup();
1143        server
1144    }
1145
1146    /// A server whose databases are cut into `width` stripes each.
1147    ///
1148    /// Not reachable from the command line yet. Every command group answers on
1149    /// a server of any width now and so does everything that walks a whole
1150    /// database, and the tests run each group at a width of one and a width of
1151    /// eight and check the two agree.
1152    ///
1153    /// What is left before this is what `--threads` sets is the engine. A
1154    /// database being several objects is what makes more than one thread
1155    /// possible, and it is not what makes more than one thread happen.
1156    #[must_use]
1157    pub fn with_width(width: usize) -> Server {
1158        let mut server = Server::new();
1159        // The server's own clock and not a fresh one, because a database
1160        // reading a different clock from the server it is on is a database
1161        // whose keys expire against a time nobody set.
1162        let clock = server.clock.clone();
1163        server.dbs = (0..DATABASES)
1164            .map(|_| Db::with_clock(clock.clone(), width))
1165            .collect();
1166        server.width = server.dbs[0].width();
1167        // Again, because the databases the first reading was taken of have just
1168        // been thrown away and replaced with wider ones, and a wider database
1169        // is a bigger baseline.
1170        server.note_startup();
1171        server
1172    }
1173
1174    /// A server on a clock the caller moves by hand, for tests.
1175    #[must_use]
1176    pub fn with_clock(clock: Clock) -> Server {
1177        let server = Server {
1178            dbs: (0..DATABASES)
1179                .map(|_| Db::with_clock(clock.clone(), 1))
1180                .collect(),
1181            width: 1,
1182            started_ms: clock.now_ms(),
1183            clock,
1184            next_db: AtomicUsize::new(0),
1185            conn_bytes: AtomicUsize::new(0),
1186            maxmemory: AtomicU64::new(0),
1187            store: Lock::new(None),
1188            maxstore: AtomicU64::new(NO_MAXSTORE),
1189            used: AtomicUsize::new(0),
1190            db_bytes: [const { AtomicUsize::new(0) }; DATABASES],
1191            startup: AtomicUsize::new(0),
1192            peak: AtomicUsize::new(0),
1193            evict_db: AtomicUsize::new(0),
1194            expire_db: AtomicUsize::new(0),
1195            expire_ms: AtomicU64::new(0),
1196            waiters: Lock::default(),
1197            parked: AtomicUsize::new(0),
1198            peers: Lock::default(),
1199            locals: one_thread(),
1200            claimed: AtomicUsize::new(0),
1201            next_client: AtomicU64::new(1),
1202            dir: working_dir(),
1203            backup: Lock::default(),
1204            sealed: AtomicBool::new(false),
1205            search: Lock::new(Registry::new()),
1206            cursors: Lock::default(),
1207            scripts: Lock::default(),
1208            libraries: Lock::default(),
1209            stopping: AtomicBool::new(false),
1210            watches: Lock::default(),
1211            watched: AtomicUsize::new(0),
1212            pubsub: Lock::default(),
1213            subs: AtomicUsize::new(0),
1214            notify: AtomicU32::new(0),
1215            clients: Lock::default(),
1216            kills: AtomicUsize::new(0),
1217            pause: AtomicU64::new(0),
1218            monitors: monitor::Monitors::default(),
1219            repl: repl::Replication::default(),
1220            follow: follow::Follower::default(),
1221            failover: failover::Failover::default(),
1222            cluster: cluster::Cluster::default(),
1223            myself: Lock::new(Weak::new()),
1224            persist: persist::Persistence::default(),
1225            acl: acl::Users::default(),
1226            acllog: acl::Log::default(),
1227            aclfile: PathBuf::new(),
1228            plain: acl::Plain::default(),
1229            debug: debug::Knobs::default(),
1230            mail: pubsub::boxes(1),
1231        };
1232        server.note_startup();
1233        server
1234    }
1235
1236    /// One database, by index.
1237    ///
1238    /// A caller that knows which key it wants names the one stripe the key is
1239    /// on rather than working over the whole thing, which is what `at` and its
1240    /// neighbours on [`Db`] are for. A caller that is about a database rather
1241    /// than about a key, which is the snapshot walk and a setting, works over
1242    /// all of them.
1243    ///
1244    /// The database is marked as having had something run against it, which is
1245    /// what this does that [`Server::striped_ref`] does not. Anything that only
1246    /// reads asks for that one and leaves the mark alone.
1247    ///
1248    /// The borrow is shared, and what makes that enough is that a database is
1249    /// several stripes behind a lock each. A caller that wants to change
1250    /// something holds the stripe it is changing, so two threads working on two
1251    /// keys work at once and two working on one key take turns, which is the
1252    /// whole point of cutting a database up.
1253    ///
1254    /// # Panics
1255    ///
1256    /// If `i` is not a database. `SELECT` is the only way a client changes the
1257    /// index and it checks, so an index that is out of range here is a bug in
1258    /// the caller and not something a client can ask for.
1259    pub fn striped(&self, i: usize) -> &Db {
1260        self.mine().mark(1u64 << i);
1261        &self.dbs[i]
1262    }
1263
1264    /// Every keyspace on the server, which is every stripe of every database.
1265    ///
1266    /// What the aggregates walk. A total over the whole server is a total over
1267    /// all of these and the stripe boundaries do not appear in it, which is
1268    /// what makes the numbers `INFO` reports the same numbers whatever the
1269    /// server was cut into.
1270    fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
1271        self.dbs
1272            .iter()
1273            .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
1274    }
1275
1276    /// How many keyspaces there are, counting every stripe of every database.
1277    ///
1278    /// The maintenance turns walk these rather than the databases, because a
1279    /// stripe is the thing that holds an arena and a deadline heap and so it is
1280    /// the thing that has anything to collect.
1281    const fn slots(&self) -> usize {
1282        DATABASES * self.width
1283    }
1284
1285    /// Which database slot `i` belongs to.
1286    const fn slot_db(&self, i: usize) -> usize {
1287        i / self.width
1288    }
1289
1290    /// Keyspace `i` of [`Server::slots`].
1291    fn slot(&self, i: usize) -> Held<'_, Keyspace> {
1292        let (db, stripe) = (i / self.width, i % self.width);
1293        self.dbs[db].hold_stripe(stripe)
1294    }
1295
1296    /// Where `BACKUP` writes and what `CONFIG GET dir` answers.
1297    #[must_use]
1298    pub fn dir(&self) -> &Path {
1299        &self.dir
1300    }
1301
1302    /// Point the server at a different directory, which `yodb serve --dir` does.
1303    ///
1304    /// Only before it is serving. There is no `CONFIG SET dir` here and there
1305    /// is none on a real server either without turning protected configs on,
1306    /// for the good reason that moving it out from under a running backup would
1307    /// leave files nothing can find again.
1308    pub fn set_dir(&mut self, dir: PathBuf) {
1309        self.dir = dir;
1310    }
1311
1312    /// The file `ACL LOAD` reads and `ACL SAVE` writes, or `None` for a server
1313    /// that was not given one.
1314    #[must_use]
1315    pub fn aclfile(&self) -> Option<&Path> {
1316        Some(self.aclfile.as_path()).filter(|p| !p.as_os_str().is_empty())
1317    }
1318
1319    /// Point the server at an ACL file, which `yodb serve --aclfile` does.
1320    ///
1321    /// Only before it is serving, and giving one does not read it: the caller
1322    /// asks for that with [`Server::load_acl`], so that a file that will not
1323    /// parse can stop the process before the port opens rather than after.
1324    pub fn set_aclfile(&mut self, path: PathBuf) {
1325        self.aclfile = path;
1326    }
1327
1328    /// Read the ACL file, if there is one, and make it the server's users.
1329    ///
1330    /// # Errors
1331    ///
1332    /// Everything the file got wrong, in one sentence. A caller starting a
1333    /// server should print it and stop, which is what a real server does: coming
1334    /// up with the users an operator did not ask for is worse than not coming up.
1335    pub fn load_acl(&self) -> std::result::Result<(), String> {
1336        match self.aclfile() {
1337            Some(path) => yo_alloc::allow(|| acl::load_file(self, path)),
1338            None => Ok(()),
1339        }
1340    }
1341
1342    /// Drop a sealed backup that has outlived `backup-sealed-ttl`.
1343    ///
1344    /// Once per batch, from the same maintenance turn that collects the arena.
1345    /// It reads two fields and returns on a server that has never taken a
1346    /// backup, which is nearly all of them.
1347    pub fn backup_expire(&self) {
1348        backup::expire(self);
1349    }
1350
1351    /// Ask for the server to stop, which is what `SHUTDOWN` does.
1352    ///
1353    /// It sets a flag and returns. Nothing here closes a socket, flushes a file
1354    /// or ends the process, because none of those belong to this layer, and a
1355    /// batch that is halfway through still has to finish and be written out.
1356    pub fn stop(&self) {
1357        self.stopping.store(true, Release);
1358    }
1359
1360    /// Whether somebody has asked the server to stop.
1361    ///
1362    /// Read once per turn by the loop, next to the flag a signal sets. The two
1363    /// mean the same thing and are separate only because one arrives from the
1364    /// operating system and the other from a client.
1365    #[must_use]
1366    pub fn stopping(&self) -> bool {
1367        self.stopping.load(Acquire)
1368    }
1369
1370    /// One database, by index, without taking it mutably.
1371    ///
1372    /// What the prefetch stage needs. It runs for all 64 commands in a batch
1373    /// before any of them executes, so it cannot hold the mutable borrow `run`
1374    /// is about to want, and it does not need one: warming a cache line reads
1375    /// nothing and changes nothing.
1376    #[must_use]
1377    pub fn striped_ref(&self, i: usize) -> &Db {
1378        &self.dbs[i]
1379    }
1380
1381    /// The stripe that answers for a database when a setting is read back.
1382    ///
1383    /// A ladder setting and an eviction policy are one number on a real server,
1384    /// and the fact that every stripe of every database carries a copy of it is
1385    /// ours rather than the client's problem. A write puts the same value on
1386    /// every one of them, so any stripe answers for all of them and this is the
1387    /// first one.
1388    fn settings(&self) -> Held<'_, Keyspace> {
1389        self.dbs[0].hold_stripe(0)
1390    }
1391
1392    /// Take a new clock reading, which every database is looking at.
1393    ///
1394    /// Once per turn of the event loop, which is the only place time moves. A
1395    /// command asking what the time is gets the answer the whole batch got, so
1396    /// two keys written by the same batch expire together (`04` section 3).
1397    ///
1398    /// Every thread does this on every turn of its own loop and they do not
1399    /// have to agree about when. The reading is only stored when the
1400    /// millisecond has changed, so what the threads are sharing is a line that
1401    /// is written about a thousand times a second and read millions.
1402    pub fn refresh_clock(&self) {
1403        self.clock.refresh();
1404    }
1405
1406    /// Move every clock here on by `ms`, for tests about expiry.
1407    ///
1408    /// The same thing [`Server::set_clock_ms`] does and by the same argument,
1409    /// except that it moves from wherever the clock is rather than to a stated
1410    /// moment, which is what a test that wants a key to have expired asks for.
1411    pub fn advance_clock_ms(&self, ms: u64) {
1412        let now = self.clock.now_ms() + ms;
1413        self.set_clock_ms(now);
1414    }
1415
1416    /// Move every clock here to `ms` by hand, for tests about expiry.
1417    ///
1418    /// A test cannot wait a hundred seconds and a test that waits a hundred
1419    /// milliseconds is a test that fails on a loaded machine, so time moves on
1420    /// request. The system clock underneath will overwrite this on the next
1421    /// [`Server::refresh_clock`], which is why this is only useful in a test
1422    /// that drives commands directly rather than through the event loop.
1423    pub fn set_clock_ms(&self, ms: u64) {
1424        self.clock.set(ms);
1425    }
1426
1427    /// Seconds since this server was built.
1428    #[must_use]
1429    pub fn uptime_secs(&self) -> u64 {
1430        self.clock.now_ms().saturating_sub(self.started_ms) / 1000
1431    }
1432
1433    /// Bytes held by every database's index and arena, plus the read and reply
1434    /// buffers of every connection.
1435    ///
1436    /// The buffers are in here because they are real and because Redis counts
1437    /// its own, so leaving them out would make the one number people compare
1438    /// flattering rather than true. They are not a database, so nothing in the
1439    /// keyspace can change them and the engine has to say when they move.
1440    #[must_use]
1441    pub fn memory_bytes(&self) -> usize {
1442        self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1443    }
1444
1445    /// What the server was holding before any client had written to it.
1446    ///
1447    /// `MEMORY STATS` reports this as `startup.allocated`.
1448    #[must_use]
1449    pub fn startup_bytes(&self) -> usize {
1450        self.startup.load(Relaxed)
1451    }
1452
1453    /// The largest total anything here has ever seen, this reading included.
1454    ///
1455    /// Peak memory is a sampled number on a real server too: `serverCron` takes
1456    /// a reading every hundred milliseconds and keeps the largest one. This is
1457    /// sampled as well, at the points where the total is already being worked
1458    /// out, which is once a batch on a server with a `maxmemory` and once a call
1459    /// on one without. So on a server with no limit that nobody is watching, the
1460    /// peak is the highest of the readings something asked for, which is the
1461    /// most a server that never takes a reading can honestly claim.
1462    #[must_use]
1463    pub fn peak_bytes(&self) -> usize {
1464        let now = self.memory_bytes();
1465        self.peak.fetch_max(now, Relaxed).max(now)
1466    }
1467
1468    /// Take the reading both of those start from.
1469    fn note_startup(&self) {
1470        let now = self.memory_bytes();
1471        self.startup.store(now, Relaxed);
1472        self.peak.store(now, Relaxed);
1473    }
1474
1475    /// What the keyspace itself is holding, live records only.
1476    ///
1477    /// `used_memory` minus this is what the store costs to run: the index, the
1478    /// space dead records are sitting in until compaction gets to them, and the
1479    /// connections' buffers.
1480    #[must_use]
1481    pub fn dataset_bytes(&self) -> usize {
1482        self.keyspaces()
1483            .map(|db| db.map().arena().live_bytes() as usize)
1484            .sum()
1485    }
1486
1487    /// Bytes the arenas are holding, live and dead together.
1488    #[must_use]
1489    pub fn arena_bytes(&self) -> usize {
1490        self.keyspaces()
1491            .map(|db| db.map().arena().reserved_bytes() as usize)
1492            .sum()
1493    }
1494
1495    /// Bytes the indexes are holding.
1496    #[must_use]
1497    pub fn index_bytes(&self) -> usize {
1498        self.keyspaces()
1499            .map(|db| db.map().index().memory_bytes())
1500            .sum()
1501    }
1502
1503    /// What arena compaction has cost, across every database.
1504    ///
1505    /// The write amplification of value separation, which is invisible from the
1506    /// outside otherwise: a client that writes a megabyte can leave the store
1507    /// copying several more, and the only sign of it without these is that the
1508    /// writes got slower.
1509    #[must_use]
1510    pub fn compaction(&self) -> yo_kv::Compaction {
1511        self.keyspaces().map(|db| db.map().compaction()).fold(
1512            yo_kv::Compaction::default(),
1513            |a, b| yo_kv::Compaction {
1514                walked: a.walked + b.walked,
1515                moved: a.moved + b.moved,
1516                bytes: a.bytes + b.bytes,
1517            },
1518        )
1519    }
1520
1521    /// Freed runs waiting on an arena size class list, across every database.
1522    ///
1523    /// How much of the store's own garbage is already back in circulation. A
1524    /// server whose value lengths repeat keeps a small number here and never
1525    /// compacts, and a server whose lengths wander keeps a large one and does,
1526    /// so the two numbers beside each other say which of the two collectors is
1527    /// doing the work.
1528    #[must_use]
1529    pub fn listed_runs(&self) -> usize {
1530        self.keyspaces()
1531            .map(|db| db.map().arena().listed_runs())
1532            .sum()
1533    }
1534
1535    /// Arena segments whose pages are real, across every database.
1536    #[must_use]
1537    pub fn segment_count(&self) -> usize {
1538        self.keyspaces()
1539            .map(|db| db.map().arena().resident_segments())
1540            .sum()
1541    }
1542
1543    /// What the connections' read and reply buffers are holding.
1544    #[must_use]
1545    pub fn conn_bytes(&self) -> usize {
1546        self.conn_bytes.load(Relaxed)
1547    }
1548
1549    /// Note that the connections are holding `delta` bytes more than they were,
1550    /// or fewer when it is negative.
1551    ///
1552    /// A delta and not a total because the alternative is a walk over every
1553    /// connection, and the walk would have to happen on a turn of the loop
1554    /// rather than when `INFO` asks, which puts the cost of a report on the
1555    /// command path of a server nobody is asking.
1556    pub fn note_conn_bytes(&self, delta: isize) {
1557        // A read and a write and not a fetch and add, because the number is a
1558        // sum of signed changes and the saturating part has to happen in the
1559        // middle. Two threads that change their buffers in the same instant can
1560        // lose one of the two changes, which is a report that is a few kilobytes
1561        // out until the next connection on either thread moves it again.
1562        self.conn_bytes
1563            .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1564    }
1565
1566    /// Keys reclaimed by running into them after their deadline.
1567    #[must_use]
1568    pub fn expired_keys(&self) -> u64 {
1569        self.keyspaces().map(|db| db.expired_keys()).sum()
1570    }
1571
1572    /// Hash fields reclaimed after their own deadline passed.
1573    #[must_use]
1574    pub fn expired_fields(&self) -> u64 {
1575        self.keyspaces().map(|db| db.expired_fields()).sum()
1576    }
1577
1578    /// The share of those the cycle found rather than a command tripping over.
1579    #[must_use]
1580    pub fn expired_fields_active(&self) -> u64 {
1581        self.keyspaces().map(|db| db.expired_fields_active()).sum()
1582    }
1583
1584    /// Keys thrown away to make room, which is the other number entirely.
1585    #[must_use]
1586    pub fn evicted_keys(&self) -> u64 {
1587        self.keyspaces().map(|db| db.evicted_keys()).sum()
1588    }
1589
1590    /// Lookups a client's read made that found the key.
1591    #[must_use]
1592    pub fn keyspace_hits(&self) -> u64 {
1593        self.keyspaces().map(|db| db.hits()).sum()
1594    }
1595
1596    /// Lookups a client's read made that did not.
1597    #[must_use]
1598    pub fn keyspace_misses(&self) -> u64 {
1599        self.keyspaces().map(|db| db.misses()).sum()
1600    }
1601
1602    /// Every command that has been seen, with its counters.
1603    ///
1604    /// Only the ones that have. A server reports a handful of lines rather than
1605    /// one per command in the table, which is what Redis does and is the
1606    /// difference between a section a person can read and one they cannot.
1607    pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1608        (0..table::count())
1609            .map(|at| (table::name_at(at), self.command_stat(at)))
1610            .filter(|(_, row)| row.seen())
1611    }
1612
1613    /// One command's counters, added up over every thread.
1614    fn command_stat(&self, at: usize) -> CommandStat {
1615        let mut sum = CommandStat::default();
1616        for thread in &self.locals {
1617            let row = &thread.cmdstats.0[at];
1618            sum.calls += row.calls.get();
1619            sum.rejected += row.rejected.get();
1620            sum.failed += row.failed.get();
1621        }
1622        sum
1623    }
1624
1625    /// The counters the calling thread writes into.
1626    ///
1627    /// The first call on a thread claims a set and every call after it is a
1628    /// thread local read and an index. A server asked to count from more threads
1629    /// than it was built for wraps round and shares a set, which loses the odd
1630    /// count between two threads and cannot happen to a server `yodb serve`
1631    /// built, because that one is told how many threads it will have before it
1632    /// starts any of them.
1633    pub fn counted(&self) -> &Stats {
1634        &self.mine().stats
1635    }
1636
1637    /// The next client id, taken.
1638    ///
1639    /// Every accept anywhere on this server comes through here, so no two
1640    /// clients share a number however many threads are accepting.
1641    pub fn next_client(&self) -> u64 {
1642        self.next_client.fetch_add(1, Relaxed)
1643    }
1644
1645    /// Say which handle this server is behind, so a background thread can hold
1646    /// one.
1647    ///
1648    /// Called by whoever wrapped it up, as many times as there are threads, and
1649    /// every call after the first says the same thing. It cannot be worked out
1650    /// from the inside, because a `&Server` has no way to reach the handle it
1651    /// is behind, so whoever made the handle has to say.
1652    pub fn is_behind(self: &Arc<Server>) {
1653        let mut myself = self.myself.lock();
1654        if myself.strong_count() == 0 {
1655            *myself = Arc::downgrade(self);
1656        }
1657    }
1658
1659    /// Put that handle down again, so the server can be reached mutably.
1660    ///
1661    /// `Arc::get_mut` counts weak handles as well as strong ones, so a server
1662    /// that knows what it is behind cannot be borrowed mutably while it knows
1663    /// it. Everything that wants a mutable one is startup, which happens before
1664    /// any thread could be holding the handle, so putting it down and picking it
1665    /// up at the next [`Server::is_behind`] costs nothing and keeps the startup
1666    /// path exactly as it was.
1667    pub fn forget_behind(&self) {
1668        let mut myself = self.myself.lock();
1669        *myself = Weak::new();
1670    }
1671
1672    /// A counted handle on this server, for a thread that outlives its caller.
1673    ///
1674    /// `None` on a server nobody wrapped up, and on one that is being dropped,
1675    /// which is the same answer for the same reason: there is no server here to
1676    /// hand a thread.
1677    #[must_use]
1678    pub(crate) fn myself(&self) -> Option<Arc<Server>> {
1679        self.myself.lock().upgrade()
1680    }
1681
1682    /// Which set of per thread state the calling thread is on.
1683    ///
1684    /// The number a blocked client is filed under, so that the thread holding
1685    /// that client's connection is the one that answers it. Claims a set on the
1686    /// first call the same way [`Server::counted`] does, and gives back the same
1687    /// number every time after.
1688    pub fn my_slot(&self) -> usize {
1689        self.mine_at()
1690    }
1691
1692    /// Everything the calling thread keeps to itself.
1693    fn mine(&self) -> &Local {
1694        &self.locals[self.mine_at()]
1695    }
1696
1697    /// The calling thread's place in `locals`, claiming one if it has none.
1698    ///
1699    /// Wraps round when more threads count here than the server was built for,
1700    /// which shares a set between two threads and loses the odd count. That
1701    /// cannot happen to the server `yodb serve` builds, because it is told how
1702    /// many threads it will have before it starts any of them.
1703    fn mine_at(&self) -> usize {
1704        let mut slot = SLOT.get();
1705        if slot == usize::MAX {
1706            slot = self.claimed.fetch_add(1, Relaxed);
1707            SLOT.set(slot);
1708        }
1709        slot % self.locals.len()
1710    }
1711
1712    /// Every thread's numbers added together, which is what `INFO` reports.
1713    #[must_use]
1714    pub fn totals(&self) -> Totals {
1715        let mut sum = Totals::default();
1716        for thread in &self.locals {
1717            sum.clients += thread.stats.clients.get();
1718            sum.connections += thread.stats.connections.get();
1719            sum.commands += thread.stats.commands.get();
1720        }
1721        sum
1722    }
1723
1724    /// The same numbers kept apart, one entry per thread, in slot order.
1725    ///
1726    /// [`Self::totals`] is the sum and it is the sum that answers how busy the
1727    /// server has been. What it cannot answer is whether the threads are
1728    /// carrying the same load as each other, and on a server where every thread
1729    /// keeps the connections it accepted for as long as they are open, that is
1730    /// a question with real consequences: an uneven split is paid by the clients
1731    /// on the crowded thread and is invisible in every number that adds the
1732    /// threads up first.
1733    ///
1734    /// The length is how many threads the server was built for rather than how
1735    /// many have counted anything, so a thread that has not run a command yet
1736    /// shows as zeroes instead of being missing.
1737    #[must_use]
1738    pub fn per_thread(&self) -> Vec<Totals> {
1739        self.locals
1740            .iter()
1741            .map(|thread| Totals {
1742                clients: thread.stats.clients.get(),
1743                connections: thread.stats.connections.get(),
1744                commands: thread.stats.commands.get(),
1745            })
1746            .collect()
1747    }
1748
1749    /// Put the totals back to zero, which is `CONFIG RESETSTAT`.
1750    ///
1751    /// Every thread's set and not only the one asking, since the number the
1752    /// client is resetting is the sum it was just shown. The open connections
1753    /// are left alone because that is a gauge and not a total: the connections
1754    /// are still open.
1755    pub fn reset_stats(&self) {
1756        for thread in &self.locals {
1757            thread.stats.connections.zero();
1758            thread.stats.commands.zero();
1759        }
1760        // These live on the stripes rather than on the threads, so resetting
1761        // them means holding each stripe for as long as it takes to write a
1762        // handful of zeroes. `CONFIG RESETSTAT` is a command a person types, and
1763        // the alternative is a set of numbers a dashboard cannot put back.
1764        for mut db in self.keyspaces() {
1765            db.zero_stats();
1766        }
1767    }
1768
1769    /// Say how many threads will run commands here, before any of them does.
1770    ///
1771    /// What it changes is how many sets of counters there are, and how many
1772    /// pub/sub mailboxes. Called once at startup by whoever is about to start
1773    /// the threads, and calling it on a running server throws away what has been
1774    /// counted so far, which is why it wants the server to itself.
1775    pub fn set_threads(&mut self, threads: usize) {
1776        self.locals = slots(threads);
1777        self.mail = pubsub::boxes(threads);
1778        self.claimed = AtomicUsize::new(0);
1779    }
1780
1781    /// How many threads will run commands here.
1782    ///
1783    /// The number [`set_threads`](Self::set_threads) was given, and one on a
1784    /// server nobody told, which is what `INFO` and `CONFIG GET io-threads`
1785    /// answer. It counts the threads the server was built for rather than the
1786    /// ones that have accepted a connection, for the same reason
1787    /// [`per_thread`](Self::per_thread) has a row for a thread that has done
1788    /// nothing: a thread that is waiting is still a thread that is there.
1789    #[must_use]
1790    pub fn io_threads(&self) -> usize {
1791        self.locals.len()
1792    }
1793
1794    /// The `maxmemory` limit in bytes, zero when there is not one.
1795    #[must_use]
1796    pub fn maxmemory(&self) -> u64 {
1797        self.maxmemory.load(Relaxed)
1798    }
1799
1800    /// Set the limit, and take a reading straight away.
1801    ///
1802    /// The reading is here rather than left to the next maintenance turn because
1803    /// a client that sets the limit and sends a write in the same batch expects
1804    /// the write to be judged against the limit it just set, and because the
1805    /// cached number is meaningless until the first time there is a limit to
1806    /// compare it with.
1807    ///
1808    /// Turning the limit on also turns on the running total every slab keeps of
1809    /// what its collections hold, and turning it off turns that back off, so a
1810    /// server with no limit is not paying to count something nobody reads. The
1811    /// first reading after switching it on is the walk that the total starts
1812    /// from, and it is the only walk.
1813    pub fn set_maxmemory(&self, bytes: u64) {
1814        self.maxmemory.store(bytes, Relaxed);
1815        for db in &self.dbs {
1816            db.track_memory(bytes != 0);
1817        }
1818        // Every database and not only the ones something has run against, since
1819        // this is the walk the running totals start from and a database that
1820        // takes its last reading from before the limit existed would be a
1821        // database counted at whatever it held then.
1822        self.mine().unmeasure(ALL_DATABASES);
1823        self.used.store(self.settled_memory(), Relaxed);
1824    }
1825
1826    /// Say where a database should get its store from when it needs one.
1827    ///
1828    /// This is what turns the eviction inversion on. Until it is called every
1829    /// database answers a memory limit by evicting, which is Redis, and after it
1830    /// is called a database under memory pressure moves values to whatever the
1831    /// closure hands back instead of throwing keys away.
1832    ///
1833    /// Called at most once per database and only under pressure, so a server
1834    /// that is given a file and never fills memory never touches it.
1835    pub fn set_store_source(
1836        &mut self,
1837        source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1838    ) {
1839        *self.store.lock() = Some(Box::new(source));
1840    }
1841
1842    /// Whether this server has been given somewhere to put cold values.
1843    #[must_use]
1844    pub fn has_store_source(&self) -> bool {
1845        self.store.lock().is_some()
1846    }
1847
1848    /// Open database `at`'s store, if it has not got one and there is one to be
1849    /// had.
1850    ///
1851    /// A store that will not open leaves the database where it was, which is
1852    /// evicting, because a memory limit that cannot be answered by moving data
1853    /// still has to be answered.
1854    fn attach_store(&self, at: usize) {
1855        if self.slot(at).store_bytes().is_some() {
1856            return;
1857        }
1858        // The closure is run with its lock held and the keyspace is taken after
1859        // it has answered, so the file is opened once however many threads asked
1860        // for it and the stripe is not held while a file is being opened.
1861        let mut source = self.store.lock();
1862        let Some(source) = source.as_mut() else {
1863            return;
1864        };
1865        if let Some(blocks) = source(at) {
1866            self.slot(at).attach(blocks);
1867        }
1868    }
1869
1870    /// The `maxstore` limit in bytes, `None` when there is not one.
1871    #[must_use]
1872    pub fn maxstore(&self) -> Option<u64> {
1873        match self.maxstore.load(Relaxed) {
1874            NO_MAXSTORE => None,
1875            bytes => Some(bytes),
1876        }
1877    }
1878
1879    /// Set the storage limit, or clear it with `None`.
1880    ///
1881    /// Nothing is read here the way [`Server::set_maxmemory`] reads the memory
1882    /// total, because this limit is compared against a number the store keeps
1883    /// and answers on demand, not against a walk.
1884    pub fn set_maxstore(&self, bytes: Option<u64>) {
1885        self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1886    }
1887
1888    /// What every attached store is holding, for `INFO memory`.
1889    ///
1890    /// Zero on a server with nothing attached, which is not the same as a server
1891    /// whose file is empty, and [`Server::regime`] is the field that tells those
1892    /// two apart.
1893    #[must_use]
1894    pub fn store_bytes(&self) -> u64 {
1895        self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1896    }
1897
1898    /// What the file has been asked to do, added up over every database.
1899    ///
1900    /// Counters and not levels, so they only ever go up and a run is the
1901    /// difference between two readings. G9 is a ratio over these: the faults a
1902    /// run took, divided by the point reads it issued, has to come out at 1.05
1903    /// or less with a working set ten times memory. There is no way to work that
1904    /// out from outside the server, so it is reported rather than inferred.
1905    ///
1906    /// A fault is a read that went to the store. Whether it also went to the
1907    /// device depends on the store: a log serves a read out of a resident page
1908    /// without touching anything. At ten times memory almost every fault is a
1909    /// real read, which is why the gate is written against this number, but the
1910    /// two are not the same thing and a run tight against the bar should be
1911    /// checked against what the operating system says.
1912    #[must_use]
1913    pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1914        let mut total = yo_kv::tier::Stats::default();
1915        for db in self.keyspaces() {
1916            let Some(tier) = db.tier() else { continue };
1917            let s = tier.stats();
1918            total.demoted += s.demoted;
1919            total.promoted += s.promoted;
1920            total.faults += s.faults;
1921            total.served += s.served;
1922            total.bytes_out += s.bytes_out;
1923            total.bytes_in += s.bytes_in;
1924        }
1925        total
1926    }
1927
1928    /// Which way this server answers a memory limit, in one word for `INFO`.
1929    ///
1930    /// `evict` is Redis: a memory limit throws keys away. `migrate` is the
1931    /// inversion: a memory limit moves values to the file and nothing stored is
1932    /// lost. A server reports one word rather than leaving an operator to work
1933    /// it out from a limit, a setting and whether a file happens to be open.
1934    #[must_use]
1935    pub fn regime(&self) -> &'static str {
1936        if (0..self.slots()).any(|at| self.migrates(at)) {
1937            "migrate"
1938        } else {
1939            "evict"
1940        }
1941    }
1942
1943    /// Whether database `at` answers a memory limit by moving values to the
1944    /// file rather than by throwing keys away.
1945    ///
1946    /// Three things have to hold. There has to be somewhere to move them, which
1947    /// is a store attached to that database or a source that can open one, and
1948    /// on a server that was never given a file this is false everywhere and
1949    /// every database behaves exactly as it did.
1950    /// The storage budget has to be more than nothing, which is what
1951    /// `maxstore 0` says it is not. And the file has to be under that budget,
1952    /// because a full file is a storage limit reached and eviction is the right
1953    /// answer to a storage limit.
1954    fn migrates(&self, at: usize) -> bool {
1955        let cap = self.maxstore();
1956        if cap == Some(0) {
1957            return false;
1958        }
1959        // Out of the stripe first. A match keeps whatever it is looking at
1960        // alive for the whole of itself, and that would be this stripe held
1961        // across the arms for no reason.
1962        let bytes = self.slot(at).store_bytes();
1963        match bytes {
1964            Some(held) => cap.is_none_or(|cap| held < cap),
1965            // Nothing attached, but somewhere to get one from the moment this
1966            // database needs it, which is what makes the answer yes rather than
1967            // no. Opening it here would mean `INFO` opened files.
1968            None => self.store.lock().is_some(),
1969        }
1970    }
1971
1972    /// The reading the shard loop takes, at most once a millisecond.
1973    ///
1974    /// The gate is the whole difference between this and
1975    /// [`Server::refresh_memory`], and it is the same gate
1976    /// [`Server::expire_slice`] puts in front of the expiry sweep. A maintenance
1977    /// turn runs on every batch and a batch is a hundred nanoseconds, so a
1978    /// reading a batch is ten thousand readings a millisecond of a number that
1979    /// moves by what sixty four commands allocated.
1980    ///
1981    /// What a reading that old costs is overshoot, and it is bounded by what a
1982    /// millisecond of writes can allocate. That is far inside the tolerance this
1983    /// number already has: space comes back a segment at a time and a segment is
1984    /// two megabytes, so the limit was never held to closer than that.
1985    ///
1986    /// The case that matters is a server sitting at its limit, and that one is
1987    /// not judged on this reading at all. [`Server::make_room`] takes its own the
1988    /// moment the cached one says the server is over, which is the moment the
1989    /// number has to be exact.
1990    pub fn refresh_memory_slice(&self) {
1991        if self.maxmemory() != 0 && self.mine().measuring(self.clock.now_ms()) {
1992            self.refresh_memory();
1993        }
1994    }
1995
1996    /// Take a fresh memory reading.
1997    ///
1998    /// Nothing at all when there is no limit, which is the default and is every
1999    /// server that has not asked for one.
2000    pub fn refresh_memory(&self) {
2001        if self.maxmemory() != 0 {
2002            let used = self.settled_memory();
2003            self.used.store(used, Relaxed);
2004            // The peak comes along for free here, because the walk that would
2005            // otherwise cost something has already happened. It is the reason a
2006            // server with a limit has a peak that means what it says and a
2007            // server without one has a peak that is only as good as the last
2008            // time somebody asked.
2009            self.peak.fetch_max(used, Relaxed);
2010        }
2011    }
2012
2013    /// [`Server::memory_bytes`], asked the cheap way.
2014    ///
2015    /// Two things make it cheaper and they cut different ways. A database that
2016    /// has been marked is asked only about the collections that could have moved
2017    /// since the last time rather than about everything it holds, which is
2018    /// [`Keyspace::settled_memory_bytes`]. A database that has not been marked is
2019    /// not asked at all and its last reading is used instead, which is what keeps
2020    /// the fifteen empty databases nearly every server has off a path that runs
2021    /// once a batch.
2022    ///
2023    /// One more database is weighed than the mask asked for, round robin, so
2024    /// that a reading cannot be stale for good if something changed a database
2025    /// without saying so.
2026    fn settled_memory(&self) -> usize {
2027        let mine = self.mine();
2028        let marked = mine.to_weigh() | 1u64 << mine.measure_next();
2029        let mut total = self.conn_bytes();
2030        for at in 0..DATABASES {
2031            if marked & (1u64 << at) == 0 {
2032                total += self.db_bytes[at].load(Relaxed);
2033                continue;
2034            }
2035            let db = &self.dbs[at];
2036            let now = (0..db.width())
2037                .map(|i| db.hold_stripe(i).settled_memory_bytes())
2038                .sum::<usize>();
2039            self.db_bytes[at].store(now, Relaxed);
2040            total += now;
2041        }
2042        total
2043    }
2044
2045    /// Make room under the `maxmemory` limit, throwing keys away if that is what
2046    /// it takes. Answers whether there is anything left it could throw away.
2047    ///
2048    /// Redis runs the same thing from `processCommand` before every command and
2049    /// so does this: a client that writes has to be judged at the moment it
2050    /// writes, not a batch later, or the limit is a suggestion.
2051    ///
2052    /// Three things happen in the loop and all three are needed. Eviction picks
2053    /// a key and drops it. Compaction gives the pages back, because dropping a
2054    /// key marks its record dead and returns nothing on its own, so a loop that
2055    /// only evicted would throw the whole keyspace away and watch the number
2056    /// stay where it was. The reading is taken again each time round, because
2057    /// the two of them together are the only thing that moves it.
2058    ///
2059    /// # Why running out of budget is not a no
2060    ///
2061    /// `false` means there was nothing left to evict, which is `noeviction`, or
2062    /// a `volatile` policy on a database where nothing has a deadline, or a
2063    /// keyspace that is already empty. It does not mean the server is still over
2064    /// its limit, and that difference is Redis's: `performEvictions` answers
2065    /// `EVICT_FAIL` only when it has run out of things to delete, and
2066    /// `processCommand` refuses the client on that and on nothing else. Running
2067    /// out of time part way through a job it is doing well comes back as
2068    /// `EVICT_RUNNING` and the command goes through, because a server that is
2069    /// evicting steadily and refusing every write while it does it is worse for
2070    /// the client than a little overshoot.
2071    ///
2072    /// # What the limit is worth
2073    ///
2074    /// Space comes back a segment at a time and a segment is two megabytes, so
2075    /// this holds a server to its limit give or take a segment. A `maxmemory` of
2076    /// a few hundred megabytes gets what it asked for. A `maxmemory` of four
2077    /// megabytes is asking for a precision this store does not have.
2078    pub fn make_room(&self) -> bool {
2079        let limit = self.maxmemory();
2080        if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
2081            return true;
2082        }
2083        // The cached reading is a batch old and the batch may have compacted
2084        // since, so take a fresh one before throwing anything away. It is the
2085        // settled reading and not the walk, so what this costs is the handful of
2086        // collections the last batch touched and not the whole database.
2087        let mut used = self.settled_memory();
2088        self.used.store(used, Relaxed);
2089        let mut budget = EVICT_BUDGET;
2090        while used as u64 > limit {
2091            let over = used - limit as usize;
2092            if !self.relieve_step(over) {
2093                return false;
2094            }
2095            self.compact_hard_step();
2096            used = self.settled_memory();
2097            self.used.store(used, Relaxed);
2098            budget -= 1;
2099            if budget == 0 {
2100                break;
2101            }
2102        }
2103        true
2104    }
2105
2106    /// Give back `over` bytes from whichever database can, by moving values to
2107    /// the file where there is one and by throwing keys away where there is not.
2108    ///
2109    /// The two answers are the eviction inversion and which one a database gets
2110    /// is [`Server::migrates`]. Answers whether anything was given back at all,
2111    /// and `false` is what refuses the client's write.
2112    ///
2113    /// A store that will not take the bytes counts as nothing given back, so the
2114    /// write is refused rather than turned into a deletion. A disk that is
2115    /// misbehaving is a reason to stop accepting writes and it is not a reason
2116    /// to start losing data that was accepted already.
2117    ///
2118    /// Round robin from a cursor rather than always starting at database zero,
2119    /// so a server using more than one of them does not empty the first before
2120    /// touching the second. Almost every server is on database zero only, where
2121    /// this is one call that answers and fifteen that say the map is empty.
2122    fn relieve_step(&self, over: usize) -> bool {
2123        let from = self.evict_db.load(Relaxed);
2124        for turn in 0..self.slots() {
2125            let i = (from + turn) % self.slots();
2126            // An empty keyspace has nothing to move and opening a log for one
2127            // would cost a resident page window to find that out.
2128            let used = !self.slot(i).is_empty();
2129            let gave = if used && self.migrates(i) {
2130                self.attach_store(i);
2131                // Whether it made room and not whether it moved a key. A round
2132                // that demoted nothing and handed back a segment is a round
2133                // that made room, and reading only the count refuses the write
2134                // that provoked it.
2135                self.slot(i)
2136                    .relieve(over)
2137                    .is_ok_and(yo_kv::tier::Relief::made_room)
2138            } else {
2139                // Against this database rather than whichever one the write
2140                // that provoked the eviction was aimed at, since the key that
2141                // goes is this one's. The funnel is already armed above and
2142                // this is a second one inside it, which is what the answer
2143                // going back into the drain is for.
2144                let armed = notify::arm(self, self.slot_db(i));
2145                let gone = self.slot(i).evict_one();
2146                notify::drain(self, armed);
2147                gone
2148            };
2149            if gave {
2150                self.evict_db.store((i + 1) % self.slots(), Relaxed);
2151                self.mine().mark(1u64 << self.slot_db(i));
2152                return true;
2153            }
2154        }
2155        false
2156    }
2157
2158    /// The sweep the shard loop calls, at most once a millisecond.
2159    ///
2160    /// The gate is the whole difference between this and [`Server::expire_step`].
2161    /// A maintenance slice runs on every turn of the loop and a turn is a
2162    /// hundred nanoseconds, so an ungated sweep would draw a fresh sample ten
2163    /// thousand times per millisecond and spend a real share of the shard on
2164    /// looking for keys that cannot have died since the last look. Nothing in a
2165    /// database changes fast enough to be worth asking about more often than the
2166    /// clock can tell the difference, and the clock here is milliseconds.
2167    ///
2168    /// A millisecond is also far finer than Redis, whose slow cycle runs at ten
2169    /// hertz, so this is not the thing that decides how promptly memory comes
2170    /// back. What it decides is that an idle server sweeps a thousand times a
2171    /// second rather than a million.
2172    pub fn expire_slice(&self, budget: usize) -> usize {
2173        // `DEBUG SET-ACTIVE-EXPIRE 0`, which is what a test that wants to see a
2174        // key that is logically gone but still on the shelf turns off. Read
2175        // before the clock because it is the cheaper of the two and because a
2176        // server with the sweep off should not be paying for the clock either.
2177        if !self.expiring() {
2178            return 0;
2179        }
2180        let now = self.clock.now_ms();
2181        if now == self.expire_ms.load(Relaxed) {
2182            return 0;
2183        }
2184        self.expire_ms.store(now, Relaxed);
2185        self.expire_step(budget)
2186    }
2187
2188    /// Sweep dead keys out of the databases, spending at most `budget` looks.
2189    ///
2190    /// Answers what it spent, so the caller can charge its maintenance slice for
2191    /// it. See [`yo_kv::expiry`] for why the budget is in keys looked at.
2192    ///
2193    /// Round robin from its own cursor, and every database gets offered whatever
2194    /// is left of the budget rather than a sixteenth of it each, so a server on
2195    /// database zero only, which is nearly every server, spends the whole slice
2196    /// where the keys are. The fifteen empty ones cost a comparison apiece
2197    /// because a database with no key carrying a deadline says so without
2198    /// drawing anything.
2199    ///
2200    /// The cursor moves to the database after whichever one did the work, so two
2201    /// busy databases take turns instead of the lower numbered one starving the
2202    /// other.
2203    pub fn expire_step(&self, budget: usize) -> usize {
2204        let slots = self.slots();
2205        let mut spent = 0;
2206        let from = self.expire_db.load(Relaxed);
2207        for turn in 0..slots {
2208            if spent >= budget {
2209                break;
2210            }
2211            let i = (from + turn) % slots;
2212            // Nothing armed this thread, because nothing asked for any of this:
2213            // the shard loop is between commands. So the sweep arms and drains
2214            // around itself, and a key it takes is news to a subscriber in the
2215            // same way a key a lookup took on the way past is.
2216            let armed = notify::arm(self, self.slot_db(i));
2217            // Held once for both cycles rather than taken again for the second.
2218            // Two takes of a stripe lock to ask two questions about the same
2219            // stripe is one more line every other thread has to wait for, and
2220            // this asks on every turn of every worker's loop.
2221            let mut slot = self.slot(i);
2222            let c = slot.expire_cycle(budget - spent);
2223            // And the fields, which are the other thing with a deadline nobody
2224            // is waiting on. It draws from its own list and charges the same
2225            // budget, so a database with no hash field deadlines anywhere pays a
2226            // comparison for it and a database full of them cannot starve the
2227            // key sweep.
2228            let left = (budget - spent).saturating_sub(c.examined);
2229            let fields = slot.field_expire_cycle(left);
2230            drop(slot);
2231            notify::drain(self, armed);
2232            // The same deletions a lookup's would be, from the other end of the
2233            // same hook. A replica hears about a key the sweep took exactly as
2234            // it hears about one a `GET` took.
2235            repl::swept(self, self.slot_db(i));
2236            spent += c.examined + fields;
2237            if c.expired > 0 {
2238                self.expire_db.store((i + 1) % slots, Relaxed);
2239                self.mine().note(1u64 << self.slot_db(i));
2240                // Keys the sweep took are bytes the database no longer holds,
2241                // and nothing else is going to say so: no command ran.
2242                self.mine().unmeasure(1u64 << self.slot_db(i));
2243            }
2244        }
2245        spent
2246    }
2247
2248    /// One slice of compaction for a server that is over its limit.
2249    ///
2250    /// Round robin the way [`Server::compact_step`] is, from its own cursor
2251    /// rather than that one's, and it stops at the first database that had
2252    /// something to move and asks with the ratios off. See
2253    /// [`Keyspace::compact_hard`] for what that changes.
2254    fn compact_hard_step(&self) -> Option<usize> {
2255        let from = self.next_db.load(Relaxed);
2256        for turn in 0..self.slots() {
2257            let i = (from + turn) % self.slots();
2258            if let Some(moved) = self.slot(i).compact_hard() {
2259                self.next_db.store((i + 1) % self.slots(), Relaxed);
2260                // A segment handed back is the whole point of the call, and
2261                // `make_room` reads the total again on the next turn of its loop
2262                // to find out whether it worked.
2263                self.mine().unmeasure(1u64 << self.slot_db(i));
2264                return Some(moved);
2265            }
2266        }
2267        None
2268    }
2269
2270    /// Take what every thread has marked and add it to the turn's own mask.
2271    ///
2272    /// The mask the turn works from is its own and not a shared one, because a
2273    /// mask it read in place and then cleared a bit of would be a mask that lost
2274    /// whatever another thread marked in between. A swap cannot lose a mark: a
2275    /// thread that ors while the swap happens either gets its bit in before the
2276    /// swap or leaves it there afterwards, and the second one costs one look at
2277    /// a database the turn has already been through.
2278    fn collect_marks(&self) {
2279        let mut marked = 0;
2280        for thread in &self.locals {
2281            marked |= thread.dirty.swap(0, Relaxed);
2282        }
2283        let mine = self.mine();
2284        mine.note(marked);
2285        // The other half of what the marks are for. A thread weighs the
2286        // databases its own commands touched on every reading it takes, and this
2287        // is where it hears about the ones somebody else touched. Oring in a bit
2288        // it has already weighed costs one database on one reading, which is why
2289        // this can share a mask that was collected for something else.
2290        mine.unmeasure(marked);
2291    }
2292
2293    /// Give one database's dead space back, if any database has enough of it to
2294    /// be worth the move. `None` when no database had a candidate.
2295    ///
2296    /// Once per batch, next to the clock. Overwriting a key writes a new record
2297    /// and counts the old one dead, so without this a server holds everything
2298    /// it has ever written: 400000 sets over 100000 keys measured at 742 bytes
2299    /// a key against Redis at 144 for the same load, and the whole difference
2300    /// was dead records nothing ever came back for.
2301    ///
2302    /// At most one segment moves per call and the search starts one database
2303    /// further along each time, so the cost of asking is a comparison per
2304    /// database and the cost of acting is bounded by a segment.
2305    pub fn compact_step(&self) -> Option<usize> {
2306        // `DEBUG DICT-RESIZING 0`. On a real server that stops a dictionary
2307        // giving back the room it grew into, and this is where the same thing
2308        // happens here: the arena keeps every segment it has taken until this
2309        // walks over and hands one back.
2310        if !self.resizing() {
2311            return None;
2312        }
2313        let slots = self.slots();
2314        let looks = COMPACT_LOOKS.min(slots);
2315        // Once a millisecond per thread rather than once a batch, because the
2316        // swap is over every thread's counter and a call per batch per worker is
2317        // the thread count squared per batch across the server. A mark a
2318        // millisecond old is still a database somebody wrote to, which is the
2319        // only thing the mask is ever asked.
2320        if self.mine().collecting(self.clock.now_ms()) {
2321            self.collect_marks();
2322        }
2323        let mine = self.mine();
2324        // This thread's cursor and not the server's. The load and the store
2325        // either side of this walk happen after every batch on every thread,
2326        // and on the server's cursor that is one line every thread is writing
2327        // to at batch rate for no reason other than to say where to start.
2328        let from = mine.compact_db.load(Relaxed);
2329        for turn in 0..looks {
2330            let i = (from + turn) % slots;
2331            // Nothing has run against this database since it last said it had
2332            // nothing to collect, so it still has nothing to collect and the
2333            // line it lives on stays where it is.
2334            let at = self.slot_db(i);
2335            if !mine.wanted(at) {
2336                continue;
2337            }
2338            if let Some(moved) = self.slot(i).compact_step() {
2339                mine.compact_db.store((i + 1) % slots, Relaxed);
2340                // The same as the hard step: the segment it gave back is memory
2341                // the next reading would otherwise still be counting.
2342                mine.unmeasure(1u64 << at);
2343                return Some(moved);
2344            }
2345            // Only once every stripe of the database has said it has nothing,
2346            // since the bit is per database and one stripe answering for all of
2347            // them would stop the others being asked at all.
2348            if i % self.width == self.width - 1 {
2349                mine.done(at);
2350            }
2351        }
2352        mine.compact_db.store((from + looks) % slots, Relaxed);
2353        None
2354    }
2355}
2356
2357impl Server {
2358    /// Whether anybody is watching anything.
2359    ///
2360    /// The one thing every write asks about watches, and it is a relaxed load of
2361    /// a word that is zero and shared on a server where no client has ever sent
2362    /// `WATCH`. Relaxed is enough because the answer only has to be right by the
2363    /// time it matters: a `WATCH` that has not been published yet has not
2364    /// returned to its client either, so no client can have started a
2365    /// transaction that depends on it.
2366    fn watching(&self) -> bool {
2367        self.watched.load(Relaxed) != 0
2368    }
2369
2370    /// Which classes of keyspace notification are turned on.
2371    ///
2372    /// Zero is off, which is the default and is what nearly every server runs
2373    /// with. Relaxed for the same reason the watch count is: a `CONFIG SET` that
2374    /// has not been published to another thread yet has not answered its client
2375    /// either.
2376    pub(crate) fn notify_flags(&self) -> u32 {
2377        self.notify.load(Relaxed)
2378    }
2379
2380    /// Turn a set of notification classes on, or turn them all off with zero.
2381    pub(crate) fn set_notify_flags(&self, flags: u32) {
2382        self.notify.store(flags, Relaxed);
2383    }
2384
2385    /// Note how many watched keys there are, after the table changed.
2386    ///
2387    /// Taken from the table under the same lock the change was made under, so
2388    /// the count can never say nobody is watching while somebody is.
2389    fn recount(&self, watches: &Watches) {
2390        self.watched.store(watches.len(), Relaxed);
2391    }
2392}
2393
2394impl Default for Server {
2395    fn default() -> Server {
2396        Server::new()
2397    }
2398}
2399
2400/// What one connection has chosen.
2401pub struct Session {
2402    db: usize,
2403    id: u64,
2404    /// Which connection slot on the front this session belongs to.
2405    ///
2406    /// Carried here so that a command can say where a reply for this connection
2407    /// goes without the front having to be asked. Pub/sub is what needs it: a
2408    /// subscription is a row on the server naming a slot, and the subscribe
2409    /// command is the only moment the connection and the server are both in
2410    /// hand. [`u32::MAX`] for a session that is not on a front, which is a test.
2411    conn: u32,
2412    name: Vec<u8>,
2413    /// The `HIMPORT` fieldsets this connection has prepared.
2414    ///
2415    /// Connection state and not keyspace state, which is the reference's design
2416    /// and not a shortcut: a fieldset is invisible to every other connection and
2417    /// the keys built from one outlive it.
2418    sets: himport::Fieldsets,
2419    /// Whether the command running right now was called by a script.
2420    ///
2421    /// The one thing it changes is what a blocking command does when it finds
2422    /// nothing to take. A client that sent `BLPOP` waits; a script that called
2423    /// `BLPOP` cannot, because the whole server is waiting on the script, and a
2424    /// script that parked would park everything behind it. So inside a script a
2425    /// blocking command times out at once and answers the null a client that
2426    /// waited its full timeout would have got. That is a real server's rule and
2427    /// it is why `BLPOP` is not on the list a script may not call.
2428    scripted: bool,
2429    /// The commands held since `MULTI`, `None` when no transaction is open.
2430    ///
2431    /// Connection state and nothing else. A transaction is invisible to every
2432    /// other connection until `EXEC` runs it, and a connection that goes away
2433    /// with one open has simply not run it.
2434    multi: Option<multi::Queue>,
2435    /// What this connection asked `WATCH` about, and what those keys looked
2436    /// like at the time.
2437    ///
2438    /// The other half is on the server, beside the keys, because a write by
2439    /// another thread has to reach it. See `multi` for why keeping the value
2440    /// here and comparing it at `EXEC` is not the same thing.
2441    watching: Vec<multi::Watched>,
2442    /// Whether the command running right now was handed over by `EXEC`.
2443    ///
2444    /// The one thing it changes is the RESP2 subscribe mode refusal, which a
2445    /// real server makes in `processCommand` and so does not make for a command
2446    /// that was queued: `MULTI`, `SUBSCRIBE z`, `GET x`, `EXEC` runs the `GET`
2447    /// on 8.10.1 even though sending it on its own would have been refused.
2448    running: bool,
2449    /// The buffer `EXEC` decodes the queued commands through.
2450    ///
2451    /// It lives here rather than in `exec` so that its capacity survives the
2452    /// transaction. A fresh one has no room for spans, so the first command of
2453    /// every transaction would allocate, and a client that runs transactions in
2454    /// a loop would be allocating on a command path forever. Everywhere else
2455    /// the buffer belongs to the connection already and the same reserve is
2456    /// free after the first command.
2457    replay: crate::request::Argv,
2458    /// What this connection has subscribed to, `None` until it subscribes to
2459    /// anything.
2460    ///
2461    /// Boxed so that a connection that never subscribes carries a null pointer
2462    /// rather than three empty vectors. The other half is on the server, keyed
2463    /// by name, because a publish arrives on a connection that cannot see this
2464    /// one. See the `pubsub` module.
2465    subs: Option<Box<pubsub::Subs>>,
2466    /// The library name and version a client library announces with
2467    /// `CLIENT SETINFO`, empty when it has not.
2468    ///
2469    /// Nothing on the server reads them. They are here because an operator
2470    /// looking at `CLIENT LIST` on a server with a hundred connections wants to
2471    /// know which of them is the Python worker and which is the dashboard, and
2472    /// every mainstream client library sends them on connect.
2473    lib_name: Vec<u8>,
2474    lib_ver: Vec<u8>,
2475    /// `CLIENT NO-EVICT`, which asks that this connection's buffers are not the
2476    /// ones given up when the server is short of memory.
2477    ///
2478    /// Nothing gives up a connection's buffers here yet, so this is remembered
2479    /// and reported and does nothing else, which is the honest half of the
2480    /// command: a client that sets it and reads it back sees what it set.
2481    no_evict: bool,
2482    /// `CLIENT NO-TOUCH`, which asks that reads by this connection do not move
2483    /// a key's place in the eviction order.
2484    no_touch: bool,
2485    /// What this connection has asked to be told about, which is `CLIENT REPLY`.
2486    reply: Reply,
2487    /// The row every other thread sees this connection through.
2488    ///
2489    /// Shared rather than owned, because `CLIENT LIST` and `CLIENT KILL` run on
2490    /// whichever thread the client asking is on and that is very often not this
2491    /// one. Everything the report says about the socket lives in there and
2492    /// nowhere else, and the handful of things the session needs for itself are
2493    /// kept here as well and written to both. See the `clients` module for why
2494    /// the row is words and a small lock rather than one lock.
2495    sock: Arc<Client>,
2496    /// Whether this connection has got past the password, if there is one.
2497    ///
2498    /// Decided when the connection is accepted and not when it first sends
2499    /// something, which is what makes `CONFIG SET requirepass` leave the clients
2500    /// that are already connected alone. False here rather than true because a
2501    /// session nobody told is a session on a server nobody gave a password to,
2502    /// and the gate only reads this when there is one. See the `auth` module.
2503    authenticated: bool,
2504    /// Which user this connection is, and the copy of it its commands are
2505    /// checked against.
2506    ///
2507    /// Boxed because it is three allocations and a connection on a server that
2508    /// has no ACL never reads past the first field of it. See the `acl` module
2509    /// for why a copy rather than a lookup.
2510    acl: Box<acl::Identity>,
2511    /// Whether what this session runs arrived from a master this server is
2512    /// following.
2513    ///
2514    /// False on every connection anybody made, which is what keeps this to a
2515    /// field read on the command path. It exempts the master's stream from the
2516    /// three refusals that are about clients and not about it, being the
2517    /// password, the access control list and the read only refusal, and from
2518    /// `CLIENT PAUSE`. See the `follow` module for why each of those.
2519    master: bool,
2520    /// Whether the command running right now was preceded by `ASKING`.
2521    ///
2522    /// It is what lets a client reach a key in a slot this node is receiving and
2523    /// does not own yet, and it lasts exactly one command, which is what makes
2524    /// it safe: a client that has been told to ask over here says so again for
2525    /// every command it sends, and a client that has not cannot stumble into a
2526    /// half moved slot.
2527    ///
2528    /// Two fields for a one command life, the same pair `CLIENT REPLY SKIP`
2529    /// uses, because the flag has to be set by a command that has not finished
2530    /// and read by the next one. `ASKING` sets the second and the end of every
2531    /// command moves the second into the first.
2532    asking: bool,
2533    asking_next: bool,
2534    /// Whether this connection is another node of the cluster rather than a
2535    /// client.
2536    ///
2537    /// Set by `AUTH "internal connection" <secret>`, where the secret is the
2538    /// forty characters the bus has gossiped the whole cluster onto, so a client
2539    /// cannot set it without already knowing something only the nodes know. What
2540    /// it opens is the slot migration protocol, which changes state a client has
2541    /// no business changing and which is deliberately not guarded against being
2542    /// driven out of order, since the only thing that ever drives it is another
2543    /// node following the same state machine.
2544    internal: bool,
2545    /// Whether the socket goes as soon as the reply to the command running right
2546    /// now has been written, which is the reference's `CLIENT_CLOSE_AFTER_REPLY`.
2547    ///
2548    /// One command sets it, which is a `CLUSTER SYNCSLOTS` from a connection
2549    /// that is not a node. The refusal on its own would be enough to be correct
2550    /// and the hang up is what makes it expensive to sit there guessing.
2551    closing: bool,
2552    /// Which node is on the other end of this connection, empty until one says.
2553    ///
2554    /// Only a node ever says, with `CLUSTER SYNCSLOTS CONF NODE-ID`, and the only
2555    /// thing that reads it is the slot migration that follows on the same
2556    /// connection: the node asking for a slot range is the node the range is
2557    /// going to, and there is nothing else on the connection that says who that
2558    /// is. See the `cluster` module.
2559    node_id: Vec<u8>,
2560}
2561
2562/// What a connection has asked to hear back, which is `CLIENT REPLY`.
2563///
2564/// The two skipping states are one command apart on purpose. `CLIENT REPLY
2565/// SKIP` says nothing itself and skips the reply of the command after it, so
2566/// the state has to survive one command and no more, and the way Redis does
2567/// that is with a pair of flags that step forward once a command.
2568#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
2569pub enum Reply {
2570    /// Everything, which is where every connection starts.
2571    #[default]
2572    On,
2573    /// Nothing at all until the client says `ON` again.
2574    Off,
2575    /// Nothing for the command after this one.
2576    SkipNext,
2577    /// This is that command.
2578    SkipNow,
2579}
2580
2581impl Session {
2582    /// A new connection, on database zero with no name.
2583    #[must_use]
2584    pub fn new(id: u64) -> Session {
2585        Session {
2586            db: 0,
2587            id,
2588            conn: u32::MAX,
2589            name: Vec::new(),
2590            sets: himport::Fieldsets::default(),
2591            scripted: false,
2592            multi: None,
2593            watching: Vec::new(),
2594            running: false,
2595            replay: crate::request::Argv::new(),
2596            subs: None,
2597            lib_name: Vec::new(),
2598            lib_ver: Vec::new(),
2599            no_evict: false,
2600            no_touch: false,
2601            reply: Reply::On,
2602            sock: Arc::new(Client::new(id)),
2603            authenticated: false,
2604            master: false,
2605            asking: false,
2606            asking_next: false,
2607            internal: false,
2608            closing: false,
2609            node_id: Vec::new(),
2610            acl: Box::default(),
2611        }
2612    }
2613
2614    /// Say whether this connection starts out past the password.
2615    ///
2616    /// Called once by whoever accepted it, which is the one place that can see
2617    /// both the connection and the server. A connection nobody tells is
2618    /// unauthenticated and gets through anyway on a server with no password,
2619    /// which is every embedded caller and every test.
2620    pub fn admit(&mut self, yes: bool) {
2621        self.authenticated = yes;
2622    }
2623
2624    /// Whether this connection has got past the password.
2625    #[must_use]
2626    pub(crate) fn authenticated(&self) -> bool {
2627        self.authenticated
2628    }
2629
2630    /// Say that everything this session runs comes from a master.
2631    ///
2632    /// Called once, by the replica link, which is the only thing that can say
2633    /// it. A session nobody tells is an ordinary client, which is every
2634    /// connection on every server that is nobody's replica.
2635    pub(crate) fn serve_master(&mut self, yes: bool) {
2636        self.master = yes;
2637    }
2638
2639    /// Whether what this session runs came from a master.
2640    #[must_use]
2641    pub(crate) fn serving_master(&self) -> bool {
2642        self.master
2643    }
2644
2645    /// Say whether this connection is another node of the cluster.
2646    ///
2647    /// The one thing that says yes is `AUTH "internal connection"` with the
2648    /// right secret, and `DEBUG MARK-INTERNAL-CLIENT` says it too so that a test
2649    /// can drive the protocol without a second node.
2650    pub(crate) fn serve_internal(&mut self, yes: bool) {
2651        self.internal = yes;
2652    }
2653
2654    /// Whether this connection is another node of the cluster.
2655    ///
2656    /// A master's own stream counts as one, because everything a replica is told
2657    /// by its master is by definition from a node, which is the reference's rule
2658    /// as well.
2659    #[must_use]
2660    pub(crate) fn internal(&self) -> bool {
2661        self.internal || self.master
2662    }
2663
2664    /// Say which node is on the other end, which only a node ever does.
2665    pub(crate) fn set_node_id(&mut self, id: &[u8]) {
2666        yo_alloc::allow(|| {
2667            self.node_id.clear();
2668            self.node_id.extend_from_slice(id);
2669        });
2670    }
2671
2672    /// Which node is on the other end, empty for every connection a client made.
2673    #[must_use]
2674    pub(crate) fn node_id(&self) -> &[u8] {
2675        &self.node_id
2676    }
2677
2678    /// Ask that the socket goes once the reply being written has gone out.
2679    pub(crate) fn hang_up(&mut self) {
2680        self.closing = true;
2681    }
2682
2683    /// Whether it has been asked.
2684    #[must_use]
2685    pub(crate) fn hanging_up(&self) -> bool {
2686        self.closing
2687    }
2688
2689    /// Let the command after this one into a slot this node is receiving, which
2690    /// is what `ASKING` does and it lasts exactly that one command.
2691    pub(crate) fn ask_next(&mut self) {
2692        self.asking_next = true;
2693    }
2694
2695    /// The row every other thread sees this connection through.
2696    ///
2697    /// Handed to the server once, when the connection is accepted, so that
2698    /// `CLIENT LIST` can find it. A session nobody hands over is one no other
2699    /// thread can see, which is every embedded caller and every test.
2700    #[must_use]
2701    pub fn row(&self) -> &Arc<Client> {
2702        &self.sock
2703    }
2704
2705    /// Say when this connection was opened, which is what `age` counts from.
2706    ///
2707    /// Called by whoever opened it, which is the only place that knows. A
2708    /// session nobody tells has no age and reports zero, which is every
2709    /// embedded caller and every test.
2710    pub fn opened(&mut self, now_ms: u64) {
2711        self.sock.since_ms.store(now_ms, Relaxed);
2712        self.sock.last_ms.store(now_ms, Relaxed);
2713    }
2714
2715    /// Say what the socket under this connection is.
2716    ///
2717    /// Called once, by whoever accepted it, which is the only place that knows.
2718    /// The two addresses are already in the spelling `CLIENT INFO` reports them
2719    /// in, because turning a socket address into that spelling is the job of the
2720    /// layer that has the socket.
2721    pub fn set_socket(&mut self, peer: &str, local: &str, fd: i32, unix: bool) {
2722        yo_alloc::allow(|| {
2723            let mut text = self.sock.text.lock();
2724            text.peer.clear();
2725            text.peer.extend_from_slice(peer.as_bytes());
2726            text.local.clear();
2727            text.local.extend_from_slice(local.as_bytes());
2728        });
2729        self.sock.fd.store(fd, Relaxed);
2730        self.sock.set_flag(clients::UNIX, unix);
2731    }
2732
2733    /// Note bytes that arrived, and that a read carried them.
2734    pub fn read_bytes(&mut self, n: usize) {
2735        let row = &self.sock;
2736        row.net_in
2737            .store(row.net_in.load(Relaxed) + n as u64, Relaxed);
2738        row.reads.store(row.reads.load(Relaxed) + 1, Relaxed);
2739    }
2740
2741    /// Note bytes that went out.
2742    pub fn wrote_bytes(&mut self, n: usize) {
2743        let row = &self.sock;
2744        row.net_out
2745            .store(row.net_out.load(Relaxed) + n as u64, Relaxed);
2746    }
2747
2748    /// Note what the two buffers are holding, and which protocol they are in.
2749    ///
2750    /// `waiting` is the framed bytes that have not been read yet, `room` is what
2751    /// is left in the read buffer after them, `held` is what the reply buffer
2752    /// still owes and `reply` is its capacity. The high water mark is kept here
2753    /// rather than by the caller so that the caller only has to say what is true
2754    /// now.
2755    pub fn note_buffers(&mut self, waiting: usize, room: usize, held: usize, reply: usize) {
2756        let row = &self.sock;
2757        row.qbuf.store(waiting as u64, Relaxed);
2758        row.qbuf_free.store(room as u64, Relaxed);
2759        row.obl.store(held as u64, Relaxed);
2760        row.rbs.store(reply as u64, Relaxed);
2761        row.rbp
2762            .store(row.rbp.load(Relaxed).max(reply as u64), Relaxed);
2763    }
2764
2765    /// Note which protocol this connection is being answered in.
2766    ///
2767    /// Written after each command rather than with the buffers, because `HELLO`
2768    /// changes it in the reply buffer and a connection that switched to RESP3
2769    /// halfway through a pipeline should be listed as being on it.
2770    pub fn note_proto(&mut self, version: i64) {
2771        self.sock.resp.store(version as u32, Relaxed);
2772    }
2773
2774    /// Note which command is running, before it runs.
2775    ///
2776    /// The clock is passed in because the session has no way to reach one, and
2777    /// the caller is holding the server anyway. `at` is where the command is in
2778    /// the table, since an index is a word another thread can read and a name is
2779    /// not.
2780    pub(crate) fn ran(&mut self, at: usize, sub: Option<&[u8]>, argv: u64, now_ms: u64) {
2781        self.sock.last_ms.store(now_ms, Relaxed);
2782        self.sock.argv_mem.store(argv, Relaxed);
2783        self.sock.note_command(at, sub);
2784    }
2785
2786    /// Note that the command running is over, and put what it changed about this
2787    /// connection where another thread can see it.
2788    ///
2789    /// The count goes up here and not where the command name is noted, so that
2790    /// a connection asking `CLIENT INFO` is told how many commands it had sent
2791    /// before this one. That is what a real server answers: it counts in
2792    /// `commandProcessed` and that runs after the body.
2793    ///
2794    /// The rest is the publishing. Which database a connection is in, what it is
2795    /// subscribed to, whether it is in a transaction and how many keys it is
2796    /// watching are all things a command can have just changed, and they are all
2797    /// things `CLIENT LIST` on another thread reports. Rather than hunting down
2798    /// every command that can move one of them, all six are written out here,
2799    /// which is six ordinary stores to a line this thread already owns.
2800    pub fn finished(&mut self) {
2801        // One command's worth of `ASKING` steps forward here, which is where a
2802        // real server clears its flag: in `resetClient`, after the body, and
2803        // only for a command that was not `ASKING` itself.
2804        self.asking = core::mem::take(&mut self.asking_next);
2805        let (sub, psub, ssub) = self.sub_counts();
2806        let (multi, multi_mem) = self.queued();
2807        let subscribed = self.subscribed();
2808        let in_multi = self.in_multi();
2809        let watching = self.watching.len();
2810        let db = self.db;
2811        let row = &self.sock;
2812        row.cmds.store(row.cmds.load(Relaxed) + 1, Relaxed);
2813        row.db.store(db as u32, Relaxed);
2814        row.sub.store(sub as u32, Relaxed);
2815        row.psub.store(psub as u32, Relaxed);
2816        row.ssub.store(ssub as u32, Relaxed);
2817        row.watch.store(watching as u32, Relaxed);
2818        row.multi.store(multi, Relaxed);
2819        row.multi_mem.store(multi_mem, Relaxed);
2820        row.set_flag(clients::SUBSCRIBED, subscribed);
2821        row.set_flag(clients::IN_MULTI, in_multi);
2822    }
2823
2824    /// What this connection has asked to hear back.
2825    #[must_use]
2826    pub const fn reply_mode(&self) -> Reply {
2827        self.reply
2828    }
2829
2830    /// Step the skipping state on by one command.
2831    ///
2832    /// Called after every command by whoever is deciding whether to keep the
2833    /// reply, so that `SKIP` covers exactly the one command after it.
2834    pub const fn step_reply(&mut self) {
2835        self.reply = match self.reply {
2836            Reply::SkipNext => Reply::SkipNow,
2837            Reply::SkipNow => Reply::On,
2838            other => other,
2839        };
2840    }
2841
2842    /// Whether a script is what is asking, which only a blocking command reads.
2843    pub(crate) const fn scripted(&self) -> bool {
2844        self.scripted
2845    }
2846
2847    /// Whether `EXEC` is what is asking.
2848    pub(crate) const fn running(&self) -> bool {
2849        self.running
2850    }
2851
2852    /// Whether this connection has sent `MONITOR` and stopped being a client.
2853    ///
2854    /// Read off the row rather than kept beside it, so there is one answer to
2855    /// the question and not two that could disagree. The row is a line this
2856    /// session has already touched by the time anything asks, since noting the
2857    /// command it is running writes to it.
2858    pub(crate) fn monitoring(&self) -> bool {
2859        self.sock.flag(clients::MONITOR)
2860    }
2861
2862    /// Whether this connection has sent `PSYNC` and stopped being a client.
2863    ///
2864    /// Off the row for the same reason the question above it is, and read on the
2865    /// way out rather than on the way in: a replica does keep sending commands,
2866    /// `REPLCONF ACK` once a second forever, and what changes is that none of
2867    /// them is answered.
2868    pub(crate) fn replicating(&self) -> bool {
2869        self.sock.flag(clients::REPLICA)
2870    }
2871
2872    /// Say which connection slot this session is in.
2873    ///
2874    /// Called by the front when it opens the connection, which is the only place
2875    /// that knows. A session nobody tells is not on a front, and the one thing
2876    /// that reads this checks the client id before it acts on it.
2877    pub(crate) fn set_conn(&mut self, conn: u32) {
2878        self.conn = conn;
2879        self.sock.conn.store(conn, Relaxed);
2880    }
2881
2882    /// The connection id, which `HELLO` reports and `CLIENT` will.
2883    #[must_use]
2884    pub const fn id(&self) -> u64 {
2885        self.id
2886    }
2887
2888    /// Which database this connection is working in.
2889    #[must_use]
2890    pub const fn db(&self) -> usize {
2891        self.db
2892    }
2893
2894    /// The name the client gave itself, empty if it gave none.
2895    #[must_use]
2896    pub fn name(&self) -> &[u8] {
2897        &self.name
2898    }
2899
2900    /// Put everything back the way it was when the connection was opened.
2901    ///
2902    /// The protocol is not here because it is not here: it lives in the reply
2903    /// buffer, and `RESET` sets it back there.
2904    pub fn reset(&mut self) {
2905        self.db = 0;
2906        self.name.clear();
2907        self.sock.set_text(|text| &mut text.name, b"");
2908        // `SELECT` leaves these alone and `RESET` does not, both checked
2909        // against 8.10.1, which is the one pair of answers you could not guess
2910        // from what the command is for.
2911        self.sets.clear();
2912        // The three `CLIENT` settings that are a choice about this connection go
2913        // back to their defaults, and the library name and version stay, since
2914        // the library behind the socket is the same library it was. Both halves
2915        // are `clearClientConnectionState`'s.
2916        self.reply = Reply::On;
2917        self.set_no_evict(false);
2918        self.set_no_touch(false);
2919        // Back on the default user, whatever it had authenticated as. The
2920        // password half of that is the caller's, because only it can see the
2921        // server and know whether there is one to ask for.
2922        self.forget_user();
2923    }
2924
2925    /// Record the name from `HELLO ... SETNAME` or `CLIENT SETNAME`.
2926    fn set_name(&mut self, name: &[u8]) {
2927        yo_alloc::allow(|| {
2928            self.name.clear();
2929            self.name.extend_from_slice(name);
2930        });
2931        self.sock.set_text(|text| &mut text.name, name);
2932    }
2933
2934    /// Record what `CLIENT SETINFO LIB-NAME` was told.
2935    fn set_lib_name(&mut self, value: &[u8]) {
2936        yo_alloc::allow(|| {
2937            self.lib_name.clear();
2938            self.lib_name.extend_from_slice(value);
2939        });
2940        self.sock.set_text(|text| &mut text.lib_name, value);
2941    }
2942
2943    /// Record what `CLIENT SETINFO LIB-VER` was told.
2944    fn set_lib_ver(&mut self, value: &[u8]) {
2945        yo_alloc::allow(|| {
2946            self.lib_ver.clear();
2947            self.lib_ver.extend_from_slice(value);
2948        });
2949        self.sock.set_text(|text| &mut text.lib_ver, value);
2950    }
2951
2952    /// Record `CLIENT NO-EVICT`.
2953    fn set_no_evict(&mut self, on: bool) {
2954        self.no_evict = on;
2955        self.sock.set_flag(clients::NO_EVICT, on);
2956    }
2957
2958    /// Record `CLIENT NO-TOUCH`.
2959    fn set_no_touch(&mut self, on: bool) {
2960        self.no_touch = on;
2961        self.sock.set_flag(clients::NO_TOUCH, on);
2962    }
2963}
2964
2965/// Give back everything a connection was holding on the server.
2966///
2967/// The transaction, the watches, the subscriptions and the monitor, and it is
2968/// here rather than in [`Session::reset`] because letting go of any of the four
2969/// is a change to the server. A `Session` on its own cannot reach one, and a
2970/// connection that dropped its lists without saying so would leave rows nobody
2971/// is watching, subscriptions nobody is listening to and a monitor nobody is
2972/// reading, which would keep every write, every publish and every command on the
2973/// server paying for clients that are not there.
2974pub fn forget_session(server: &Server, session: &mut Session) {
2975    multi::release(server, session);
2976    pubsub::release(server, session);
2977    if session.monitoring() {
2978        server.watch_no_more(session.row());
2979    }
2980    if session.replicating() {
2981        server.drop_replica(session.row().id);
2982    }
2983    // A slot migration this connection was one of the two halves of cannot go on
2984    // without it, and half a slot range on the far side is the one outcome
2985    // nobody may be left with.
2986    if session.internal() {
2987        server.asm_forget(session.row().id);
2988    }
2989}
2990
2991/// Run one command and write its reply.
2992///
2993/// The name is looked up and the arity is checked here, once, so that no body
2994/// has to. Everything after that is the command's own.
2995pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
2996    // The decoder never produces a command with no name. If one ever arrives,
2997    // it is not something to answer.
2998    if args.is_empty() {
2999        return Flow::Continue;
3000    }
3001    let flow = resolved(server, session, lookup(args.name()), args, out);
3002    // The engine does this itself, after the reply has been decided, because it
3003    // is also what settles `CLIENT REPLY`. An embedded caller has no engine, so
3004    // it happens here instead, and the two paths never both run: the engine
3005    // reaches the funnel through `resolved` and not through this.
3006    //
3007    // Not for a command the pause held, because that command has not run and is
3008    // going to be run again. An embedded caller has nowhere to park it, so it
3009    // gets the answer back and decides for itself; a caller that has not paused
3010    // its own server, which is nearly all of them, never sees this.
3011    if flow != Flow::Hold {
3012        session.finished();
3013    }
3014    flow
3015}
3016
3017/// The commands that are a container for a set of subcommands.
3018///
3019/// A hand written list because the table has one row per container and none per
3020/// subcommand, so there is nothing to ask. It goes away with D-114, which gives
3021/// every subcommand a row of its own and makes this a flag on the container.
3022const CONTAINERS: [&str; 13] = [
3023    "acl", "backup", "client", "cluster", "command", "config", "function", "memory", "object",
3024    "pubsub", "script", "xgroup", "xinfo",
3025];
3026
3027/// The subcommand a container command was given, for the `cmd` field of
3028/// `CLIENT INFO`, which reads `client|info` and not `client`.
3029///
3030/// `None` for everything else, and for a container called with nothing after
3031/// it, which is a wrong arity and has no subcommand to name.
3032fn container_sub<'a>(spec: &Spec, args: &Args<'a>) -> Option<&'a [u8]> {
3033    (args.len() > 1 && CONTAINERS.contains(&spec.name)).then(|| args.get(1))
3034}
3035
3036/// The six commands Redis marks `may-replicate` and does not mark `write`.
3037///
3038/// A short list rather than a flag on every row, because six is what it is and
3039/// the only thing that asks is the pause gate below. It goes away with the flag
3040/// if anything else ever needs the same question answered.
3041const MAY_REPLICATE: [&str; 6] = ["eval", "evalsha", "fcall", "pfcount", "publish", "spublish"];
3042
3043/// Whether `CLIENT PAUSE WRITE` holds this command.
3044///
3045/// The writes, the six above, and `EXEC` when the transaction it is about to run
3046/// holds one of either. That last part is why this is asked of the session as
3047/// well as of the command: a transaction of nothing but reads runs through a
3048/// write pause, and one write anywhere in it makes the whole transaction wait.
3049fn may_replicate(spec: &Spec, session: &Session) -> bool {
3050    spec.flags.contains(&"write")
3051        || MAY_REPLICATE.contains(&spec.name)
3052        || (spec.name == "exec" && session.queued_writes())
3053}
3054
3055/// Whether a monitor is refused this command, which is anything that goes near
3056/// the keyspace.
3057///
3058/// The writes, the reads and the six above, which is Redis's list read out of
3059/// the same three questions in the same order. `EXEC` is not on it and does not
3060/// need to be: a monitor cannot have queued one of these, because the refusal is
3061/// in front of the queue.
3062fn touches_keyspace(spec: &Spec) -> bool {
3063    spec.flags.contains(&"write")
3064        || spec.flags.contains(&"readonly")
3065        || MAY_REPLICATE.contains(&spec.name)
3066}
3067
3068/// The same, for a caller that has already found the command.
3069///
3070/// The engine frames a command before it runs it, and between those two it also
3071/// asks which key the command touches so the record can be prefetched. That is
3072/// two more chances to look the name up, and looking it up three times to run it
3073/// once is three times the cost of the cheapest thing in the path. So the engine
3074/// resolves the name where it frames the command, carries the answer on the
3075/// framed command, and both the other two take it from there.
3076///
3077/// `spec` is `None` for a name that is not a command, which is the same thing
3078/// [`lookup`] says and lands in the same reply.
3079pub fn resolved(
3080    server: &Server,
3081    session: &mut Session,
3082    spec: Option<&'static Spec>,
3083    args: Args<'_>,
3084    out: &mut Out,
3085) -> Flow {
3086    if args.is_empty() {
3087        return Flow::Continue;
3088    }
3089    server.mine().stats.commands.bump();
3090
3091    // The four refusals below are the ones a real server makes in
3092    // `processCommand`, before the command's own body is reached, and they are
3093    // the ones that kill an open transaction. That is the whole of the rule: an
3094    // error raised here means `EXEC` will refuse to run anything, and an error
3095    // raised by a command body does not, which is why `MULTI` inside `MULTI`
3096    // complains and leaves the transaction alive.
3097    let Some(spec) = spec else {
3098        multi::refuse(server, session, None, &args::unknown_command(args), out);
3099        return Flow::Continue;
3100    };
3101    if !arity_ok(spec, args.len()) {
3102        server.mine().cmdstats.at(spec).rejected.bump();
3103        multi::refuse(
3104            server,
3105            session,
3106            Some(spec),
3107            &args::wrong_arity(spec.name),
3108            out,
3109        );
3110        return Flow::Continue;
3111    }
3112    // What this connection is doing, which only `CLIENT` reads back. Here and
3113    // not further down because a command that is about to be refused or queued
3114    // is still the last command the connection sent, which is what a real
3115    // server reports: it notes the name in `processCommand` before any of the
3116    // decisions below.
3117    let argv = (0..args.len()).map(|i| args.get(i).len() as u64).sum();
3118    session.ran(
3119        table::index_of(spec),
3120        container_sub(spec, &args),
3121        argv,
3122        server.now_ms(),
3123    );
3124
3125    // The password, and this is the whole of it on the command path: one
3126    // acquire load on a server nobody gave a password to. Here, after the two
3127    // refusals above and before everything below, which is where a real server
3128    // puts it, so a command with the wrong number of arguments is told that
3129    // rather than told to authenticate, and everything else is told to
3130    // authenticate before it is told anything at all.
3131    //
3132    // The commands carrying `no_auth` go through, which is `AUTH` itself and the
3133    // three that a client has to be able to send before it has a password
3134    // accepted: `HELLO`, which carries the option that authenticates, `RESET`,
3135    // which is how a client says it is starting over, and `QUIT`.
3136    if server.guarded()
3137        && !session.authenticated()
3138        && !session.serving_master()
3139        && !spec.flags.contains(&"no_auth")
3140    {
3141        server.mine().cmdstats.at(spec).rejected.bump();
3142        if spec.name == "exec" {
3143            multi::abort(server, session, auth::NOAUTH, out);
3144        } else {
3145            session.dirty_multi();
3146            out.error(auth::NOAUTH.as_bytes());
3147        }
3148        return Flow::Continue;
3149    }
3150
3151    if session.in_multi()
3152        && let Some(e) = multi::refused_in_multi(spec)
3153    {
3154        server.mine().cmdstats.at(spec).rejected.bump();
3155        multi::refuse(server, session, Some(spec), &e, out);
3156        return Flow::Continue;
3157    }
3158
3159    // The ACL, here and in this order because this is where a real server puts
3160    // it: after the refusal above and before the memory limit, so a user who may
3161    // not run a command is told that rather than told the server is full.
3162    //
3163    // One relaxed load on a server nobody has written an ACL for, which is every
3164    // server that only ever set `requirepass`, because setting a password leaves
3165    // the default user able to do everything and a user who can do everything
3166    // cannot be refused anything.
3167    if server.restricted()
3168        && !session.serving_master()
3169        && let Some(said) = acl::gate(server, session, spec, args, out)
3170    {
3171        server.mine().cmdstats.at(spec).rejected.bump();
3172        if spec.name == "exec" {
3173            multi::abort(server, session, &said, out);
3174        } else {
3175            session.dirty_multi();
3176            out.error(said.as_bytes());
3177        }
3178        return Flow::Continue;
3179    }
3180
3181    // Where this command's keys say it should run, which is the whole of
3182    // routing and is one field read on a server that is not a cluster node,
3183    // which is nearly every server there is. Here, after the access control list
3184    // and before the queue below, which is where a real server puts it: a user
3185    // who may not touch a key is told that rather than told to go somewhere
3186    // else, and a command queued inside a transaction is refused as it is queued
3187    // so the whole transaction comes back as an `EXECABORT`.
3188    //
3189    // A command the master sent goes through untouched. A replica applies
3190    // whatever its master wrote, including writes to slots the master owned and
3191    // it does not, and a replica that redirected its own master would be a
3192    // replica that stopped following.
3193    if server.cluster_enabled()
3194        && !session.serving_master()
3195        && let Some(said) = cluster::gate(
3196            server,
3197            session.db,
3198            session.asking || cluster::asks(spec),
3199            spec,
3200            args,
3201        )
3202    {
3203        server.mine().cmdstats.at(spec).rejected.bump();
3204        if spec.name == "exec" {
3205            multi::abort(server, session, said.message(), out);
3206        } else {
3207            session.dirty_multi();
3208            out.error(said.message().as_bytes());
3209        }
3210        return Flow::Continue;
3211    }
3212
3213    // The limit first, so a server with no `maxmemory`, which is the default and
3214    // is nearly all of them, pays one comparison against a field that is already
3215    // warm. Every command and not only the writes, because that is where Redis
3216    // puts it: making room is the server's job whatever the client asked for,
3217    // and the flag only decides who gets told no when there is no room to make.
3218    //
3219    // The flag is Redis's own `denyoom` and the list of commands carrying it is
3220    // Redis's list, so a command that only frees is let through with nothing
3221    // left, which is what lets a client dig itself out with `DEL`.
3222    if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
3223        server.mine().cmdstats.at(spec).rejected.bump();
3224        session.dirty_multi();
3225        out.error_line(b"OOM ", OOM);
3226        return Flow::Continue;
3227    }
3228
3229    // A write from a client on a replica is refused, which is what
3230    // `replica-read-only` is and is on by default. Here, after the memory limit
3231    // and before the queue below, which is where a real server puts it, so a
3232    // write queued inside a transaction on a replica is refused as it is queued
3233    // and the whole transaction comes back as an `EXECABORT`.
3234    //
3235    // Two loads on a server that is nobody's replica, both of a bool that is
3236    // false, and the first of them is the one that is nearly always the answer.
3237    // The master's own stream goes through, which is the entire point: a replica
3238    // that refused its master's writes would be a replica of nothing.
3239    if server.read_only_replica() && !session.serving_master() && spec.flags.contains(&"write") {
3240        server.mine().cmdstats.at(spec).rejected.bump();
3241        if spec.name == "exec" {
3242            multi::abort(server, session, follow::READONLY, out);
3243        } else {
3244            session.dirty_multi();
3245            out.error(follow::READONLY.as_bytes());
3246        }
3247        return Flow::Continue;
3248    }
3249
3250    // A RESP2 connection that has subscribed to something may only send a
3251    // handful of commands, because RESP2 sends a published message as an
3252    // ordinary array and a client with a reply outstanding could not tell the
3253    // two apart. Here, after the refusals above and before the queue below,
3254    // which is where a real server puts it: `EXEC` sent while subscribed comes
3255    // back as an `EXECABORT` rather than as this error, and a command `EXEC`
3256    // hands over is not asked at all.
3257    if let Some(e) = pubsub::refused(session, spec, out) {
3258        server.mine().cmdstats.at(spec).rejected.bump();
3259        multi::refuse(server, session, Some(spec), &e, out);
3260        return Flow::Continue;
3261    }
3262
3263    // A monitor may not touch the keyspace. Redis flags one a replica and this
3264    // is the refusal a replica gets, which reads like an accident of the
3265    // implementation and is not one: a monitor is exempt from the pause below,
3266    // so a connection that could pause the server and then become a monitor
3267    // would have a way past its own pause that nothing else has.
3268    //
3269    // Here, in front of the pause and in front of the queue, which is where a
3270    // real server puts it. In front of the queue is what makes `MULTI`, `GET x`,
3271    // `EXEC` on a monitor come back as an `EXECABORT`: the `GET` is refused as
3272    // it is queued rather than as it runs.
3273    if session.monitoring() && touches_keyspace(spec) {
3274        server.mine().cmdstats.at(spec).rejected.bump();
3275        multi::refuse(server, session, Some(spec), &monitor::replica(), out);
3276        return Flow::Continue;
3277    }
3278
3279    // `CLIENT PAUSE`, and this is the whole of it on the command path: one
3280    // relaxed load on a server nobody has paused. Here, after every refusal
3281    // above and before the queue below, which is where a real server puts it. So
3282    // a command that would have been refused is still refused while the server
3283    // is paused, and `MULTI` on a paused server waits rather than opening a
3284    // transaction that would queue commands nobody is allowed to send yet.
3285    //
3286    // Nothing is exempt but a monitor, not even `CLIENT UNPAUSE`, which is
3287    // Redis's behaviour and is worth being clear about: a `CLIENT PAUSE 10000
3288    // ALL` cannot be called off, by anybody, until it runs out. The monitor is
3289    // exempt because a real server exempts its replicas and a monitor is flagged
3290    // one, and it costs nothing to let through because the gate above has
3291    // already refused it everything that reaches a key.
3292    // A command `EXEC` is replaying is not a command the client just sent, and a
3293    // real server runs those through `call` rather than through
3294    // `processCommand`, so the gate is not in front of them. Holding one would
3295    // mean a transaction that has written half of itself and stopped.
3296    if !session.running
3297        && !session.monitoring()
3298        && !session.serving_master()
3299        && let Some(all) = server.paused(server.now_ms())
3300        && (all || may_replicate(spec, session))
3301    {
3302        return Flow::Hold;
3303    }
3304
3305    // And the same thing for the moment a full resync is taking its image. A
3306    // write held here runs a moment later against a keyspace it has not missed
3307    // anything of, which is the whole reason it is held: the image and the
3308    // offset stamped with it have to be the two halves of one instant, and a
3309    // write that landed between them would be in both or in neither. Only the
3310    // writes, and never a command `EXEC` is replaying, for the same reasons the
3311    // pause above gives.
3312    if !session.running && server.frozen() && may_replicate(spec, session) {
3313        return Flow::Hold;
3314    }
3315
3316    // Held rather than run, and the reply is `QUEUED`. After the refusals above
3317    // and before everything below, which is where a real server puts it: a
3318    // command has to be a real command with the right number of arguments to be
3319    // queued at all, and nothing it would have done gets done now.
3320    if session.queues(spec.name) {
3321        return multi::queue(session, spec, args, out);
3322    }
3323
3324    // Which databases the maintenance turn after this batch has to ask. Marked
3325    // for every command and not only for the writes, because a read can make
3326    // garbage too: a `GET` on a key whose expiry has passed reaps it, and the
3327    // record it dropped is exactly the kind of thing the collector is for.
3328    // `COPY`, `SWAPDB` and `FLUSHALL` reach a database nobody selected, so the
3329    // two groups that hold them mark all of them rather than the session's.
3330    server.mine().mark(match spec.group {
3331        "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
3332        | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
3333            1u64 << session.db
3334        }
3335        _ => ALL_DATABASES,
3336    });
3337
3338    // Everybody watching, told about a command that is going to run. The load is
3339    // what this costs a server nobody is watching, which is nearly all of them.
3340    //
3341    // A script is reported before it runs and everything else after, because a
3342    // script's own calls come back through here and a reader wants the `EVAL`
3343    // in front of what it did. Every other command goes below, next to where a
3344    // real server feeds from, which is what puts `EXEC` after the commands it
3345    // replayed rather than in front of them.
3346    let watched = server.monitored() && !monitor::hidden(spec, args);
3347    if watched && monitor::SCRIPTS.contains(&spec.name) {
3348        monitor::feed(server, session, args);
3349    }
3350
3351    let mark = out.len();
3352    // Before the group, because the five that block are list commands and would
3353    // otherwise land in `lists`, which is handed one database and nothing that
3354    // could park a client. The flag is the right thing to branch on rather than
3355    // a list of names: it is what `COMMAND INFO` reports about exactly these
3356    // commands, and the sorted set and stream ones that arrive later carry it
3357    // too.
3358    // What the command is about to do to the keyspace, for anybody subscribed to
3359    // hear about it. Armed here and drained after the group, because the bodies
3360    // below are handed a database and their arguments and have no way to reach
3361    // the pub/sub registry from there. Off costs one thread local store.
3362    let armed = notify::arm(server, session.db);
3363    // And whether what it does has to reach a replica, or the node a slot range
3364    // is being handed to, which the bodies ask about for the same reason and get
3365    // an answer by the same route. That
3366    // arming is done by `notify::arm` above, since the two listeners hear about
3367    // an expired key through the same hook and only one of them can install it.
3368    // What is left here is whether the command as the client sent it would be a
3369    // fair thing to hand a replica, which is the write flag and nothing else: a
3370    // read sends only what its body pushed, which is normally nothing.
3371    let copying = server.propagating();
3372    let verbatim = spec.flags.contains(&"write");
3373    // Which of the keys this command reads are not there. A real server says
3374    // this from inside each lookup and this says all of them in front, which is
3375    // the same order for every command whose first act is to read what it was
3376    // given, and that is nearly all of them.
3377    misses::report(&server.dbs[session.db], session.db, spec, args);
3378    // And whether the lookups it is about to make are reads, for the two
3379    // counters in `INFO stats`. Armed after the walk above so that the walk's
3380    // own probes are not counted, and dropped after the body so that nothing the
3381    // dispatcher does afterwards is either.
3382    let reading = lookups::reading(misses::reading(spec, args));
3383    let done = if spec.flags.contains(&"blocking") {
3384        blocking::execute(server, session, spec, args, out)
3385    } else {
3386        match spec.group {
3387            "string" => {
3388                let db = session.db;
3389                strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3390            }
3391            // Its own group and its own file, and the same values underneath:
3392            // a bitmap is a string, so `STRLEN` on one answers and `SETBIT` on
3393            // something a `SET` left behind works.
3394            "bitmap" => {
3395                let db = session.db;
3396                bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3397            }
3398            // The same again: a sketch is a string with a documented layout, so
3399            // `GET` hands one to a client and `SET` takes it back.
3400            "hyperloglog" => {
3401                let db = session.db;
3402                hll::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3403            }
3404            "set" => {
3405                let db = session.db;
3406                sets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3407            }
3408            // The one hash command whose state is not in the keyspace. A
3409            // fieldset belongs to the connection, so this is handed the session
3410            // as well as the database, the same exception `MIGRATE` gets in the
3411            // keyspace group for the socket it keeps.
3412            "hash" if spec.name == "himport" => {
3413                let db = session.db;
3414                himport::execute(&server.dbs[db], &mut session.sets, args, out)
3415                    .map(|()| Flow::Continue)
3416            }
3417            // The one group that reaches back into the server after it has
3418            // written its reply, because a hash is what a search index is
3419            // made of. What comes back is what the indexes have to be told,
3420            // which is not the same as whether the command was a write.
3421            "hash" => {
3422                let db = session.db;
3423                let changed = hashes::execute(&server.dbs[db], db, spec, args, out);
3424                changed.map(|changed| {
3425                    indexing::changed(server, db, args.get(1), changed);
3426                    Flow::Continue
3427                })
3428            }
3429            "list" => {
3430                let db = session.db;
3431                lists::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3432            }
3433            "zset" => {
3434                let db = session.db;
3435                zsets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3436            }
3437            // A geo key is a sorted set and these are sorted set commands with
3438            // arithmetic on the way in and on the way out, so a client can ZREM
3439            // a place out of one and ZCARD it to count them.
3440            "geo" => {
3441                let db = session.db;
3442                geo::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
3443            }
3444            "array" => {
3445                let db = session.db;
3446                arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3447            }
3448            "graph" => {
3449                let db = session.db;
3450                graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3451            }
3452            // A document under a key, reached by a path. The group is Redis's
3453            // module surface and the storage is ours, the same trade the vector
3454            // set group makes.
3455            "json" => {
3456                let db = session.db;
3457                json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3458            }
3459            "vector" => {
3460                let db = session.db;
3461                vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3462            }
3463            "bloom" => {
3464                let db = session.db;
3465                bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3466            }
3467            "cuckoo" => {
3468                let db = session.db;
3469                cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3470            }
3471            "cms" => {
3472                let db = session.db;
3473                cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3474            }
3475            "topk" => {
3476                let db = session.db;
3477                topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3478            }
3479            "tdigest" => {
3480                let db = session.db;
3481                tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3482            }
3483            "ts" => {
3484                let db = session.db;
3485                ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3486            }
3487            // The clock is read before the database is borrowed, because every
3488            // stream command needs the time and it lives on the server. An
3489            // `XADD` with no ID, an `XCLAIM` working out what is idle and an
3490            // `XINFO` reporting it all have to agree about what moment this is.
3491            "stream" => {
3492                let db = session.db;
3493                let now = server.now_ms();
3494                streams::execute(&server.dbs[db], db, spec, args, now, out).map(|()| Flow::Continue)
3495            }
3496            // The one keyspace command that needs more than the databases,
3497            // because the socket it talks down is held on the server between
3498            // commands and not opened again for each one.
3499            "keyspace" if spec.name == "migrate" => {
3500                migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
3501            }
3502            // Every database and not the one the session is on, because `COPY` takes
3503            // a `DB n` and writes into a database nobody selected. The other group
3504            // that reaches back into the server afterwards, and it hands back a list
3505            // rather than one answer, because `DEL a b c` is three keys and a rename
3506            // is two.
3507            // `RESTORE-ASKING` is `RESTORE` with an `ASKING` built into it and
3508            // runs the same body, but the reference files it under the server
3509            // group rather than the keyspace one, so it has to be named here to
3510            // reach the arm below.
3511            "keyspace" | "server" if spec.group == "keyspace" || spec.name == "restore-asking" => {
3512                let mut touched = indexing::Touched::new(server);
3513                let done =
3514                    keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
3515                done.map(|()| {
3516                    indexing::touched(server, &touched);
3517                    Flow::Continue
3518                })
3519            }
3520            // No database at all, because an index is not a key. The registry
3521            // is the whole of what these sixteen commands touch, and then
3522            // `FT.CREATE` hands back the name it made so the keys that
3523            // already match its prefix can be read into it. The lock goes
3524            // before the scan runs, since the scan takes it again for every
3525            // key it reads.
3526            "search" if spec.name == "FT.SEARCH" => {
3527                // The two search commands that read documents, and so the two
3528                // that need the keyspace as well as the registry. They take and
3529                // let go of the registry themselves, because they cannot hold
3530                // that and a stripe at the same time.
3531                search::find(server, session.db, args, out).map(|()| Flow::Continue)
3532            }
3533            "search" if spec.name == "FT.AGGREGATE" => {
3534                search::roll(server, session.db, args, out).map(|()| Flow::Continue)
3535            }
3536            "search" if spec.name == "FT.HYBRID" => {
3537                search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
3538            }
3539            "search" if spec.name == "FT.PROFILE" => {
3540                // Which is one of those two with the working shown, so it needs
3541                // everything they need and takes the same route to it.
3542                search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
3543            }
3544            // The four search commands that name a key rather than an index.
3545            // A suggestion dictionary is a real key with a type of its own, so
3546            // these are handed a database and never touch the registry.
3547            "search" if spec.name.starts_with("FT.SUG") => {
3548                let db = session.db;
3549                suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
3550            }
3551            // The five deprecated document commands, which are the other search
3552            // commands that need the keyspace as well as the registry: what they
3553            // write and read is an ordinary hash.
3554            "search"
3555                if matches!(
3556                    spec.name,
3557                    "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
3558                ) =>
3559            {
3560                let db = session.db;
3561                search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
3562            }
3563            "search" if spec.name == "FT.CURSOR" => {
3564                // Its own arm because the cursors are not in the registry, and
3565                // it takes and lets go of the registry itself to look up the
3566                // index name it is given.
3567                search::cursor::execute(server, args, out).map(|()| Flow::Continue)
3568            }
3569            "search" => {
3570                let db = session.db;
3571                let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
3572                made.map(|made| {
3573                    match made {
3574                        Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
3575                        Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
3576                        None => {}
3577                    }
3578                    Flow::Continue
3579                })
3580            }
3581            "scripting" => {
3582                scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
3583            }
3584            "transactions" => multi::execute(server, session, spec, args, out),
3585            // No database either, and the one group whose replies do not all go
3586            // to the connection that asked. The session is in it because a
3587            // subscription is connection state as well as server state.
3588            "pubsub" => pubsub::execute(server, session, spec, args, out),
3589            _ => server::execute(server, session, spec, args, out),
3590        }
3591    };
3592    drop(reading);
3593    // The other half of the feed. After the body, so that `SELECT 3` is reported
3594    // on the database it moved to, and before the reply is written, which is
3595    // where a real server has it.
3596    if watched && !monitor::SCRIPTS.contains(&spec.name) {
3597        monitor::feed(server, session, args);
3598    }
3599    // Before the error is written and not after, because a command that failed
3600    // half way through still changed whatever it changed before it failed and a
3601    // real server has already published those. Draining here also keeps the
3602    // notifications of a command run by `EXEC` in front of the next one's.
3603    // And back out the misses reported in front of a command that turned out to
3604    // have failed on its own arguments, since a server that fires from inside
3605    // the lookup never reached one.
3606    if let Err(e) = &done {
3607        misses::undo(spec, e);
3608    }
3609    notify::drain(server, armed);
3610
3611    // And copy it to the replicas. Only the writes, because a read changes
3612    // nothing there is anything to copy, and only the ones that got through,
3613    // because a command that was refused on its own arguments would be refused
3614    // there too and sending it would be asking a second server to make the same
3615    // mistake. `EVAL` and `EXEC` are not writes and are not sent: what they did
3616    // came through here one command at a time and each of those was sent on its
3617    // own, which is effect replication and is what a real server settled on for
3618    // the same reason.
3619    //
3620    // On the database the command ran on rather than the one the session is on
3621    // now, which are the same thing for everything but `SELECT`, and `SELECT` is
3622    // not a write.
3623    // Whatever went away on its own goes first, ahead of the command's own
3624    // effect and whether or not the command has one. A read that reaped a key on
3625    // the way past has a deletion to send and nothing else.
3626    repl::swept(server, session.db);
3627    if copying {
3628        if done.is_ok() {
3629            repl::feed(server, session.db, args, verbatim);
3630        } else {
3631            repl::forget();
3632        }
3633    }
3634
3635    let flow = match done {
3636        Ok(flow) => flow,
3637        Err(e) => {
3638            out.truncate(mark);
3639            write_error(out, &e);
3640            Flow::Continue
3641        }
3642    };
3643
3644    // After the command rather than before, so that whether each key it named is
3645    // there is read at the moment a real server would have signalled the change.
3646    // The load is what this costs a server nobody has sent `WATCH` to, and the
3647    // flag is Redis's own, so a command that only reads is never asked.
3648    if server.watching() && spec.flags.contains(&"write") {
3649        multi::touched(server, session, spec, args);
3650    }
3651
3652    // Counted here and not before the call, which is where Redis counts it, so
3653    // that `INFO commandstats` leaves out the `INFO` that asked for it in the
3654    // same way theirs does.
3655    //
3656    // Failure is read off the reply rather than off the `Result`, because the
3657    // two are not the same set. A command that ran out of arguments comes back
3658    // as an `Err` and a command that was sent the wrong password writes its own
3659    // error line and comes back `Ok`, and both of those are a call that failed.
3660    // The first byte at the mark is what a client would branch on, and it is `-`
3661    // for an error on either protocol and `!` for RESP3's long form.
3662    let row = server.mine().cmdstats.at(spec);
3663    row.calls.bump();
3664    if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
3665        row.failed.bump();
3666    }
3667    // Last of all, because the reply the client is being hung up on still has to
3668    // be written first. A command that asked for this has decided the connection
3669    // is not one it wants to keep talking to, which so far is only a client
3670    // caught reaching for the slot migration protocol.
3671    if session.hanging_up() {
3672        return Flow::Close;
3673    }
3674    flow
3675}
3676
3677/// The error line for an error value.
3678///
3679/// The prefix is what a client branches on, and there are three of them:
3680/// `WRONGTYPE` for a command sent at the wrong kind of value, `INVALIDOBJ` for a
3681/// HyperLogLog whose opcodes do not add up, and `ERR` for everything else. The three errors that need a different one,
3682/// `NOPROTO`, `WRONGPASS` and `OOM`, are written where they are decided rather
3683/// than routed through here. `OOM` is not a [`Code`] of its own because
3684/// [`Code::Full`] already covers the string that is too long for
3685/// `proto-max-bulk-len`, and that one goes out as `ERR` on a real server.
3686fn write_error(out: &mut Out, e: &Error) {
3687    let prefix: &[u8] = match e.code() {
3688        Code::WrongType => b"WRONGTYPE ",
3689        // Only the HyperLogLog commands answer this one, and the prefix is the
3690        // sentence a client branches on to tell a sketch it cannot read from a
3691        // sketch it sent wrong.
3692        Code::Corrupt => b"INVALIDOBJ ",
3693        _ => b"ERR ",
3694    };
3695    out.error_line(prefix, e.message().as_bytes());
3696}
3697
3698#[cfg(test)]
3699mod tests {
3700    use super::*;
3701    use crate::proto::{Limits, Proto};
3702    use crate::request::Argv;
3703
3704    /// Build the wire bytes for a command.
3705    ///
3706    /// Tests go through the codec rather than around it, so an argument in a
3707    /// test is the same borrowed slice a connection produces.
3708    pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
3709        let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
3710        for p in parts {
3711            wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
3712            wire.extend_from_slice(p);
3713            wire.extend_from_slice(b"\r\n");
3714        }
3715        wire
3716    }
3717
3718    /// A server, a connection and a buffer, driven the way the reactor will.
3719    struct Fixture {
3720        server: Server,
3721        session: Session,
3722        argv: Argv,
3723        out: Out,
3724        /// How far into the replication stream [`Fixture::crossed`] has read.
3725        mark: u64,
3726    }
3727
3728    /// The number out of an integer reply, for a test that compares two of them
3729    /// rather than checking one against a constant.
3730    fn int_of(reply: &str) -> i64 {
3731        reply
3732            .strip_prefix(':')
3733            .and_then(|s| s.strip_suffix("\r\n"))
3734            .unwrap_or_else(|| panic!("not an integer reply: {reply:?}"))
3735            .parse()
3736            .expect("an integer reply holds an integer")
3737    }
3738
3739    impl Fixture {
3740        fn new() -> Fixture {
3741            Fixture::on(Server::new())
3742        }
3743
3744        /// The same, on a server whose databases are cut into `width` stripes.
3745        fn striped(width: usize) -> Fixture {
3746            Fixture::on(Server::with_width(width))
3747        }
3748
3749        fn on(server: Server) -> Fixture {
3750            Fixture {
3751                server,
3752                session: Session::new(7),
3753                argv: Argv::new(),
3754                out: Out::new(Proto::Resp2),
3755                mark: 0,
3756            }
3757        }
3758
3759        /// The same, on a server that believes it has a replica.
3760        ///
3761        /// Nothing is attached to it. What the tests below read is the
3762        /// replication stream itself, which is written whether or not there is
3763        /// anybody to send it to, so a server told this is a master in every
3764        /// way that these tests can see.
3765        fn replicated() -> Fixture {
3766            let f = Fixture::new();
3767            f.server.pretend_replica();
3768            f
3769        }
3770
3771        /// Run one command and answer with what crossed to a replica.
3772        ///
3773        /// Only what this command added, so a test reads one line rather than
3774        /// the whole history, and the `SELECT` the stream opens with is part of
3775        /// the first answer for the same reason it is part of the stream.
3776        fn crossed(&mut self, parts: &[&[u8]]) -> String {
3777            self.run(parts);
3778            let (text, upto) = self.server.stream_since(self.mark);
3779            self.mark = upto;
3780            text
3781        }
3782
3783        /// Run one command and answer with the bytes it wrote.
3784        fn run(&mut self, parts: &[&[u8]]) -> String {
3785            self.flow(parts).1
3786        }
3787
3788        /// Run one command and answer with the bytes exactly as written.
3789        ///
3790        /// [`Fixture::run`] goes through `from_utf8_lossy`, which is fine for
3791        /// every reply that is text and destroys a `DUMP` payload, since a
3792        /// payload is arbitrary bytes and a checksum on the end of them.
3793        fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
3794            let wire = encode(parts);
3795            self.argv.decode(&wire, &Limits::default()).unwrap();
3796            self.out.clear();
3797            execute(
3798                &self.server,
3799                &mut self.session,
3800                Args::new(&self.argv, &wire),
3801                &mut self.out,
3802            );
3803            self.out.as_slice().to_vec()
3804        }
3805
3806        /// Move every clock in the server on by `ms`.
3807        fn advance(&mut self, ms: u64) {
3808            self.server.advance_clock_ms(ms);
3809        }
3810
3811        /// Run one command as a second connection to the same server.
3812        ///
3813        /// What `WATCH` is for is a write another connection made, and a test
3814        /// that only has one connection cannot tell the two apart.
3815        fn other(&mut self, parts: &[&[u8]]) -> String {
3816            self.other_in(self.session.db(), parts)
3817        }
3818
3819        /// The same, on a database of its own.
3820        fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
3821            let mut session = Session::new(8);
3822            session.db = db;
3823            let reply = self.by(&mut session, parts);
3824            forget_session(&self.server, &mut session);
3825            reply
3826        }
3827
3828        /// Run one command on a session the caller holds.
3829        fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
3830            let wire = encode(parts);
3831            let mut argv = Argv::new();
3832            argv.decode(&wire, &Limits::default()).unwrap();
3833            let mut out = Out::new(Proto::Resp2);
3834            execute(&self.server, session, Args::new(&argv, &wire), &mut out);
3835            String::from_utf8_lossy(out.as_slice()).into_owned()
3836        }
3837
3838        /// The same, with what the connection should do next.
3839        fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
3840            let wire = encode(parts);
3841            self.argv.decode(&wire, &Limits::default()).unwrap();
3842            self.out.clear();
3843            let flow = execute(
3844                &self.server,
3845                &mut self.session,
3846                Args::new(&self.argv, &wire),
3847                &mut self.out,
3848            );
3849            (
3850                flow,
3851                String::from_utf8_lossy(self.out.as_slice()).into_owned(),
3852            )
3853        }
3854    }
3855
3856    #[test]
3857    fn multi_holds_commands_and_exec_runs_them() {
3858        let mut f = Fixture::new();
3859        assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
3860        assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
3861        assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
3862        // Nothing ran while it was being queued.
3863        assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
3864        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
3865        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
3866    }
3867
3868    /// The test the `high_water` claim in `multi::exec` asks for.
3869    ///
3870    /// A `Vec` reaches the allocator exactly when its capacity changes, so a
3871    /// replay buffer whose room is the same before and after is one that did
3872    /// not allocate. The first transaction is what sets the room, which is the
3873    /// high water mark, and the second is the one that has to be free. Before
3874    /// the buffer moved onto the session this failed on every transaction,
3875    /// because `exec` made a new one each time and the room went back to zero.
3876    #[test]
3877    fn the_second_exec_of_a_shape_does_not_grow_the_buffer() {
3878        let mut f = Fixture::new();
3879        for _ in 0..2 {
3880            f.run(&[b"MULTI"]);
3881            f.run(&[b"SET", b"k", b"1"]);
3882            f.run(&[b"INCR", b"k"]);
3883            f.run(&[b"EXEC"]);
3884        }
3885        let room = f.session.replay.room();
3886        assert!(room > 0, "the first transaction should have set the room");
3887        f.run(&[b"MULTI"]);
3888        f.run(&[b"SET", b"k", b"1"]);
3889        f.run(&[b"INCR", b"k"]);
3890        f.run(&[b"EXEC"]);
3891        assert_eq!(f.session.replay.room(), room);
3892    }
3893
3894    #[test]
3895    fn an_empty_transaction_answers_an_empty_array() {
3896        let mut f = Fixture::new();
3897        f.run(&[b"MULTI"]);
3898        assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
3899    }
3900
3901    #[test]
3902    fn exec_and_discard_want_a_transaction_to_be_open() {
3903        let mut f = Fixture::new();
3904        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3905        assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
3906        // And `UNWATCH` does not, which is the one of the three that is happy
3907        // being sent for no reason.
3908        assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
3909    }
3910
3911    #[test]
3912    fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
3913        let mut f = Fixture::new();
3914        f.run(&[b"MULTI"]);
3915        assert_eq!(
3916            f.run(&[b"MULTI"]),
3917            "-ERR MULTI calls can not be nested\r\n",
3918            "nested MULTI is raised by the command and not by the funnel"
3919        );
3920        assert_eq!(
3921            f.run(&[b"WATCH", b"k"]),
3922            "-ERR WATCH inside MULTI is not allowed\r\n"
3923        );
3924        f.run(&[b"SET", b"k", b"1"]);
3925        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
3926    }
3927
3928    #[test]
3929    fn an_error_the_funnel_raises_kills_the_transaction() {
3930        for bad in [
3931            &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
3932            &[b"GET".as_slice()],
3933        ] {
3934            let mut f = Fixture::new();
3935            f.run(&[b"MULTI"]);
3936            assert!(f.run(bad).starts_with("-ERR "));
3937            assert_eq!(
3938                f.run(&[b"SET", b"k", b"1"]),
3939                "+QUEUED\r\n",
3940                "a dead transaction still answers QUEUED, which is Redis"
3941            );
3942            assert_eq!(
3943                f.run(&[b"EXEC"]),
3944                "-EXECABORT Transaction discarded because of previous errors.\r\n"
3945            );
3946            assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3947        }
3948    }
3949
3950    #[test]
3951    fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
3952        let mut f = Fixture::new();
3953        f.run(&[b"MULTI"]);
3954        f.run(&[b"SET", b"k", b"1"]);
3955        assert_eq!(
3956            f.run(&[b"EXEC", b"x"]),
3957            "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
3958        );
3959        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3960        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3961    }
3962
3963    #[test]
3964    fn a_command_a_transaction_may_not_hold_kills_it() {
3965        let mut f = Fixture::new();
3966        f.run(&[b"MULTI"]);
3967        assert_eq!(
3968            f.run(&[b"SHUTDOWN", b"NOSAVE"]),
3969            "-ERR Command not allowed inside a transaction\r\n"
3970        );
3971        assert_eq!(
3972            f.run(&[b"EXEC"]),
3973            "-EXECABORT Transaction discarded because of previous errors.\r\n"
3974        );
3975    }
3976
3977    #[test]
3978    fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
3979        let mut f = Fixture::new();
3980        f.run(&[b"RPUSH", b"l", b"v"]);
3981        f.run(&[b"MULTI"]);
3982        f.run(&[b"INCR", b"l"]);
3983        f.run(&[b"SET", b"y", b"2"]);
3984        assert_eq!(
3985            f.run(&[b"EXEC"]),
3986            "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
3987        );
3988        assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
3989    }
3990
3991    #[test]
3992    fn discard_and_reset_both_throw_the_queue_away() {
3993        let mut f = Fixture::new();
3994        f.run(&[b"MULTI"]);
3995        f.run(&[b"SET", b"k", b"1"]);
3996        assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
3997        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
3998        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3999
4000        f.run(&[b"MULTI"]);
4001        f.run(&[b"SET", b"k", b"1"]);
4002        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
4003        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
4004        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4005    }
4006
4007    #[test]
4008    fn select_is_queued_and_applied_when_exec_runs_it() {
4009        let mut f = Fixture::new();
4010        f.run(&[b"MULTI"]);
4011        assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
4012        f.run(&[b"SET", b"k", b"1"]);
4013        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
4014        assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
4015        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
4016    }
4017
4018    #[test]
4019    fn a_write_by_another_connection_fails_the_transaction() {
4020        let mut f = Fixture::new();
4021        f.run(&[b"SET", b"k", b"1"]);
4022        assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
4023        f.other(&[b"SET", b"k", b"2"]);
4024        f.run(&[b"MULTI"]);
4025        f.run(&[b"GET", b"k"]);
4026        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4027    }
4028
4029    #[test]
4030    fn a_write_that_puts_the_same_value_back_still_fails_it() {
4031        let mut f = Fixture::new();
4032        f.run(&[b"SET", b"k", b"1"]);
4033        f.run(&[b"WATCH", b"k"]);
4034        f.other(&[b"SET", b"k", b"1"]);
4035        f.run(&[b"MULTI"]);
4036        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4037    }
4038
4039    #[test]
4040    fn a_read_by_another_connection_does_not() {
4041        let mut f = Fixture::new();
4042        f.run(&[b"SET", b"k", b"1"]);
4043        f.run(&[b"WATCH", b"k"]);
4044        f.other(&[b"GET", b"k"]);
4045        f.other(&[b"STRLEN", b"k"]);
4046        f.run(&[b"MULTI"]);
4047        f.run(&[b"GET", b"k"]);
4048        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
4049    }
4050
4051    #[test]
4052    fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
4053        let mut f = Fixture::new();
4054        f.run(&[b"WATCH", b"k"]);
4055        f.other(&[b"DEL", b"k"]);
4056        f.run(&[b"MULTI"]);
4057        f.run(&[b"PING"]);
4058        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
4059        // And creating it does, which is the other half of the same rule.
4060        f.run(&[b"WATCH", b"k"]);
4061        f.other(&[b"SET", b"k", b"1"]);
4062        f.run(&[b"MULTI"]);
4063        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4064    }
4065
4066    #[test]
4067    fn a_watched_key_that_expires_fails_the_transaction() {
4068        let mut f = Fixture::new();
4069        f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
4070        f.run(&[b"WATCH", b"k"]);
4071        f.run(&[b"MULTI"]);
4072        f.advance(100);
4073        assert_eq!(
4074            f.run(&[b"EXEC"]),
4075            "*-1\r\n",
4076            "nothing wrote to the key, so only the liveness check can catch this"
4077        );
4078    }
4079
4080    #[test]
4081    fn every_way_a_transaction_ends_lets_go_of_the_watches() {
4082        for end in [
4083            &[b"EXEC".as_slice()] as &[&[u8]],
4084            &[b"DISCARD".as_slice()],
4085            &[b"UNWATCH".as_slice()],
4086            &[b"RESET".as_slice()],
4087        ] {
4088            let mut f = Fixture::new();
4089            f.run(&[b"SET", b"k", b"1"]);
4090            f.run(&[b"WATCH", b"k"]);
4091            if end[0] != b"UNWATCH" && end[0] != b"RESET" {
4092                f.run(&[b"MULTI"]);
4093            }
4094            f.run(end);
4095            assert!(!f.server.watching(), "{end:?} left a row behind");
4096            // And the connection can start again with nothing carried over.
4097            f.other(&[b"SET", b"k", b"2"]);
4098            f.run(&[b"MULTI"]);
4099            f.run(&[b"GET", b"k"]);
4100            assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
4101        }
4102    }
4103
4104    #[test]
4105    fn a_connection_going_away_lets_go_of_its_watches() {
4106        let mut f = Fixture::new();
4107        f.run(&[b"SET", b"k", b"1"]);
4108        f.run(&[b"WATCH", b"k"]);
4109        assert!(f.server.watching());
4110        forget_session(&f.server, &mut f.session);
4111        assert!(!f.server.watching());
4112    }
4113
4114    #[test]
4115    fn watching_the_same_key_twice_is_one_watch() {
4116        let mut f = Fixture::new();
4117        f.run(&[b"SET", b"k", b"1"]);
4118        f.run(&[b"WATCH", b"k", b"k"]);
4119        f.run(&[b"UNWATCH"]);
4120        assert!(
4121            !f.server.watching(),
4122            "the row counts watchers, so a doubled watch would leave one behind"
4123        );
4124    }
4125
4126    #[test]
4127    fn two_connections_can_watch_the_same_key() {
4128        let mut f = Fixture::new();
4129        f.run(&[b"SET", b"k", b"1"]);
4130        f.run(&[b"WATCH", b"k"]);
4131        let mut second = Session::new(9);
4132        second.db = f.session.db();
4133        assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
4134        // One lets go and the other's watch still works.
4135        forget_session(&f.server, &mut second);
4136        assert!(f.server.watching());
4137        f.other(&[b"SET", b"k", b"2"]);
4138        f.run(&[b"MULTI"]);
4139        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4140    }
4141
4142    #[test]
4143    fn flushdb_fails_a_watch_on_a_key_that_was_there() {
4144        let mut f = Fixture::new();
4145        f.run(&[b"SET", b"k", b"1"]);
4146        f.run(&[b"WATCH", b"k"]);
4147        f.other(&[b"FLUSHDB"]);
4148        f.run(&[b"MULTI"]);
4149        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
4150    }
4151
4152    #[test]
4153    fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
4154        let mut f = Fixture::new();
4155        f.run(&[b"WATCH", b"k"]);
4156        f.other(&[b"FLUSHDB"]);
4157        f.run(&[b"MULTI"]);
4158        f.run(&[b"PING"]);
4159        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
4160    }
4161
4162    #[test]
4163    fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
4164        let mut f = Fixture::new();
4165        f.run(&[b"SET", b"k", b"1"]);
4166        f.run(&[b"WATCH", b"k"]);
4167        // The same name in another database is another key.
4168        let elsewhere = f.session.db() + 1;
4169        f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
4170        f.run(&[b"MULTI"]);
4171        f.run(&[b"GET", b"k"]);
4172        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
4173    }
4174
4175    #[test]
4176    fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
4177        let mut f = Fixture::new();
4178        f.run(&[b"RPUSH", b"src", b"1"]);
4179        f.run(&[b"WATCH", b"dst"]);
4180        f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
4181        f.run(&[b"MULTI"]);
4182        assert_eq!(
4183            f.run(&[b"EXEC"]),
4184            "*-1\r\n",
4185            "SORT is movablekeys, so every watched key in the database is asked"
4186        );
4187    }
4188
4189    #[test]
4190    fn a_server_nobody_is_watching_says_so() {
4191        let mut f = Fixture::new();
4192        assert!(!f.server.watching());
4193        f.run(&[b"SET", b"k", b"1"]);
4194        assert!(!f.server.watching());
4195    }
4196
4197    /// The count on the end of a subscribe reply is channels and patterns
4198    /// together, which is a thing a client uses to know when it is out of
4199    /// subscribe mode and so has to be the number the mode is decided on.
4200    /// Shard channels are counted on their own because they are their own
4201    /// namespace.
4202    #[test]
4203    fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
4204        let mut f = Fixture::new();
4205        assert_eq!(
4206            f.run(&[b"SUBSCRIBE", b"a", b"b"]),
4207            "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:1\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nb\r\n:2\r\n"
4208        );
4209        assert_eq!(
4210            f.run(&[b"PSUBSCRIBE", b"c*"]),
4211            "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
4212        );
4213        assert_eq!(
4214            f.run(&[b"SSUBSCRIBE", b"s"]),
4215            "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
4216        );
4217        // Subscribing again to something already held answers again with the
4218        // count unchanged, rather than counting it twice or saying nothing.
4219        assert_eq!(
4220            f.run(&[b"SUBSCRIBE", b"a"]),
4221            "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
4222        );
4223    }
4224
4225    /// Unsubscribe has three shapes and a client has to be able to tell them
4226    /// apart, because the last one is what tells it the mode is over.
4227    #[test]
4228    fn unsubscribe_answers_for_names_it_was_not_holding_too() {
4229        let mut f = Fixture::new();
4230        f.run(&[b"SUBSCRIBE", b"a"]);
4231
4232        // A name that was never subscribed still gets a reply, with the count
4233        // as it stands.
4234        assert_eq!(
4235            f.run(&[b"UNSUBSCRIBE", b"zz"]),
4236            "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
4237        );
4238        // With no names, one reply per channel held, counting down.
4239        f.run(&[b"SUBSCRIBE", b"b"]);
4240        f.run(&[b"PSUBSCRIBE", b"p*"]);
4241        assert_eq!(
4242            f.run(&[b"UNSUBSCRIBE"]),
4243            "*3\r\n$11\r\nunsubscribe\r\n$1\r\na\r\n:2\r\n*3\r\n$11\r\nunsubscribe\r\n$1\r\nb\r\n:1\r\n"
4244        );
4245        // With no names and none of that family held, one reply with a nil
4246        // where the name goes and the count that is left.
4247        assert_eq!(
4248            f.run(&[b"UNSUBSCRIBE"]),
4249            "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
4250            "the pattern is still held, so the count is one"
4251        );
4252        assert_eq!(
4253            f.run(&[b"SUNSUBSCRIBE"]),
4254            "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
4255            "shard channels are counted on their own"
4256        );
4257    }
4258
4259    /// The gate is on the funnel and the funnel is what `EXEC` goes through
4260    /// for the commands it queued, so it has to know it is running one.
4261    /// Redis lets a queued command through, and a transaction that subscribes
4262    /// and then reads is the case that says which way round it is.
4263    #[test]
4264    fn the_subscribe_gate_does_not_reach_inside_exec() {
4265        let mut f = Fixture::new();
4266        f.run(&[b"SET", b"k", b"1"]);
4267        f.run(&[b"MULTI"]);
4268        assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
4269        assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
4270        assert_eq!(
4271            f.run(&[b"EXEC"]),
4272            "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
4273        );
4274        // And once EXEC is done the connection really is subscribed, so the
4275        // gate is back on.
4276        assert_eq!(
4277            f.run(&[b"GET", b"k"]),
4278            "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
4279        );
4280    }
4281
4282    /// `EXEC` sent by a subscribed RESP2 client is refused by the gate like
4283    /// anything else, and a refusal on the funnel kills the transaction.
4284    #[test]
4285    fn exec_sent_by_a_subscriber_aborts_the_transaction() {
4286        let mut f = Fixture::new();
4287        f.run(&[b"MULTI"]);
4288        f.run(&[b"SET", b"k", b"1"]);
4289        f.run(&[b"SUBSCRIBE", b"z"]);
4290        f.run(&[b"EXEC"]);
4291        f.run(&[b"MULTI"]);
4292        assert_eq!(
4293            f.run(&[b"EXEC"]),
4294            "-EXECABORT Transaction discarded because of: Can't execute 'exec': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
4295        );
4296    }
4297
4298    /// `RESET` is one of the few things a subscriber may send, and what it
4299    /// resets includes every subscription it is holding.
4300    #[test]
4301    fn reset_lets_go_of_every_subscription() {
4302        let mut f = Fixture::new();
4303        f.run(&[b"SUBSCRIBE", b"a"]);
4304        f.run(&[b"PSUBSCRIBE", b"p*"]);
4305        f.run(&[b"SSUBSCRIBE", b"s"]);
4306        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
4307        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
4308        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
4309        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
4310        // And the connection takes ordinary commands again.
4311        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4312    }
4313
4314    /// What `PUBSUB` can be asked, on a server with one subscriber holding one
4315    /// of each.
4316    #[test]
4317    fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
4318        let mut f = Fixture::new();
4319        let mut sub = Session::new(9);
4320        f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
4321        f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
4322        f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
4323
4324        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
4325        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
4326        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
4327        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
4328        assert_eq!(
4329            f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
4330            "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
4331        );
4332        assert_eq!(
4333            f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
4334            "*2\r\n$1\r\na\r\n:1\r\n",
4335            "the shard channel and the channel share a name and not a count"
4336        );
4337        assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
4338
4339        forget_session(&f.server, &mut sub);
4340        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
4341        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
4342    }
4343
4344    /// The one setting whose value is neither a number nor a word, and whose
4345    /// spelling on the way out is not the spelling on the way in.
4346    #[test]
4347    fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
4348        let mut f = Fixture::new();
4349        assert_eq!(
4350            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4351            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
4352        );
4353        assert_eq!(
4354            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
4355            "+OK\r\n"
4356        );
4357        // `A` is a class of its own on the way in and stays one on the way out,
4358        // and the two channel letters move to the end.
4359        assert_eq!(
4360            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4361            "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
4362        );
4363        assert_eq!(
4364            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
4365            "+OK\r\n"
4366        );
4367        assert_eq!(
4368            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4369            "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
4370        );
4371    }
4372
4373    #[test]
4374    fn a_letter_the_notification_setting_does_not_know_is_refused() {
4375        let mut f = Fixture::new();
4376        assert_eq!(
4377            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
4378            "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
4379             - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
4380        );
4381        // And nothing was applied, since the whole setting is parsed before any
4382        // of it is stored.
4383        assert_eq!(
4384            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
4385            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
4386        );
4387    }
4388
4389    /// One mistake in a `PUBSUB` subcommand has two error shapes depending on
4390    /// which subcommand it is, because the ones with a fixed argument count are
4391    /// checked by the subcommand table and the ones without fall through to
4392    /// the generic syntax error. Both are copied here rather than tidied,
4393    /// since a client that matches on the text sees the difference.
4394    #[test]
4395    fn pubsub_says_no_two_different_ways() {
4396        let mut f = Fixture::new();
4397        assert_eq!(
4398            f.run(&[b"PUBSUB"]),
4399            "-ERR wrong number of arguments for 'pubsub' command\r\n"
4400        );
4401        assert_eq!(
4402            f.run(&[b"PUBSUB", b"NOPE"]),
4403            "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
4404        );
4405        assert_eq!(
4406            f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
4407            "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
4408        );
4409        assert_eq!(
4410            f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
4411            "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
4412        );
4413        assert_eq!(
4414            f.run(&[b"PUBSUB", b"HELP", b"x"]),
4415            "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
4416        );
4417    }
4418
4419    /// Publishing to nobody costs a lookup and answers zero, which is the
4420    /// common case on a server that has pub/sub compiled in and not in use.
4421    #[test]
4422    fn publishing_to_nobody_answers_zero() {
4423        let mut f = Fixture::new();
4424        assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
4425        assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
4426        // An empty channel name is a name like any other.
4427        assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
4428    }
4429
4430    /// A publish counts everybody it reached, which is not the same as the
4431    /// number of subscribers: one connection holding two patterns that both
4432    /// match is two.
4433    #[test]
4434    fn a_publish_counts_the_deliveries_and_not_the_clients() {
4435        let mut f = Fixture::new();
4436        let mut sub = Session::new(9);
4437        f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
4438        f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
4439        f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
4440        assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
4441        forget_session(&f.server, &mut sub);
4442    }
4443
4444    /// What a client does all day: write the same keys again and again. Every
4445    /// one of those writes leaves the previous record behind, so a server that
4446    /// never compacts holds every version of every key it has ever been sent.
4447    ///
4448    /// Not under Miri, and not because of anything it would find. The bound
4449    /// only means something once several megabytes have gone through the
4450    /// arena, which reclaims a segment at a time and has segments of two
4451    /// megabytes, so a server that reclaimed nothing would still be under the
4452    /// bound in any smaller version of this. Thirty two megabytes is thirty
4453    /// two thousand commands and was over forty minutes interpreted. The paths
4454    /// it walks are walked by the hundreds of tests around it that write a key
4455    /// and read it back, which do run there.
4456    #[cfg_attr(miri, ignore = "megabytes through the arena")]
4457    #[test]
4458    fn rewriting_the_same_keys_does_not_grow_the_server() {
4459        let mut f = Fixture::new();
4460        let val = vec![b'v'; 1024];
4461        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
4462
4463        for k in &keys {
4464            f.run(&[b"SET", k, &val]);
4465        }
4466        f.server.compact_step();
4467        let after_first = f.server.memory_bytes();
4468
4469        // 64 KiB a pass, five hundred passes, and the same 64 keys at the end
4470        // of it. Thirty two megabytes written to hold sixty four kilobytes,
4471        // which is the shape of a real workload and is enough churn to fill
4472        // sixteen segments if nothing ever comes back.
4473        for _ in 0..500 {
4474            for k in &keys {
4475                f.run(&[b"SET", k, &val]);
4476            }
4477            f.server.compact_step();
4478        }
4479
4480        assert!(
4481            f.server.memory_bytes() <= after_first * 2,
4482            "held {} after five hundred passes against {after_first} after one",
4483            f.server.memory_bytes()
4484        );
4485        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
4486        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
4487    }
4488
4489    /// The same churn on a database nobody starts on, either side of a quiet
4490    /// spell long enough for the maintenance turn to stop asking about it.
4491    ///
4492    /// The turn after each batch skips a database that has already said it has
4493    /// nothing to collect and has not been touched since, which is what keeps a
4494    /// server whose clients are all on database zero from loading and storing
4495    /// in the other fifteen every batch to be told no. Two things could go
4496    /// wrong with that. A database might never be marked at all, so this uses
4497    /// database nine, which nothing marks by accident. And a database whose
4498    /// mark was cleared might never get it back, so this drains the collector
4499    /// until it says there is nothing left, checks the mark really is gone, and
4500    /// then writes another thirty two megabytes through the same sixty four
4501    /// keys. If either went wrong the server would hold all of it.
4502    ///
4503    /// Not under Miri, for the reason on the test above: the volume is the
4504    /// claim, and the volume is what the interpreter charges for.
4505    #[cfg_attr(miri, ignore = "megabytes through the arena")]
4506    #[test]
4507    fn a_database_nobody_started_on_is_still_collected() {
4508        let mut f = Fixture::new();
4509        assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
4510        let val = vec![b'v'; 1024];
4511        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
4512
4513        for k in &keys {
4514            f.run(&[b"SET", k, &val]);
4515        }
4516        // A call looks at [`COMPACT_LOOKS`] stripes and not at all of them, so
4517        // draining takes calls in proportion to the width and one call saying
4518        // there was nothing to move is not the whole database saying it.
4519        let drain = |f: &Fixture| {
4520            for _ in 0..4 * f.server.slots() {
4521                if f.server.compact_step().is_none() && !f.server.mine().wanted(9) {
4522                    return;
4523                }
4524            }
4525            panic!("compaction never got to the end of database nine");
4526        };
4527        drain(&f);
4528        assert!(
4529            !f.server.mine().wanted(9),
4530            "database nine was drained and should not be asked again until it is written to"
4531        );
4532        let after_first = f.server.memory_bytes();
4533
4534        for _ in 0..500 {
4535            for k in &keys {
4536                f.run(&[b"SET", k, &val]);
4537            }
4538            f.server.compact_step();
4539        }
4540
4541        assert!(
4542            f.server.memory_bytes() <= after_first * 2,
4543            "held {} after five hundred passes against {after_first} after one",
4544            f.server.memory_bytes()
4545        );
4546        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
4547        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
4548        // And nothing landed anywhere else on the way.
4549        f.run(&[b"SELECT", b"0"]);
4550        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4551    }
4552
4553    /// The maintenance turn moves its own cursor and leaves the server's alone.
4554    ///
4555    /// The turn runs after every batch on every thread, so anything it writes
4556    /// that the whole server can see is a line every thread is writing to at
4557    /// batch rate, and the cost of that goes up with the thread count instead
4558    /// of staying still. The cursor is the last thing in the turn that was
4559    /// shared, and it was shared for a reason that only ever applied to the
4560    /// other caller: [`Server::compact_hard_step`] runs when a server is over
4561    /// its memory limit, which is a rare thing and not a per batch thing.
4562    ///
4563    /// What is checked is both halves of that. The turn is asked to walk, and
4564    /// afterwards this thread's cursor has moved and the server's has not.
4565    #[test]
4566    fn the_maintenance_turn_does_not_write_a_shared_cursor() {
4567        let f = Fixture::new();
4568        let before = f.server.next_db.load(Relaxed);
4569        let mine = f.server.mine().compact_db.load(Relaxed);
4570        // Nothing to compact, which is the case that matters: a turn that found
4571        // nothing is nearly every turn, and it used to write the shared cursor
4572        // anyway just to say where the next one should start.
4573        assert!(f.server.compact_step().is_none());
4574        assert_eq!(
4575            f.server.next_db.load(Relaxed),
4576            before,
4577            "the turn wrote the cursor the over limit path reads"
4578        );
4579        assert_ne!(
4580            f.server.mine().compact_db.load(Relaxed),
4581            mine,
4582            "the turn did not move on, so it will look at the same stripes forever"
4583        );
4584    }
4585
4586    /// Two threads start their walk in different places.
4587    ///
4588    /// Splitting the cursor gave up the one thing sharing bought, which is two
4589    /// threads not arriving at the same database at the same moment. Seeding
4590    /// each thread's cursor at its own index buys most of it back for nothing,
4591    /// and this is that: a server built for eight threads has eight cursors and
4592    /// no two of them start together.
4593    #[test]
4594    fn each_thread_starts_its_compaction_somewhere_else() {
4595        let mut server = Server::new();
4596        server.set_threads(8);
4597        let starts: Vec<usize> = server
4598            .locals
4599            .iter()
4600            .map(|t| t.compact_db.load(Relaxed))
4601            .collect();
4602        assert_eq!(starts, (0..8).collect::<Vec<usize>>());
4603    }
4604
4605    #[test]
4606    fn a_command_goes_from_bytes_to_bytes() {
4607        let mut f = Fixture::new();
4608        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
4609        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
4610        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
4611        assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
4612        // The name is matched whatever case it came in, and so are the options.
4613        assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
4614        assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
4615    }
4616
4617    #[test]
4618    fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
4619        let mut f = Fixture::new();
4620        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
4621        // A key named twice exists twice and can only be deleted once, and both
4622        // of those are Redis's answers rather than tidier ones.
4623        assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
4624        assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
4625        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
4626        // UNLINK is the same body and reports the same way.
4627        assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
4628        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4629    }
4630
4631    #[test]
4632    fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
4633        let mut f = Fixture::new();
4634        f.run(&[b"SET", b"k", b"v"]);
4635        // A simple string on both protocols, which is unusual: most replies
4636        // that carry a word are bulk strings.
4637        assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
4638        assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
4639    }
4640
4641    #[test]
4642    fn touch_counts_the_way_exists_counts() {
4643        let mut f = Fixture::new();
4644        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4645        assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
4646        assert_eq!(
4647            f.run(&[b"TOUCH", b"a", b"a"]),
4648            ":2\r\n",
4649            "twice counts twice"
4650        );
4651        assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
4652        assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
4653    }
4654
4655    #[test]
4656    fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
4657        let mut f = Fixture::new();
4658        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
4659        f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
4660
4661        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
4662        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
4663        assert_eq!(
4664            f.run(&[b"TTL", b"b"]),
4665            ":100\r\n",
4666            "the source's and not b's"
4667        );
4668        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
4669    }
4670
4671    #[test]
4672    fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
4673        let mut f = Fixture::new();
4674        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
4675        // The source is checked before the destination, so this is the error
4676        // and not the zero RENAMENX would otherwise answer for a taken name.
4677        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
4678    }
4679
4680    #[test]
4681    fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
4682        let mut f = Fixture::new();
4683        f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
4684
4685        assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
4686        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
4687        // Renaming onto itself is 0 here and OK for plain RENAME, which is the
4688        // one call the two disagree about and neither does any work for.
4689        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
4690        assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
4691        assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
4692        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
4693    }
4694
4695    #[test]
4696    fn renaming_a_set_does_not_touch_a_member() {
4697        let mut f = Fixture::new();
4698        for i in 0..300 {
4699            f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
4700        }
4701        let before = f.server.memory_bytes();
4702
4703        assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
4704        assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
4705        assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
4706        assert!(
4707            f.server.memory_bytes().abs_diff(before) < 256,
4708            "the members were copied: {} against {before}",
4709            f.server.memory_bytes()
4710        );
4711    }
4712
4713    #[test]
4714    fn a_copy_is_a_second_value_and_not_a_second_name() {
4715        let mut f = Fixture::new();
4716        f.run(&[b"SADD", b"s", b"m1", b"m2"]);
4717
4718        assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
4719        f.run(&[b"SADD", b"t", b"m3"]);
4720        assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
4721        assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
4722    }
4723
4724    /// Every type a key can hold, copied, because two of them used to panic.
4725    ///
4726    /// `COPY` reads the value out of the source through one match on the type
4727    /// tag, and that match had a catch all at the bottom from back when a set
4728    /// and a hash were the only bodies. The list and the sorted set landed after
4729    /// it and nobody came back, so `COPY mylist other` took the shard down. It
4730    /// is an ordinary command against a type the server supports everywhere
4731    /// else, so this walks all five rather than the two that were broken: the
4732    /// point is that the next type cannot land the same way.
4733    #[test]
4734    fn every_type_can_be_copied() {
4735        let mut f = Fixture::new();
4736        f.run(&[b"SET", b"str", b"v1"]);
4737        f.run(&[b"SADD", b"set", b"m1"]);
4738        f.run(&[b"HSET", b"hash", b"f", b"v"]);
4739        f.run(&[b"RPUSH", b"list", b"a", b"b"]);
4740        f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
4741
4742        for name in [
4743            &b"str"[..],
4744            &b"set"[..],
4745            &b"hash"[..],
4746            &b"list"[..],
4747            &b"zset"[..],
4748        ] {
4749            let dst = [name, b":copy"].concat();
4750            assert_eq!(
4751                f.run(&[b"COPY", name, &dst]),
4752                ":1\r\n",
4753                "copying {}",
4754                String::from_utf8_lossy(name)
4755            );
4756            assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
4757        }
4758
4759        assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
4760            let mut want = String::from("*2\r\n");
4761            want.push_str("$1\r\na\r\n$1\r\nb\r\n");
4762            want
4763        });
4764        assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
4765
4766        // And the copy is its own value, not a second name for the source.
4767        f.run(&[b"RPUSH", b"list:copy", b"c"]);
4768        assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
4769        assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
4770    }
4771
4772    #[test]
4773    fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
4774        let mut f = Fixture::new();
4775        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
4776        f.run(&[b"SET", b"b", b"v2"]);
4777
4778        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
4779        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
4780        assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
4781        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
4782        assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
4783        assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
4784    }
4785
4786    #[test]
4787    fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
4788        let mut f = Fixture::new();
4789        f.run(&[b"SET", b"a", b"v1"]);
4790
4791        // Same key, different database, so this is not the same object and is
4792        // an ordinary copy. Same key in the same database is the error below.
4793        assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
4794        f.run(&[b"SELECT", b"1"]);
4795        assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
4796        assert_eq!(
4797            f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
4798            ":0\r\n",
4799            "taken"
4800        );
4801        assert_eq!(
4802            f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
4803            ":1\r\n"
4804        );
4805    }
4806
4807    #[test]
4808    fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
4809        let mut f = Fixture::new();
4810        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
4811        assert_eq!(
4812            f.run(&[b"SORT", b"l"]),
4813            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
4814        );
4815        // DESC then ASC is ASC, because the only thing ASC does is undo a DESC.
4816        assert_eq!(
4817            f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
4818            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
4819        );
4820        assert_eq!(
4821            f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
4822            "*1\r\n$1\r\n2\r\n"
4823        );
4824    }
4825
4826    #[test]
4827    fn sort_reads_a_key_per_element_for_by_and_for_get() {
4828        let mut f = Fixture::new();
4829        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4830        f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
4831        // `b` weighs less so it comes first, and its `GET` hits where `a`'s
4832        // misses, which is a nil in the middle of the array and not a short one.
4833        assert_eq!(
4834            f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
4835            "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
4836        );
4837    }
4838
4839    #[test]
4840    fn sort_store_writes_a_list_and_answers_its_length() {
4841        let mut f = Fixture::new();
4842        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
4843        assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
4844        assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
4845        assert_eq!(
4846            f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
4847            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
4848        );
4849        // An empty result takes the destination with it rather than leaving a
4850        // list that holds nothing.
4851        assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
4852        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
4853    }
4854
4855    #[test]
4856    fn sort_ro_does_not_know_the_word_store() {
4857        let mut f = Fixture::new();
4858        f.run(&[b"RPUSH", b"l", b"2", b"1"]);
4859        assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
4860        assert_eq!(
4861            f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
4862            "-ERR syntax error\r\n"
4863        );
4864        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
4865    }
4866
4867    #[test]
4868    fn sort_refuses_what_it_cannot_sort() {
4869        let mut f = Fixture::new();
4870        assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
4871        f.run(&[b"SET", b"s", b"x"]);
4872        assert_eq!(
4873            f.run(&[b"SORT", b"s"]),
4874            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
4875        );
4876        f.run(&[b"RPUSH", b"words", b"one", b"two"]);
4877        assert_eq!(
4878            f.run(&[b"SORT", b"words"]),
4879            "-ERR One or more scores can't be converted into double\r\n"
4880        );
4881        assert_eq!(
4882            f.run(&[b"SORT", b"words", b"ALPHA"]),
4883            "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
4884        );
4885        assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
4886    }
4887
4888    #[test]
4889    fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
4890        let mut f = Fixture::new();
4891        assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
4892        assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
4893        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
4894        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4895        assert_eq!(
4896            f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
4897            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4898        );
4899        // And back, which proves the body survived the trip rather than being
4900        // rebuilt from a copy that happened to look the same.
4901        assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
4902        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
4903    }
4904
4905    #[test]
4906    fn move_answers_zero_when_either_end_says_no() {
4907        let mut f = Fixture::new();
4908        assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
4909        assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
4910        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4911        assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
4912        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
4913        // The destination is taken, so nothing moves and the source is still
4914        // there with what it had.
4915        assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
4916        assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
4917        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4918        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
4919    }
4920
4921    #[test]
4922    fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
4923        let mut f = Fixture::new();
4924        assert_eq!(
4925            f.run(&[b"MOVE", b"a", b"0"]),
4926            "-ERR source and destination objects are the same\r\n"
4927        );
4928        assert_eq!(
4929            f.run(&[b"MOVE", b"a", b"99"]),
4930            "-ERR DB index is out of range\r\n"
4931        );
4932        assert_eq!(
4933            f.run(&[b"MOVE", b"a", b"-1"]),
4934            "-ERR DB index is out of range\r\n"
4935        );
4936        assert_eq!(
4937            f.run(&[b"MOVE", b"a", b"x"]),
4938            "-ERR value is not an integer or out of range\r\n"
4939        );
4940    }
4941
4942    #[test]
4943    fn swapdb_swaps_what_two_connections_would_see() {
4944        let mut f = Fixture::new();
4945        assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
4946        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4947        assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
4948        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
4949
4950        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
4951        // Still on database zero, and database zero is a different database.
4952        assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
4953        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4954        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
4955        // A database swapped with itself is fine and changes nothing.
4956        assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
4957        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
4958    }
4959
4960    /// Every database on a server reads the server's clock and not one of its
4961    /// own. They used to be told the time one at a time and now they share the
4962    /// reading, so a server that built its databases from a second clock would
4963    /// answer a deadline worked out against a time nobody had set.
4964    #[test]
4965    fn a_wide_server_puts_its_databases_on_its_own_clock() {
4966        let mut f = Fixture::striped(8);
4967        f.server.set_clock_ms(1_700_000_000_000);
4968        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
4969        assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
4970        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4971        f.server.set_clock_ms(1_700_000_050_000);
4972        assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
4973    }
4974
4975    /// The swap is stripe by stripe, so a database cut into more than one
4976    /// stripe is the case that would catch it exchanging some of the keys and
4977    /// leaving the rest. Sixteen keys over four stripes is enough that every
4978    /// stripe has something in it whatever the hashes come out as.
4979    #[test]
4980    fn swapdb_swaps_every_stripe_of_a_wide_database() {
4981        let mut f = Fixture::striped(4);
4982        for i in 0..16u32 {
4983            let key = format!("k{i}");
4984            assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
4985        }
4986        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4987        assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
4988        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
4989
4990        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
4991        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4992        assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
4993        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
4994        assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
4995        for i in 0..16u32 {
4996            let key = format!("k{i}");
4997            assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
4998        }
4999    }
5000
5001    #[test]
5002    fn swapdb_says_which_index_it_could_not_read() {
5003        let mut f = Fixture::new();
5004        assert_eq!(
5005            f.run(&[b"SWAPDB", b"x", b"1"]),
5006            "-ERR invalid first DB index\r\n"
5007        );
5008        assert_eq!(
5009            f.run(&[b"SWAPDB", b"0", b"y"]),
5010            "-ERR invalid second DB index\r\n"
5011        );
5012        // A number too big to be an index on a server that keeps one in an int
5013        // is the same complaint, and a plausible one that is not ours is the
5014        // range complaint instead. The split is Redis's.
5015        assert_eq!(
5016            f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
5017            "-ERR invalid first DB index\r\n"
5018        );
5019        assert_eq!(
5020            f.run(&[b"SWAPDB", b"0", b"99"]),
5021            "-ERR DB index is out of range\r\n"
5022        );
5023        assert_eq!(
5024            f.run(&[b"SWAPDB", b"-1", b"0"]),
5025            "-ERR DB index is out of range\r\n"
5026        );
5027    }
5028
5029    #[test]
5030    fn wait_answers_zero_replicas_without_waiting() {
5031        let mut f = Fixture::new();
5032        assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
5033        assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
5034        // A replica that is never going to arrive, and a timeout that would be
5035        // a real wait on a server that had one.
5036        assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
5037        // Negative replicas is not an error, because zero is already more than
5038        // it asked for.
5039        assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
5040        assert_eq!(
5041            f.run(&[b"WAIT", b"x", b"0"]),
5042            "-ERR value is not an integer or out of range\r\n"
5043        );
5044        assert_eq!(
5045            f.run(&[b"WAIT", b"0", b"-1"]),
5046            "-ERR timeout is negative\r\n"
5047        );
5048        assert_eq!(
5049            f.run(&[b"WAIT", b"0", b"1.5"]),
5050            "-ERR timeout is not an integer or out of range\r\n"
5051        );
5052    }
5053
5054    #[test]
5055    fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
5056        let mut f = Fixture::new();
5057        assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
5058        assert_eq!(
5059            f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
5060            "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
5061        );
5062        assert_eq!(
5063            f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
5064            "-ERR value is out of range, value must between 0 and 1\r\n"
5065        );
5066        assert_eq!(
5067            f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
5068            "-ERR value is out of range, must be positive\r\n"
5069        );
5070        // The arguments are all read before the server looks at itself, so a
5071        // bad timeout beats the append only complaint even with numlocal set.
5072        assert_eq!(
5073            f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
5074            "-ERR timeout is negative\r\n"
5075        );
5076    }
5077
5078    /// The bytes inside a bulk reply, with the header and the trailing break
5079    /// taken off. Every `DUMP` test needs this and none of them care how the
5080    /// length was written.
5081    fn payload(reply: &[u8]) -> Vec<u8> {
5082        let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
5083        reply[head + 2..reply.len() - 2].to_vec()
5084    }
5085
5086    #[test]
5087    fn a_value_survives_a_dump_and_a_restore() {
5088        let mut f = Fixture::new();
5089        f.run(&[b"SET", b"s", b"hello"]);
5090        f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
5091        f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
5092        f.run(&[b"SADD", b"u", b"x", b"y"]);
5093        f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
5094        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
5095
5096        for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
5097            let mut copy = key.to_vec();
5098            copy.push(b'2');
5099            let bytes = payload(&f.raw(&[b"DUMP", key]));
5100            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
5101            assert_eq!(f.run(&[b"TYPE", &copy]), f.run(&[b"TYPE", key]));
5102        }
5103
5104        assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
5105        assert_eq!(
5106            f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
5107            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
5108        );
5109        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
5110        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
5111        assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
5112        assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
5113        // The encoding survives too, since the payload names the plainest legal
5114        // type and the loader puts the value back on the rung it belongs on.
5115        assert_eq!(
5116            f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
5117            f.run(&[b"OBJECT", b"ENCODING", b"t"])
5118        );
5119    }
5120
5121    #[test]
5122    fn a_dumped_hash_keeps_its_field_deadlines() {
5123        let mut f = Fixture::new();
5124        f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
5125        assert_eq!(
5126            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
5127            "*1\r\n:1\r\n"
5128        );
5129        let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
5130        assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
5131        assert_eq!(
5132            f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
5133            "*2\r\n:-1\r\n:100\r\n"
5134        );
5135    }
5136
5137    #[test]
5138    fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
5139        let mut f = Fixture::new();
5140        f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
5141        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
5142        assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
5143        assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
5144        assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
5145        assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
5146        // An absolute deadline that has already gone is not an error. The key is
5147        // not created and the reply is the same OK a live one gets.
5148        assert_eq!(
5149            f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
5150            "+OK\r\n"
5151        );
5152        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
5153    }
5154
5155    #[test]
5156    fn dump_answers_nothing_for_a_key_that_is_not_there() {
5157        let mut f = Fixture::new();
5158        assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
5159        f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
5160        f.advance(50);
5161        assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
5162    }
5163
5164    #[test]
5165    fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
5166        let mut f = Fixture::new();
5167        f.run(&[b"SET", b"a", b"first"]);
5168        f.run(&[b"SET", b"b", b"second"]);
5169        let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
5170        assert_eq!(
5171            f.run(&[b"RESTORE", b"a", b"0", &bytes]),
5172            "-BUSYKEY Target key name already exists.\r\n"
5173        );
5174        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
5175        assert_eq!(
5176            f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
5177            "+OK\r\n"
5178        );
5179        assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
5180    }
5181
5182    /// The busy key comes before the payload, which is not the order the
5183    /// arguments read in. Whether a key is taken should not depend on whether
5184    /// the bytes behind it happened to be good.
5185    #[test]
5186    fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
5187        let mut f = Fixture::new();
5188        f.run(&[b"SET", b"a", b"v"]);
5189        assert_eq!(
5190            f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
5191            "-BUSYKEY Target key name already exists.\r\n"
5192        );
5193        // And the options come before even that, so a bad FREQ beats the busy
5194        // key the same way a bad DB beats a missing source in COPY.
5195        assert_eq!(
5196            f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
5197            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
5198        );
5199    }
5200
5201    #[test]
5202    fn restore_can_tell_a_bad_footer_from_bad_bytes() {
5203        let mut f = Fixture::new();
5204        f.run(&[b"SET", b"a", b"hello"]);
5205        let good = payload(&f.raw(&[b"DUMP", b"a"]));
5206
5207        let mut flipped = good.clone();
5208        flipped[2] ^= 0x40;
5209        assert_eq!(
5210            f.run(&[b"RESTORE", b"b", b"0", &flipped]),
5211            "-ERR DUMP payload version or checksum are wrong\r\n"
5212        );
5213        assert_eq!(
5214            f.run(&[b"RESTORE", b"b", b"0", b"short"]),
5215            "-ERR DUMP payload version or checksum are wrong\r\n"
5216        );
5217        // A footer that is right over a body that is not. The type byte says
5218        // string and there is nothing behind it, so the checksum agrees and the
5219        // value does not exist.
5220        let mut truncated = good[..1].to_vec();
5221        truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
5222        let crc = yo_common::crc::crc64(0, &truncated);
5223        truncated.extend_from_slice(&crc.to_le_bytes());
5224        assert_eq!(
5225            f.run(&[b"RESTORE", b"b", b"0", &truncated]),
5226            "-ERR Bad data format\r\n"
5227        );
5228        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
5229    }
5230
5231    #[test]
5232    fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
5233        let mut f = Fixture::new();
5234        f.run(&[b"SET", b"a", b"v"]);
5235        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
5236        assert_eq!(
5237            f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
5238            "-ERR Invalid TTL value, must be >= 0\r\n"
5239        );
5240        assert_eq!(
5241            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
5242            "-ERR Invalid IDLETIME value, must be >= 0\r\n"
5243        );
5244        assert_eq!(
5245            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
5246            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
5247        );
5248        // Both are accepted and both are then dropped, which is D-26.
5249        assert_eq!(
5250            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
5251            "+OK\r\n"
5252        );
5253        assert_eq!(
5254            f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
5255            "+OK\r\n"
5256        );
5257    }
5258
5259    /// Neither word is refused for being the wrong one. Each is only accepted
5260    /// while the other is unset, so the second of the two falls through to the
5261    /// plain syntax error rather than getting a message of its own.
5262    #[test]
5263    fn restore_takes_idletime_or_freq_and_not_both() {
5264        let mut f = Fixture::new();
5265        f.run(&[b"SET", b"a", b"v"]);
5266        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
5267        assert_eq!(
5268            f.run(&[
5269                b"RESTORE",
5270                b"b",
5271                b"0",
5272                &bytes,
5273                b"IDLETIME",
5274                b"1",
5275                b"FREQ",
5276                b"2"
5277            ]),
5278            "-ERR syntax error\r\n"
5279        );
5280        assert_eq!(
5281            f.run(&[
5282                b"RESTORE",
5283                b"b",
5284                b"0",
5285                &bytes,
5286                b"FREQ",
5287                b"2",
5288                b"IDLETIME",
5289                b"1"
5290            ]),
5291            "-ERR syntax error\r\n"
5292        );
5293        assert_eq!(
5294            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
5295            "-ERR syntax error\r\n"
5296        );
5297        assert_eq!(
5298            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
5299            "-ERR syntax error\r\n"
5300        );
5301    }
5302
5303    #[test]
5304    fn copy_checks_its_options_before_it_looks_for_anything() {
5305        let mut f = Fixture::new();
5306        // No key exists at all, and every one of these is still the option
5307        // complaint rather than a zero, which is the order a real server uses.
5308        assert_eq!(
5309            f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
5310            "-ERR DB index is out of range\r\n"
5311        );
5312        assert_eq!(
5313            f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
5314            "-ERR DB index is out of range\r\n"
5315        );
5316        assert_eq!(
5317            f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
5318            "-ERR value is not an integer or out of range\r\n"
5319        );
5320        assert_eq!(
5321            f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
5322            "-ERR syntax error\r\n"
5323        );
5324        assert_eq!(
5325            f.run(&[b"COPY", b"a", b"a"]),
5326            "-ERR source and destination objects are the same\r\n"
5327        );
5328        // Repeated, reordered and lowercased, and the last DB wins.
5329        assert_eq!(
5330            f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
5331            ":0\r\n"
5332        );
5333    }
5334
5335    #[test]
5336    fn time_is_two_bulk_strings_and_moves() {
5337        let mut f = Fixture::new();
5338        let first = f.run(&[b"TIME"]);
5339        assert!(first.starts_with("*2\r\n$"), "got {first}");
5340        let parts: Vec<&str> = first.split("\r\n").collect();
5341        let secs: i64 = parts[2].parse().expect("seconds as decimal text");
5342        let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
5343        assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
5344        assert!((0..1_000_000).contains(&micros), "got {micros}");
5345        // The coarse clock the keyspace uses is a cached millisecond that a
5346        // background tick refreshes, so a TIME built on it would answer the
5347        // same microsecond twice in a row here.
5348        assert_ne!(first, f.run(&[b"TIME"]));
5349    }
5350
5351    #[test]
5352    fn a_keyspace_scan_walks_every_key_once() {
5353        // The count below is thirty two, so ninety six keys is three pages of
5354        // cursor and says the same thing as five hundred at a fifth of the
5355        // interpreted work.
5356        let n = if cfg!(miri) { 96 } else { 500 };
5357        let mut f = Fixture::new();
5358        for i in 0..n {
5359            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
5360        }
5361
5362        let mut seen: Vec<String> = Vec::new();
5363        let mut cursor = "0".to_owned();
5364        let mut calls = 0;
5365        loop {
5366            let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
5367            seen.extend(keys);
5368            cursor = next;
5369            calls += 1;
5370            assert!(calls < 10_000, "the cursor is not advancing");
5371            if cursor == "0" {
5372                break;
5373            }
5374        }
5375
5376        seen.sort();
5377        seen.dedup();
5378        assert_eq!(seen.len(), n, "every key once and only once");
5379        // And more than one call to get them, or the COUNT is being ignored and
5380        // the loop above proved nothing about resuming.
5381        assert!(calls > 1, "{n} keys came back in one batch");
5382    }
5383
5384    #[test]
5385    fn a_scan_narrows_by_pattern_and_by_type() {
5386        let mut f = Fixture::new();
5387        f.run(&[b"SET", b"str", b"v"]);
5388        f.run(&[b"SADD", b"members", b"a"]);
5389        f.run(&[b"HSET", b"fields", b"f", b"v"]);
5390
5391        let all = |f: &mut Fixture, args: &[&[u8]]| {
5392            let mut out: Vec<String> = Vec::new();
5393            let mut cursor = "0".to_owned();
5394            loop {
5395                let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
5396                line.extend_from_slice(args);
5397                let (next, keys) = scan_reply(&f.run(&line));
5398                out.extend(keys);
5399                cursor = next;
5400                if cursor == "0" {
5401                    break;
5402                }
5403            }
5404            out.sort();
5405            out
5406        };
5407
5408        assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
5409        assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
5410        assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
5411        // Case insensitive, the same as Redis's own comparison.
5412        assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
5413        // A type nothing can hold is not an error, it just matches nothing.
5414        assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
5415        assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
5416        // Both filters at once, and they are an and rather than an or.
5417        assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
5418    }
5419
5420    #[test]
5421    fn a_scan_says_what_is_wrong_with_it() {
5422        let mut f = Fixture::new();
5423        assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
5424        assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
5425        assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
5426        assert_eq!(
5427            f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
5428            "-ERR syntax error\r\n"
5429        );
5430        assert_eq!(
5431            f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
5432            "-ERR value is not an integer or out of range\r\n"
5433        );
5434        assert_eq!(
5435            f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
5436            "-ERR syntax error\r\n"
5437        );
5438        // A cursor the client made up is a cursor. It resumes somewhere
5439        // arbitrary and answers whatever is there, which is what Redis does and
5440        // is the only behaviour that does not need the server to remember every
5441        // cursor it has handed out.
5442        assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
5443    }
5444
5445    #[test]
5446    fn keys_and_randomkey_look_at_the_whole_database() {
5447        let mut f = Fixture::new();
5448        assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
5449        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
5450
5451        for name in ["one", "two", "three"] {
5452            f.run(&[b"SET", name.as_bytes(), b"v"]);
5453        }
5454        assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
5455        assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
5456        assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
5457
5458        for _ in 0..50 {
5459            let got = f.run(&[b"RANDOMKEY"]);
5460            assert!(
5461                ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
5462                "got {got}"
5463            );
5464        }
5465    }
5466
5467    #[test]
5468    fn a_walk_does_not_answer_keys_that_have_expired() {
5469        let mut f = Fixture::new();
5470        f.run(&[b"SET", b"alive", b"v"]);
5471        f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
5472        f.server.advance_clock_ms(2);
5473        assert_eq!(
5474            f.run(&[b"DBSIZE"]),
5475            ":2\r\n",
5476            "nothing has collected it yet"
5477        );
5478
5479        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
5480        let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
5481        assert_eq!(keys, ["alive"]);
5482        for _ in 0..20 {
5483            assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
5484        }
5485        // The walk collected it on the way past, which is what makes DBSIZE
5486        // here answer what Redis answers once its own cycle has been round.
5487        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
5488    }
5489
5490    #[test]
5491    fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
5492        let mut f = Fixture::new();
5493        f.run(&[b"SET", b"k", b"v"]);
5494        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
5495        assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
5496
5497        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
5498        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
5499        let ms = int(&f.run(&[b"PTTL", b"k"]));
5500        assert!((99_000..=100_000).contains(&ms), "got {ms}");
5501
5502        // The absolute pair, derived from the same one number the store kept.
5503        let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
5504        let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
5505        assert_eq!(at, (at_ms + 500) / 1000);
5506        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
5507
5508        assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
5509        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
5510        assert_eq!(
5511            f.run(&[b"PERSIST", b"k"]),
5512            ":0\r\n",
5513            "nothing to take off the second time"
5514        );
5515        assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
5516        assert_eq!(
5517            f.run(&[b"GET", b"k"]),
5518            "$1\r\nv\r\n",
5519            "and the value went through all of that untouched"
5520        );
5521    }
5522
5523    #[test]
5524    fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
5525        let mut f = Fixture::new();
5526        f.run(&[b"SET", b"str", b"v"]);
5527        f.run(&[b"SADD", b"set", b"a", b"b"]);
5528        f.run(&[b"HSET", b"hash", b"f", b"v"]);
5529
5530        for key in [b"str".as_slice(), b"set", b"hash"] {
5531            assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
5532            assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
5533        }
5534        // The body is not touched by any of that, which is the whole reason the
5535        // deadline lives in the record and the body lives somewhere else.
5536        assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
5537        assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
5538        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
5539    }
5540
5541    #[test]
5542    fn a_deadline_that_has_already_gone_deletes_the_key_now() {
5543        let mut f = Fixture::new();
5544        for key in [b"a".as_slice(), b"b", b"c", b"d"] {
5545            f.run(&[b"SET", key, b"v"]);
5546        }
5547        // Four ways of naming a moment that has passed, and all four are a
5548        // delete answering 1 rather than an error. Zero is a moment, minus one
5549        // is a moment, and the hash field commands refuse the negative one.
5550        assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
5551        assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
5552        assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
5553        assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
5554        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5555        assert_eq!(
5556            f.run(&[b"EXPIRE", b"a", b"100"]),
5557            ":0\r\n",
5558            "and the key really went, so there is nothing to put a deadline on"
5559        );
5560    }
5561
5562    #[test]
5563    fn the_four_conditions_decide_whether_the_deadline_moves() {
5564        let mut f = Fixture::new();
5565        f.run(&[b"SET", b"k", b"v"]);
5566
5567        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
5568        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
5569        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
5570        assert_eq!(
5571            f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
5572            ":1\r\n",
5573            "no deadline reads as infinitely far away, so LT passes where GT fails"
5574        );
5575
5576        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
5577        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
5578        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
5579        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
5580        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
5581        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
5582
5583        // The condition is answered before the past check, so this is a 0 and
5584        // the key survives. The other order would delete it.
5585        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
5586        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
5587        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
5588        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
5589    }
5590
5591    #[test]
5592    fn the_conditions_are_a_set_and_not_a_keyword() {
5593        let mut f = Fixture::new();
5594        f.run(&[b"SET", b"k", b"v"]);
5595
5596        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
5597        assert_eq!(
5598            f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
5599            ":0\r\n",
5600            "the same keyword twice means it once, and NX now has a deadline to fail on"
5601        );
5602
5603        // XX with LT is the one pair that is not either of them on its own: LT
5604        // alone would accept a key with no deadline and this does not.
5605        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
5606        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
5607        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
5608        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
5609        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
5610        f.run(&[b"PERSIST", b"k"]);
5611        assert_eq!(
5612            f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
5613            ":0\r\n",
5614            "where LT on its own would have taken it"
5615        );
5616        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
5617    }
5618
5619    #[test]
5620    fn a_key_is_gone_once_its_moment_passes() {
5621        let mut f = Fixture::new();
5622        f.run(&[b"SET", b"k", b"v"]);
5623        f.run(&[b"EXPIRE", b"k", b"100"]);
5624
5625        let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
5626        f.server.set_clock_ms(at as u64 + 1);
5627        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
5628        assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
5629        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
5630        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5631    }
5632
5633    #[test]
5634    fn the_expiry_commands_refuse_what_a_real_server_refuses() {
5635        let mut f = Fixture::new();
5636        f.run(&[b"SET", b"k", b"v"]);
5637        for (bad, want) in [
5638            (
5639                &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
5640                "-ERR value is not an integer or out of range\r\n",
5641            ),
5642            (
5643                &[b"EXPIRE", b"k", b"100", b"MAYBE"],
5644                "-ERR Unsupported option MAYBE\r\n",
5645            ),
5646            (
5647                &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
5648                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
5649            ),
5650            (
5651                &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
5652                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
5653            ),
5654            (
5655                &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
5656                "-ERR GT and LT options at the same time are not compatible\r\n",
5657            ),
5658            // Seconds that overflow when multiplied into milliseconds. Every
5659            // message names the command it came from.
5660            (
5661                &[b"EXPIRE", b"k", b"9223372036854775807"],
5662                "-ERR invalid expire time in 'expire' command\r\n",
5663            ),
5664            (
5665                &[b"EXPIREAT", b"k", b"9223372036854775807"],
5666                "-ERR invalid expire time in 'expireat' command\r\n",
5667            ),
5668            (
5669                &[b"PEXPIRE", b"k", b"9223372036854775807"],
5670                "-ERR invalid expire time in 'pexpire' command\r\n",
5671            ),
5672        ] {
5673            assert_eq!(f.run(bad), want, "for {bad:?}");
5674        }
5675        assert_eq!(
5676            f.run(&[b"TTL", b"k"]),
5677            ":-1\r\n",
5678            "and none of those put a deadline on anything"
5679        );
5680
5681        // The one of the four that has no arithmetic to overflow. Redis takes
5682        // it and holds the number as given, and a record here holds forty six
5683        // bits, so it lands in the year 4199 instead. D-17.
5684        assert_eq!(
5685            f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
5686            ":1\r\n"
5687        );
5688        assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
5689    }
5690
5691    #[test]
5692    fn flushing_empties_this_database_or_every_one_of_them() {
5693        let mut f = Fixture::new();
5694        f.run(&[b"SELECT", b"0"]);
5695        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
5696        f.run(&[b"SELECT", b"1"]);
5697        f.run(&[b"SET", b"c", b"3"]);
5698        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
5699        // ASYNC and SYNC are both taken and neither changes anything, since the
5700        // keyspace is empty before the OK goes out either way.
5701        assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
5702        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5703        // Only database one was emptied.
5704        f.run(&[b"SELECT", b"0"]);
5705        assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
5706        assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
5707        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5708        f.run(&[b"SELECT", b"1"]);
5709        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5710        // Anything else after the name is a syntax error, and so is a third
5711        // argument even when the second one is a word we take.
5712        assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
5713        assert_eq!(
5714            f.run(&[b"FLUSHDB", b"sync", b"sync"]),
5715            "-ERR syntax error\r\n"
5716        );
5717    }
5718
5719    #[test]
5720    fn the_script_cache_and_the_library_set_answer_for_being_empty() {
5721        let mut f = Fixture::new();
5722        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
5723        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
5724        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
5725        // Nothing is cached, so nothing is there, one answer per hash asked
5726        // about.
5727        assert_eq!(
5728            f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
5729            "*2\r\n:0\r\n:0\r\n"
5730        );
5731        assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
5732        assert_eq!(
5733            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
5734            "*0\r\n"
5735        );
5736        assert_eq!(
5737            f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
5738            "-ERR Library not found\r\n"
5739        );
5740
5741        // Redis's two messages here are its own, one per container, and one of
5742        // them reads like a typo.
5743        assert_eq!(
5744            f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
5745            "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
5746        );
5747        assert_eq!(
5748            f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
5749            "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
5750        );
5751        // A second argument after the mode is the generic one instead, because
5752        // the count is checked before the word is looked at. The subcommand in
5753        // the sentence is the client's own spelling and not the canonical one,
5754        // which is the same thing `unknown subcommand` does.
5755        assert_eq!(
5756            f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
5757            "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
5758        );
5759        assert_eq!(
5760            f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
5761            "-ERR Unknown argument bogus\r\n"
5762        );
5763        assert_eq!(
5764            f.run(&[b"SCRIPT", b"EXISTS"]),
5765            "-ERR wrong number of arguments for 'script|exists' command\r\n"
5766        );
5767
5768        assert_eq!(
5769            f.run(&[b"FUNCTION", b"NOPE"]),
5770            "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
5771        );
5772    }
5773
5774    #[test]
5775    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5776    fn the_script_cache_holds_what_was_loaded_into_it() {
5777        let mut f = Fixture::new();
5778        // The hash is the sha1 of the body and nothing else, so it is the same
5779        // number a real server answers and a client can compute it itself.
5780        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
5781        assert_eq!(
5782            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
5783            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
5784        );
5785        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
5786        assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
5787        // Loading is idempotent and a body that will not parse is refused
5788        // where it was written rather than where it is called.
5789        assert_eq!(
5790            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
5791            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
5792        );
5793        assert!(
5794            f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
5795                .starts_with("-ERR Error compiling script"),
5796        );
5797
5798        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
5799        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
5800        assert_eq!(
5801            f.run(&[b"EVALSHA", sha, b"0"]),
5802            "-NOSCRIPT No matching script. Please use EVAL.\r\n"
5803        );
5804
5805        // Running the body puts it in the cache too, which is what makes the
5806        // load then call then fall back to load pattern a client uses work.
5807        assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
5808        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
5809
5810        // Nothing here can run long enough to be killed, which is D-101, so
5811        // the answer is the one a real server gives when nothing is stuck.
5812        assert_eq!(
5813            f.run(&[b"SCRIPT", b"KILL"]),
5814            "-NOTBUSY No scripts in execution right now.\r\n"
5815        );
5816        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
5817        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
5818        assert_eq!(
5819            f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
5820            "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
5821        );
5822    }
5823
5824    #[test]
5825    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5826    fn eval_counts_its_keys_before_it_compiles_anything() {
5827        let mut f = Fixture::new();
5828        assert_eq!(
5829            f.run(&[b"EVAL", b"return 1"]),
5830            "-ERR wrong number of arguments for 'eval' command\r\n"
5831        );
5832        assert_eq!(
5833            f.run(&[b"EVAL", b"return 1", b"abc"]),
5834            "-ERR value is not an integer or out of range\r\n"
5835        );
5836        assert_eq!(
5837            f.run(&[b"EVAL", b"return 1", b"-1"]),
5838            "-ERR Number of keys can't be negative\r\n"
5839        );
5840        assert_eq!(
5841            f.run(&[b"EVAL", b"return 1", b"1"]),
5842            "-ERR Number of keys can't be greater than number of args\r\n"
5843        );
5844        // The count splits the tail, and everything past the keys is ARGV.
5845        assert_eq!(
5846            f.run(&[
5847                b"EVAL",
5848                b"return {KEYS[1],KEYS[2],ARGV[1]}",
5849                b"2",
5850                b"a",
5851                b"b",
5852                b"c"
5853            ]),
5854            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
5855        );
5856        assert_eq!(
5857            f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
5858            ":0\r\n"
5859        );
5860        assert_eq!(
5861            f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
5862            ":2\r\n"
5863        );
5864    }
5865
5866    #[test]
5867    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5868    fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
5869        let mut f = Fixture::new();
5870        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
5871
5872        // A number is truncated toward zero rather than rounded, and the two
5873        // ends of the range saturate the way the cast does.
5874        assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
5875        assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
5876        assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
5877        assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
5878        assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
5879        assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
5880        assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
5881
5882        assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
5883        assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
5884        // Everything that is not there is the same nothing.
5885        assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
5886        assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
5887        assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
5888        assert_eq!(eval(&mut f, b""), "$-1\r\n");
5889
5890        // A table is an array that stops at the first hole, which is what makes
5891        // a script build a reply by appending rather than by indexing.
5892        assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
5893        assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
5894        assert_eq!(
5895            eval(&mut f, b"return {1,'a',{2}}"),
5896            "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
5897        );
5898
5899        // The named fields, in the order a real server looks for them.
5900        assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
5901        assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
5902        assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
5903        assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
5904        // A line break inside one of them becomes a space, because the reply is
5905        // a single line and a client that saw the break would lose the frame.
5906        assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a  b\r\n");
5907        // A field of the wrong type is not that kind of reply at all, and falls
5908        // through to the array walk, which finds nothing.
5909        assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
5910        assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
5911    }
5912
5913    #[test]
5914    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5915    fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
5916        let mut f = Fixture::new();
5917        // Under RESP2 the four typed tables have to come back as something a
5918        // client that only knows RESP2 can read.
5919        assert_eq!(
5920            f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
5921            "$3\r\n3.5\r\n"
5922        );
5923        assert_eq!(
5924            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
5925            "$3\r\n123\r\n"
5926        );
5927        assert_eq!(
5928            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
5929            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5930        );
5931        assert_eq!(
5932            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
5933            "*1\r\n$1\r\na\r\n"
5934        );
5935        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
5936
5937        f.out = Out::new(Proto::Resp3);
5938        assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
5939        assert_eq!(
5940            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
5941            "(123\r\n"
5942        );
5943        assert_eq!(
5944            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
5945            "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
5946        );
5947        assert_eq!(
5948            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
5949            "~1\r\n$1\r\na\r\n"
5950        );
5951        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
5952    }
5953
5954    #[test]
5955    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
5956    fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
5957        let mut f = Fixture::new();
5958        f.run(&[b"SET", b"s", b"hello"]);
5959        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
5960        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
5961
5962        assert_eq!(
5963            eval(&mut f, b"return type(redis.call('get','s'))"),
5964            "$6\r\nstring\r\n"
5965        );
5966        assert_eq!(
5967            eval(&mut f, b"return type(redis.call('llen','l'))"),
5968            "$6\r\nnumber\r\n"
5969        );
5970        assert_eq!(
5971            eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
5972            "$5\r\ntable\r\n"
5973        );
5974        // A status is a table with one field, which is what lets a script pass
5975        // one straight back out again.
5976        assert_eq!(
5977            eval(&mut f, b"return redis.call('set','s','v')['ok']"),
5978            "$2\r\nOK\r\n"
5979        );
5980        // A missing key is false under RESP2 and nil once the script asks for
5981        // RESP3, which is the one conversion the script gets to choose.
5982        assert_eq!(
5983            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
5984            "$5\r\nfalse\r\n"
5985        );
5986        assert_eq!(
5987            eval(
5988                &mut f,
5989                b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
5990            ),
5991            "$3\r\nnil\r\n"
5992        );
5993        // The choice does not outlive the script that made it.
5994        assert_eq!(
5995            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
5996            "$5\r\nfalse\r\n"
5997        );
5998    }
5999
6000    #[test]
6001    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6002    fn an_error_from_a_script_names_the_line_it_came_from() {
6003        let mut f = Fixture::new();
6004        // The position is the script's own, not the prelude's, and the suffix
6005        // names the script so a client can find it in the cache.
6006        assert_eq!(
6007            f.run(&[b"EVAL", b"error('boom')", b"0"]),
6008            "-ERR user_script:1: boom script: \
6009             82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
6010        );
6011        // Level zero says the message already knows where it came from.
6012        assert_eq!(
6013            f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
6014            "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
6015        );
6016        // A table with an err field keeps its own text and gets the suffix.
6017        assert!(
6018            f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
6019                .starts_with("-structured script: "),
6020        );
6021        // A script that will not parse is refused before it runs, so there is
6022        // no script and nothing to name.
6023        assert_eq!(
6024            f.run(&[b"EVAL", b"return this is not lua", b"0"]),
6025            "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
6026        );
6027
6028        // A table that came out of pcall is a string by the time the script
6029        // sees it, which is a real server's own wrapping and not Lua's.
6030        assert_eq!(
6031            f.run(&[
6032                b"EVAL",
6033                b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
6034                b"0"
6035            ]),
6036            "$8\r\nstring:z\r\n"
6037        );
6038        assert_eq!(
6039            f.run(&[
6040                b"EVAL",
6041                b"local a, b = pcall(function() error({a=1}) end) return type(b)",
6042                b"0"
6043            ]),
6044            "$5\r\ntable\r\n"
6045        );
6046    }
6047
6048    #[test]
6049    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6050    fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
6051        let mut f = Fixture::new();
6052        let sentence = |f: &mut Fixture, body: &[u8]| {
6053            let reply = f.run(&[b"EVAL", body, b"0"]);
6054            reply.split(" script: ").next().unwrap().to_owned()
6055        };
6056
6057        assert_eq!(
6058            sentence(&mut f, b"return redis.call()"),
6059            "-ERR Please specify at least one argument for this redis lib call"
6060        );
6061        assert_eq!(
6062            sentence(&mut f, b"return redis.call('get', {})"),
6063            "-ERR Lua redis lib command arguments must be strings or integers"
6064        );
6065        assert_eq!(
6066            sentence(&mut f, b"return redis.call('nosuchcmd')"),
6067            "-ERR Unknown Redis command called from script"
6068        );
6069        assert_eq!(
6070            sentence(&mut f, b"return redis.call('get')"),
6071            "-ERR Wrong number of args calling Redis command from script"
6072        );
6073        // The commands that make no sense inside a script are refused by name
6074        // rather than by not being implemented, so the sentence is the same one
6075        // a real server writes for each of them.
6076        for name in [
6077            &b"return redis.call('multi')"[..],
6078            b"return redis.call('exec')",
6079            b"return redis.call('watch','k')",
6080            b"return redis.call('subscribe','c')",
6081            b"return redis.call('debug','jmap')",
6082            b"return redis.call('eval','return 1',0)",
6083            b"return redis.call('config','get','maxmemory')",
6084        ] {
6085            assert_eq!(
6086                sentence(&mut f, name),
6087                "-ERR This Redis command is not allowed from script",
6088                "for {}",
6089                String::from_utf8_lossy(name)
6090            );
6091        }
6092        // HELP is the one subcommand of a refused container that is allowed,
6093        // because it reads nothing and changes nothing.
6094        assert!(
6095            f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
6096                .starts_with('*'),
6097        );
6098
6099        // pcall answers the same sentence as a value instead of raising it, and
6100        // the value has an err field a script can read.
6101        assert_eq!(
6102            f.run(&[
6103                b"EVAL",
6104                b"local x = redis.pcall('nosuchcmd') return x.err",
6105                b"0"
6106            ]),
6107            "$44\r\nERR Unknown Redis command called from script\r\n"
6108        );
6109        // Returning it unread raises it, because the table has an err field.
6110        assert_eq!(
6111            f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
6112            "-ERR Unknown Redis command called from script\r\n"
6113        );
6114    }
6115
6116    #[test]
6117    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6118    fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
6119        let mut f = Fixture::new();
6120        f.run(&[b"SET", b"k", b"v"]);
6121        assert_eq!(
6122            f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
6123            "$1\r\nv\r\n"
6124        );
6125        assert!(
6126            f.run(&[
6127                b"EVAL_RO",
6128                b"return redis.call('set', KEYS[1], 'x')",
6129                b"1",
6130                b"k"
6131            ])
6132            .starts_with("-ERR Write commands are not allowed from read-only scripts."),
6133        );
6134        // The write did not happen, and the same body under EVAL does.
6135        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
6136        assert_eq!(
6137            f.run(&[
6138                b"EVAL",
6139                b"return redis.call('set', KEYS[1], 'x')",
6140                b"1",
6141                b"k"
6142            ]),
6143            "+OK\r\n"
6144        );
6145        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
6146
6147        // EVALSHA_RO runs a cached body under the same rule.
6148        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
6149        f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
6150        assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
6151    }
6152
6153    #[test]
6154    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6155    fn a_script_cannot_leave_anything_behind_for_the_next_one() {
6156        let mut f = Fixture::new();
6157        // A plain global write and a write through a name on the redis table
6158        // both raise, with the position the script wrote them at.
6159        for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
6160            let reply = f.run(&[b"EVAL", body, b"0"]);
6161            assert!(
6162                reply
6163                    .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
6164                "{body:?} gave {reply}",
6165            );
6166        }
6167        // Walking round the guard with rawset or setmetatable raises too, and
6168        // without the position, which is where a real server raises it from.
6169        for body in [
6170            &b"rawset(redis, 'call', 1)"[..],
6171            b"rawset(_G, 'zz', 1)",
6172            b"setmetatable(_G, {})",
6173            b"setmetatable(redis, {})",
6174        ] {
6175            let reply = f.run(&[b"EVAL", body, b"0"]);
6176            assert!(
6177                reply.starts_with("-ERR Attempt to modify a readonly table script: "),
6178                "{body:?} gave {reply}",
6179            );
6180        }
6181        // Reading a name that is not there is a mistake rather than a nil, so a
6182        // misspelled global stops the script instead of doing nothing quietly.
6183        assert!(
6184            f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
6185                .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
6186        );
6187        // Reading a name that is not on the redis table is a nil, which is how
6188        // a script tests for a helper that an older server does not have.
6189        assert_eq!(
6190            f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
6191            "$3\r\nnil\r\n"
6192        );
6193
6194        // The one write that lands, D-103, is taken back out before the next
6195        // script starts, so nothing a script does reaches the one after it.
6196        assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
6197        assert_eq!(
6198            f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
6199            "$8\r\nfunction\r\n"
6200        );
6201        assert_eq!(
6202            f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
6203            "$8\r\nfunction\r\n"
6204        );
6205    }
6206
6207    #[test]
6208    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6209    fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
6210        let mut f = Fixture::new();
6211        // The guard in front of the table is empty, so the three base library
6212        // readers that skip a metatable are pointed at the real table behind
6213        // it. A script counts what a real server counts.
6214        assert_eq!(
6215            f.run(&[
6216                b"EVAL",
6217                b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
6218                b"0",
6219            ]),
6220            ":23\r\n"
6221        );
6222        assert_eq!(
6223            f.run(&[
6224                b"EVAL",
6225                b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
6226                  table.sort(t) return table.concat(t, ' ')",
6227                b"0",
6228            ]),
6229            "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
6230             REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
6231             acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
6232             set_repl setresp sha1hex status_reply\r\n"
6233        );
6234        // The loop hands over the values as well as the names, so the twelve
6235        // helpers are callable from inside a traversal and not just findable.
6236        assert_eq!(
6237            f.run(&[
6238                b"EVAL",
6239                b"local n = 0 for k, v in pairs(redis) do \
6240                  if type(v) == 'function' then n = n + 1 end end return n",
6241                b"0",
6242            ]),
6243            ":12\r\n"
6244        );
6245        // The other two readers agree with it.
6246        assert_eq!(
6247            f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
6248            "$6\r\nstring\r\n"
6249        );
6250        assert_eq!(
6251            f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
6252            "$8\r\nfunction\r\n"
6253        );
6254        assert_eq!(
6255            f.run(&[
6256                b"EVAL",
6257                b"return tostring(rawget(redis, 'nosuchfield'))",
6258                b"0",
6259            ]),
6260            "$3\r\nnil\r\n"
6261        );
6262        // Reading round the guard is the only thing that was given back. A
6263        // write still lands on the guard and still raises.
6264        for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
6265            assert!(
6266                f.run(&[b"EVAL", body, b"0"])
6267                    .contains("Attempt to modify a readonly table script: "),
6268                "{body:?}",
6269            );
6270        }
6271        // A table nobody guards walks the way it always did, whether a script
6272        // made it or the standard library did.
6273        assert_eq!(
6274            f.run(&[
6275                b"EVAL",
6276                b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
6277                b"0",
6278            ]),
6279            ":2\r\n"
6280        );
6281        assert_eq!(
6282            f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
6283            "$3\r\nnil\r\n"
6284        );
6285        assert_eq!(
6286            f.run(&[
6287                b"EVAL",
6288                b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
6289                  return type(f)",
6290                b"0",
6291            ]),
6292            "$8\r\nfunction\r\n"
6293        );
6294    }
6295
6296    #[test]
6297    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6298    fn a_script_gets_the_bit_library_a_real_server_carries() {
6299        let mut f = Fixture::new();
6300        // Every answer is a signed word, which is why the ones past two to the
6301        // thirty one come back negative.
6302        for (body, want) in [
6303            ("bit.tobit(1)", ":1\r\n"),
6304            ("bit.tobit(2^32 + 1)", ":1\r\n"),
6305            ("bit.tobit(2^31)", ":-2147483648\r\n"),
6306            ("bit.tobit(0xffffffff)", ":-1\r\n"),
6307            // The rounding is to the nearest and not toward zero.
6308            ("bit.tobit(1.5)", ":2\r\n"),
6309            ("bit.tobit(2.5)", ":2\r\n"),
6310            ("bit.bnot(0)", ":-1\r\n"),
6311            ("bit.band(0xff, 0x0f)", ":15\r\n"),
6312            ("bit.band(1, 2, 3)", ":0\r\n"),
6313            ("bit.bor(1, 2, 4)", ":7\r\n"),
6314            ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
6315            // Only the low five bits of a count are read.
6316            ("bit.lshift(1, 31)", ":-2147483648\r\n"),
6317            ("bit.lshift(1, 32)", ":1\r\n"),
6318            ("bit.lshift(1, 33)", ":2\r\n"),
6319            ("bit.rshift(-1, 1)", ":2147483647\r\n"),
6320            ("bit.arshift(-1, 1)", ":-1\r\n"),
6321            ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
6322            ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
6323            ("bit.bswap(0x12345678)", ":2018915346\r\n"),
6324            // A string that reads as a number is a number, which is Lua's rule
6325            // and not a courtesy of this library.
6326            ("bit.tobit('0x10')", ":16\r\n"),
6327        ] {
6328            let script = format!("return {body}");
6329            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6330        }
6331        // The digits are the low ones, a negative count asks for upper case,
6332        // and a count outside eight is brought back to it.
6333        for (body, want) in [
6334            ("bit.tohex(1)", "00000001"),
6335            ("bit.tohex(-1)", "ffffffff"),
6336            ("bit.tohex(255, 2)", "ff"),
6337            ("bit.tohex(255, -8)", "000000FF"),
6338            ("bit.tohex(0x87654321, 4)", "4321"),
6339            ("bit.tohex(1, 0)", ""),
6340            ("bit.tohex(1, 9)", "00000001"),
6341        ] {
6342            let script = format!("return {body}");
6343            assert_eq!(
6344                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6345                format!("${}\r\n{want}\r\n", want.len()),
6346                "{body}",
6347            );
6348        }
6349        // A bad argument names the position, the function and what was passed,
6350        // and the line in front of it is the script's own.
6351        for (body, want) in [
6352            (
6353                "return bit.band()",
6354                "bad argument #1 to 'band' (number expected, got no value)",
6355            ),
6356            (
6357                "return bit.band('x')",
6358                "bad argument #1 to 'band' (number expected, got string)",
6359            ),
6360            (
6361                "return bit.tobit(true)",
6362                "bad argument #1 to 'tobit' (number expected, got boolean)",
6363            ),
6364            (
6365                "return bit.lshift(1)",
6366                "bad argument #2 to 'lshift' (number expected, got no value)",
6367            ),
6368        ] {
6369            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6370            assert!(
6371                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6372                "{body} gave {reply}",
6373            );
6374        }
6375        // The name in the message is the one the call site used, so a call that
6376        // went through `pcall` has no name to report.
6377        assert_eq!(
6378            f.run(&[
6379                b"EVAL",
6380                b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
6381                b"0",
6382            ]),
6383            "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
6384        );
6385        // The table is readable and not writable, the same as `redis`.
6386        assert_eq!(
6387            f.run(&[
6388                b"EVAL",
6389                b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
6390                  table.sort(t) return table.concat(t, ' ')",
6391                b"0",
6392            ]),
6393            "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
6394        );
6395        for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
6396            assert!(
6397                f.run(&[b"EVAL", body, b"0"])
6398                    .contains("Attempt to modify a readonly table script: "),
6399                "{body:?}",
6400            );
6401        }
6402    }
6403
6404    #[test]
6405    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6406    fn a_script_gets_the_cjson_library_a_real_server_carries() {
6407        let mut f = Fixture::new();
6408        // Encoding, including the three shapes nobody guesses right: an empty
6409        // table is an object, a number is fourteen significant digits, and a
6410        // hole in an array is a null rather than a shorter array.
6411        for (body, want) in [
6412            ("cjson.encode(nil)", "null"),
6413            ("cjson.encode(true)", "true"),
6414            ("cjson.encode(cjson.null)", "null"),
6415            ("cjson.encode(100)", "100"),
6416            ("cjson.encode(1/3)", "0.33333333333333"),
6417            ("cjson.encode(1e300)", "1e+300"),
6418            ("cjson.encode(2^53)", "9.007199254741e+15"),
6419            ("cjson.encode({})", "{}"),
6420            ("cjson.encode({1,2,3})", "[1,2,3]"),
6421            ("cjson.encode({a=1})", "{\"a\":1}"),
6422            ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
6423            ("cjson.encode({[0]=1})", "{\"0\":1}"),
6424            ("cjson.encode('a\\nb')", "\"a\\nb\""),
6425            // A tab and a backslash have short escapes, a vertical tab does not.
6426            ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
6427            ("cjson.encode('\\11')", "\"\\u000b\""),
6428            // Reading and writing again is the shortest way to say the decoder
6429            // built what the encoder expected.
6430            (
6431                "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
6432                "[1,[2,{\"a\":null}]]",
6433            ),
6434            // An empty array comes back as an object, because a table with
6435            // nothing in it has nothing to say about which it was.
6436            ("cjson.encode(cjson.decode('[]'))", "{}"),
6437        ] {
6438            let script = format!("return {body}");
6439            assert_eq!(
6440                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6441                format!("${}\r\n{want}\r\n", want.len()),
6442                "{body}",
6443            );
6444        }
6445        // Decoding, where the leniency about numbers is on by default and a
6446        // null is a value of its own rather than a missing key.
6447        for (body, want) in [
6448            ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
6449            ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
6450            ("cjson.decode('0x10')", ":16\r\n"),
6451            ("cjson.decode('+1')", ":1\r\n"),
6452            ("cjson.decode('01')", ":1\r\n"),
6453            ("cjson.decode(1) + 1", ":2\r\n"),
6454            // A long bracket, because Lua 5.1 would eat the backslash first.
6455            ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
6456            ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
6457            ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
6458        ] {
6459            let script = format!("return {body}");
6460            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6461        }
6462        // The settings, each of which answers with what it now holds.
6463        for (body, want) in [
6464            (
6465                "cjson.encode_number_precision(3) return cjson.encode(1/3)",
6466                "0.333",
6467            ),
6468            (
6469                "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
6470                "null",
6471            ),
6472            (
6473                "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
6474                "inf",
6475            ),
6476            (
6477                "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
6478                "{\"1\":1,\"100\":1}",
6479            ),
6480            (
6481                "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
6482                "[]",
6483            ),
6484            ("return tostring(cjson.encode_max_depth())", "1000"),
6485            ("return tostring(cjson.encode_keep_buffer(false))", "false"),
6486            ("return tostring(cjson.encode_sparse_array())", "false"),
6487            // A setting one script changed is not a setting the next one sees,
6488            // which is D-105.
6489            ("return tostring(cjson.encode_number_precision())", "14"),
6490        ] {
6491            assert_eq!(
6492                f.run(&[b"EVAL", body.as_bytes(), b"0"]),
6493                format!("${}\r\n{want}\r\n", want.len()),
6494                "{body}",
6495            );
6496        }
6497        // A failure names what stopped it and, when it was the text, where.
6498        for (body, want) in [
6499            (
6500                "return cjson.encode(1/0)",
6501                "Cannot serialise number: must not be NaN or Inf",
6502            ),
6503            (
6504                "return cjson.encode({[1]=1,[100]=1})",
6505                "Cannot serialise table: excessively sparse array",
6506            ),
6507            (
6508                "return cjson.encode({[true]=1})",
6509                "Cannot serialise boolean: table key must be a number or string",
6510            ),
6511            (
6512                "return cjson.encode(tostring)",
6513                "Cannot serialise function: type not supported",
6514            ),
6515            (
6516                "return cjson.encode()",
6517                "bad argument #1 to 'encode' (expected 1 argument)",
6518            ),
6519            (
6520                "return cjson.decode('[1,2')",
6521                "Expected comma or array end but found T_END at character 5",
6522            ),
6523            (
6524                "return cjson.decode('{\"a\" 1}')",
6525                "Expected colon but found T_NUMBER at character 6",
6526            ),
6527            (
6528                "return cjson.decode('tru')",
6529                "Expected value but found invalid token at character 1",
6530            ),
6531            (
6532                "return cjson.decode('[1] 2')",
6533                "Expected the end but found T_NUMBER at character 5",
6534            ),
6535            (
6536                "return cjson.encode_max_depth(0)",
6537                "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
6538            ),
6539            (
6540                "return cjson.encode_invalid_numbers('yes')",
6541                "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
6542            ),
6543            (
6544                "return cjson.encode_max_depth(1, 2)",
6545                "bad argument #2 to 'encode_max_depth' (found too many arguments)",
6546            ),
6547        ] {
6548            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6549            assert!(
6550                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6551                "{body} gave {reply}",
6552            );
6553        }
6554        // A module of its own, with settings of its own and no guard on it,
6555        // which is what a real server hands back.
6556        assert_eq!(
6557            f.run(&[
6558                b"EVAL",
6559                b"local n = cjson.new() n.encode_number_precision(3) \
6560                  return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
6561                b"0",
6562            ]),
6563            "$22\r\n0.33333333333333 0.333\r\n"
6564        );
6565        // The table is readable and not writable, the same as `redis`.
6566        let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
6567                     decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
6568                     encode_max_depth encode_number_precision encode_sparse_array new null";
6569        assert_eq!(
6570            f.run(&[
6571                b"EVAL",
6572                b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
6573                  table.sort(t) return table.concat(t, ' ')",
6574                b"0",
6575            ]),
6576            format!("${}\r\n{names}\r\n", names.len())
6577        );
6578        for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
6579            assert!(
6580                f.run(&[b"EVAL", body, b"0"])
6581                    .contains("Attempt to modify a readonly table script: "),
6582                "{body:?}",
6583            );
6584        }
6585    }
6586
6587    #[test]
6588    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6589    fn a_script_gets_the_struct_library_a_real_server_carries() {
6590        let mut f = Fixture::new();
6591        // Packing, where the sizes are the ones a sixty four bit build gives
6592        // and the order is the machine's own unless the format says otherwise.
6593        for (body, want) in [
6594            ("#struct.pack('i4', 1)", ":4\r\n"),
6595            ("#struct.pack('l', 1)", ":8\r\n"),
6596            ("#struct.pack('d', 1)", ":8\r\n"),
6597            ("#struct.pack('f', 1)", ":4\r\n"),
6598            ("#struct.pack('s', 'abc')", ":4\r\n"),
6599            ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
6600            ("#struct.pack('x')", ":1\r\n"),
6601            ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
6602            ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
6603            ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
6604            // Past eight bytes the C shifts an unsigned long off the end, so
6605            // the rest of the bytes are zero and a negative is not carried.
6606            ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
6607            ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
6608            // A count of zero on `c` writes the whole string, `s` adds the
6609            // terminator, and `x` writes a zero byte nobody reads back.
6610            ("#struct.pack('c0', 'abcd')", ":4\r\n"),
6611            ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
6612            ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
6613        ] {
6614            let script = format!("return {body}");
6615            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6616        }
6617        // Sizes, including the two the C is lenient about: an unknown letter
6618        // and a bare digit are both nothing at all rather than a complaint.
6619        for (body, want) in [
6620            ("struct.size('i')", ":4\r\n"),
6621            ("struct.size('l')", ":8\r\n"),
6622            ("struct.size('T')", ":8\r\n"),
6623            ("struct.size('h')", ":2\r\n"),
6624            ("struct.size('c10')", ":10\r\n"),
6625            ("struct.size('ic')", ":5\r\n"),
6626            ("struct.size('!8ic')", ":5\r\n"),
6627            ("struct.size('!4i')", ":4\r\n"),
6628            // Nothing is padded until `!` turns alignment on, and then a
6629            // double is pushed out to the next eight byte boundary.
6630            ("struct.size('bd')", ":9\r\n"),
6631            ("struct.size('!bd')", ":16\r\n"),
6632            ("struct.size('A')", ":0\r\n"),
6633            ("struct.size('7')", ":0\r\n"),
6634        ] {
6635            let script = format!("return {body}");
6636            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6637        }
6638        // Unpacking, which hands back the values and then where it stopped, so
6639        // the last number can be passed straight back in as the next offset.
6640        for (body, want) in [
6641            ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
6642            ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
6643            ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
6644            ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
6645            ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
6646            (
6647                "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
6648                ":-70000\r\n",
6649            ),
6650            ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
6651            // A `c0` takes its length from the value read just before it and
6652            // swallows it, so one byte says how long the next three are and
6653            // only the string and the position come back.
6654            ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
6655            ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
6656        ] {
6657            let script = format!("return {body}");
6658            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
6659        }
6660        for (body, want) in [
6661            ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
6662            ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
6663            ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
6664        ] {
6665            let script = format!("return {body}");
6666            assert_eq!(
6667                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6668                format!("${}\r\n{want}\r\n", want.len()),
6669                "{body}",
6670            );
6671        }
6672        // A failure names the argument the C names, which is not always the
6673        // argument a reader would pick.
6674        for (body, want) in [
6675            (
6676                "return struct.pack()",
6677                "bad argument #1 to 'pack' (string expected, got no value)",
6678            ),
6679            // The C pushes a nil before it reads anything, so a missing value
6680            // is a nil rather than nothing at all.
6681            (
6682                "return struct.pack('i4')",
6683                "bad argument #2 to 'pack' (number expected, got nil)",
6684            ),
6685            // And it reads the string with a post increment before it checks
6686            // the length, so the number here is one past the real argument.
6687            (
6688                "return struct.pack('c6', 'abc')",
6689                "bad argument #3 to 'pack' (string too short)",
6690            ),
6691            (
6692                "return struct.pack('A', 'x')",
6693                "bad argument #1 to 'pack' (invalid format option 'A')",
6694            ),
6695            (
6696                "return struct.pack('i33', 1)",
6697                "integral size 33 is larger than limit of 32",
6698            ),
6699            (
6700                "return struct.pack('!3i', 1)",
6701                "alignment 3 is not a power of 2",
6702            ),
6703            (
6704                "return struct.unpack()",
6705                "bad argument #1 to 'unpack' (string expected, got no value)",
6706            ),
6707            (
6708                "return struct.unpack('i4')",
6709                "bad argument #2 to 'unpack' (string expected, got no value)",
6710            ),
6711            (
6712                "return struct.unpack('i4', 'ab')",
6713                "bad argument #2 to 'unpack' (data string too short)",
6714            ),
6715            (
6716                "return struct.unpack('i1', 'abc', 0)",
6717                "bad argument #3 to 'unpack' (offset must be 1 or greater)",
6718            ),
6719            (
6720                "return struct.unpack('c0', 'abc')",
6721                "format 'c0' needs a previous size",
6722            ),
6723            (
6724                "return struct.unpack('s', 'abc')",
6725                "unfinished string in data",
6726            ),
6727            (
6728                "return struct.size()",
6729                "bad argument #1 to 'size' (string expected, got no value)",
6730            ),
6731            (
6732                "return struct.size('s')",
6733                "bad argument #1 to 'size' (option 's' has no fixed size)",
6734            ),
6735            (
6736                "return struct.size('c0')",
6737                "bad argument #1 to 'size' (option 'c0' has no fixed size)",
6738            ),
6739        ] {
6740            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6741            assert!(
6742                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6743                "{body} gave {reply}",
6744            );
6745        }
6746        // Three members and no version, which is all the C registers.
6747        let names = "pack size unpack";
6748        assert_eq!(
6749            f.run(&[
6750                b"EVAL",
6751                b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
6752                  table.sort(t) return table.concat(t, ' ')",
6753                b"0",
6754            ]),
6755            format!("${}\r\n{names}\r\n", names.len())
6756        );
6757        for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
6758            assert!(
6759                f.run(&[b"EVAL", body, b"0"])
6760                    .contains("Attempt to modify a readonly table script: "),
6761                "{body:?}",
6762            );
6763        }
6764    }
6765
6766    #[test]
6767    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6768    fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
6769        let mut f = Fixture::new();
6770        // Every value goes out in the shortest form that holds it, and several
6771        // arguments are packed one after another into one string.
6772        let hex = "local function hx(s) return (string.gsub(s, '.', \
6773                   function(c) return string.format('%02x', string.byte(c)) end)) end ";
6774        for (body, want) in [
6775            ("cmsgpack.pack(nil)", "c0"),
6776            ("cmsgpack.pack(true)", "c3"),
6777            ("cmsgpack.pack(false)", "c2"),
6778            ("cmsgpack.pack(0)", "00"),
6779            ("cmsgpack.pack(127)", "7f"),
6780            ("cmsgpack.pack(128)", "cc80"),
6781            ("cmsgpack.pack(-1)", "ff"),
6782            ("cmsgpack.pack(-33)", "d0df"),
6783            ("cmsgpack.pack(65535)", "cdffff"),
6784            ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
6785            ("cmsgpack.pack(2^53)", "cf0020000000000000"),
6786            ("cmsgpack.pack(-2^63)", "d38000000000000000"),
6787            // Past what an integer holds it is a number again, and a number
6788            // goes out narrow whenever four bytes give it back unchanged.
6789            ("cmsgpack.pack(2^64)", "ca5f800000"),
6790            ("cmsgpack.pack(1.5)", "ca3fc00000"),
6791            ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
6792            ("cmsgpack.pack('abc')", "a3616263"),
6793            ("cmsgpack.pack('')", "a0"),
6794            ("cmsgpack.pack({})", "90"),
6795            ("cmsgpack.pack({1, 2})", "920102"),
6796            ("cmsgpack.pack({a = 1})", "81a16101"),
6797            ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
6798            // Sixteen levels of table are packed and the seventeenth is a nil,
6799            // which is what the C does rather than refusing the whole thing.
6800            (
6801                "(function() local t = {} local c = t \
6802                 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
6803                "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
6804                 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
6805            ),
6806        ] {
6807            let script = format!("{hex} return hx({body})");
6808            assert_eq!(
6809                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6810                format!("${}\r\n{want}\r\n", want.len()),
6811                "{body}",
6812            );
6813        }
6814        // Unpacking reads the whole stream, so a string holding three values
6815        // hands back three. The two that take an offset put where they got to
6816        // in front of the values, and answer minus one when nothing is left.
6817        for (body, want) in [
6818            ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
6819            ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
6820            ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
6821            ("select('#', cmsgpack.unpack(''))", 0),
6822            ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
6823            ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
6824            ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
6825            ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
6826            ("select(1, cmsgpack.unpack_one('\\1'))", -1),
6827            ("select(1, cmsgpack.unpack_one('', 0))", -1),
6828            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
6829            ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
6830            // A limit of nothing at all takes the read everything path, which
6831            // has no offset in front of it.
6832            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
6833            ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
6834        ] {
6835            let script = format!("return {body}");
6836            assert_eq!(
6837                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6838                format!(":{want}\r\n"),
6839                "{body}",
6840            );
6841        }
6842        for (body, want) in [
6843            ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
6844            ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
6845            ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
6846            (
6847                "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
6848                "inf",
6849            ),
6850            ("cmsgpack._NAME", "cmsgpack"),
6851            ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
6852            (
6853                "cmsgpack._COPYRIGHT",
6854                "Copyright (C) 2012, Salvatore Sanfilippo",
6855            ),
6856            (
6857                "cmsgpack._DESCRIPTION",
6858                "MessagePack C implementation for Lua",
6859            ),
6860        ] {
6861            let script = format!("return {body}");
6862            assert_eq!(
6863                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
6864                format!("${}\r\n{want}\r\n", want.len()),
6865                "{body}",
6866            );
6867        }
6868        for (body, want) in [
6869            // The C counts the arguments before it reads any of them, so the
6870            // one it names when there are none is the one before the first.
6871            (
6872                "return cmsgpack.pack()",
6873                "bad argument #0 to 'pack' (MessagePack pack needs input.)",
6874            ),
6875            (
6876                "return cmsgpack.unpack()",
6877                "bad argument #1 to 'unpack' (string expected, got no value)",
6878            ),
6879            (
6880                "return cmsgpack.unpack(string.char(193))",
6881                "Bad data format in input.",
6882            ),
6883            (
6884                "return cmsgpack.unpack(string.char(204))",
6885                "Missing bytes in input.",
6886            ),
6887            (
6888                "return cmsgpack.unpack(string.char(146, 1))",
6889                "Missing bytes in input.",
6890            ),
6891            (
6892                "return cmsgpack.unpack_one('\\1', 5)",
6893                "Start offset 5 greater than input length 1.",
6894            ),
6895            (
6896                "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
6897                "Start offset 5 greater than input length 2.",
6898            ),
6899            // The second number here is the length of the input rather than
6900            // the limit, which is a mixed up argument in the C kept on purpose.
6901            (
6902                "return cmsgpack.unpack_one('\\1', -1)",
6903                "Invalid request to unpack with offset of -1 and limit of 1.",
6904            ),
6905            (
6906                "return cmsgpack.unpack_limit('\\1', -1, 0)",
6907                "Invalid request to unpack with offset of 0 and limit of 1.",
6908            ),
6909        ] {
6910            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
6911            assert!(
6912                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
6913                "{body} gave {reply}",
6914            );
6915        }
6916        // Four calls and the four names the C sets on the table beside them.
6917        let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
6918        assert_eq!(
6919            f.run(&[
6920                b"EVAL",
6921                b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
6922                  table.sort(t) return table.concat(t, ' ')",
6923                b"0",
6924            ]),
6925            format!("${}\r\n{names}\r\n", names.len())
6926        );
6927        for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
6928            assert!(
6929                f.run(&[b"EVAL", body, b"0"])
6930                    .contains("Attempt to modify a readonly table script: "),
6931                "{body:?}",
6932            );
6933        }
6934        // A library is a table like any other from a script's side, so packing
6935        // one walks its members rather than finding the guard in front empty.
6936        assert_eq!(
6937            f.run(&[
6938                b"EVAL",
6939                b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
6940                b"0",
6941            ]),
6942            "$8\r\ncmsgpack\r\n"
6943        );
6944    }
6945
6946    /// The library used by most of the function tests below.
6947    ///
6948    /// Written out once because every one of them wants a library that has
6949    /// something to call, and because the line numbers in the failures a couple
6950    /// of them check are line numbers in this.
6951    const LIB: &[u8] = b"#!lua name=mylib\n\
6952        local counter = 0\n\
6953        redis.register_function{function_name = 'ping', description = 'says pong',\n\
6954        callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
6955        redis.register_function('count', function() counter = counter + 1 return counter end)\n\
6956        redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
6957        redis.register_function('setit', function(keys, args) \
6958        return redis.call('SET', keys[1], args[1]) end)\n\
6959        redis.register_function('raise', function() error('boom') end)\n";
6960
6961    /// A second library, for the tests that need two of them.
6962    const OTHER: &[u8] = b"#!lua name=other\n\
6963        redis.register_function('twice', function(keys, args) return 2 end)\n";
6964
6965    #[test]
6966    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
6967    fn a_library_is_loaded_once_and_called_by_name_forever_after() {
6968        let mut f = Fixture::new();
6969        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6970        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6971        // The dictionary FCALL looks in is one for the whole server and it does
6972        // not care about case, which is why this finds the same function.
6973        assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
6974        // Keys and arguments arrive as the two arguments of the callback rather
6975        // than as globals, and a function that reads KEYS is reading a name
6976        // that is not there.
6977        assert_eq!(
6978            f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
6979            "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6980        );
6981        assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
6982        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
6983        // A library's own local outlives the call that made it, which is the
6984        // whole reason a library is not a script.
6985        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
6986        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
6987        // The name a failure ends with is the function's, where a script's is
6988        // its digest, and the line is a line in the library.
6989        assert_eq!(
6990            f.run(&[b"FCALL", b"raise", b"0"]),
6991            "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
6992        );
6993        // Deleting is by the exact name, so the upper case spelling that found
6994        // the function a moment ago does not find the library.
6995        assert_eq!(
6996            f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
6997            "-ERR Library not found\r\n"
6998        );
6999        assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
7000        assert_eq!(
7001            f.run(&[b"FCALL", b"ping", b"0"]),
7002            "-ERR Function not found\r\n"
7003        );
7004    }
7005
7006    #[test]
7007    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7008    fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
7009        let mut f = Fixture::new();
7010        for (code, want) in [
7011            (&b"return 1"[..], "ERR Missing library metadata"),
7012            (b"#!lua name=x", "ERR Invalid library metadata"),
7013            (b"#!\n", "ERR Library name was not given"),
7014            (b"#!lua\nx", "ERR Library name was not given"),
7015            (
7016                b"#!lua name=a name=b\nx",
7017                "ERR Invalid metadata value, name argument was given multiple times",
7018            ),
7019            (
7020                b"#!lua nome=a\nx",
7021                "ERR Invalid metadata value given: nome=a",
7022            ),
7023            (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
7024            (
7025                b"#!lua name=a-b\nx",
7026                "ERR Library names can only contain letters, numbers, or underscores(_) \
7027                 and must be at least one character long",
7028            ),
7029            (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
7030            (
7031                b"#!lua name=c\nthis is not lua",
7032                "ERR Error compiling function: user_function:2: '=' expected near 'is'",
7033            ),
7034            // Nothing at all is on the global table during a load except one
7035            // table with eight names on it, so `error` is as absent as anything
7036            // a library misspelled would be.
7037            (
7038                b"#!lua name=r\nerror('boom')",
7039                "ERR Error registering functions: ERR user_function:2: \
7040                 Script attempted to access nonexistent global variable 'error'",
7041            ),
7042            // And `redis` is there but `redis.call` is not, so the name the
7043            // complaint gives is `call` and not `redis`.
7044            (
7045                b"#!lua name=r\nredis.call('PING')",
7046                "ERR Error registering functions: ERR user_function:2: \
7047                 Script attempted to access nonexistent global variable 'call'",
7048            ),
7049            (
7050                b"#!lua name=r\nx = 1",
7051                "ERR Error registering functions: ERR user_function:2: \
7052                 Attempt to modify a readonly table",
7053            ),
7054            (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
7055        ] {
7056            assert_eq!(
7057                f.run(&[b"FUNCTION", b"LOAD", code]),
7058                format!("-{want}\r\n"),
7059                "{}",
7060                String::from_utf8_lossy(code),
7061            );
7062        }
7063    }
7064
7065    #[test]
7066    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7067    fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
7068        let mut f = Fixture::new();
7069        for (call, want) in [
7070            (
7071                &b"redis.register_function()"[..],
7072                "wrong number of arguments to redis.register_function",
7073            ),
7074            (
7075                b"redis.register_function('a', function() end, 1)",
7076                "wrong number of arguments to redis.register_function",
7077            ),
7078            (
7079                b"redis.register_function('a')",
7080                "calling redis.register_function with a single argument is only \
7081                 applicable to Lua table (representing named arguments).",
7082            ),
7083            (
7084                b"redis.register_function({foo = 'a'})",
7085                "unknown argument given to redis.register_function",
7086            ),
7087            (
7088                b"redis.register_function({callback = function() end})",
7089                "redis.register_function must get a function name argument",
7090            ),
7091            (
7092                b"redis.register_function({function_name = 'a'})",
7093                "redis.register_function must get a callback argument",
7094            ),
7095            (
7096                b"redis.register_function({function_name = {}, callback = function() end})",
7097                "function_name argument given to redis.register_function must be a string",
7098            ),
7099            (
7100                b"redis.register_function({function_name = 'a', description = {}, \
7101                  callback = function() end})",
7102                "description argument given to redis.register_function must be a string",
7103            ),
7104            (
7105                b"redis.register_function({function_name = 'a', callback = 1})",
7106                "callback argument given to redis.register_function must be a function",
7107            ),
7108            (
7109                b"redis.register_function({function_name = 'a', callback = function() end, \
7110                  flags = 1})",
7111                "flags argument to redis.register_function must be a table \
7112                 representing function flags",
7113            ),
7114            (
7115                b"redis.register_function({function_name = 'a', callback = function() end, \
7116                  flags = {'zz'}})",
7117                "unknown flag given",
7118            ),
7119            (
7120                b"redis.register_function({}, function() end)",
7121                "first argument to redis.register_function must be a string",
7122            ),
7123            (
7124                b"redis.register_function('a', 1)",
7125                "second argument to redis.register_function must be a function",
7126            ),
7127            (
7128                b"redis.register_function('a-b', function() end)",
7129                "Library names can only contain letters, numbers, or underscores(_) \
7130                 and must be at least one character long",
7131            ),
7132            (
7133                b"redis.register_function('d', function() end) \
7134                  redis.register_function('d', function() end)",
7135                "Function already exists in the library",
7136            ),
7137        ] {
7138            let mut code = b"#!lua name=e\n".to_vec();
7139            code.extend_from_slice(call);
7140            // Two `ERR` in a row on purpose. The sentence comes back as a table
7141            // with the code already on it, which is what keeps the position off
7142            // the front of it, and then the code goes on the line as well.
7143            assert_eq!(
7144                f.run(&[b"FUNCTION", b"LOAD", &code]),
7145                format!("-ERR Error registering functions: ERR {want}\r\n"),
7146                "{}",
7147                String::from_utf8_lossy(call),
7148            );
7149        }
7150        // A number is a name, because the C reads an argument that should be a
7151        // string through a helper that takes a number and prints it.
7152        assert_eq!(
7153            f.run(&[
7154                b"FUNCTION",
7155                b"LOAD",
7156                b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
7157            ]),
7158            "$1\r\nn\r\n"
7159        );
7160        assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
7161        // The dictionary inside one library is case sensitive where the one
7162        // across libraries is not, so these are two functions.
7163        assert_eq!(
7164            f.run(&[
7165                b"FUNCTION",
7166                b"LOAD",
7167                b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
7168                  redis.register_function('D', function() return 2 end)",
7169            ]),
7170            "$1\r\nc\r\n"
7171        );
7172    }
7173
7174    #[test]
7175    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7176    fn a_library_cannot_take_a_name_another_library_already_has() {
7177        let mut f = Fixture::new();
7178        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7179        assert_eq!(
7180            f.run(&[b"FUNCTION", b"LOAD", LIB]),
7181            "-ERR Library 'mylib' already exists\r\n"
7182        );
7183        // A different library that registers a name the first one already has,
7184        // which is checked without regard to case because the dictionary it is
7185        // checked against is.
7186        assert_eq!(
7187            f.run(&[
7188                b"FUNCTION",
7189                b"LOAD",
7190                b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
7191            ]),
7192            "-ERR Function PING already exists\r\n"
7193        );
7194        // REPLACE reloads a library over itself, and the collision check leaves
7195        // the library being replaced out or nothing could ever be reloaded.
7196        assert_eq!(
7197            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
7198            "$5\r\nmylib\r\n"
7199        );
7200        // The counter went back to zero with the reload, since the library is a
7201        // new one and its locals are new with it.
7202        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
7203        assert_eq!(
7204            f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
7205            "-ERR Unknown option given: NOPE\r\n"
7206        );
7207        // The loop that reads the options stops one short of the end, so the
7208        // last argument is the code whatever it looks like.
7209        assert_eq!(
7210            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
7211            "-ERR Missing library metadata\r\n"
7212        );
7213        assert_eq!(
7214            f.run(&[b"FUNCTION", b"LOAD"]),
7215            "-ERR wrong number of arguments for 'function|load' command\r\n"
7216        );
7217    }
7218
7219    #[test]
7220    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7221    fn fcall_checks_the_name_before_it_looks_at_anything_else() {
7222        let mut f = Fixture::new();
7223        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7224        for (args, want) in [
7225            (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
7226            (&[b"ping", b"x"], "ERR Bad number of keys provided"),
7227            (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
7228            (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
7229            (
7230                &[b"ping", b"99999999999999999999"],
7231                "ERR Bad number of keys provided",
7232            ),
7233            (
7234                &[b"ping", b"3", b"a"],
7235                "ERR Number of keys can't be greater than number of args",
7236            ),
7237            (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
7238        ] {
7239            let mut wire: Vec<&[u8]> = vec![b"FCALL"];
7240            wire.extend_from_slice(args);
7241            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
7242        }
7243        // The read-only spelling refuses a function the library did not mark
7244        // no-writes, and it refuses it before anything runs.
7245        assert_eq!(
7246            f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
7247            "-ERR Can not execute a script with write flag using *_ro command.\r\n"
7248        );
7249        assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
7250        assert_eq!(
7251            f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
7252            "-ERR Function not found\r\n"
7253        );
7254        // And a function that was marked no-writes is held to it whichever
7255        // spelling called it.
7256        assert_eq!(
7257            f.run(&[
7258                b"FUNCTION",
7259                b"LOAD",
7260                b"#!lua name=w\nredis.register_function{function_name = 'w', \
7261                  flags = {'no-writes'}, callback = function(keys) \
7262                  return redis.call('SET', keys[1], 'x') end}",
7263            ]),
7264            "$1\r\nw\r\n"
7265        );
7266        assert!(
7267            f.run(&[b"FCALL", b"w", b"1", b"k"])
7268                .starts_with("-ERR Write commands are not allowed from read-only scripts."),
7269        );
7270    }
7271
7272    #[test]
7273    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7274    fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
7275        let mut f = Fixture::new();
7276        // The three names on the `redis` table that only mean something inside
7277        // EVAL are not there, and neither is the error handler EVAL installs.
7278        let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
7279                     REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
7280                     acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
7281                     status_reply";
7282        let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
7283                       gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
7284                       rawequal rawget rawset redis select setmetatable string struct table \
7285                       tonumber tostring type unpack xpcall";
7286        assert_eq!(
7287            f.run(&[
7288                b"FUNCTION",
7289                b"LOAD",
7290                b"#!lua name=g\n\
7291                  local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
7292                  table.sort(o) return table.concat(o, ' ') end\n\
7293                  redis.register_function('names', function() return sorted(redis) end)\n\
7294                  redis.register_function('globals', function() return sorted(_G) end)\n\
7295                  redis.register_function('keysg', function() return KEYS[1] end)\n\
7296                  redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
7297                  redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
7298                  redis.register_function('gwr', function() _G.pcall = 1 end)\n",
7299            ]),
7300            "$1\r\ng\r\n"
7301        );
7302        assert_eq!(
7303            f.run(&[b"FCALL", b"names", b"0"]),
7304            format!("${}\r\n{names}\r\n", names.len())
7305        );
7306        assert_eq!(
7307            f.run(&[b"FCALL", b"globals", b"0"]),
7308            format!("${}\r\n{globals}\r\n", globals.len())
7309        );
7310        // No `KEYS`, and reading a global that is not there is a mistake rather
7311        // than a nil, so this is the sandbox's own complaint.
7312        assert!(
7313            f.run(&[b"FCALL", b"keysg", b"1", b"k"])
7314                .contains("nonexistent global variable 'KEYS'"),
7315        );
7316        // The `redis` table has no error metatable on it, unlike the global
7317        // table, so a name that is not on it is a nil and not a complaint.
7318        assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
7319        // The global table cannot be written to either way round, which is a
7320        // stricter rule than the one a script runs under.
7321        for name in [&b"wr"[..], b"gwr"] {
7322            assert!(
7323                f.run(&[b"FCALL", name, b"0"])
7324                    .contains("Attempt to modify a readonly table"),
7325                "{}",
7326                String::from_utf8_lossy(name),
7327            );
7328        }
7329    }
7330
7331    #[test]
7332    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7333    fn function_list_says_what_every_library_registered() {
7334        let mut f = Fixture::new();
7335        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7336        // One map per library on RESP3, and the functions inside it in the
7337        // order the library registered them, which is D-109.
7338        f.out = Out::new(Proto::Resp3);
7339        let listed = f.run(&[b"FUNCTION", b"LIST"]);
7340        assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
7341        assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
7342        assert!(listed.contains(
7343            "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
7344             $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
7345        ));
7346        // A function with no description gets a null rather than an empty
7347        // string, and no flags is an empty set rather than a missing field.
7348        assert!(listed.contains(
7349            "$4\r\nname\r\n$5\r\ncount\r\n$11\r\ndescription\r\n_\r\n$5\r\nflags\r\n~0\r\n"
7350        ));
7351        assert!(!listed.contains("library_code"));
7352        assert!(
7353            f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
7354                .contains("library_code")
7355        );
7356        // The pattern is matched without regard to case, which is a third rule
7357        // again next to the two the two dictionaries use.
7358        assert!(
7359            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
7360                .starts_with("*1\r\n")
7361        );
7362        assert_eq!(
7363            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
7364            "*0\r\n"
7365        );
7366        // On RESP2 the same reply is a flat array of six, which is what `map`
7367        // means on a protocol that has no map.
7368        f.out = Out::new(Proto::Resp2);
7369        assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
7370        for (args, want) in [
7371            (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
7372            (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
7373            (
7374                &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
7375                "ERR Unknown argument LIBRARYNAME",
7376            ),
7377            (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
7378        ] {
7379            let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
7380            wire.extend_from_slice(args);
7381            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
7382        }
7383    }
7384
7385    #[test]
7386    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7387    fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
7388        let mut f = Fixture::new();
7389        f.out = Out::new(Proto::Resp3);
7390        assert_eq!(
7391            f.run(&[b"FUNCTION", b"STATS"]),
7392            "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
7393             %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
7394        );
7395        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7396        assert!(
7397            f.run(&[b"FUNCTION", b"STATS"])
7398                .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
7399        );
7400        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
7401        assert!(
7402            f.run(&[b"FUNCTION", b"STATS"])
7403                .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
7404        );
7405    }
7406
7407    #[test]
7408    fn every_function_subcommand_complains_about_its_own_arity() {
7409        let mut f = Fixture::new();
7410        for (args, want) in [
7411            (
7412                &[&b"STATS"[..], b"X"][..],
7413                "ERR wrong number of arguments for 'function|stats' command",
7414            ),
7415            (
7416                &[b"KILL", b"X"],
7417                "ERR wrong number of arguments for 'function|kill' command",
7418            ),
7419            (
7420                &[b"HELP", b"X"],
7421                "ERR wrong number of arguments for 'function|help' command",
7422            ),
7423            (
7424                &[b"DELETE"],
7425                "ERR wrong number of arguments for 'function|delete' command",
7426            ),
7427            (
7428                &[b"DELETE", b"a", b"b"],
7429                "ERR wrong number of arguments for 'function|delete' command",
7430            ),
7431            (
7432                &[b"DUMP", b"X"],
7433                "ERR wrong number of arguments for 'function|dump' command",
7434            ),
7435            (
7436                &[b"RESTORE"],
7437                "ERR wrong number of arguments for 'function|restore' command",
7438            ),
7439            // RESTORE is the other one that falls through to the generic
7440            // sentence, and for the same reason FLUSH does.
7441            (
7442                &[b"RESTORE", b"a", b"FLUSH", b"X"],
7443                "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
7444                 Try FUNCTION HELP.",
7445            ),
7446            (
7447                &[b"RESTORE", b"a", b"ZZ"],
7448                "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
7449                 or REPLACE.",
7450            ),
7451            // FLUSH is the one that does not, because it checks the count
7452            // itself before it looks at the argument.
7453            (
7454                &[b"FLUSH", b"SYNC", b"X"],
7455                "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
7456                 Try FUNCTION HELP.",
7457            ),
7458            (
7459                &[b"FLUSH", b"ZZ"],
7460                "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
7461            ),
7462            (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
7463        ] {
7464            let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
7465            wire.extend_from_slice(args);
7466            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
7467        }
7468        assert_eq!(
7469            f.run(&[b"FUNCTION"]),
7470            "-ERR wrong number of arguments for 'function' command\r\n"
7471        );
7472        assert_eq!(
7473            f.run(&[b"FUNCTION", b"KILL"]),
7474            "-NOTBUSY No scripts in execution right now.\r\n"
7475        );
7476    }
7477
7478    /// The two ends of the same pipe, so they are tested as one.
7479    ///
7480    /// An empty server dumps ten bytes rather than nothing, because the footer
7481    /// is there whether or not a library is in front of it, and restoring those
7482    /// ten bytes is a working no op.
7483    #[test]
7484    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7485    fn a_library_survives_a_dump_and_a_restore() {
7486        let mut f = Fixture::new();
7487        let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7488        assert_eq!(empty.len(), 10);
7489        assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
7490
7491        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7492        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7493        assert!(full.len() > empty.len());
7494
7495        // The default policy is APPEND, so restoring onto the library the
7496        // payload came from is a name collision and not a quiet replacement.
7497        assert_eq!(
7498            f.run(&[b"FUNCTION", b"RESTORE", &full]),
7499            "-ERR Library mylib already exists\r\n"
7500        );
7501        assert_eq!(
7502            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
7503            "+OK\r\n"
7504        );
7505        assert_eq!(
7506            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
7507            "+OK\r\n"
7508        );
7509        // Whichever way it went back, the functions in it still run.
7510        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
7511
7512        // FLUSH keeps only what the payload held, so a library that was there
7513        // and is not in the payload is gone.
7514        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
7515        assert_eq!(
7516            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
7517            "+OK\r\n"
7518        );
7519        assert_eq!(
7520            f.run(&[b"FUNCTION", b"DELETE", b"other"]),
7521            "-ERR Library not found\r\n"
7522        );
7523    }
7524
7525    /// A payload that is going to be refused has to leave the server alone.
7526    ///
7527    /// Every one of these is refused for a different reason and at a different
7528    /// depth, from bytes that are not a payload at all down to a library that
7529    /// compiles and then collides, and the library that was already there has to
7530    /// still be there afterwards in every case.
7531    #[test]
7532    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7533    fn a_restore_that_fails_changes_nothing() {
7534        let mut f = Fixture::new();
7535        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7536        let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7537
7538        // Put the footer back on, so that each of these is refused for the
7539        // reason it is meant to be testing rather than for a checksum the edit
7540        // broke on the way.
7541        let reseal = |body: &[u8], version: u16| {
7542            let mut out = body.to_vec();
7543            out.extend_from_slice(&version.to_le_bytes());
7544            let crc = yo_common::crc::crc64(0, &out);
7545            out.extend_from_slice(&crc.to_le_bytes());
7546            out
7547        };
7548        let body = &good[..good.len() - 10];
7549
7550        let mut torn = good.clone();
7551        let n = torn.len();
7552        torn[n - 1] ^= 0xff;
7553        let future = reseal(body, 999);
7554        // The opcode in front of the one library, changed to the one the 7.0
7555        // release candidates wrote and then to one that is not a library at all.
7556        let mut pre_ga = body.to_vec();
7557        pre_ga[0] = 246;
7558        let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
7559        let mut other = body.to_vec();
7560        other[0] = 0;
7561        let other = reseal(&other, yo_kv::rdb::VERSION);
7562        // A library whose length says there is more of it than there is.
7563        let mut cut = body.to_vec();
7564        cut.truncate(body.len() - 1);
7565        let cut = reseal(&cut, yo_kv::rdb::VERSION);
7566
7567        for (bytes, want) in [
7568            (vec![], "ERR DUMP payload version or checksum are wrong"),
7569            (
7570                b"0123456789".to_vec(),
7571                "ERR DUMP payload version or checksum are wrong",
7572            ),
7573            (torn, "ERR DUMP payload version or checksum are wrong"),
7574            (future, "ERR DUMP payload version or checksum are wrong"),
7575            (pre_ga, "ERR Pre-GA function format not supported"),
7576            (other, "ERR given type is not a function"),
7577            (cut, "ERR Failed loading library payload"),
7578        ] {
7579            assert_eq!(
7580                f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
7581                format!("-{want}\r\n")
7582            );
7583        }
7584
7585        // Still exactly the one library, and it still runs.
7586        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
7587        let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7588        assert_eq!(again, good);
7589    }
7590
7591    /// A REPLACE takes a library's name off another library and still refuses to
7592    /// take a function name off one it is leaving alone.
7593    #[test]
7594    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7595    fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
7596        let mut f = Fixture::new();
7597        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
7598        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
7599        // A second library registering the name the payload's library uses.
7600        let clash =
7601            b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
7602                .as_slice();
7603        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
7604        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
7605        assert_eq!(
7606            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
7607            "-ERR Function ping already exists\r\n"
7608        );
7609        // Untouched, so the name still belongs to the library that had it.
7610        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
7611    }
7612
7613    #[test]
7614    fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
7615        let mut f = Fixture::new();
7616        assert_eq!(
7617            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
7618            "*1\r\n$1\r\nk\r\n"
7619        );
7620        assert_eq!(
7621            f.run(&[
7622                b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
7623            ]),
7624            "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
7625        );
7626        // None is a real answer for a script and the arguments past the count
7627        // are not keys, so they are not listed.
7628        assert_eq!(
7629            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
7630            "*0\r\n"
7631        );
7632        // A count that makes no sense finds no keys rather than being an error,
7633        // which is what a real server's key spec does with it.
7634        assert_eq!(
7635            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
7636            "*0\r\n"
7637        );
7638        assert_eq!(
7639            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
7640            "*0\r\n"
7641        );
7642        assert_eq!(
7643            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
7644            "*0\r\n"
7645        );
7646        // The count itself has to be there, and that is an arity question.
7647        assert_eq!(
7648            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
7649            "-ERR Invalid number of arguments specified for command\r\n"
7650        );
7651    }
7652
7653    #[test]
7654    #[cfg_attr(miri, ignore = "a Lua state is C, and Miri interprets Rust")]
7655    fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
7656        let mut f = Fixture::new();
7657        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
7658
7659        assert_eq!(
7660            eval(&mut f, b"return redis.sha1hex('')"),
7661            "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
7662        );
7663        assert_eq!(
7664            eval(&mut f, b"return redis.sha1hex('return 1')"),
7665            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
7666        );
7667        // A message with no space in it gets the generic code in front, and one
7668        // that already looks like a coded error is left alone.
7669        assert_eq!(
7670            eval(&mut f, b"return redis.error_reply('boom')"),
7671            "-ERR boom\r\n"
7672        );
7673        assert_eq!(
7674            eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
7675            "-WRONGTYPE nope\r\n"
7676        );
7677        assert_eq!(
7678            eval(&mut f, b"return redis.status_reply('fine')"),
7679            "+fine\r\n"
7680        );
7681        // Neither of them raises when it is called wrongly, they answer a value
7682        // that is an error, which is a difference a script can see.
7683        assert_eq!(
7684            eval(&mut f, b"return redis.error_reply(1)"),
7685            "-ERR wrong number or type of arguments\r\n"
7686        );
7687        assert_eq!(
7688            eval(&mut f, b"local x = redis.status_reply() return x.err"),
7689            "$37\r\nERR wrong number or type of arguments\r\n"
7690        );
7691
7692        // The constants a script branches on.
7693        assert_eq!(
7694            eval(
7695                &mut f,
7696                b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
7697            ),
7698            "$4\r\n0123\r\n"
7699        );
7700        assert_eq!(
7701            eval(
7702                &mut f,
7703                b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
7704            ),
7705            "$5\r\n01223\r\n"
7706        );
7707        // The calls that exist so an old script keeps working.
7708        assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
7709        assert_eq!(
7710            eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
7711            ":1\r\n"
7712        );
7713        assert_eq!(
7714            eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
7715            ":1\r\n"
7716        );
7717        assert_eq!(
7718            eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
7719            ":1\r\n"
7720        );
7721        // Each of those checks its arguments the way a real server does.
7722        assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
7723        assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
7724        assert!(
7725            eval(&mut f, b"redis.log('x', 'y')")
7726                .contains("First argument must be a number (log level)."),
7727        );
7728        assert!(
7729            eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
7730                .contains("Invalid command passed to redis.acl_check_cmd()"),
7731        );
7732        assert!(
7733            eval(&mut f, b"return redis.acl_check_cmd('get')")
7734                .contains("Wrong number of args for redis.acl_check_cmd()"),
7735        );
7736    }
7737
7738    #[test]
7739    fn a_counter_is_an_integer_and_not_a_string_of_digits() {
7740        let mut f = Fixture::new();
7741        assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
7742        assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
7743        assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
7744        // Read back as a string it is still an integer, written out as digits
7745        // only because somebody asked for them.
7746        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
7747        assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
7748        // A counter that is not a number is the error the store raises and this
7749        // layer only spells, which is the whole point of the split.
7750        f.run(&[b"SET", b"k", b"hello"]);
7751        assert_eq!(
7752            f.run(&[b"INCR", b"k"]),
7753            "-ERR value is not an integer or out of range\r\n"
7754        );
7755        assert_eq!(
7756            f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
7757            "-ERR increment would produce NaN or Infinity\r\n"
7758        );
7759    }
7760
7761    /// Every one of these was read off a running 8.8. They are the answers a
7762    /// client library's own test suite checks, and the shapes are not
7763    /// guessable: `DIGEST` is hexadecimal in a bulk string, `MSETEX` is an
7764    /// integer, `INCREX` is a pair.
7765    #[test]
7766    fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
7767        let mut f = Fixture::new();
7768        assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
7769        // The same digest a real 8.8 answers for the same five bytes, which is
7770        // what makes `IFDEQ` usable against a mixed deployment.
7771        assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
7772        assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
7773        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
7774        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
7775        assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
7776        assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
7777        assert_eq!(
7778            f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
7779            "*2\r\n:1\r\n:0\r\n",
7780            "a refused increment reports the value it left alone and applied nothing"
7781        );
7782        assert_eq!(
7783            f.run(&[
7784                b"INCREX",
7785                b"n",
7786                b"BYINT",
7787                b"5",
7788                b"UBOUND",
7789                b"3",
7790                b"SATURATE"
7791            ]),
7792            "*2\r\n:3\r\n:2\r\n"
7793        );
7794        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
7795        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
7796    }
7797
7798    #[test]
7799    fn the_same_answers_come_out_in_resp3_spelling() {
7800        let mut f = Fixture::new();
7801        assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
7802        assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
7803        // A float counter is a double on RESP3 and the digits in a bulk string
7804        // on RESP2, and `INCRBYFLOAT` is a bulk string on both.
7805        assert_eq!(
7806            f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
7807            "*2\r\n,1.5\r\n,1.5\r\n"
7808        );
7809        assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
7810        // `RESET` puts the protocol back, which is the part that is easy to
7811        // miss and leaves a pooled connection speaking the wrong one.
7812        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
7813        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
7814    }
7815
7816    #[test]
7817    fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
7818        let mut f = Fixture::new();
7819        let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
7820        assert_eq!(flow, Flow::Continue);
7821        assert_eq!(
7822            reply,
7823            "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
7824        );
7825        // A name with a line ending in it cannot write its own frame into the
7826        // stream, which is the reason the error writer maps them to spaces.
7827        let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
7828        assert_eq!(reply.matches("\r\n").count(), 1);
7829    }
7830
7831    #[test]
7832    fn arity_is_checked_before_the_command_is() {
7833        let mut f = Fixture::new();
7834        assert_eq!(
7835            f.run(&[b"GET"]),
7836            "-ERR wrong number of arguments for 'get' command\r\n"
7837        );
7838        assert_eq!(
7839            f.run(&[b"MSET", b"k"]),
7840            "-ERR wrong number of arguments for 'mset' command\r\n"
7841        );
7842        // The table says `PING` takes one or more and a real server then
7843        // refuses three, which is the sort of thing that only shows up against
7844        // the real thing.
7845        assert_eq!(
7846            f.run(&[b"PING", b"a", b"b"]),
7847            "-ERR wrong number of arguments for 'ping' command\r\n"
7848        );
7849        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
7850        assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
7851        // `DELEX` takes two or four and nothing between.
7852        assert_eq!(
7853            f.run(&[b"DELEX", b"k", b"IFEQ"]),
7854            "-ERR wrong number of arguments for 'delex' command\r\n"
7855        );
7856    }
7857
7858    /// The option rules, all of them measured against 8.8 rather than read off
7859    /// the documentation. The surprising one is that `SET` accepts the same
7860    /// keyword twice and `INCREX` does not.
7861    #[test]
7862    fn the_option_combinations_are_the_ones_a_real_server_accepts() {
7863        let mut f = Fixture::new();
7864        let syntax = "-ERR syntax error\r\n";
7865        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
7866        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
7867        assert_eq!(
7868            f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
7869            syntax
7870        );
7871        assert_eq!(
7872            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
7873            syntax
7874        );
7875        assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
7876        // Twice is fine, and the last one wins.
7877        assert_eq!(
7878            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
7879            "+OK\r\n"
7880        );
7881        assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
7882        assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
7883        // `INCREX` refuses what `SET` allows.
7884        assert_eq!(
7885            f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
7886            syntax
7887        );
7888        assert_eq!(
7889            f.run(&[b"INCREX", b"n", b"ENX"]),
7890            "-ERR ENX flag requires an expiration\r\n"
7891        );
7892        assert_eq!(
7893            f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
7894            "-ERR UBOUND is not an integer or out of range\r\n"
7895        );
7896        assert_eq!(
7897            f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
7898            "-ERR LBOUND can't be greater than UBOUND\r\n"
7899        );
7900        assert_eq!(
7901            f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
7902            "-ERR If you want both the length and indexes, please just use IDX.\r\n"
7903        );
7904    }
7905
7906    /// Where the expiration rules bite. The one worth the test is `GETEX` on a
7907    /// key that is not there, which answers null without ever looking at the
7908    /// expiration it was given.
7909    #[test]
7910    fn the_expiry_rules_are_redis_own() {
7911        let mut f = Fixture::new();
7912        let bad = "-ERR invalid expire time in 'set' command\r\n";
7913        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
7914        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
7915        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
7916        assert_eq!(
7917            f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
7918            bad
7919        );
7920        assert_eq!(
7921            f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
7922            "-ERR value is not an integer or out of range\r\n"
7923        );
7924        assert_eq!(
7925            f.run(&[b"SETEX", b"k", b"0", b"v"]),
7926            "-ERR invalid expire time in 'setex' command\r\n"
7927        );
7928        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
7929        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
7930        assert_eq!(
7931            f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
7932            "-ERR syntax error\r\n",
7933            "the option list is still checked before the key is looked up"
7934        );
7935        // A deadline in the past is accepted and the key goes with it.
7936        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
7937        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
7938        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
7939    }
7940
7941    #[test]
7942    fn mset_takes_its_pairs_from_the_read_buffer() {
7943        let mut f = Fixture::new();
7944        assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
7945        assert_eq!(
7946            f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
7947            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
7948        );
7949        assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
7950        assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
7951        assert_eq!(
7952            f.run(&[b"MSETEX", b"2", b"e", b"5"]),
7953            "-ERR wrong number of key-value pairs\r\n"
7954        );
7955        assert_eq!(
7956            f.run(&[b"MSETEX", b"0", b"e", b"5"]),
7957            "-ERR invalid numkeys value\r\n"
7958        );
7959        assert_eq!(
7960            f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
7961            "-ERR invalid numkeys value\r\n"
7962        );
7963    }
7964
7965    #[test]
7966    fn lcs_answers_the_length_the_string_and_the_runs() {
7967        let mut f = Fixture::new();
7968        f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
7969        assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
7970        assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
7971        assert_eq!(
7972            f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
7973            "*4\r\n$7\r\nmatches\r\n*1\r\n*2\r\n*2\r\n:4\r\n:7\r\n*2\r\n:5\r\n:8\r\n$3\r\nlen\r\n:6\r\n"
7974        );
7975        // Without `IDX` the two options that only mean something with it are
7976        // accepted and ignored, which is what a real server does.
7977        assert_eq!(
7978            f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
7979            "$6\r\nmytext\r\n"
7980        );
7981    }
7982
7983    #[test]
7984    fn select_moves_the_connection_and_the_databases_stay_apart() {
7985        let mut f = Fixture::new();
7986        f.run(&[b"SET", b"k", b"zero"]);
7987        assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
7988        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
7989        f.run(&[b"SET", b"k", b"four"]);
7990        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
7991        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
7992        assert_eq!(
7993            f.run(&[b"SELECT", b"99"]),
7994            "-ERR DB index is out of range\r\n"
7995        );
7996        assert_eq!(
7997            f.run(&[b"SELECT", b"-1"]),
7998            "-ERR DB index is out of range\r\n"
7999        );
8000        assert_eq!(
8001            f.run(&[b"SELECT", b"abc"]),
8002            "-ERR value is not an integer or out of range\r\n"
8003        );
8004        // `RESET` brings it back to zero.
8005        f.run(&[b"SELECT", b"4"]);
8006        f.run(&[b"RESET"]);
8007        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
8008    }
8009
8010    #[test]
8011    fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
8012        let mut f = Fixture::new();
8013        let reply = f.run(&[b"HELLO"]);
8014        assert!(reply.starts_with("*14\r\n"), "{reply}");
8015        assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
8016        assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
8017        assert!(
8018            reply.contains(":7\r\n"),
8019            "the connection id is in there: {reply}"
8020        );
8021        assert_eq!(
8022            f.run(&[b"HELLO", b"4"]),
8023            "-NOPROTO unsupported protocol version\r\n"
8024        );
8025        assert_eq!(
8026            f.run(&[b"HELLO", b"abc"]),
8027            "-ERR Protocol version is not an integer or out of range\r\n"
8028        );
8029        assert_eq!(
8030            f.run(&[b"HELLO", b"3", b"SETNAME"]),
8031            "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
8032        );
8033        assert!(
8034            f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
8035                .starts_with("%7\r\n")
8036        );
8037        assert_eq!(f.session.name(), b"bob");
8038        f.run(&[b"RESET"]);
8039        assert_eq!(f.session.name(), b"");
8040    }
8041
8042    #[test]
8043    fn command_describes_this_server_in_the_shape_a_driver_reads() {
8044        let mut f = Fixture::new();
8045        let count = format!(":{}\r\n", COMMANDS.len());
8046        assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
8047        let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
8048        assert_eq!(
8049            info,
8050            "*1\r\n*10\r\n$3\r\nget\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n\
8051             *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*1\r\n*6\r\n\
8052             $5\r\nflags\r\n*2\r\n+RO\r\n+access\r\n\
8053             $12\r\nbegin_search\r\n*4\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
8054             *2\r\n$5\r\nindex\r\n:1\r\n\
8055             $9\r\nfind_keys\r\n*4\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
8056             *6\r\n$7\r\nlastkey\r\n:0\r\n$7\r\nkeystep\r\n:1\r\n$5\r\nlimit\r\n:0\r\n\
8057             *0\r\n"
8058        );
8059        // A null in the list, and the plain one: `$-1` and not `*-1`.
8060        assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
8061        assert_eq!(
8062            f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
8063            "*1\r\n$8\r\ngetrange\r\n"
8064        );
8065        assert_eq!(
8066            f.run(&[b"COMMAND", b"NOPE"]),
8067            "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
8068        );
8069    }
8070
8071    /// A cluster aware client asks this question and then routes on the
8072    /// answer, so `MSETEX`, whose keys are not where the table says, is the one
8073    /// that matters.
8074    #[test]
8075    fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
8076        let mut f = Fixture::new();
8077        assert_eq!(
8078            f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
8079            "*1\r\n$1\r\nk\r\n"
8080        );
8081        assert_eq!(
8082            f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
8083            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
8084        );
8085        assert_eq!(
8086            f.run(&[
8087                b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
8088            ]),
8089            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
8090        );
8091        assert_eq!(
8092            f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
8093            "-ERR The command has no key arguments\r\n"
8094        );
8095        assert_eq!(
8096            f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
8097            "-ERR Invalid number of arguments specified for command\r\n"
8098        );
8099    }
8100
8101    #[test]
8102    fn config_answers_what_it_can_and_refuses_what_it_cannot() {
8103        let mut f = Fixture::new();
8104        assert_eq!(
8105            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
8106            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
8107        );
8108        // A pattern matches more than one, and a setting two patterns both ask
8109        // for is still sent once.
8110        let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
8111        assert!(both.starts_with("*6\r\n"), "{both}");
8112        assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
8113        assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
8114        assert_eq!(
8115            f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
8116            "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
8117        );
8118        assert_eq!(
8119            f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
8120            "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
8121        );
8122        assert_eq!(
8123            f.run(&[b"CONFIG", b"GET"]),
8124            "-ERR wrong number of arguments for 'config|get' command\r\n"
8125        );
8126        // Too few arguments and an odd number of them are different
8127        // complaints, which is the sort of thing only the real server tells
8128        // you.
8129        assert_eq!(
8130            f.run(&[b"CONFIG", b"SET", b"appendonly"]),
8131            "-ERR wrong number of arguments for 'config|set' command\r\n"
8132        );
8133        assert_eq!(
8134            f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
8135            "-ERR syntax error\r\n"
8136        );
8137        assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
8138        assert_eq!(
8139            f.run(&[b"CONFIG", b"REWRITE"]),
8140            "-ERR The server is running without a config file\r\n"
8141        );
8142    }
8143
8144    /// A name is matched without regard to case, in both of the two ways a name
8145    /// can be given. This was case sensitive and a real server is not, so
8146    /// `CONFIG GET MAXMEMORY` answered nothing at all.
8147    ///
8148    /// And the name it answers under is the one the client spelled when they
8149    /// spelled it out, and its own when they gave a pattern, which is the shape
8150    /// of upstream's code rather than a decision it made.
8151    #[test]
8152    fn a_setting_is_found_whatever_case_it_is_asked_for_in() {
8153        let mut f = Fixture::new();
8154        assert_eq!(
8155            f.run(&[b"CONFIG", b"GET", b"MAXMEMORY"]),
8156            "*2\r\n$9\r\nMAXMEMORY\r\n$1\r\n0\r\n"
8157        );
8158        let starred = f.run(&[b"CONFIG", b"GET", b"MAXMEM*"]);
8159        assert!(starred.starts_with("*6\r\n"), "{starred}");
8160        assert!(starred.contains("maxmemory"), "{starred}");
8161        assert!(!starred.contains("MAXMEM"), "{starred}");
8162        // The first argument that matches decides, because upstream has the
8163        // setting in its match table by the time it looks at the second.
8164        assert_eq!(
8165            f.run(&[b"CONFIG", b"GET", b"MAXMEMORY", b"maxmemory"]),
8166            "*2\r\n$9\r\nMAXMEMORY\r\n$1\r\n0\r\n"
8167        );
8168        let both = f.run(&[b"CONFIG", b"GET", b"maxmem*", b"MAXMEMORY"]);
8169        assert!(both.starts_with("*6\r\n"), "{both}");
8170        assert!(!both.contains("MAXMEMORY"), "{both}");
8171    }
8172
8173    /// The four settings a slot migration runs under, two of which a pattern
8174    /// finds and two of which only their own name does.
8175    #[test]
8176    fn the_migration_settings_read_and_write_like_the_reference() {
8177        let mut f = Fixture::new();
8178        let group = f.run(&[b"CONFIG", b"GET", b"cluster-slot-migration-*"]);
8179        assert!(group.starts_with("*4\r\n"), "{group}");
8180        assert!(group.contains("handoff-max-lag-bytes"), "{group}");
8181        assert!(group.contains("write-pause-timeout"), "{group}");
8182        assert!(!group.contains("max-archived-tasks"), "{group}");
8183        assert!(!group.contains("sync-buffer-drain-timeout"), "{group}");
8184        // Hidden means a pattern does not find it, not that it is not there.
8185        assert_eq!(
8186            f.run(&[
8187                b"CONFIG",
8188                b"GET",
8189                b"cluster-slot-migration-max-archived-tasks"
8190            ]),
8191            "*2\r\n$41\r\ncluster-slot-migration-max-archived-tasks\r\n$2\r\n32\r\n"
8192        );
8193        // The one that counts bytes takes a unit and reads back as a plain
8194        // number of bytes, the same way `maxmemory` does.
8195        assert_eq!(
8196            f.run(&[
8197                b"CONFIG",
8198                b"SET",
8199                b"cluster-slot-migration-handoff-max-lag-bytes",
8200                b"2mb"
8201            ]),
8202            "+OK\r\n"
8203        );
8204        assert_eq!(
8205            f.run(&[
8206                b"CONFIG",
8207                b"GET",
8208                b"cluster-slot-migration-handoff-max-lag-bytes"
8209            ]),
8210            "*2\r\n$44\r\ncluster-slot-migration-handoff-max-lag-bytes\r\n$7\r\n2097152\r\n"
8211        );
8212        // And the three that count something else do not take one.
8213        assert_eq!(
8214            f.run(&[
8215                b"CONFIG",
8216                b"SET",
8217                b"cluster-slot-migration-write-pause-timeout",
8218                b"10s"
8219            ]),
8220            "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-write-pause-timeout') - argument couldn't be parsed into an integer\r\n"
8221        );
8222        assert_eq!(
8223            f.run(&[
8224                b"CONFIG",
8225                b"SET",
8226                b"cluster-slot-migration-handoff-max-lag-bytes",
8227                b"-1"
8228            ]),
8229            "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-handoff-max-lag-bytes') - argument must be a memory value\r\n"
8230        );
8231        assert_eq!(
8232            f.run(&[
8233                b"CONFIG",
8234                b"SET",
8235                b"cluster-slot-migration-write-pause-timeout",
8236                b"-1"
8237            ]),
8238            "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-write-pause-timeout') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
8239        );
8240        // The archived count is the only one with a ceiling, because it is an
8241        // int on the other side and the rest are a long long.
8242        assert_eq!(
8243            f.run(&[
8244                b"CONFIG",
8245                b"SET",
8246                b"cluster-slot-migration-max-archived-tasks",
8247                b"0"
8248            ]),
8249            "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-max-archived-tasks') - argument must be between 1 and 2147483647 inclusive\r\n"
8250        );
8251        assert_eq!(
8252            f.run(&[
8253                b"CONFIG",
8254                b"SET",
8255                b"cluster-slot-migration-max-archived-tasks",
8256                b"2147483648"
8257            ]),
8258            "-ERR CONFIG SET failed (possibly related to argument 'cluster-slot-migration-max-archived-tasks') - argument must be between 1 and 2147483647 inclusive\r\n"
8259        );
8260    }
8261
8262    #[test]
8263    fn the_eviction_policy_reads_back_what_was_written_to_it() {
8264        let mut f = Fixture::new();
8265        assert_eq!(
8266            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
8267            "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
8268        );
8269        assert_eq!(
8270            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
8271            "+OK\r\n",
8272            "the name is matched without regard to case, like every other one"
8273        );
8274        assert_eq!(
8275            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
8276            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
8277        );
8278        // And INFO agrees with CONFIG, which it did not when it was a literal.
8279        assert!(
8280            f.run(&[b"INFO", b"memory"])
8281                .contains("maxmemory_policy:allkeys-lfu"),
8282            "INFO and CONFIG disagree about the policy"
8283        );
8284        // The refusal names every legal value in the order the real server's
8285        // enum table lists them, because a client comparing the message compares
8286        // the whole string.
8287        assert_eq!(
8288            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
8289            "-ERR CONFIG SET failed (possibly related to argument 'maxmemory-policy') - argument(s) must be one of the following: volatile-lru, volatile-lfu, volatile-random, volatile-ttl, volatile-lrm, allkeys-lru, allkeys-lfu, allkeys-random, allkeys-lrm, noeviction\r\n"
8290        );
8291        // A bad pair leaves the good one in the same command alone, and the
8292        // policy is checked by the same pass that checks the numbers.
8293        assert_eq!(
8294            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
8295            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
8296        );
8297        f.run(&[
8298            b"CONFIG",
8299            b"SET",
8300            b"hash-max-listpack-entries",
8301            b"7",
8302            b"maxmemory-policy",
8303            b"nonsense",
8304        ]);
8305        assert_eq!(
8306            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
8307            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
8308        );
8309    }
8310
8311    #[test]
8312    fn the_three_eviction_numbers_read_back_too() {
8313        let mut f = Fixture::new();
8314        for (name, default, set) in [
8315            ("maxmemory-samples", "5", "12"),
8316            ("lfu-log-factor", "10", "3"),
8317            ("lfu-decay-time", "1", "60"),
8318        ] {
8319            let get = || {
8320                format!(
8321                    "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
8322                    name.len(),
8323                    default.len()
8324                )
8325            };
8326            assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
8327            assert_eq!(
8328                f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
8329                "+OK\r\n"
8330            );
8331            assert_eq!(
8332                f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
8333                format!(
8334                    "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
8335                    name.len(),
8336                    set.len()
8337                )
8338            );
8339            // A number that is not a number is refused with the same sentence
8340            // every other number gets, which names the setting the client typed.
8341            assert_eq!(
8342                f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
8343                format!(
8344                    "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
8345                )
8346            );
8347        }
8348    }
8349
8350    #[test]
8351    fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
8352        let mut f = Fixture::new();
8353        assert_eq!(
8354            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
8355            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
8356            "no limit is the default"
8357        );
8358        // The pairing is Redis's and it is a trap: the bare letter is a power of
8359        // ten and the one with the b is a power of two.
8360        for (typed, bytes) in [
8361            (&b"1024"[..], "1024"),
8362            (b"1k", "1000"),
8363            (b"1kb", "1024"),
8364            (b"1M", "1000000"),
8365            (b"1Mb", "1048576"),
8366            (b"1gb", "1073741824"),
8367            (b"100mb", "104857600"),
8368        ] {
8369            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
8370            assert_eq!(
8371                f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
8372                format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
8373                "set {}",
8374                String::from_utf8_lossy(typed)
8375            );
8376        }
8377        assert!(
8378            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
8379            "the report agrees with the setting"
8380        );
8381
8382        // A unit nobody has heard of, and a negative number, which is not a very
8383        // large one however it is spelled.
8384        for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
8385            assert_eq!(
8386                f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
8387                "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
8388                "refused {}",
8389                String::from_utf8_lossy(bad)
8390            );
8391        }
8392        assert!(
8393            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
8394            "and the refusal left the old one alone"
8395        );
8396    }
8397
8398    #[test]
8399    fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
8400        let mut f = Fixture::new();
8401        f.run(&[b"SET", b"here", b"already"]);
8402        // A byte, which is under what an empty server holds, so nothing this
8403        // command could do would get it under. The default policy is
8404        // `noeviction`, so nothing is what it does.
8405        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
8406        assert_eq!(
8407            f.run(&[b"SET", b"k", b"v"]),
8408            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
8409        );
8410        assert_eq!(
8411            f.run(&[b"LPUSH", b"l", b"v"]),
8412            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
8413        );
8414        // Reading is allowed, and so is the one thing that would help.
8415        assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
8416        assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
8417        assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
8418
8419        // Taking the limit away lets the write through again.
8420        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
8421        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
8422    }
8423
8424    /// Not under Miri, for the reason in `filled`: what it is watching is a
8425    /// whole two megabyte segment going back, so the megabytes are the claim
8426    /// and there is no smaller version of it that says the same thing.
8427    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
8428    #[test]
8429    fn an_allkeys_policy_makes_room_instead_of_refusing() {
8430        let mut f = Fixture::new();
8431        let val = vec![b'v'; 256];
8432        for i in 0..24000u32 {
8433            let k = format!("key:{i:08}");
8434            f.run(&[b"SET", k.as_bytes(), &val]);
8435        }
8436        let full = f.server.memory_bytes();
8437        assert!(
8438            full > 3 * 1024 * 1024,
8439            "the arena is several segments: {full}"
8440        );
8441
8442        // Two megabytes under what it is holding, which is one segment's worth,
8443        // so getting there means giving a whole segment back and not just
8444        // dropping a few records.
8445        let limit = full - 2 * 1024 * 1024;
8446        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
8447        f.run(&[
8448            b"CONFIG",
8449            b"SET",
8450            b"maxmemory",
8451            limit.to_string().as_bytes(),
8452        ]);
8453
8454        // Writes keep working the whole way down. The budget means one command
8455        // does not do it all, so this runs until the server has settled and
8456        // checks that nothing was refused on the way.
8457        for i in 0..2000u32 {
8458            let k = format!("new:{i:08}");
8459            assert_eq!(
8460                f.run(&[b"SET", k.as_bytes(), &val]),
8461                "+OK\r\n",
8462                "write {i} was refused"
8463            );
8464            f.server.refresh_memory();
8465            if f.server.memory_bytes() <= limit {
8466                break;
8467            }
8468        }
8469        assert!(
8470            f.server.memory_bytes() <= limit,
8471            "it never got under: {} against {limit}",
8472            f.server.memory_bytes()
8473        );
8474        let info = f.run(&[b"INFO", b"stats"]);
8475        assert!(!info.contains("evicted_keys:0"), "{info}");
8476        assert!(
8477            f.run(&[b"DBSIZE"]) != ":0\r\n",
8478            "and it did not empty the database to get there"
8479        );
8480    }
8481
8482    /// Not under Miri. Every round is eleven commands over six collections
8483    /// holding two hundred byte values, which is a third of a second each
8484    /// interpreted, and the rounds cannot come down far: one in seven takes an
8485    /// entry back out, so under about a hundred and seventy of them the
8486    /// collections never reach the hundred and twenty eight entries where the
8487    /// small representations give up and become the big ones, and a
8488    /// representation changing under the running total is one of the five
8489    /// things this is here to watch. What is left is an hour, for an accounting
8490    /// claim rather than a safety one, and the commands it sends are sent a few
8491    /// at a time by the tests around it.
8492    #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
8493    #[test]
8494    fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
8495        // The limit is judged against a number kept as the collections move,
8496        // rather than found by asking all of them, and the two have to be the
8497        // same number or the limit is enforced against a fiction. This does the
8498        // things that move it, which is growing a collection, shrinking one,
8499        // changing its representation, deleting it and reusing its slot, across
8500        // all five types, and checks the two against each other as it goes.
8501        let mut f = Fixture::new();
8502        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8503        let big = vec![b'v'; 200];
8504
8505        for i in 0..400u32 {
8506            let n = i.to_string();
8507            let n = n.as_bytes();
8508            f.run(&[b"SADD", b"s", n]);
8509            f.run(&[b"SADD", b"s2", &big]);
8510            f.run(&[b"HSET", b"h", n, &big]);
8511            f.run(&[b"RPUSH", b"l", &big]);
8512            f.run(&[b"ZADD", b"z", n, n]);
8513            f.run(&[b"ARSET", b"a", n, &big]);
8514            if i % 7 == 0 {
8515                f.run(&[b"SREM", b"s", n]);
8516                f.run(&[b"HDEL", b"h", n]);
8517                f.run(&[b"LPOP", b"l"]);
8518                f.run(&[b"ZREM", b"z", n]);
8519                f.run(&[b"ARDEL", b"a", n]);
8520            }
8521            if i % 53 == 0 {
8522                // Every type deleted and made again, so a slot goes on the free
8523                // list and comes back holding something else.
8524                f.run(&[b"DEL", b"s2"]);
8525            }
8526            assert_eq!(
8527                f.server.settled_memory(),
8528                f.server.memory_bytes(),
8529                "after round {i}"
8530            );
8531        }
8532
8533        // The run has to have built something, or the two numbers agreeing is
8534        // two zeroes agreeing.
8535        assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
8536        assert!(
8537            f.server.memory_bytes() > 512 * 1024,
8538            "{}",
8539            f.server.memory_bytes()
8540        );
8541
8542        // And it survives the collections going away entirely.
8543        f.run(&[b"FLUSHALL"]);
8544        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
8545    }
8546
8547    #[test]
8548    fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
8549        // A server with no limit does not keep the running total, so setting a
8550        // limit on a database that is already full has to start it from a walk.
8551        // If it did not, the first reading would be zero and the server would
8552        // think it had all the room in the world.
8553        let mut f = Fixture::new();
8554        for i in 0..200u32 {
8555            let n = i.to_string();
8556            f.run(&[b"SADD", b"s", n.as_bytes()]);
8557            f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
8558        }
8559        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8560        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
8561
8562        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
8563        for i in 200..400u32 {
8564            let n = i.to_string();
8565            f.run(&[b"SADD", b"s", n.as_bytes()]);
8566        }
8567        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8568        assert_eq!(
8569            f.server.settled_memory(),
8570            f.server.memory_bytes(),
8571            "the writes it was not watching are in the number it started from"
8572        );
8573    }
8574
8575    /// A reading adds up sixteen databases and only weighs the ones that moved,
8576    /// so the ones it did not weigh have to be in the total at what they were
8577    /// holding when it last did.
8578    ///
8579    /// The walk is the number this is checked against, because the walk is what
8580    /// the server actually holds. Getting this wrong in the direction that
8581    /// forgets a database is a limit enforced against a fiction, and in the other
8582    /// direction it is a server evicting keys to get under a number it is already
8583    /// under.
8584    #[test]
8585    fn a_database_nobody_has_touched_is_still_in_the_total() {
8586        let mut f = Fixture::new();
8587        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8588        f.run(&[b"SELECT", b"3"]);
8589        for i in 0..200u32 {
8590            let n = i.to_string();
8591            f.run(&[b"SADD", b"s", n.as_bytes()]);
8592        }
8593        f.run(&[b"SELECT", b"0"]);
8594        for i in 0..200u32 {
8595            let k = format!("key:{i}");
8596            f.run(&[b"SET", k.as_bytes(), b"value"]);
8597        }
8598
8599        // More readings in a row than there are databases, with nothing running
8600        // in between, so every one of them after the first is answering mostly
8601        // out of what it remembers.
8602        for turn in 0..DATABASES * 2 {
8603            assert_eq!(
8604                f.server.settled_memory(),
8605                f.server.memory_bytes(),
8606                "reading {turn}"
8607            );
8608        }
8609
8610        // And a database that empties while it is not the selected one is not
8611        // still counted at what it used to hold.
8612        f.run(&[b"SELECT", b"3"]);
8613        f.run(&[b"FLUSHDB"]);
8614        f.run(&[b"SELECT", b"0"]);
8615        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
8616    }
8617
8618    /// One database is weighed again on every reading whatever the marks say, so
8619    /// a change nothing marked is out of date for a while rather than for good.
8620    ///
8621    /// The marks are thrown away by hand here, which is what a path that changed
8622    /// a database and did not say so would leave behind. Sixteen readings is the
8623    /// worst case, because the cursor moves one database a reading.
8624    #[test]
8625    fn a_change_nothing_marked_is_found_within_a_turn_of_the_databases() {
8626        let mut f = Fixture::new();
8627        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
8628        f.server.settled_memory();
8629        f.run(&[b"SELECT", b"7"]);
8630        for i in 0..200u32 {
8631            let n = i.to_string();
8632            f.run(&[b"SADD", b"s", n.as_bytes()]);
8633        }
8634        f.server.mine().to_weigh();
8635
8636        let walk = f.server.memory_bytes();
8637        let mut readings = 0;
8638        while f.server.settled_memory() != walk {
8639            readings += 1;
8640            assert!(readings < DATABASES, "still out of date after {readings}");
8641        }
8642    }
8643
8644    /// The reading is taken once a millisecond and not once a batch, and the
8645    /// gate is what says so.
8646    ///
8647    /// A batch is a hundred nanoseconds, so the difference between the two is
8648    /// four orders of magnitude of walking the databases to be told the same
8649    /// number back.
8650    #[test]
8651    fn a_thread_takes_one_memory_reading_a_millisecond() {
8652        let f = Fixture::new();
8653        let mine = f.server.mine();
8654        assert!(mine.measuring(7));
8655        assert!(!mine.measuring(7));
8656        assert!(!mine.measuring(7));
8657        assert!(mine.measuring(8));
8658        assert!(!mine.measuring(8));
8659    }
8660
8661    #[test]
8662    fn evicted_keys_and_expired_keys_are_different_numbers() {
8663        let mut f = Fixture::new();
8664        // Nothing has been evicted and nothing can be under the default policy,
8665        // so this stays at zero while the other one moves.
8666        f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
8667        f.server.advance_clock_ms(20);
8668        f.run(&[b"GET", b"gone"]);
8669        let info = f.run(&[b"INFO", b"stats"]);
8670        assert!(info.contains("expired_keys:1"), "{info}");
8671        assert!(info.contains("evicted_keys:0"), "{info}");
8672    }
8673
8674    #[test]
8675    fn the_two_counters_count_the_reads_and_nothing_else() {
8676        let mut f = Fixture::new();
8677        f.run(&[b"SET", b"k", b"v"]);
8678        f.run(&[b"GET", b"k"]);
8679        f.run(&[b"GET", b"nope"]);
8680        f.run(&[b"EXISTS", b"k", b"nope"]);
8681        // The write at the top is not in either number, and the three reads
8682        // under it are, once for each key each of them names.
8683        let info = f.run(&[b"INFO", b"stats"]);
8684        assert!(info.contains("keyspace_hits:2"), "{info}");
8685        assert!(info.contains("keyspace_misses:2"), "{info}");
8686
8687        f.run(&[b"CONFIG", b"RESETSTAT"]);
8688        let info = f.run(&[b"INFO", b"stats"]);
8689        assert!(info.contains("keyspace_hits:0"), "{info}");
8690        assert!(info.contains("keyspace_misses:0"), "{info}");
8691    }
8692
8693    /// The shapes that look one key up more than once, which a real server
8694    /// counts once because it only looks once. See `misses::reading`.
8695    #[test]
8696    fn a_read_that_visits_its_key_twice_is_counted_once() {
8697        let mut f = Fixture::new();
8698        f.run(&[b"ZADD", b"z", b"1", b"m"]);
8699        f.run(&[b"ZRANGE", b"z", b"0", b"-1"]);
8700        f.run(&[b"ZMSCORE", b"z", b"m", b"gone", b"also gone"]);
8701        f.run(&[b"OBJECT", b"ENCODING", b"z"]);
8702        f.run(&[b"DUMP", b"z"]);
8703        let info = f.run(&[b"INFO", b"stats"]);
8704        assert!(info.contains("keyspace_hits:4"), "{info}");
8705        // A member that is not in the sorted set is not a miss. Only a key that
8706        // is not there is one.
8707        assert!(info.contains("keyspace_misses:0"), "{info}");
8708    }
8709
8710    /// A lookup on the way to a write is not a read, which is the other half of
8711    /// what `lookups::quiet` is for.
8712    #[test]
8713    fn the_key_a_read_writes_afterwards_is_not_counted() {
8714        let mut f = Fixture::new();
8715        f.run(&[b"SET", b"s", b"v"]);
8716        f.run(&[b"COPY", b"s", b"dst"]);
8717        f.run(&[b"GETEX", b"s", b"EX", b"100"]);
8718        f.run(&[b"BITOP", b"AND", b"into", b"s", b"nope"]);
8719        let info = f.run(&[b"INFO", b"stats"]);
8720        // The source of the copy, the key `GETEX` answers with, and one of the
8721        // two sources of the operation. The three destinations are written and
8722        // never read, so none of them is in here.
8723        assert!(info.contains("keyspace_hits:3"), "{info}");
8724        assert!(info.contains("keyspace_misses:1"), "{info}");
8725    }
8726
8727    #[test]
8728    fn the_object_subcommands_follow_the_policy() {
8729        let mut f = Fixture::new();
8730        f.run(&[b"SET", b"s", b"v"]);
8731        // Under the default the clock is kept and the counter is not, and under
8732        // an LFU policy it is the other way round. Each subcommand refuses on
8733        // the side where its reading of the three bytes means nothing.
8734        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
8735        assert!(
8736            f.run(&[b"OBJECT", b"FREQ", b"s"])
8737                .starts_with("-ERR An LFU maxmemory policy is not selected"),
8738        );
8739
8740        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
8741        assert!(
8742            f.run(&[b"OBJECT", b"IDLETIME", b"s"])
8743                .starts_with("-ERR An LFU maxmemory policy is selected"),
8744        );
8745        // The key was written under a clock policy, so what comes back is that
8746        // clock read as a counter. It is a number and not an error, which is the
8747        // point: switching at runtime does not invalidate anything, it only makes
8748        // the old field mean something else until the key is used again.
8749        assert!(
8750            f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
8751            "FREQ should answer under an LFU policy"
8752        );
8753    }
8754
8755    #[test]
8756    fn object_says_which_rung_of_the_ladder_a_key_is_on() {
8757        let mut f = Fixture::new();
8758        f.run(&[b"SET", b"s", b"hello"]);
8759        f.run(&[b"SET", b"n", b"123"]);
8760        f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
8761        f.run(&[b"SADD", b"ss", b"a", b"b"]);
8762        f.run(&[b"HSET", b"h", b"f", b"v"]);
8763        for (key, want) in [
8764            (b"s".as_slice(), "embstr"),
8765            (b"n", "int"),
8766            (b"si", "intset"),
8767            (b"ss", "listpack"),
8768            (b"h", "listpack"),
8769        ] {
8770            let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
8771            assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
8772        }
8773
8774        // A field deadline widens the blob rather than promoting it, and this
8775        // is the only place a client can see that happen.
8776        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
8777        assert_eq!(
8778            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
8779            "$10\r\nlistpackex\r\n"
8780        );
8781
8782        assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
8783        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
8784        assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
8785    }
8786
8787    #[test]
8788    fn object_answers_nil_for_a_key_that_is_not_there() {
8789        let mut f = Fixture::new();
8790        for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
8791            assert_eq!(
8792                f.run(&[b"OBJECT", sub, b"nokey"]),
8793                "$-1\r\n",
8794                "a nil and not an error, which is what 8.10.1 does"
8795            );
8796        }
8797        // And the key is looked up before FREQ has its complaint, so the
8798        // complaint only reaches a key that exists.
8799        f.run(&[b"SET", b"s", b"v"]);
8800        assert!(
8801            f.run(&[b"OBJECT", b"FREQ", b"s"])
8802                .starts_with("-ERR An LFU maxmemory policy is not"),
8803        );
8804        assert_eq!(
8805            f.run(&[b"OBJECT", b"NOPE", b"s"]),
8806            "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
8807        );
8808        assert_eq!(
8809            f.run(&[b"OBJECT", b"ENCODING"]),
8810            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
8811        );
8812        assert_eq!(
8813            f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
8814            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
8815        );
8816        assert_eq!(
8817            f.run(&[b"OBJECT"]),
8818            "-ERR wrong number of arguments for 'object' command\r\n"
8819        );
8820    }
8821
8822    #[test]
8823    fn memory_usage_counts_the_record_the_body_and_a_share_of_the_index() {
8824        let mut f = Fixture::new();
8825        assert_eq!(
8826            f.run(&[b"MEMORY", b"USAGE", b"nokey"]),
8827            "$-1\r\n",
8828            "a null and not an error, the same as OBJECT"
8829        );
8830        f.run(&[b"SET", b"s", b"hello"]);
8831        let small = int_of(&f.run(&[b"MEMORY", b"USAGE", b"s"]));
8832        assert!(
8833            small > 5,
8834            "the value is in there and so are the name and the header"
8835        );
8836        // A longer value under the same name costs more, and by about what the
8837        // extra bytes are, since a string lives in its own record.
8838        //
8839        // About and not exactly, because what is counted is the run the record
8840        // sits in and a run is rounded up to the arena's alignment. Two runs can
8841        // therefore differ by up to one more alignment than the values in them
8842        // do, which is what the slack at either end of this is for.
8843        f.run(&[b"SET", b"s", &[b'x'; 1000]]);
8844        let big = int_of(&f.run(&[b"MEMORY", b"USAGE", b"s"]));
8845        assert!(
8846            big - small >= 980 && big - small <= 1020,
8847            "{small} then {big}"
8848        );
8849        // A collection costs its body, so a set of a hundred members is worth
8850        // far more than a set of one.
8851        f.run(&[b"SADD", b"one", b"a"]);
8852        f.run(&[b"SADD", b"many", b"a"]);
8853        for i in 0..100u32 {
8854            f.run(&[b"SADD", b"many", format!("member:{i}").as_bytes()]);
8855        }
8856        assert!(
8857            int_of(&f.run(&[b"MEMORY", b"USAGE", b"many"]))
8858                > int_of(&f.run(&[b"MEMORY", b"USAGE", b"one"]))
8859        );
8860        // Asking twice gives the same answer, which is the property a sampled
8861        // estimate does not have.
8862        assert_eq!(
8863            f.run(&[b"MEMORY", b"USAGE", b"many"]),
8864            f.run(&[b"MEMORY", b"USAGE", b"many"])
8865        );
8866    }
8867
8868    #[test]
8869    fn memory_usage_reads_samples_and_does_not_use_it() {
8870        let mut f = Fixture::new();
8871        f.run(&[b"SET", b"s", b"v"]);
8872        let plain = f.run(&[b"MEMORY", b"USAGE", b"s"]);
8873        for count in [b"0".as_slice(), b"1", b"5", b"1000"] {
8874            assert_eq!(
8875                f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", count]),
8876                plain
8877            );
8878        }
8879        // The last one wins, which is what the reference's loop does rather
8880        // than something it decided to do.
8881        assert_eq!(
8882            f.run(&[
8883                b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"1", b"SAMPLES", b"2"
8884            ]),
8885            plain
8886        );
8887        assert_eq!(
8888            f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES"]),
8889            "-ERR syntax error\r\n"
8890        );
8891        assert_eq!(
8892            f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"-1"]),
8893            "-ERR syntax error\r\n"
8894        );
8895        assert_eq!(
8896            f.run(&[b"MEMORY", b"USAGE", b"s", b"SAMPLES", b"nine"]),
8897            "-ERR value is not an integer or out of range\r\n"
8898        );
8899        assert_eq!(
8900            f.run(&[b"MEMORY", b"USAGE", b"s", b"BAD", b"1"]),
8901            "-ERR syntax error\r\n"
8902        );
8903        assert_eq!(
8904            f.run(&[b"MEMORY", b"USAGE"]),
8905            "-ERR wrong number of arguments for 'memory|usage' command\r\n"
8906        );
8907    }
8908
8909    #[test]
8910    fn memory_stats_grows_a_field_for_every_database_holding_a_key() {
8911        let mut f = Fixture::new();
8912        assert!(
8913            f.run(&[b"MEMORY", b"STATS"]).starts_with("*72\r\n"),
8914            "thirty six pairs on a server nobody has written to"
8915        );
8916        f.run(&[b"SET", b"a", b"1"]);
8917        assert!(f.run(&[b"MEMORY", b"STATS"]).starts_with("*74\r\n"));
8918        f.run(&[b"SELECT", b"7"]);
8919        f.run(&[b"SET", b"b", b"2"]);
8920        let reply = f.run(&[b"MEMORY", b"STATS"]);
8921        assert!(reply.starts_with("*76\r\n"));
8922        assert!(reply.contains("\r\n$4\r\ndb.0\r\n"));
8923        assert!(reply.contains("\r\n$4\r\ndb.7\r\n"));
8924        // And the row for a database is the pair a real server puts there.
8925        assert!(reply.contains("overhead.hashtable.main"));
8926        assert!(reply.contains("overhead.hashtable.expires"));
8927        assert!(reply.contains("fragmentation.bytes"));
8928    }
8929
8930    #[test]
8931    fn memory_answers_the_four_that_only_look() {
8932        let mut f = Fixture::new();
8933        assert!(f.run(&[b"MEMORY", b"HELP"]).starts_with("*14\r\n+MEMORY "));
8934        assert_eq!(f.run(&[b"MEMORY", b"PURGE"]), "+OK\r\n");
8935        assert_eq!(
8936            f.run(&[b"MEMORY", b"MALLOC-STATS"]),
8937            "$45\r\nStats not supported for the current allocator\r\n"
8938        );
8939        // An empty server is one the doctor will not form an opinion about, and
8940        // it says so in Sam's own words.
8941        assert!(
8942            f.run(&[b"MEMORY", b"DOCTOR"])
8943                .contains("my issues detector can't be used in these conditions")
8944        );
8945        assert_eq!(
8946            f.run(&[b"MEMORY", b"NOPE"]),
8947            "-ERR unknown subcommand 'NOPE'. Try MEMORY HELP.\r\n"
8948        );
8949        for sub in [
8950            b"STATS".as_slice(),
8951            b"DOCTOR",
8952            b"PURGE",
8953            b"MALLOC-STATS",
8954            b"HELP",
8955        ] {
8956            let name = String::from_utf8_lossy(sub).to_lowercase();
8957            assert_eq!(
8958                f.run(&[b"MEMORY", sub, b"extra"]),
8959                format!("-ERR wrong number of arguments for 'memory|{name}' command\r\n"),
8960                "the subcommand is named and not the container"
8961            );
8962        }
8963        assert_eq!(
8964            f.run(&[b"MEMORY"]),
8965            "-ERR wrong number of arguments for 'memory' command\r\n"
8966        );
8967    }
8968
8969    #[test]
8970    fn command_getkeys_finds_the_key_memory_usage_names() {
8971        let mut f = Fixture::new();
8972        assert_eq!(
8973            f.run(&[b"COMMAND", b"GETKEYS", b"MEMORY", b"USAGE", b"k"]),
8974            "*1\r\n$1\r\nk\r\n"
8975        );
8976        // And the subcommands that name none say so rather than answering an
8977        // empty list.
8978        assert!(
8979            f.run(&[b"COMMAND", b"GETKEYS", b"MEMORY", b"DOCTOR"])
8980                .starts_with("-ERR ")
8981        );
8982    }
8983
8984    #[test]
8985    fn config_moves_the_ladder_and_object_encoding_agrees() {
8986        let mut f = Fixture::new();
8987        assert_eq!(
8988            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
8989            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
8990            "512 and not the 128 everyone remembers, which is what 8.10.1 says"
8991        );
8992        // The old spelling is the same number under a different name, and a
8993        // glob that catches both sends both.
8994        assert_eq!(
8995            f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
8996            "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
8997        );
8998        assert!(
8999            f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
9000                .starts_with("*8\r\n")
9001        );
9002        assert!(
9003            f.run(&[b"CONFIG", b"GET", b"set-max-*"])
9004                .starts_with("*6\r\n")
9005        );
9006
9007        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
9008        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
9009
9010        assert_eq!(
9011            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
9012            "+OK\r\n",
9013            "written under the old name and read back under the new one"
9014        );
9015        assert_eq!(
9016            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
9017            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
9018        );
9019        assert_eq!(
9020            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
9021            "$8\r\nlistpack\r\n",
9022            "the hash that already exists is left exactly where it was"
9023        );
9024        f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
9025        assert_eq!(
9026            f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
9027            "$9\r\nhashtable\r\n",
9028            "and the next one built goes straight to a table"
9029        );
9030
9031        // The set has three of these and all three move.
9032        f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
9033        f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
9034        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
9035        f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
9036        f.run(&[b"SADD", b"s2", b"abcdefgh"]);
9037        assert_eq!(
9038            f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
9039            "$9\r\nhashtable\r\n"
9040        );
9041    }
9042
9043    #[test]
9044    fn config_set_takes_all_of_the_ladder_or_none_of_it() {
9045        let mut f = Fixture::new();
9046        assert_eq!(
9047            f.run(&[
9048                b"CONFIG",
9049                b"SET",
9050                b"hash-max-listpack-entries",
9051                b"7",
9052                b"set-max-listpack-entries",
9053                b"abc"
9054            ]),
9055            "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
9056        );
9057        assert_eq!(
9058            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
9059            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
9060            "the pair in front of the bad one did not go in"
9061        );
9062        // The name in the complaint is the one that was typed, so the old
9063        // spelling comes back as the old spelling.
9064        assert_eq!(
9065            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
9066            "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
9067        );
9068        assert_eq!(
9069            f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
9070            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
9071        );
9072        // A number past what an i64 holds is the parse complaint and not the
9073        // range one, which is upstream reading it before it checks it.
9074        assert_eq!(
9075            f.run(&[
9076                b"CONFIG",
9077                b"SET",
9078                b"set-max-intset-entries",
9079                b"99999999999999999999"
9080            ]),
9081            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
9082        );
9083        assert_eq!(
9084            f.run(&[
9085                b"CONFIG",
9086                b"SET",
9087                b"set-max-intset-entries",
9088                b"9223372036854775807"
9089            ]),
9090            "+OK\r\n"
9091        );
9092    }
9093
9094    #[test]
9095    fn a_setting_moved_on_one_database_moved_on_all_of_them() {
9096        let mut f = Fixture::new();
9097        f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
9098        f.run(&[b"SELECT", b"3"]);
9099        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9100        assert_eq!(
9101            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
9102            "$9\r\nhashtable\r\n",
9103            "these are one server wide number in Redis, whatever a Keyspace carries"
9104        );
9105    }
9106
9107    #[test]
9108    fn info_reports_the_numbers_it_can_stand_behind() {
9109        let mut f = Fixture::new();
9110        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
9111        let all = f.run(&[b"INFO"]);
9112        assert!(all.contains("redis_version:8.8.0"), "{all}");
9113        assert!(
9114            all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
9115            "{all}"
9116        );
9117        assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
9118        assert!(all.contains("role:master"), "{all}");
9119        // One section is one section.
9120        let clients = f.run(&[b"INFO", b"clients"]);
9121        assert!(clients.contains("connected_clients:0"), "{clients}");
9122        assert!(!clients.contains("redis_version"), "{clients}");
9123        assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
9124    }
9125
9126    /// The sections a bare `INFO` gives back, and the ones you have to ask for.
9127    ///
9128    /// This is Redis's `unit/info-command` written against the fixture. Every
9129    /// assertion in it is one of theirs, in their order, and the two fields it
9130    /// turns on are the two that suite was failing on: `master_repl_offset`,
9131    /// which is in the default set, and `rejected_calls`, which is not.
9132    #[test]
9133    fn commandstats_is_asked_for_and_replication_is_not() {
9134        let mut f = Fixture::new();
9135        for arg in ["", "all", "default", "everything"] {
9136            let info = if arg.is_empty() {
9137                f.run(&[b"INFO"])
9138            } else {
9139                f.run(&[b"INFO", arg.as_bytes()])
9140            };
9141            assert!(info.contains("redis_version"), "{arg}: {info}");
9142            assert!(info.contains("used_cpu_user"), "{arg}: {info}");
9143            assert!(info.contains("used_memory"), "{arg}: {info}");
9144            assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
9145            let asked = arg == "all" || arg == "everything";
9146            assert_eq!(
9147                info.contains("rejected_calls"),
9148                asked,
9149                "{arg} should{} carry the command counters: {info}",
9150                if asked { "" } else { " not" }
9151            );
9152        }
9153
9154        let cpu = f.run(&[b"INFO", b"cpu"]);
9155        assert!(cpu.contains("used_cpu_user"), "{cpu}");
9156        assert!(!cpu.contains("used_memory"), "{cpu}");
9157
9158        // Their case, to make the point that a section name is not case
9159        // sensitive any more than a command name is.
9160        let stats = f.run(&[b"INFO", b"commandSTATS"]);
9161        assert!(!stats.contains("used_memory"), "{stats}");
9162        assert!(stats.contains("rejected_calls"), "{stats}");
9163
9164        // Two sections named, and neither of them pulls in a third.
9165        let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
9166        assert!(pair.contains("used_cpu_user"), "{pair}");
9167        assert!(!pair.contains("master_repl_offset"), "{pair}");
9168
9169        let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
9170        assert!(with_all.contains("used_memory"), "{with_all}");
9171        assert!(with_all.contains("master_repl_offset"), "{with_all}");
9172        assert!(with_all.contains("rejected_calls"), "{with_all}");
9173        // A section named twice is still written once.
9174        assert_eq!(
9175            with_all.matches("used_cpu_user_children").count(),
9176            1,
9177            "{with_all}"
9178        );
9179
9180        let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
9181        assert!(with_default.contains("used_memory"), "{with_default}");
9182        assert!(
9183            with_default.contains("master_repl_offset"),
9184            "{with_default}"
9185        );
9186        assert!(!with_default.contains("rejected_calls"), "{with_default}");
9187        assert_eq!(
9188            with_default.matches("used_cpu_user_children").count(),
9189            1,
9190            "{with_default}"
9191        );
9192    }
9193
9194    /// The threads section is the sum taken apart again.
9195    ///
9196    /// A connection belongs to the thread that accepted it for as long as it is
9197    /// open, so how the connections landed decides who does the work, and every
9198    /// other number in `INFO` adds the threads up before anybody sees it. This
9199    /// is the one place the split itself is visible. The test runs on one
9200    /// thread, so what it can show is that the section has a row per thread, and
9201    /// that the work it did all landed in one of them and adds back up to the
9202    /// total.
9203    #[test]
9204    fn the_threads_section_says_where_the_work_landed() {
9205        let mut server = Server::new();
9206        server.set_threads(4);
9207        let mut f = Fixture::on(server);
9208        for _ in 0..3 {
9209            f.run(&[b"PING"]);
9210        }
9211
9212        assert!(!f.run(&[b"INFO"]).contains("# Threads"));
9213        assert!(f.run(&[b"INFO", b"all"]).contains("# Threads"));
9214
9215        let info = f.run(&[b"INFO", b"threads"]);
9216        assert!(info.contains("io_threads:4"), "{info}");
9217        for at in 0..4 {
9218            assert!(info.contains(&format!("thread_{at}:clients=")), "{info}");
9219        }
9220        assert!(!info.contains("thread_4:"), "{info}");
9221
9222        let per = f.server.per_thread();
9223        assert_eq!(per.len(), 4);
9224        assert_eq!(
9225            per.iter().map(|t| t.commands).sum::<u64>(),
9226            f.server.totals().commands
9227        );
9228        assert_eq!(per.iter().filter(|t| t.commands > 0).count(), 1, "{per:?}");
9229    }
9230
9231    /// The three places the thread count is published all say the same number.
9232    ///
9233    /// `io_threads_active` and `io-threads` were both written down rather than
9234    /// read, so a server started with four threads told every client it had one
9235    /// and was not using it. A dashboard reading `INFO server` and a person
9236    /// reading `CONFIG GET` are asking the same question the `# Threads` section
9237    /// answers, and the three of them disagreeing is worse than any one of them
9238    /// being missing.
9239    #[test]
9240    fn the_thread_count_is_the_same_number_wherever_it_is_asked_for() {
9241        let mut f = Fixture::new();
9242        assert!(f.run(&[b"INFO", b"server"]).contains("io_threads_active:1"));
9243        assert_eq!(
9244            f.run(&[b"CONFIG", b"GET", b"io-threads"]),
9245            "*2\r\n$10\r\nio-threads\r\n$1\r\n1\r\n"
9246        );
9247
9248        let mut server = Server::new();
9249        server.set_threads(4);
9250        let mut f = Fixture::on(server);
9251        let info = f.run(&[b"INFO", b"all"]);
9252        assert!(info.contains("io_threads_active:4"), "{info}");
9253        assert!(info.contains("io_threads:4"), "{info}");
9254        assert_eq!(
9255            f.run(&[b"CONFIG", b"GET", b"io-threads"]),
9256            "*2\r\n$10\r\nio-threads\r\n$1\r\n4\r\n"
9257        );
9258        // Immutable the way the fixed settings are, so the write that changes
9259        // nothing is taken and every other one is refused.
9260        assert_eq!(f.run(&[b"CONFIG", b"SET", b"io-threads", b"4"]), "+OK\r\n");
9261        assert_eq!(
9262            f.run(&[b"CONFIG", b"SET", b"io-threads", b"1"]),
9263            "-ERR CONFIG SET failed (possibly related to argument 'io-threads') - can't set immutable config\r\n"
9264        );
9265        assert_eq!(
9266            f.run(&[b"CONFIG", b"SET", b"io-threads", b"lots"]),
9267            "-ERR CONFIG SET failed (possibly related to argument 'io-threads') - can't set immutable config\r\n"
9268        );
9269    }
9270
9271    /// The memory section says what this process may use, not what the machine
9272    /// has.
9273    ///
9274    /// The distinction is the whole point of it. A server inside a container
9275    /// that reports the host's memory is a server whose operator sizes it for
9276    /// memory it will be killed for touching, so all three numbers are there:
9277    /// what the machine has, what the cgroup allows, and the quarter of the
9278    /// tighter one that pools are sized from.
9279    #[test]
9280    fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
9281        let mut f = Fixture::new();
9282        let info = f.run(&[b"INFO", b"memory"]);
9283        for field in [
9284            "total_system_memory:",
9285            "mem_cgroup_limit:",
9286            "mem_limit:",
9287            "mem_budget:",
9288        ] {
9289            assert!(info.contains(field), "no {field} in {info}");
9290        }
9291
9292        let field = |name: &str| -> u64 {
9293            info.lines()
9294                .find_map(|l| l.strip_prefix(name))
9295                .unwrap_or_else(|| panic!("no {name} in {info}"))
9296                .trim()
9297                .parse()
9298                .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
9299        };
9300        let limit = field("mem_limit:");
9301        assert_eq!(field("mem_budget:"), limit / 4, "{info}");
9302        // Zero means there is no limit to report, which is a real answer on a
9303        // machine with no cgroups and no way to ask how big it is.
9304        if limit != 0 {
9305            let host = field("total_system_memory:");
9306            let cgroup = field("mem_cgroup_limit:");
9307            assert!(
9308                limit == host || limit == cgroup,
9309                "the limit came from neither number: {info}"
9310            );
9311        }
9312    }
9313
9314    /// The three counters, each on the path that raises it.
9315    ///
9316    /// `calls` on a command that worked, `failed_calls` on one that ran and
9317    /// answered with an error, and `rejected_calls` on one that never ran at
9318    /// all. The last two are the pair that is easy to collapse into one number
9319    /// and that Redis keeps apart, because a client sending the wrong number of
9320    /// arguments and a client asking for a list element that is not there are
9321    /// not the same problem.
9322    #[test]
9323    fn a_command_counts_what_it_did_separately_from_what_it_refused() {
9324        let mut f = Fixture::new();
9325        f.run(&[b"SET", b"k", b"v"]);
9326        f.run(&[b"SET", b"k", b"w"]);
9327        // Ran, and answered with an error, because `k` is not a list.
9328        f.run(&[b"LPUSH", b"k", b"x"]);
9329        // Never ran: `LPUSH` takes at least three arguments.
9330        f.run(&[b"LPUSH", b"k"]);
9331
9332        let stats = f.run(&[b"INFO", b"commandstats"]);
9333        assert!(
9334            stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
9335            "{stats}"
9336        );
9337        assert!(
9338            stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
9339            "{stats}"
9340        );
9341        assert!(
9342            !stats.contains("cmdstat_zadd"),
9343            "a command nobody has sent has no row: {stats}"
9344        );
9345    }
9346
9347    /// A cache that writes with a deadline and never reads back used to hold
9348    /// every key it had ever written, because lazy expiry needs somebody to walk
9349    /// past a key before it can reclaim it and nobody ever did.
9350    #[test]
9351    fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
9352        // Four thousand keys is four thousand trips through dispatch, and what
9353        // Miri charges for is trips rather than keys, so this was over five
9354        // minutes there. An eighth of each keeps everything the test is about,
9355        // which is three keys with a deadline for every one without and a
9356        // sweep that has to reclaim all of the first kind and none of the
9357        // second.
9358        let (dead, live) = if cfg!(miri) {
9359            (375, 125)
9360        } else {
9361            (3_000, 1_000)
9362        };
9363        let mut f = Fixture::new();
9364        for i in 0..dead {
9365            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
9366        }
9367        for i in 0..live {
9368            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
9369        }
9370        let all = format!(":{}\r\n", dead + live);
9371        assert_eq!(f.run(&[b"DBSIZE"]), all);
9372        f.advance(100);
9373        assert_eq!(
9374            f.run(&[b"DBSIZE"]),
9375            all,
9376            "DBSIZE counts records and nothing has read past the dead ones yet"
9377        );
9378
9379        // What the shard loop does, one slice at a time.
9380        let rest = format!(":{live}\r\n");
9381        let mut spent = 0;
9382        for _ in 0..2_000 {
9383            spent += f.server.expire_step(4096);
9384            if f.run(&[b"DBSIZE"]) == rest {
9385                break;
9386            }
9387        }
9388        assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
9389        assert!(
9390            f.run(&[b"INFO", b"stats"])
9391                .contains(&format!("expired_keys:{dead}"))
9392        );
9393        for i in 0..live {
9394            assert_eq!(
9395                f.run(&[b"GET", format!("k{i}").as_bytes()]),
9396                "$1\r\nv\r\n",
9397                "it took a key that had no deadline"
9398            );
9399        }
9400    }
9401
9402    #[test]
9403    fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
9404        // The keys are only here so that the database the sweep walks is not an
9405        // empty one. Two hundred of them fills as many slots as a sweep looks
9406        // at and is a tenth of the interpreted work.
9407        let n = if cfg!(miri) { 200 } else { 2_000 };
9408        let mut f = Fixture::new();
9409        for i in 0..n {
9410            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
9411        }
9412        assert_eq!(f.server.expire_step(4096), 0);
9413        // And one database having them does not make the other fifteen pay.
9414        f.run(&[b"SELECT", b"3"]);
9415        f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
9416        f.advance(100);
9417        for _ in 0..64 {
9418            f.server.expire_step(4096);
9419        }
9420        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9421        f.run(&[b"SELECT", b"0"]);
9422        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
9423        assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
9424    }
9425
9426    /// The gate, which is what stops a maintenance slice that runs every hundred
9427    /// nanoseconds from drawing a sample every hundred nanoseconds.
9428    #[test]
9429    fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
9430        let mut f = Fixture::new();
9431        for i in 0..500u32 {
9432            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
9433        }
9434        f.advance(100);
9435        let at = f.server.striped(0).now_ms();
9436        f.server.set_clock_ms(at);
9437        // A small budget, so that one slice cannot finish the job and a second
9438        // one having nothing to do would mean the gate and not an empty
9439        // database.
9440        assert!(f.server.expire_slice(8) > 0, "the first one works");
9441        for _ in 0..1_000 {
9442            assert_eq!(
9443                f.server.expire_slice(8),
9444                0,
9445                "the millisecond has not moved and neither should this"
9446            );
9447        }
9448        assert!(
9449            f.server.striped(0).expires() > 400,
9450            "there is plenty left to take"
9451        );
9452        f.server.set_clock_ms(at + 1);
9453        assert!(f.server.expire_slice(8) > 0, "and then it goes again");
9454    }
9455
9456    /// `expires=` used to be a hardcoded zero, which meant a dashboard watching
9457    /// how much of a cache is volatile was reading a constant.
9458    #[test]
9459    fn info_keyspace_counts_the_keys_that_have_a_deadline() {
9460        let mut f = Fixture::new();
9461        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
9462        assert!(
9463            f.run(&[b"INFO", b"keyspace"])
9464                .contains("db0:keys=3,expires=0"),
9465            "none of them has one yet"
9466        );
9467        f.run(&[b"EXPIRE", b"a", b"1000"]);
9468        f.run(&[b"EXPIRE", b"b", b"1000"]);
9469        let two = f.run(&[b"INFO", b"keyspace"]);
9470        assert!(two.contains("db0:keys=3,expires=2"), "{two}");
9471        f.run(&[b"PERSIST", b"a"]);
9472        f.run(&[b"DEL", b"b"]);
9473        let none = f.run(&[b"INFO", b"keyspace"]);
9474        assert!(none.contains("db0:keys=2,expires=0"), "{none}");
9475
9476        // Each database answers for itself, the way Redis reports it.
9477        f.run(&[b"SELECT", b"1"]);
9478        f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
9479        let both = f.run(&[b"INFO", b"keyspace"]);
9480        assert!(both.contains("db0:keys=2,expires=0"), "{both}");
9481        assert!(both.contains("db1:keys=1,expires=1"), "{both}");
9482    }
9483
9484    /// Not under Miri, which reads a zero on purpose because it has no
9485    /// `getrusage` to call, so the second half of this would burn a billion
9486    /// interpreted multiplications waiting for a number that is never going to
9487    /// move. The first half, that the section is there and has the fields Redis
9488    /// clients look for, is checked by the `INFO` tests above as well, and
9489    /// those do run there.
9490    #[cfg(unix)]
9491    #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
9492    #[test]
9493    fn info_cpu_reports_processor_time_that_was_really_measured() {
9494        let mut f = Fixture::new();
9495        let cpu = f.run(&[b"INFO", b"cpu"]);
9496        assert!(cpu.contains("# CPU"), "{cpu}");
9497        // Redis's unit/info-command asks for this one by name in three tests.
9498        assert!(cpu.contains("used_cpu_user:"), "{cpu}");
9499        assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
9500        assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
9501        assert!(!cpu.contains("redis_version"), "{cpu}");
9502
9503        // It is a measurement and not a constant, so it goes up when work
9504        // happens. A tight loop rather than a sleep, because sleeping is the
9505        // one thing that does not move this number.
9506        let before = used_cpu_user(&cpu);
9507        let mut n = 0u64;
9508        let mut rounds = 0;
9509        while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
9510            for i in 0..1_000_000u64 {
9511                n = n.wrapping_add(i.wrapping_mul(i));
9512            }
9513            rounds += 1;
9514            // A bound rather than a spin, so a platform where this number does
9515            // not move fails here instead of hanging. Even a clock with whole
9516            // millisecond granularity gets there in the first round or two.
9517            assert!(rounds < 1_000, "cpu time never moved, n is {n}");
9518        }
9519    }
9520
9521    /// Pull `used_cpu_user` back out of an `INFO cpu` reply.
9522    #[cfg(unix)]
9523    fn used_cpu_user(info: &str) -> f64 {
9524        info.lines()
9525            .find_map(|l| l.strip_prefix("used_cpu_user:"))
9526            .expect("no used_cpu_user in the reply")
9527            .trim()
9528            .parse()
9529            .expect("used_cpu_user is not a number")
9530    }
9531
9532    /// The safety net under the rule that a body checks its arguments before
9533    /// it writes anything. `MGET` writes its array header first and then reads
9534    /// each key, so if a later argument could fail the header would already be
9535    /// out. Nothing in the string group does that today and this is what would
9536    /// catch the first one that did.
9537    #[test]
9538    fn a_command_that_fails_leaves_nothing_half_written() {
9539        let mut f = Fixture::new();
9540        let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
9541        assert_eq!(reply, "-ERR offset is out of range\r\n");
9542        assert!(!reply.contains(':'), "no integer went out in front of it");
9543    }
9544
9545    #[test]
9546    fn quit_answers_first_and_closes_after() {
9547        let mut f = Fixture::new();
9548        let (flow, reply) = f.flow(&[b"QUIT"]);
9549        assert_eq!(reply, "+OK\r\n");
9550        assert_eq!(flow, Flow::Close);
9551    }
9552
9553    /// A server that has not been asked to stop is not stopping, and one that
9554    /// has says so without writing anything back.
9555    ///
9556    /// The empty reply is the point. Redis answers nothing at all here and the
9557    /// client sees the socket close, and an `OK` would be a promise from a
9558    /// process that is about to not exist.
9559    #[test]
9560    fn shutdown_writes_nothing_and_sets_the_flag() {
9561        let mut f = Fixture::new();
9562        assert!(!f.server.stopping(), "nobody has asked yet");
9563
9564        let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
9565        assert_eq!(reply, "");
9566        assert_eq!(flow, Flow::Close);
9567        assert!(f.server.stopping());
9568    }
9569
9570    /// Every flag combination 8.10.1 takes, and every one it refuses.
9571    ///
9572    /// The refusals are the half worth pinning down. `SAVE` and `NOSAVE`
9573    /// contradict each other, `ABORT` says to do nothing so it cannot be
9574    /// combined with a word about how to do it, and repeating any one of them
9575    /// is fine. All of it was read off a running 8.10.1 rather than worked out
9576    /// from the documentation, which does not say.
9577    ///
9578    /// The fixtures here save into a directory of their own because two of the
9579    /// combinations carry `SAVE`, and a test that writes a file into whatever
9580    /// directory the test runner happened to start in leaves it there.
9581    #[test]
9582    fn shutdown_takes_the_flags_redis_takes() {
9583        let s = Saves::new("shutdown-flags");
9584        for flags in [
9585            &[b"NOSAVE".as_slice()][..],
9586            &[b"SAVE"],
9587            &[b"NOW"],
9588            &[b"FORCE"],
9589            &[b"nosave"],
9590            &[b"NOW", b"NOW"],
9591            &[b"SAVE", b"SAVE"],
9592            &[b"NOSAVE", b"NOW", b"FORCE"],
9593        ] {
9594            let mut f = Fixture::new();
9595            f.server.set_dir(s.dir.clone());
9596            let mut parts = vec![b"SHUTDOWN".as_slice()];
9597            parts.extend_from_slice(flags);
9598            let (flow, reply) = f.flow(&parts);
9599            assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
9600            assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
9601            assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
9602        }
9603
9604        for flags in [
9605            &[b"BOGUS".as_slice()][..],
9606            &[b"SAVE", b"NOSAVE"],
9607            &[b"NOSAVE", b"SAVE"],
9608            &[b"ABORT", b"NOW"],
9609            &[b"NOSAVE", b"ABORT"],
9610            &[b"NOW", b"FORCE", b"ABORT"],
9611        ] {
9612            let mut f = Fixture::new();
9613            let mut parts = vec![b"SHUTDOWN".as_slice()];
9614            parts.extend_from_slice(flags);
9615            assert_eq!(
9616                f.run(&parts),
9617                "-ERR syntax error\r\n",
9618                "SHUTDOWN {flags:?} was accepted"
9619            );
9620            assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
9621        }
9622    }
9623
9624    /// `ABORT` has nothing to call off, ever.
9625    ///
9626    /// A shutdown here is decided and done inside one turn of the loop, so
9627    /// there is no window in which one is in progress. That makes Redis's
9628    /// message for a cancel with nothing to cancel the right answer every time
9629    /// rather than only when nothing happens to be pending. Two `ABORT`s is
9630    /// still one `ABORT`, which is what 8.10.1 does.
9631    #[test]
9632    fn shutdown_abort_never_has_anything_to_abort() {
9633        let mut f = Fixture::new();
9634        for parts in [
9635            &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
9636            &[b"SHUTDOWN", b"ABORT", b"ABORT"],
9637        ] {
9638            assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
9639            assert!(!f.server.stopping(), "an abort stopped the server");
9640        }
9641    }
9642
9643    /// A fixture whose server writes into a directory of its own.
9644    ///
9645    /// Every test here really writes files, because the whole point of the
9646    /// command is the files and a backup that is only a state machine would
9647    /// pass a test suite and fail the first person who tried to restore one.
9648    /// The directory carries the test's name so that the suite can run its
9649    /// tests in parallel the way it always does.
9650    struct Backups {
9651        f: Fixture,
9652        dir: PathBuf,
9653    }
9654
9655    impl Backups {
9656        fn new(name: &str) -> Backups {
9657            let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
9658            let _ = std::fs::remove_dir_all(&dir);
9659            std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
9660            let mut f = Fixture::new();
9661            f.server.set_dir(dir.clone());
9662            Backups { f, dir }
9663        }
9664
9665        fn run(&mut self, parts: &[&[u8]]) -> String {
9666            self.f.run(parts)
9667        }
9668
9669        /// The names in `backupdir`, sorted, so a test can say what is on disk.
9670        fn files(&self) -> Vec<String> {
9671            let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
9672                Ok(entries) => entries
9673                    .filter_map(|e| e.ok())
9674                    .map(|e| e.file_name().to_string_lossy().into_owned())
9675                    .collect(),
9676                Err(_) => Vec::new(),
9677            };
9678            names.sort();
9679            names
9680        }
9681
9682        fn read(&self, name: &str) -> Vec<u8> {
9683            std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
9684        }
9685    }
9686
9687    impl Drop for Backups {
9688        fn drop(&mut self) {
9689            let _ = std::fs::remove_dir_all(&self.dir);
9690        }
9691    }
9692
9693    /// The four states and the moves between them, in the order a client walks
9694    /// them, with the files checked at every step.
9695    #[test]
9696    fn backup_walks_the_states_the_reference_walks() {
9697        let mut b = Backups::new("states");
9698        let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
9699
9700        assert!(status(&mut b).contains("idle"));
9701        assert!(b.files().is_empty(), "an idle server has written a backup");
9702
9703        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
9704        assert!(status(&mut b).contains("incrementing"));
9705        assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
9706
9707        assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
9708        assert!(status(&mut b).contains("sealed"));
9709        assert_eq!(
9710            b.files(),
9711            [
9712                "appendonly.aof.1.base.rdb",
9713                "appendonly.aof.1.incr.aof",
9714                "appendonly.aof.manifest",
9715            ]
9716        );
9717
9718        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
9719        assert!(status(&mut b).contains("idle"));
9720        assert!(b.files().is_empty(), "cleanup left something behind");
9721    }
9722
9723    /// Every move that is refused, in the reference's words.
9724    #[test]
9725    fn backup_refuses_the_moves_the_reference_refuses() {
9726        let mut b = Backups::new("refusals");
9727
9728        assert_eq!(
9729            b.run(&[b"BACKUP", b"SEAL"]),
9730            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
9731        );
9732        assert_eq!(
9733            b.run(&[b"BACKUP", b"ABORT"]),
9734            "-ERR No backup in progress\r\n"
9735        );
9736        // Cleanup from idle is not an error, it is a way of saying there was
9737        // nothing to clean up.
9738        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
9739
9740        b.run(&[b"BACKUP", b"START"]);
9741        assert_eq!(
9742            b.run(&[b"BACKUP", b"START"]),
9743            "-ERR A backup is already in progress, ABORT it first\r\n"
9744        );
9745        assert_eq!(
9746            b.run(&[b"BACKUP", b"CLEANUP"]),
9747            "-ERR Backup is in progress\r\n"
9748        );
9749
9750        b.run(&[b"BACKUP", b"SEAL"]);
9751        assert_eq!(
9752            b.run(&[b"BACKUP", b"START"]),
9753            "-ERR A sealed backup exists, CLEANUP it first\r\n"
9754        );
9755        assert_eq!(
9756            b.run(&[b"BACKUP", b"SEAL"]),
9757            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
9758        );
9759        assert_eq!(
9760            b.run(&[b"BACKUP", b"ABORT"]),
9761            "-ERR No backup in progress\r\n"
9762        );
9763    }
9764
9765    /// An abort takes the base file away and leaves a state saying who did it.
9766    ///
9767    /// The next backup takes the next sequence number rather than reusing the
9768    /// one whose files were just thrown away, so a directory somebody copied a
9769    /// half finished backup out of cannot end up with two different files under
9770    /// one name.
9771    #[test]
9772    fn backup_abort_removes_the_file_and_says_who_did_it() {
9773        let mut b = Backups::new("abort");
9774        b.run(&[b"BACKUP", b"START"]);
9775        assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
9776
9777        let status = b.run(&[b"BACKUP", b"STATUS"]);
9778        assert!(status.contains("failed"), "{status}");
9779        assert!(status.contains("aborted by user"), "{status}");
9780        assert!(b.files().is_empty(), "abort left the base file behind");
9781        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
9782
9783        // A start from failed works, and is the second backup.
9784        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
9785        assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
9786        let status = b.run(&[b"BACKUP", b"STATUS"]);
9787        assert!(status.contains("incrementing"), "{status}");
9788        assert!(!status.contains("aborted"), "the old error was kept");
9789    }
9790
9791    /// `LIST` names nothing, then one file, then three, and they are absolute.
9792    #[test]
9793    fn backup_list_names_the_files_that_are_pinned_so_far() {
9794        let mut b = Backups::new("list");
9795        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
9796
9797        b.run(&[b"BACKUP", b"START"]);
9798        let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
9799        let base = base.to_string_lossy().into_owned();
9800        assert_eq!(
9801            b.run(&[b"BACKUP", b"LIST"]),
9802            format!("*1\r\n${}\r\n{base}\r\n", base.len())
9803        );
9804
9805        b.run(&[b"BACKUP", b"SEAL"]);
9806        let listed = b.run(&[b"BACKUP", b"LIST"]);
9807        assert!(listed.starts_with("*3\r\n"), "{listed}");
9808        // The order is the manifest's order, base then incremental then the
9809        // manifest itself, which is the order a restore needs them in.
9810        let names: Vec<&str> = listed
9811            .lines()
9812            .filter(|l| l.starts_with('/') || l.contains(":\\"))
9813            .collect();
9814        assert_eq!(names.len(), 3, "{listed}");
9815        assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
9816        assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
9817        assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
9818    }
9819
9820    /// The base file is the dataset as it was at `START` and not at `SEAL`.
9821    ///
9822    /// That is D-46 and it is the one thing about this a client can notice, so
9823    /// it is pinned here rather than left to be discovered by whoever restores
9824    /// one. The incremental file is empty for the same reason: there is no
9825    /// append only log underneath this server to copy the writes in between out
9826    /// of.
9827    #[test]
9828    fn a_backup_holds_the_dataset_as_it_was_at_start() {
9829        let mut b = Backups::new("contents");
9830        b.run(&[b"SET", b"bk", b"v1"]);
9831        b.run(&[b"BACKUP", b"START"]);
9832        b.run(&[b"SET", b"bk", b"v2"]);
9833        b.run(&[b"BACKUP", b"SEAL"]);
9834
9835        let base = b.read("appendonly.aof.1.base.rdb");
9836        assert!(base.starts_with(b"REDIS"), "not an RDB file");
9837        assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
9838        assert!(
9839            !base.windows(2).any(|w| w == b"v2"),
9840            "the base file moved on after START"
9841        );
9842        // The aux field a loader acts on, and the one that says this file is
9843        // the base of an append only file rather than a standalone dump. Its
9844        // value is the one byte string 1, which the encoder writes as an
9845        // integer the way a real server writes it.
9846        let at = base
9847            .windows(8)
9848            .position(|w| w == b"aof-base")
9849            .expect("no aof-base aux field");
9850        assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
9851
9852        assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
9853        assert_eq!(
9854            String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
9855            "file appendonly.aof.1.base.rdb seq 1 type b\n\
9856             file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
9857        );
9858    }
9859
9860    /// `STATUS` is a map of four pairs on RESP3 and the same pairs flat on
9861    /// RESP2, which is what every other map shaped reply in this server does.
9862    #[test]
9863    fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
9864        let mut b = Backups::new("status");
9865        b.f.server.set_clock_ms(1_700_000_000_000);
9866
9867        assert_eq!(
9868            b.run(&[b"BACKUP", b"STATUS"]),
9869            "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
9870             $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
9871        );
9872
9873        b.f.out = Out::new(Proto::Resp3);
9874        b.run(&[b"BACKUP", b"START"]);
9875        assert_eq!(
9876            b.run(&[b"BACKUP", b"STATUS"]),
9877            "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
9878             $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
9879        );
9880
9881        b.run(&[b"BACKUP", b"SEAL"]);
9882        let sealed = b.run(&[b"BACKUP", b"STATUS"]);
9883        assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
9884    }
9885
9886    /// A sealed backup that nobody cleans up goes away on its own once
9887    /// `backup-sealed-ttl` seconds have passed since the seal.
9888    #[test]
9889    fn a_sealed_backup_is_swept_away_after_the_timeout() {
9890        let mut b = Backups::new("ttl");
9891        b.f.server.set_clock_ms(1_000_000);
9892        assert_eq!(
9893            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
9894            "+OK\r\n"
9895        );
9896        b.run(&[b"BACKUP", b"START"]);
9897        b.run(&[b"BACKUP", b"SEAL"]);
9898
9899        // A minute short of the deadline, nothing happens.
9900        b.f.server.set_clock_ms(1_000_000 + 59_000);
9901        b.f.server.backup_expire();
9902        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
9903        assert_eq!(b.files().len(), 3);
9904
9905        b.f.server.set_clock_ms(1_000_000 + 60_000);
9906        b.f.server.backup_expire();
9907        let status = b.run(&[b"BACKUP", b"STATUS"]);
9908        assert!(status.contains("idle"), "{status}");
9909        assert!(b.files().is_empty(), "the timeout left the files behind");
9910
9911        // Zero is the default and means a sealed backup is kept for ever.
9912        b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
9913        b.run(&[b"BACKUP", b"START"]);
9914        b.run(&[b"BACKUP", b"SEAL"]);
9915        b.f.server.set_clock_ms(9_000_000_000);
9916        b.f.server.backup_expire();
9917        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
9918    }
9919
9920    /// The three settings around the command, read and written the way 8.10.1
9921    /// reads and writes them.
9922    #[test]
9923    fn the_backup_settings_behave_the_way_the_reference_does() {
9924        let mut b = Backups::new("config");
9925        let dir = b.dir.to_string_lossy().into_owned();
9926
9927        assert_eq!(
9928            b.run(&[b"CONFIG", b"GET", b"dir"]),
9929            format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
9930        );
9931        assert_eq!(
9932            b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
9933            "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
9934        );
9935        assert_eq!(
9936            b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
9937            "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
9938        );
9939
9940        // `dir` is a protected config, so it is refused even for the value it
9941        // already holds, and `backupdirname` is immutable.
9942        assert_eq!(
9943            b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
9944            "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
9945        );
9946        assert_eq!(
9947            b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
9948            "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
9949        );
9950        assert!(
9951            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
9952                .contains("argument couldn't be parsed into an integer")
9953        );
9954        assert!(
9955            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
9956                .contains("argument must be between 0 and 9223372036854775807 inclusive")
9957        );
9958    }
9959
9960    /// The help text, which has `HELP` in it twice because the reference's does.
9961    #[test]
9962    fn backup_help_is_the_text_the_reference_sends() {
9963        let mut f = Fixture::new();
9964        let help = f.run(&[b"BACKUP", b"HELP"]);
9965        assert!(help.starts_with("*17\r\n"), "{help}");
9966        assert!(
9967            help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
9968        );
9969        assert!(help.contains("+    Start a new backup into the configured 'backupdirname'.\r\n"));
9970        assert!(help.contains("+    Freeze the current backup (BASE + INCR + manifest).\r\n"));
9971        assert!(help.contains("+    Return this help.\r\n+HELP\r\n+    Print this help.\r\n"));
9972    }
9973
9974    /// What a mistyped `BACKUP` gets told.
9975    ///
9976    /// The arity error names `backup` where the reference names `backup|start`,
9977    /// which is D-46: the table reports one arity for the container the way the
9978    /// reference does, and the per subcommand table that would carry the better
9979    /// name is not built yet. Every subcommand is exactly two words, so nothing
9980    /// legal is refused by it.
9981    #[test]
9982    fn backup_refuses_what_it_cannot_read() {
9983        let mut f = Fixture::new();
9984        assert_eq!(
9985            f.run(&[b"BACKUP"]),
9986            "-ERR wrong number of arguments for 'backup' command\r\n"
9987        );
9988        assert_eq!(
9989            f.run(&[b"BACKUP", b"START", b"x"]),
9990            "-ERR wrong number of arguments for 'backup' command\r\n"
9991        );
9992        assert_eq!(
9993            f.run(&[b"BACKUP", b"NOPE"]),
9994            "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
9995        );
9996    }
9997
9998    /// A fixture whose server saves into a directory of its own.
9999    ///
10000    /// The same shape and the same reason as [`Backups`]: these tests write real
10001    /// files, because a save that only moved a counter would pass a test suite
10002    /// and hand somebody an empty file.
10003    struct Saves {
10004        f: Fixture,
10005        dir: PathBuf,
10006    }
10007
10008    impl Saves {
10009        fn new(name: &str) -> Saves {
10010            let dir = std::env::temp_dir().join(format!("yo-save-{name}-{}", std::process::id()));
10011            let _ = std::fs::remove_dir_all(&dir);
10012            std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
10013            let mut f = Fixture::new();
10014            f.server.set_dir(dir.clone());
10015            Saves { f, dir }
10016        }
10017
10018        fn run(&mut self, parts: &[&[u8]]) -> String {
10019            self.f.run(parts)
10020        }
10021
10022        /// The names in the directory, sorted.
10023        fn files(&self) -> Vec<String> {
10024            let mut names: Vec<String> = match std::fs::read_dir(&self.dir) {
10025                Ok(entries) => entries
10026                    .filter_map(|e| e.ok())
10027                    .map(|e| e.file_name().to_string_lossy().into_owned())
10028                    .collect(),
10029                Err(_) => Vec::new(),
10030            };
10031            names.sort();
10032            names
10033        }
10034
10035        fn image(&self) -> Vec<u8> {
10036            std::fs::read(self.dir.join("dump.rdb")).expect("could not read the file")
10037        }
10038
10039        /// One field out of `INFO persistence`.
10040        fn field(&mut self, name: &str) -> String {
10041            let text = self.run(&[b"INFO", b"persistence"]);
10042            let head = format!("\r\n{name}:");
10043            let at = text.find(&head).expect("the field is not in the section");
10044            let rest = &text[at + head.len()..];
10045            rest[..rest.find("\r\n").expect("the field has no end")].to_owned()
10046        }
10047    }
10048
10049    impl Drop for Saves {
10050        fn drop(&mut self) {
10051            let _ = std::fs::remove_dir_all(&self.dir);
10052        }
10053    }
10054
10055    #[test]
10056    fn save_writes_a_file_that_carries_the_dataset() {
10057        let mut s = Saves::new("writes");
10058        s.run(&[b"SET", b"k", b"v"]);
10059        s.run(&[b"RPUSH", b"l", b"a", b"b"]);
10060        assert!(
10061            s.files().is_empty(),
10062            "a server has saved without being asked"
10063        );
10064
10065        assert_eq!(s.run(&[b"SAVE"]), "+OK\r\n");
10066        assert_eq!(s.files(), ["dump.rdb"]);
10067
10068        // The header, the two databases the keys are in and the end marker,
10069        // which is as far as this test goes: what is between them is the
10070        // snapshot writer's own test, and a real server starting on one of
10071        // these files is what the harness checks.
10072        let image = s.image();
10073        assert!(
10074            image.starts_with(b"REDIS00"),
10075            "the header is not an RDB one"
10076        );
10077        assert!(
10078            image.windows(1).any(|w| w == [0xFF]),
10079            "there is no end marker"
10080        );
10081        assert!(image.len() > 40, "the file is too small to hold anything");
10082    }
10083
10084    #[test]
10085    fn a_save_leaves_no_temporary_file_behind() {
10086        let mut s = Saves::new("temp");
10087        s.run(&[b"SET", b"k", b"v"]);
10088        s.run(&[b"SAVE"]);
10089        s.run(&[b"BGSAVE"]);
10090        assert_eq!(s.files(), ["dump.rdb"]);
10091    }
10092
10093    #[test]
10094    fn a_save_that_cannot_write_says_so_in_one_word() {
10095        let mut s = Saves::new("nowhere");
10096        // A directory that is not there, which is the failure a real server
10097        // answers `-ERR` to with nothing after it.
10098        s.f.server.set_dir(s.dir.join("gone"));
10099        assert_eq!(s.run(&[b"SAVE"]), "-ERR\r\n");
10100        assert_eq!(s.field("rdb_last_bgsave_status"), "err");
10101        // And the count of attempts moved, because the attempt happened.
10102        assert_eq!(s.field("rdb_saves"), "1");
10103    }
10104
10105    #[test]
10106    fn lastsave_starts_at_the_time_the_server_did_and_moves_on_a_save() {
10107        let mut s = Saves::new("lastsave");
10108        let started = s.run(&[b"LASTSAVE"]);
10109        assert_eq!(started, format!(":{}\r\n", s.f.server.started_ms / 1_000));
10110
10111        s.f.server.set_clock_ms(s.f.server.started_ms + 5_000);
10112        s.run(&[b"SAVE"]);
10113        let after = s.run(&[b"LASTSAVE"]);
10114        assert_eq!(after, format!(":{}\r\n", s.f.server.started_ms / 1_000 + 5));
10115
10116        // A write does not move it. Only a save does.
10117        s.run(&[b"SET", b"k", b"v"]);
10118        assert_eq!(s.run(&[b"LASTSAVE"]), after);
10119    }
10120
10121    #[test]
10122    fn bgsave_takes_the_one_word_it_takes_and_nothing_else() {
10123        let mut s = Saves::new("bgsave");
10124        for parts in [
10125            &[b"BGSAVE".as_slice()][..],
10126            &[b"BGSAVE", b"SCHEDULE"],
10127            &[b"BGSAVE", b"schedule"],
10128        ] {
10129            assert_eq!(s.run(parts), "+Background saving started\r\n");
10130        }
10131        for parts in [
10132            &[b"BGSAVE".as_slice(), b"x"][..],
10133            &[b"BGSAVE", b"SCHEDULE", b"x"],
10134            &[b"BGSAVE", b"SCHEDULE", b"SCHEDULE"],
10135        ] {
10136            assert_eq!(s.run(parts), "-ERR syntax error\r\n");
10137        }
10138    }
10139
10140    #[test]
10141    fn a_save_inside_a_transaction_says_it_was_scheduled() {
10142        let mut s = Saves::new("queued");
10143        // `SAVE` never gets there, because it carries `no_multi`.
10144        assert_eq!(s.run(&[b"MULTI"]), "+OK\r\n");
10145        assert_eq!(
10146            s.run(&[b"SAVE"]),
10147            "-ERR Command not allowed inside a transaction\r\n"
10148        );
10149        assert_eq!(
10150            s.run(&[b"EXEC"]),
10151            "-EXECABORT Transaction discarded because of previous errors.\r\n"
10152        );
10153
10154        assert_eq!(s.run(&[b"MULTI"]), "+OK\r\n");
10155        assert_eq!(s.run(&[b"BGSAVE"]), "+QUEUED\r\n");
10156        assert_eq!(s.run(&[b"BGREWRITEAOF"]), "+QUEUED\r\n");
10157        assert_eq!(
10158            s.run(&[b"EXEC"]),
10159            "*2\r\n+Background saving scheduled\r\n\
10160             +Background append only file rewriting scheduled\r\n"
10161        );
10162        // And the file is there, which is the half of it that is not the words.
10163        assert_eq!(s.files(), ["dump.rdb"]);
10164    }
10165
10166    #[test]
10167    fn a_rewrite_counts_itself_and_writes_nothing() {
10168        let mut s = Saves::new("rewrite");
10169        assert_eq!(
10170            s.run(&[b"BGREWRITEAOF"]),
10171            "+Background append only file rewriting started\r\n"
10172        );
10173        assert_eq!(s.field("aof_rewrites"), "1");
10174        assert_eq!(s.field("aof_enabled"), "0");
10175        assert!(s.files().is_empty(), "a rewrite has written a file");
10176    }
10177
10178    #[test]
10179    fn role_says_master_with_nothing_following_it() {
10180        let mut f = Fixture::new();
10181        assert_eq!(f.run(&[b"ROLE"]), "*3\r\n$6\r\nmaster\r\n:0\r\n*0\r\n");
10182        assert_eq!(
10183            f.run(&[b"ROLE", b"x"]),
10184            "-ERR wrong number of arguments for 'role' command\r\n"
10185        );
10186    }
10187
10188    #[test]
10189    fn the_persistence_section_counts_the_saves_that_were_asked_for() {
10190        let mut s = Saves::new("counts");
10191        assert_eq!(s.field("rdb_saves"), "0");
10192        assert_eq!(s.field("rdb_last_bgsave_status"), "ok");
10193        s.run(&[b"SAVE"]);
10194        s.run(&[b"BGSAVE"]);
10195        s.run(&[b"BGSAVE", b"SCHEDULE"]);
10196        assert_eq!(s.field("rdb_saves"), "3");
10197        assert_eq!(s.field("rdb_bgsave_in_progress"), "0");
10198        assert_eq!(s.field("loading"), "0");
10199    }
10200
10201    #[test]
10202    fn the_persistence_section_is_in_a_bare_info_and_not_in_another_one() {
10203        let mut f = Fixture::new();
10204        assert!(f.run(&[b"INFO"]).contains("# Persistence"));
10205        assert!(f.run(&[b"INFO", b"persistence"]).contains("# Persistence"));
10206        assert!(f.run(&[b"INFO", b"all"]).contains("# Persistence"));
10207        assert!(!f.run(&[b"INFO", b"clients"]).contains("# Persistence"));
10208    }
10209
10210    #[test]
10211    fn the_file_name_reads_back_and_cannot_be_written() {
10212        let mut f = Fixture::new();
10213        assert_eq!(
10214            f.run(&[b"CONFIG", b"GET", b"dbfilename"]),
10215            "*2\r\n$10\r\ndbfilename\r\n$8\r\ndump.rdb\r\n"
10216        );
10217        assert_eq!(
10218            f.run(&[b"CONFIG", b"SET", b"dbfilename", b"other.rdb"]),
10219            "-ERR CONFIG SET failed (possibly related to argument 'dbfilename') - can't set protected config\r\n"
10220        );
10221        // Refused even when it is set to what it already is, which is what
10222        // being protected means and is not what being immutable means.
10223        assert_eq!(
10224            f.run(&[b"CONFIG", b"SET", b"dbfilename", b"dump.rdb"]),
10225            "-ERR CONFIG SET failed (possibly related to argument 'dbfilename') - can't set protected config\r\n"
10226        );
10227    }
10228
10229    #[test]
10230    fn shutdown_save_writes_the_file_and_shutdown_on_its_own_does_not() {
10231        let mut s = Saves::new("shutdown");
10232        s.run(&[b"SET", b"k", b"v"]);
10233        s.run(&[b"SHUTDOWN", b"NOSAVE"]);
10234        assert!(s.files().is_empty(), "a nosave shutdown wrote a file");
10235
10236        let mut s = Saves::new("shutdown-save");
10237        s.run(&[b"SET", b"k", b"v"]);
10238        s.run(&[b"SHUTDOWN", b"SAVE"]);
10239        assert_eq!(s.files(), ["dump.rdb"]);
10240    }
10241
10242    /// Every type survives the trip out to the file and back.
10243    ///
10244    /// This is the test the Redis suite is really running when it calls `DEBUG
10245    /// RELOAD` after a case: not that the command answers, but that what was in
10246    /// memory before it is what is in memory after it.
10247    #[test]
10248    fn debug_reload_brings_every_type_back_the_way_it_went_in() {
10249        let mut s = Saves::new("reload-types");
10250        s.run(&[b"SET", b"str", b"hello"]);
10251        s.run(&[b"SET", b"num", b"1234"]);
10252        s.run(&[b"RPUSH", b"list", b"a", b"b", b"c"]);
10253        s.run(&[b"SADD", b"set", b"x", b"y"]);
10254        s.run(&[b"SADD", b"ints", b"1", b"2", b"3"]);
10255        s.run(&[b"HSET", b"hash", b"f", b"v", b"g", b"w"]);
10256        s.run(&[b"ZADD", b"zset", b"1.5", b"m", b"2", b"n"]);
10257        s.run(&[b"XADD", b"stream", b"1-1", b"f", b"v"]);
10258        s.run(&[b"PEXPIREAT", b"str", b"4102444800000"]);
10259        let before = s.run(&[b"DBSIZE"]);
10260
10261        assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "+OK\r\n");
10262
10263        assert_eq!(s.run(&[b"DBSIZE"]), before);
10264        assert_eq!(s.run(&[b"GET", b"str"]), "$5\r\nhello\r\n");
10265        assert_eq!(s.run(&[b"GET", b"num"]), "$4\r\n1234\r\n");
10266        assert_eq!(
10267            s.run(&[b"LRANGE", b"list", b"0", b"-1"]),
10268            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10269        );
10270        assert_eq!(s.run(&[b"SCARD", b"set"]), ":2\r\n");
10271        assert_eq!(s.run(&[b"SISMEMBER", b"set", b"y"]), ":1\r\n");
10272        assert_eq!(s.run(&[b"SCARD", b"ints"]), ":3\r\n");
10273        assert_eq!(s.run(&[b"HGET", b"hash", b"g"]), "$1\r\nw\r\n");
10274        assert_eq!(s.run(&[b"ZSCORE", b"zset", b"m"]), "$3\r\n1.5\r\n");
10275        assert_eq!(s.run(&[b"XLEN", b"stream"]), ":1\r\n");
10276        // The deadline travels with the key, and it is the same deadline and not
10277        // one worked out again from a remaining time.
10278        assert_eq!(s.run(&[b"PEXPIRETIME", b"str"]), ":4102444800000\r\n");
10279        assert_eq!(s.run(&[b"PEXPIRETIME", b"num"]), ":-1\r\n");
10280    }
10281
10282    /// A key goes back into the database it came out of.
10283    #[test]
10284    fn debug_reload_puts_every_key_back_in_its_own_database() {
10285        let mut s = Saves::new("reload-dbs");
10286        s.run(&[b"SET", b"home", b"zero"]);
10287        s.run(&[b"SELECT", b"9"]);
10288        s.run(&[b"SET", b"away", b"nine"]);
10289        s.run(&[b"SELECT", b"0"]);
10290
10291        assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "+OK\r\n");
10292
10293        assert_eq!(s.run(&[b"GET", b"home"]), "$4\r\nzero\r\n");
10294        assert_eq!(s.run(&[b"EXISTS", b"away"]), ":0\r\n");
10295        s.run(&[b"SELECT", b"9"]);
10296        assert_eq!(s.run(&[b"GET", b"away"]), "$4\r\nnine\r\n");
10297        assert_eq!(s.run(&[b"EXISTS", b"home"]), ":0\r\n");
10298    }
10299
10300    /// `NOSAVE` reads the file that is there rather than writing a new one.
10301    #[test]
10302    fn debug_reload_nosave_reads_the_file_that_is_already_there() {
10303        let mut s = Saves::new("reload-nosave");
10304        s.run(&[b"SET", b"k", b"first"]);
10305        s.run(&[b"SAVE"]);
10306        s.run(&[b"SET", b"k", b"second"]);
10307        s.run(&[b"SET", b"later", b"x"]);
10308
10309        assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), "+OK\r\n");
10310
10311        // Both changes are gone, because the file knows nothing about either.
10312        assert_eq!(s.run(&[b"GET", b"k"]), "$5\r\nfirst\r\n");
10313        assert_eq!(s.run(&[b"EXISTS", b"later"]), ":0\r\n");
10314    }
10315
10316    /// `NOFLUSH` lets the file land on what is already in memory.
10317    #[test]
10318    fn debug_reload_noflush_keeps_what_the_file_does_not_mention() {
10319        let mut s = Saves::new("reload-noflush");
10320        s.run(&[b"SET", b"k", b"first"]);
10321        s.run(&[b"SAVE"]);
10322        s.run(&[b"SET", b"k", b"second"]);
10323        s.run(&[b"SET", b"later", b"x"]);
10324
10325        assert_eq!(
10326            s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE", b"NOFLUSH"]),
10327            "+OK\r\n"
10328        );
10329
10330        // The file wins where the two disagree and memory keeps the rest, which
10331        // is what `MERGE` buys on a real server and is what happens here whether
10332        // the word was sent or not.
10333        assert_eq!(s.run(&[b"GET", b"k"]), "$5\r\nfirst\r\n");
10334        assert_eq!(s.run(&[b"GET", b"later"]), "$1\r\nx\r\n");
10335    }
10336
10337    /// The three words it takes, in any case, and one sentence for anything else.
10338    #[test]
10339    fn debug_reload_takes_its_three_words_and_no_others() {
10340        let mut s = Saves::new("reload-words");
10341        s.run(&[b"SET", b"k", b"v"]);
10342        for parts in [
10343            &[b"DEBUG".as_slice(), b"RELOAD"][..],
10344            &[b"DEBUG", b"RELOAD", b"NOSAVE"],
10345            &[b"DEBUG", b"RELOAD", b"nosave"],
10346            &[b"DEBUG", b"RELOAD", b"MERGE"],
10347            &[b"DEBUG", b"RELOAD", b"NOFLUSH"],
10348            &[b"DEBUG", b"RELOAD", b"MERGE", b"NOFLUSH", b"NOSAVE"],
10349            // Repeated is not an error on a real server either.
10350            &[b"DEBUG", b"RELOAD", b"NOSAVE", b"NOSAVE"],
10351        ] {
10352            assert_eq!(s.run(parts), "+OK\r\n", "{parts:?}");
10353        }
10354        for parts in [
10355            &[b"DEBUG".as_slice(), b"RELOAD", b"BOGUS"][..],
10356            &[b"DEBUG", b"RELOAD", b"NOSAVE", b"BOGUS"],
10357            &[b"DEBUG", b"RELOAD", b""],
10358        ] {
10359            assert_eq!(
10360                s.run(parts),
10361                "-ERR DEBUG RELOAD only supports the MERGE, NOFLUSH and NOSAVE options.\r\n",
10362                "{parts:?}"
10363            );
10364        }
10365        // And the dataset is still there after all of that.
10366        assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10367    }
10368
10369    /// A reload that cannot write its file says what a save says.
10370    #[test]
10371    fn debug_reload_that_cannot_write_the_file_says_so_in_one_word() {
10372        let mut s = Saves::new("reload-nowhere");
10373        s.run(&[b"SET", b"k", b"v"]);
10374        s.f.server.set_dir(s.dir.join("gone"));
10375        assert_eq!(s.run(&[b"DEBUG", b"RELOAD"]), "-ERR\r\n");
10376        // Nothing was thrown away, because nothing was read.
10377        assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10378    }
10379
10380    /// A reload that cannot read its file says to look in the log.
10381    ///
10382    /// Two ways to get there, a file that is not there and a file that is not
10383    /// one, and the reply is the same sentence for both because a client can do
10384    /// nothing with the difference.
10385    #[test]
10386    fn debug_reload_that_cannot_read_the_file_says_to_check_the_log() {
10387        let mut s = Saves::new("reload-unreadable");
10388        s.run(&[b"SET", b"k", b"v"]);
10389        let failed = "-ERR Error trying to load the RDB dump, check server logs.\r\n";
10390        assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), failed);
10391        // Refused before the flush, so the dataset is still here.
10392        assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10393
10394        s.run(&[b"SAVE"]);
10395        std::fs::write(s.dir.join("dump.rdb"), b"not an RDB file at all")
10396            .expect("could not write over the file");
10397        assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), failed);
10398        assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10399    }
10400
10401    /// A reload says what it would lose rather than losing it.
10402    ///
10403    /// A time series has no RDB type byte, so it is not in the file the save
10404    /// wrote, and flushing would make the round trip a delete. `NOFLUSH` is the
10405    /// way through: everything in the file lands on top of what is there and the
10406    /// key that could not be written stays where it is.
10407    #[test]
10408    fn debug_reload_refuses_to_drop_a_key_with_no_rdb_form() {
10409        let mut s = Saves::new("reload-foreign");
10410        s.run(&[b"SET", b"k", b"v"]);
10411        s.run(&[b"TS.CREATE", b"ts"]);
10412        s.run(&[b"TS.ADD", b"ts", b"1000", b"1.5"]);
10413
10414        assert_eq!(
10415            s.run(&[b"DEBUG", b"RELOAD"]),
10416            "-ERR DEBUG RELOAD would drop 1 key with no RDB form, use NOFLUSH to keep it\r\n"
10417        );
10418        assert_eq!(s.run(&[b"EXISTS", b"ts"]), ":1\r\n");
10419
10420        s.run(&[b"TS.CREATE", b"ts2"]);
10421        assert_eq!(
10422            s.run(&[b"DEBUG", b"RELOAD"]),
10423            "-ERR DEBUG RELOAD would drop 2 keys with no RDB form, use NOFLUSH to keep them\r\n"
10424        );
10425
10426        // And the way through keeps everything.
10427        assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOFLUSH"]), "+OK\r\n");
10428        assert_eq!(s.run(&[b"EXISTS", b"ts"]), ":1\r\n");
10429        assert_eq!(s.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10430        assert_eq!(s.run(&[b"TS.GET", b"ts"]), "*2\r\n:1000\r\n+1.5\r\n");
10431    }
10432
10433    /// A key that died while the file was on disk does not come back.
10434    #[test]
10435    fn debug_reload_drops_a_key_whose_deadline_went_by() {
10436        let mut s = Saves::new("reload-expired");
10437        s.run(&[b"SET", b"gone", b"v"]);
10438        s.run(&[b"SET", b"stays", b"v"]);
10439        s.run(&[b"PEXPIREAT", b"gone", b"4102444800000"]);
10440        s.run(&[b"SAVE"]);
10441        s.f.server.set_clock_ms(4_102_444_800_001);
10442
10443        assert_eq!(s.run(&[b"DEBUG", b"RELOAD", b"NOSAVE"]), "+OK\r\n");
10444
10445        assert_eq!(s.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10446        assert_eq!(s.run(&[b"GET", b"stays"]), "$1\r\nv\r\n");
10447    }
10448
10449    /// The other caller of the same walk, which is `yodb serve --restore` and
10450    /// `yodb restore`: a file one server wrote, read into a server that has never
10451    /// seen it.
10452    ///
10453    /// The interesting half is that the second server is a different one. A
10454    /// reload reads a file its own writer produced a moment ago into a keyspace
10455    /// whose thresholds have not moved, and a restore does not, so this is the
10456    /// shape the migration story actually has.
10457    #[test]
10458    fn a_file_one_server_wrote_loads_into_a_server_that_has_never_seen_it() {
10459        let mut wrote = Saves::new("restore-across");
10460        wrote.run(&[b"SET", b"s", b"hello"]);
10461        wrote.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10462        wrote.run(&[b"HSET", b"h", b"f", b"v"]);
10463        wrote.run(&[b"ZADD", b"z", b"1.5", b"m"]);
10464        wrote.run(&[b"SELECT", b"7"]);
10465        wrote.run(&[b"SADD", b"far", b"x"]);
10466        assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10467
10468        let mut fresh = Fixture::new();
10469        let done = fresh
10470            .server
10471            .load_image(&wrote.image(), true)
10472            .expect("the file one server wrote is a file another can read");
10473        assert_eq!(done.keys[0], 4);
10474        assert_eq!(done.keys[7], 1);
10475        assert_eq!(done.total(), 5);
10476        assert_eq!(done.expired, 0);
10477
10478        assert_eq!(fresh.run(&[b"GET", b"s"]), "$5\r\nhello\r\n");
10479        assert_eq!(
10480            fresh.run(&[b"LRANGE", b"l", b"0", b"-1"]),
10481            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10482        );
10483        assert_eq!(fresh.run(&[b"HGET", b"h", b"f"]), "$1\r\nv\r\n");
10484        assert_eq!(fresh.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n1.5\r\n");
10485        fresh.run(&[b"SELECT", b"7"]);
10486        assert_eq!(fresh.run(&[b"SMEMBERS", b"far"]), "*1\r\n$1\r\nx\r\n");
10487    }
10488
10489    /// A load says how much of the file landed and how much of it was too old to
10490    /// keep, and `INFO persistence` says the same two numbers afterwards.
10491    #[test]
10492    fn a_load_reports_what_it_kept_and_what_had_already_died() {
10493        let mut wrote = Saves::new("restore-counts");
10494        wrote.run(&[b"SET", b"gone", b"v"]);
10495        wrote.run(&[b"SET", b"stays", b"v"]);
10496        wrote.run(&[b"PEXPIREAT", b"gone", b"4102444800000"]);
10497        assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10498
10499        let mut fresh = Saves::new("restore-counts-into");
10500        fresh.f.server.set_clock_ms(4_102_444_800_001);
10501        let done = fresh
10502            .f
10503            .server
10504            .load_image(&wrote.image(), true)
10505            .expect("a file with a dead key in it is still a good file");
10506        assert_eq!(done.total(), 1);
10507        assert_eq!(done.expired, 1);
10508
10509        assert_eq!(fresh.field("rdb_last_load_keys_loaded"), "1");
10510        assert_eq!(fresh.field("rdb_last_load_keys_expired"), "1");
10511        assert_eq!(fresh.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10512        assert_eq!(fresh.run(&[b"GET", b"stays"]), "$1\r\nv\r\n");
10513    }
10514
10515    /// A server that has not loaded anything reports nought for both, which is
10516    /// true rather than a placeholder.
10517    #[test]
10518    fn a_server_that_has_loaded_nothing_says_so() {
10519        let mut s = Saves::new("restore-never");
10520        assert_eq!(s.field("rdb_last_load_keys_loaded"), "0");
10521        assert_eq!(s.field("rdb_last_load_keys_expired"), "0");
10522    }
10523
10524    /// Bytes that are not an RDB at all leave the keyspace exactly as it was.
10525    ///
10526    /// The magic is checked before anything is thrown away, which is the whole
10527    /// reason a restore is safe to point at the wrong file.
10528    #[test]
10529    fn a_file_that_is_not_an_rdb_is_refused_with_the_dataset_still_there() {
10530        let mut f = Fixture::new();
10531        f.run(&[b"SET", b"k", b"v"]);
10532        let refused = f
10533            .server
10534            .load_image(b"this is not a Redis dump at all, not even close", true)
10535            .expect_err("that is not an RDB");
10536        assert_eq!(refused.to_string(), "the file does not start with REDIS");
10537        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10538    }
10539
10540    /// A file whose last eight bytes do not add up is refused too, and for the
10541    /// same reason it is safe: the checksum is over the whole file and is read
10542    /// before the first key comes out.
10543    #[test]
10544    fn a_damaged_file_is_refused_with_the_dataset_still_there() {
10545        let mut wrote = Saves::new("restore-damaged");
10546        wrote.run(&[b"SET", b"a", b"b"]);
10547        assert_eq!(wrote.run(&[b"SAVE"]), "+OK\r\n");
10548        let mut image = wrote.image();
10549        // One byte in the middle, so that the frame still parses and only the
10550        // checksum knows. Flipping the footer would be a different test.
10551        let middle = image.len() / 2;
10552        image[middle] ^= 0xff;
10553
10554        let mut f = Fixture::new();
10555        f.run(&[b"SET", b"k", b"v"]);
10556        let refused = f
10557            .server
10558            .load_image(&image, true)
10559            .expect_err("the checksum does not match");
10560        assert_eq!(
10561            refused.to_string(),
10562            "the checksum does not match, so the file is damaged"
10563        );
10564        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
10565    }
10566
10567    /// The fields of one `DEBUG OBJECT` line, read off a string.
10568    ///
10569    /// Every number in it is checked somewhere and this is the one that checks
10570    /// the shape: the field order, the spacing and the two that are constant.
10571    #[test]
10572    fn debug_object_describes_how_a_value_is_written_down() {
10573        let mut f = Fixture::new();
10574        f.run(&[b"SET", b"s", b"hello"]);
10575
10576        let line = f.run(&[b"DEBUG", b"OBJECT", b"s"]);
10577        let line = line
10578            .strip_prefix('+')
10579            .and_then(|l| l.strip_suffix("\r\n"))
10580            .expect("a simple string");
10581        let mut fields = line.split(' ');
10582        assert_eq!(fields.next(), Some("Value"));
10583        assert!(
10584            fields.next().expect("an address").starts_with("at:0x"),
10585            "{line}"
10586        );
10587        assert_eq!(fields.next(), Some("refcount:1"));
10588        assert_eq!(fields.next(), Some("encoding:embstr"));
10589        // Five bytes of hello and the one byte header a short string is
10590        // written with, which is the body and neither the type byte in front
10591        // of it nor the footer behind.
10592        assert_eq!(fields.next(), Some("serializedlength:6"));
10593        assert!(
10594            fields.next().expect("a clock").starts_with("lru:"),
10595            "{line}"
10596        );
10597        assert_eq!(fields.next(), Some("lru_seconds_idle:0"));
10598        assert_eq!(fields.next(), None);
10599    }
10600
10601    /// The five extra fields a list that broke into nodes carries.
10602    #[test]
10603    fn debug_object_counts_the_nodes_a_list_broke_into() {
10604        let mut f = Fixture::new();
10605        // Enough long members to be past the eight kilobyte band, so that the
10606        // list is a quicklist rather than one packed run.
10607        let member = vec![b'x'; 200];
10608        for _ in 0..100 {
10609            f.run(&[b"RPUSH", b"l", &member]);
10610        }
10611        assert_eq!(
10612            f.run(&[b"OBJECT", b"ENCODING", b"l"]),
10613            "$9\r\nquicklist\r\n"
10614        );
10615
10616        let line = f.run(&[b"DEBUG", b"OBJECT", b"l"]);
10617        let nodes: usize = field(&line, "ql_nodes:").parse().expect("a count");
10618        assert!(nodes > 1, "{line}");
10619        let avg: f64 = field(&line, "ql_avg_node:").parse().expect("an average");
10620        assert!((avg - 100.0 / nodes as f64).abs() < 0.01, "{line}");
10621        assert_eq!(field(&line, "ql_listpack_max:"), "-2");
10622        assert_eq!(field(&line, "ql_compressed:"), "0");
10623        let bytes: usize = field(&line, "ql_uncompressed_size:")
10624            .parse()
10625            .expect("a size");
10626        assert!(bytes > 100 * 200, "{line}");
10627
10628        // A list small enough to stay packed has none of them.
10629        f.run(&[b"RPUSH", b"small", b"a"]);
10630        let line = f.run(&[b"DEBUG", b"OBJECT", b"small"]);
10631        assert!(!line.contains("ql_nodes"), "{line}");
10632    }
10633
10634    /// Looking is not using, which is the property the whole subcommand rests
10635    /// on: a diagnostic that reset the number it reports would answer nought
10636    /// every time it was asked.
10637    #[test]
10638    fn debug_object_does_not_count_as_using_the_key() {
10639        let mut f = Fixture::new();
10640        f.run(&[b"SET", b"s", b"hello"]);
10641        let was = field(&f.run(&[b"DEBUG", b"OBJECT", b"s"]), "lru:").to_owned();
10642
10643        f.server.set_clock_ms(f.server.clock.now_ms() + 60_000);
10644        let line = f.run(&[b"DEBUG", b"OBJECT", b"s"]);
10645
10646        assert_eq!(field(&line, "lru_seconds_idle:"), "60");
10647        // The clock the idle time counts back from has not moved, because
10648        // nothing has touched the key.
10649        assert_eq!(field(&line, "lru:"), was);
10650    }
10651
10652    /// The two lengths `DEBUG SDSLEN` is read for, and the four numbers about
10653    /// an allocator that is not here, which is D-135.
10654    #[test]
10655    fn debug_sdslen_measures_the_name_and_the_string_under_it() {
10656        let mut f = Fixture::new();
10657        f.run(&[b"SET", b"name", b"hello"]);
10658
10659        assert_eq!(
10660            f.run(&[b"DEBUG", b"SDSLEN", b"name"]),
10661            "+key_sds_len:4, key_sds_avail:0, key_zmalloc: 4, \
10662             val_sds_len:5, val_sds_avail:0, val_zmalloc: 5\r\n"
10663        );
10664    }
10665
10666    /// What each of the four refuses, which is the half a suite branches on.
10667    #[test]
10668    fn the_inspecting_subcommands_refuse_what_they_cannot_describe() {
10669        let mut f = Fixture::new();
10670        f.run(&[b"SET", b"s", b"hello"]);
10671        f.run(&[b"SET", b"n", b"12345"]);
10672        f.run(&[b"RPUSH", b"l", b"a"]);
10673
10674        // A key that is not there is the same sentence from all four, and it is
10675        // an error rather than the nil `OBJECT ENCODING` answers.
10676        for sub in [
10677            b"OBJECT".as_slice(),
10678            b"SDSLEN".as_slice(),
10679            b"LISTPACK".as_slice(),
10680            b"QUICKLIST".as_slice(),
10681        ] {
10682            assert_eq!(
10683                f.run(&[b"DEBUG", sub, b"nosuch"]),
10684                "-ERR no such key\r\n",
10685                "{}",
10686                String::from_utf8_lossy(sub)
10687            );
10688        }
10689
10690        // An integer encoded string has no string in it to measure.
10691        assert_eq!(
10692            f.run(&[b"DEBUG", b"SDSLEN", b"n"]),
10693            "-ERR Not an sds encoded string.\r\n"
10694        );
10695        assert_eq!(
10696            f.run(&[b"DEBUG", b"SDSLEN", b"l"]),
10697            "-ERR Not an sds encoded string.\r\n"
10698        );
10699
10700        // Each structure dump takes the representation it is named after and
10701        // nothing else, whatever type the value is.
10702        assert_eq!(
10703            f.run(&[b"DEBUG", b"LISTPACK", b"l"]),
10704            "+Listpack structure printed on stdout\r\n"
10705        );
10706        assert_eq!(
10707            f.run(&[b"DEBUG", b"QUICKLIST", b"l"]),
10708            "-ERR Not a quicklist encoded object.\r\n"
10709        );
10710        assert_eq!(
10711            f.run(&[b"DEBUG", b"LISTPACK", b"s"]),
10712            "-ERR Not a listpack encoded object.\r\n"
10713        );
10714    }
10715
10716    /// A listpack is a representation and not a type, so the same subcommand
10717    /// answers for four different types and refuses the intset next to them.
10718    #[test]
10719    fn debug_listpack_answers_for_anything_written_as_one() {
10720        let mut f = Fixture::new();
10721        f.run(&[b"RPUSH", b"l", b"a"]);
10722        f.run(&[b"HSET", b"h", b"f", b"v"]);
10723        f.run(&[b"SADD", b"st", b"a"]);
10724        f.run(&[b"ZADD", b"z", b"1", b"m"]);
10725        f.run(&[b"SADD", b"ints", b"1", b"2"]);
10726
10727        for key in [b"l".as_slice(), b"h", b"st", b"z"] {
10728            assert_eq!(
10729                f.run(&[b"DEBUG", b"LISTPACK", key]),
10730                "+Listpack structure printed on stdout\r\n",
10731                "{}",
10732                String::from_utf8_lossy(key)
10733            );
10734        }
10735        assert_eq!(
10736            f.run(&[b"OBJECT", b"ENCODING", b"ints"]),
10737            "$6\r\nintset\r\n"
10738        );
10739        assert_eq!(
10740            f.run(&[b"DEBUG", b"LISTPACK", b"ints"]),
10741            "-ERR Not a listpack encoded object.\r\n"
10742        );
10743    }
10744
10745    /// The level argument on `QUICKLIST`, which is read and dropped, and the
10746    /// wrong argument count on either, which is the container's own sentence.
10747    #[test]
10748    fn debug_quicklist_takes_a_level_it_does_nothing_with() {
10749        let mut f = Fixture::new();
10750        let member = vec![b'x'; 200];
10751        for _ in 0..100 {
10752            f.run(&[b"RPUSH", b"l", &member]);
10753        }
10754
10755        let said = "+Quicklist structure printed on stdout\r\n";
10756        assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l"]), said);
10757        assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"1"]), said);
10758        // A word that is not a number is taken rather than refused, which is
10759        // the reference: it reads the argument with atoi and gets nought.
10760        assert_eq!(f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"abc"]), said);
10761
10762        assert_eq!(
10763            f.run(&[b"DEBUG", b"QUICKLIST", b"l", b"1", b"2"]),
10764            "-ERR unknown subcommand or wrong number of arguments for \
10765             'QUICKLIST'. Try DEBUG HELP.\r\n"
10766        );
10767        assert_eq!(
10768            f.run(&[b"DEBUG", b"LISTPACK", b"l", b"0"]),
10769            "-ERR unknown subcommand or wrong number of arguments for \
10770             'LISTPACK'. Try DEBUG HELP.\r\n"
10771        );
10772    }
10773
10774    /// Every one of these is a number read off redis-server 8.10.1 rather than
10775    /// one this build produced, which is the only kind of assertion worth
10776    /// making about a digest: a number computed a different way is not a worse
10777    /// digest, it is a useless one.
10778    #[test]
10779    fn a_value_digest_is_the_number_the_reference_computes() {
10780        let mut f = Fixture::new();
10781        f.run(&[b"SET", b"s", b"hello"]);
10782        f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10783        f.run(&[b"SADD", b"t", b"a", b"b", b"c"]);
10784        f.run(&[b"HSET", b"h", b"f", b"v"]);
10785        f.run(&[b"ZADD", b"z", b"1", b"a", b"2.5", b"b"]);
10786        f.run(&[b"XADD", b"x", b"1-1", b"f", b"v"]);
10787
10788        for (key, want) in [
10789            (&b"s"[..], "36b23a1456b2dce2c3ed252c456761301dba8060"),
10790            (b"l", "8bf72d812571eea9b927f3c11beb0c4165a6ff89"),
10791            (b"t", "593c2414786d75446e97f4ea5d4b731f3313da72"),
10792            (b"h", "90c76e9e9f4c62d642a34fc97c7dad503b51f906"),
10793            (b"z", "c45c5b051acd64070e5ed1a949939d5145f806c5"),
10794            (b"x", "2ed9a7a81688084b1f7eae33456ef7727d357031"),
10795        ] {
10796            assert_eq!(
10797                f.run(&[b"DEBUG", b"DIGEST-VALUE", key]),
10798                format!("*1\r\n+{want}\r\n"),
10799                "{}",
10800                String::from_utf8_lossy(key)
10801            );
10802        }
10803    }
10804
10805    /// The deadline is in the digest and the time left is not, which is what
10806    /// lets two servers that agree about a dataset agree about the number.
10807    #[test]
10808    fn a_deadline_shows_up_without_the_time_left_showing_up() {
10809        let mut f = Fixture::new();
10810        f.run(&[b"SET", b"e", b"value"]);
10811        let bare = "*1\r\n+d59ec93db87f4cd915db3cdf44bb63755bc4a635\r\n";
10812        let dated = "*1\r\n+331b37c26446a68dd4cdd72701d1acee416ae7b6\r\n";
10813        assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), bare);
10814
10815        f.run(&[b"EXPIRE", b"e", b"1000"]);
10816        assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), dated);
10817        // A different deadline on the same value is the same digest.
10818        f.run(&[b"EXPIRE", b"e", b"999999"]);
10819        assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), dated);
10820        f.run(&[b"PERSIST", b"e"]);
10821        assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"e"]), bare);
10822
10823        // The same again for a field of a hash, which says so with its own
10824        // word rather than with the key's.
10825        f.run(&[b"HSET", b"he", b"f1", b"v1", b"f2", b"v2"]);
10826        f.run(&[b"HEXPIRE", b"he", b"1000", b"FIELDS", b"1", b"f2"]);
10827        assert_eq!(
10828            f.run(&[b"DEBUG", b"DIGEST-VALUE", b"he"]),
10829            "*1\r\n+8911d6d4d198f5e022f80dfefd5e15d6c0eaabe3\r\n"
10830        );
10831    }
10832
10833    /// The value and not the entry, which is the difference between the two
10834    /// subcommands and is why a copy answers the same forty characters.
10835    #[test]
10836    fn a_value_digest_does_not_know_what_the_key_is_called() {
10837        let mut f = Fixture::new();
10838        f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
10839        f.run(&[b"COPY", b"l", b"l2"]);
10840        let want = "*1\r\n+8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n";
10841        assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l"]), want);
10842        assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l2"]), want);
10843
10844        // Several at once, in the order asked for, with a key that is not
10845        // there answering forty zeros rather than an error.
10846        assert_eq!(
10847            f.run(&[b"DEBUG", b"DIGEST-VALUE", b"l", b"gone", b"l2"]),
10848            format!(
10849                "*3\r\n+8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n+{0}\r\n\
10850                 +8bf72d812571eea9b927f3c11beb0c4165a6ff89\r\n",
10851                "0".repeat(40)
10852            )
10853        );
10854        assert_eq!(f.run(&[b"DEBUG", b"DIGEST-VALUE"]), "*0\r\n");
10855    }
10856
10857    /// The whole server, where the name is in it and the database number is
10858    /// in it and an empty database is not.
10859    #[test]
10860    fn the_whole_digest_folds_in_the_names_and_the_database_numbers() {
10861        let mut f = Fixture::new();
10862        let empty = format!("+{}\r\n", "0".repeat(40));
10863        assert_eq!(f.run(&[b"DEBUG", b"DIGEST"]), empty);
10864
10865        f.run(&[b"SET", b"k", b"hello"]);
10866        assert_eq!(
10867            f.run(&[b"DEBUG", b"DIGEST"]),
10868            "+d101db227d1e3b31616b18b0b8700f84c3ffa5e9\r\n"
10869        );
10870
10871        f.run(&[b"SELECT", b"3"]);
10872        f.run(&[b"SET", b"k", b"hello"]);
10873        assert_eq!(
10874            f.run(&[b"DEBUG", b"DIGEST"]),
10875            "+a541f66c15932c1014da1569ff27c15bcde7d1dc\r\n"
10876        );
10877
10878        // Emptying the first one leaves the same key in the same place and a
10879        // different number, because the database it is in is folded in.
10880        f.run(&[b"SELECT", b"0"]);
10881        f.run(&[b"FLUSHDB"]);
10882        assert_eq!(
10883            f.run(&[b"DEBUG", b"DIGEST"]),
10884            "+f9b35ab00ad2f456386a2f73d316bf8266013606\r\n"
10885        );
10886
10887        f.run(&[b"FLUSHALL"]);
10888        assert_eq!(f.run(&[b"DEBUG", b"DIGEST"]), empty);
10889    }
10890
10891    /// Digesting is not using, which is what makes it safe for a suite to call
10892    /// between every step of whatever it is measuring.
10893    #[test]
10894    fn digesting_does_not_count_as_using_anything() {
10895        let mut f = Fixture::new();
10896        f.run(&[b"SET", b"k", b"value"]);
10897        f.run(&[b"CONFIG", b"RESETSTAT"]);
10898        f.run(&[b"DEBUG", b"DIGEST"]);
10899        f.run(&[b"DEBUG", b"DIGEST-VALUE", b"k", b"gone"]);
10900        let stats = f.run(&[b"INFO", b"stats"]);
10901        assert!(stats.contains("keyspace_hits:0\r\n"), "{stats}");
10902        assert!(stats.contains("keyspace_misses:0\r\n"), "{stats}");
10903
10904        // And the counters are working, so the nought above is the command
10905        // holding still rather than the statistic never moving.
10906        f.run(&[b"GET", b"k"]);
10907        f.run(&[b"GET", b"gone"]);
10908        let stats = f.run(&[b"INFO", b"stats"]);
10909        assert!(stats.contains("keyspace_hits:1\r\n"), "{stats}");
10910        assert!(stats.contains("keyspace_misses:1\r\n"), "{stats}");
10911    }
10912
10913    /// One field out of a `DEBUG OBJECT` line, named by its label.
10914    fn field<'a>(line: &'a str, label: &str) -> &'a str {
10915        let at = line
10916            .find(label)
10917            .unwrap_or_else(|| panic!("no {label} in {line}"));
10918        let rest = &line[at + label.len()..];
10919        rest.split([' ', '\r']).next().expect("a value")
10920    }
10921
10922    /// A fixture on a server with a password, on a connection that has not met
10923    /// it.
10924    ///
10925    /// The two calls have to be in this order and both have to happen. Setting
10926    /// the password is the server's half and admitting nothing is the
10927    /// connection's, and the connection's half is what a real front does at
10928    /// accept time. A fixture that only set the password would be a connection
10929    /// that was open before it went on, which is the case in
10930    /// [`a_password_set_under_an_open_connection_leaves_it_alone`].
10931    fn guarded(password: &[u8]) -> Fixture {
10932        let mut f = Fixture::new();
10933        f.server.set_password(password);
10934        f.session.admit(false);
10935        f
10936    }
10937
10938    /// Nothing gets through without the password, and the sentence is the one
10939    /// a client branches on.
10940    #[test]
10941    fn a_server_with_a_password_answers_everything_else_with_noauth() {
10942        let mut f = guarded(b"hunter2");
10943        for parts in [
10944            &[b"PING".as_slice()][..],
10945            &[b"GET", b"k"],
10946            &[b"SET", b"k", b"v"],
10947            &[b"COMMAND", b"COUNT"],
10948            &[b"SUBSCRIBE", b"ch"],
10949            &[b"MULTI"],
10950            &[b"INFO"],
10951        ] {
10952            assert_eq!(
10953                f.run(parts),
10954                "-NOAUTH Authentication required.\r\n",
10955                "{parts:?} got through"
10956            );
10957        }
10958    }
10959
10960    /// The four commands a client may send before it has authenticated.
10961    ///
10962    /// `AUTH` because it is the way in, `HELLO` because it carries the option
10963    /// that is the other way in, `RESET` because starting over cannot need a
10964    /// password, and `QUIT` because leaving cannot either. Redis marks all four
10965    /// `no_auth` and the gate reads the flag rather than the names.
10966    #[test]
10967    fn the_four_commands_that_do_not_need_the_password_get_through() {
10968        for name in ["auth", "hello", "reset", "quit"] {
10969            let spec = table::lookup(name.as_bytes()).expect(name);
10970            assert!(spec.flags.contains(&"no_auth"), "{name} is not no_auth");
10971        }
10972        let mut f = guarded(b"hunter2");
10973        assert_eq!(f.run(&[b"QUIT"]), "+OK\r\n");
10974        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
10975        assert_eq!(
10976            f.run(&[b"AUTH", b"wrong"]),
10977            "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
10978        );
10979        assert_eq!(f.run(&[b"AUTH", b"hunter2"]), "+OK\r\n");
10980        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10981    }
10982
10983    /// Both spellings of `AUTH`, and the one user there is.
10984    #[test]
10985    fn auth_takes_the_password_on_its_own_or_behind_the_user_name() {
10986        let mut f = guarded(b"hunter2");
10987        let wrong = "-WRONGPASS invalid username-password pair or user is disabled.\r\n";
10988        assert_eq!(f.run(&[b"AUTH", b"default", b"hunter2"]), "+OK\r\n");
10989        assert_eq!(f.run(&[b"AUTH", b"default", b"wrong"]), wrong);
10990        // And a failed attempt does not throw out the connection that had
10991        // already got in, which was read off 8.10.1 rather than assumed.
10992        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
10993        assert_eq!(f.run(&[b"AUTH", b"someone", b"hunter2"]), wrong);
10994        assert_eq!(
10995            f.run(&[b"AUTH"]),
10996            "-ERR wrong number of arguments for 'auth' command\r\n"
10997        );
10998        assert_eq!(f.run(&[b"AUTH", b"a", b"b", b"c"]), "-ERR syntax error\r\n");
10999    }
11000
11001    /// On a server with no password the default user is `nopass`, and what that
11002    /// means is not what it sounds like.
11003    ///
11004    /// Any password at all is the right one for it, so the two argument form
11005    /// says `OK`. The one argument form is the exception and gets a sentence
11006    /// about the configuration instead, because a client that sends it has
11007    /// almost certainly reached a server it did not mean to reach.
11008    #[test]
11009    fn auth_on_a_server_with_no_password_says_so_at_length() {
11010        let mut f = Fixture::new();
11011        assert_eq!(
11012            f.run(&[b"AUTH", b"anything"]),
11013            "-ERR AUTH <password> called without any password configured for the \
11014             default user. Are you sure your configuration is correct?\r\n"
11015        );
11016        assert_eq!(f.run(&[b"AUTH", b"default", b"anything"]), "+OK\r\n");
11017        assert_eq!(
11018            f.run(&[b"AUTH", b"nobody", b"anything"]),
11019            "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
11020        );
11021        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11022    }
11023
11024    /// `HELLO` has a sentence of its own, and the order it decides things in is
11025    /// not the order they are written in.
11026    ///
11027    /// The protocol version first, so a bad one is a `NOPROTO` even from a
11028    /// connection that has not authenticated and would have been let in by the
11029    /// `AUTH` option on the same line. Then the option, so a wrong password is a
11030    /// `WRONGPASS`. Then the password at all, which is what a bare `HELLO` on a
11031    /// guarded server gets. All three read off 8.10.1.
11032    #[test]
11033    fn hello_says_which_option_would_have_worked() {
11034        let long = "-NOAUTH HELLO must be called with the client already authenticated, \
11035                    otherwise the HELLO <proto> AUTH <user> <pass> option can be used to \
11036                    authenticate the client and select the RESP protocol version at the \
11037                    same time\r\n";
11038        let mut f = guarded(b"hunter2");
11039        assert_eq!(f.run(&[b"HELLO"]), long);
11040        assert_eq!(f.run(&[b"HELLO", b"3"]), long);
11041        assert_eq!(
11042            f.run(&[b"HELLO", b"9"]),
11043            "-NOPROTO unsupported protocol version\r\n"
11044        );
11045        // The version is refused before the option is applied, so this leaves
11046        // the connection exactly as unauthenticated as it found it.
11047        assert_eq!(
11048            f.run(&[b"HELLO", b"9", b"AUTH", b"default", b"hunter2"]),
11049            "-NOPROTO unsupported protocol version\r\n"
11050        );
11051        assert_eq!(f.run(&[b"PING"]), "-NOAUTH Authentication required.\r\n");
11052        assert_eq!(
11053            f.run(&[b"HELLO", b"2", b"AUTH", b"default", b"wrong"]),
11054            "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
11055        );
11056        assert!(
11057            f.run(&[b"HELLO", b"3", b"AUTH", b"default", b"hunter2"])
11058                .starts_with("%7\r\n"),
11059            "the option did not let it in"
11060        );
11061        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11062    }
11063
11064    /// `EXEC` is answered the abort rather than the refusal, with the refusal
11065    /// spliced into it.
11066    ///
11067    /// A client that sent `EXEC` is waiting for the transaction to be over one
11068    /// way or another, so the reference turns every refusal the funnel makes of
11069    /// an `EXEC` into an abort carrying the reason. The code word is in the
11070    /// spliced reason as well as in front of the reply it would have been.
11071    #[test]
11072    fn exec_without_the_password_is_an_abort_and_says_why() {
11073        let mut f = guarded(b"hunter2");
11074        assert_eq!(f.run(&[b"MULTI"]), "-NOAUTH Authentication required.\r\n");
11075        assert_eq!(
11076            f.run(&[b"EXEC"]),
11077            "-EXECABORT Transaction discarded because of: NOAUTH Authentication \
11078             required.\r\n"
11079        );
11080    }
11081
11082    /// `RESET` puts the connection back to how it was accepted, password and
11083    /// all.
11084    #[test]
11085    fn reset_gives_the_password_back_to_the_server_to_ask_for_again() {
11086        let mut f = guarded(b"hunter2");
11087        assert_eq!(f.run(&[b"AUTH", b"hunter2"]), "+OK\r\n");
11088        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11089        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
11090        assert_eq!(f.run(&[b"PING"]), "-NOAUTH Authentication required.\r\n");
11091
11092        // And on a server with no password it puts back the same nothing.
11093        let mut f = Fixture::new();
11094        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
11095        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11096    }
11097
11098    /// A password set under a connection that is already open leaves it alone.
11099    ///
11100    /// This is the rule nobody would guess and it is the reference's: the flag
11101    /// is decided when the connection is accepted, so `CONFIG SET requirepass`
11102    /// locks out everybody who connects after it and nobody who is already
11103    /// there, including the connection that sent it.
11104    #[test]
11105    fn a_password_set_under_an_open_connection_leaves_it_alone() {
11106        let mut f = Fixture::new();
11107        f.session.admit(true);
11108        assert_eq!(
11109            f.run(&[b"CONFIG", b"SET", b"requirepass", b"hunter2"]),
11110            "+OK\r\n"
11111        );
11112        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
11113        // And taking it off again lets in a connection that never met it.
11114        let mut later = Fixture::on(Server::new());
11115        later.server.set_password(b"hunter2");
11116        later.session.admit(false);
11117        assert_eq!(
11118            later.run(&[b"PING"]),
11119            "-NOAUTH Authentication required.\r\n"
11120        );
11121        later.server.set_password(b"");
11122        assert_eq!(later.run(&[b"PING"]), "+PONG\r\n");
11123    }
11124
11125    /// The password reads back in the clear and is set and cleared by the same
11126    /// pair of words.
11127    #[test]
11128    fn requirepass_reads_back_what_was_written_and_an_empty_one_clears_it() {
11129        let mut f = Fixture::new();
11130        // Open before the password goes on, so the connection keeps talking
11131        // after it does and can read it back.
11132        f.session.admit(true);
11133        assert_eq!(
11134            f.run(&[b"CONFIG", b"GET", b"requirepass"]),
11135            "*2\r\n$11\r\nrequirepass\r\n$0\r\n\r\n"
11136        );
11137        f.run(&[b"CONFIG", b"SET", b"requirepass", b"hunter2"]);
11138        assert_eq!(
11139            f.run(&[b"CONFIG", b"GET", b"requirepass"]),
11140            "*2\r\n$11\r\nrequirepass\r\n$7\r\nhunter2\r\n"
11141        );
11142        assert!(f.server.guarded());
11143        f.run(&[b"CONFIG", b"SET", b"requirepass", b""]);
11144        assert!(!f.server.guarded(), "an empty password did not clear it");
11145        assert_eq!(
11146            f.run(&[b"CONFIG", b"GET", b"requirepass"]),
11147            "*2\r\n$11\r\nrequirepass\r\n$0\r\n\r\n"
11148        );
11149    }
11150
11151    /// Every `DEBUG PROTOCOL` type, on RESP2, byte for byte off 8.10.1.
11152    #[test]
11153    fn debug_protocol_writes_what_the_reference_writes_on_resp2() {
11154        let mut f = Fixture::new();
11155        for (kind, want) in [
11156            ("string", "$11\r\nHello World\r\n"),
11157            ("integer", ":12345\r\n"),
11158            ("double", "$5\r\n3.141\r\n"),
11159            ("bignum", "$37\r\n1234567999999999999999999999999999999\r\n"),
11160            ("null", "$-1\r\n"),
11161            ("array", "*3\r\n:0\r\n:1\r\n:2\r\n"),
11162            ("set", "*3\r\n:0\r\n:1\r\n:2\r\n"),
11163            ("map", "*6\r\n:0\r\n:0\r\n:1\r\n:1\r\n:2\r\n:0\r\n"),
11164            (
11165                "attrib",
11166                "$39\r\nSome real reply following the attribute\r\n",
11167            ),
11168            ("push", "-ERR RESP2 is not supported by this command\r\n"),
11169            ("verbatim", "$25\r\nThis is a verbatim\nstring\r\n"),
11170            ("true", ":1\r\n"),
11171            ("false", ":0\r\n"),
11172        ] {
11173            assert_eq!(
11174                f.run(&[b"DEBUG", b"PROTOCOL", kind.as_bytes()]),
11175                want,
11176                "{kind}"
11177            );
11178        }
11179    }
11180
11181    /// And on RESP3, where all thirteen are their own type.
11182    #[test]
11183    fn debug_protocol_writes_what_the_reference_writes_on_resp3() {
11184        let mut f = Fixture::new();
11185        f.out = Out::new(Proto::Resp3);
11186        for (kind, want) in [
11187            ("string", "$11\r\nHello World\r\n"),
11188            ("integer", ":12345\r\n"),
11189            ("double", ",3.141\r\n"),
11190            ("bignum", "(1234567999999999999999999999999999999\r\n"),
11191            ("null", "_\r\n"),
11192            ("array", "*3\r\n:0\r\n:1\r\n:2\r\n"),
11193            ("set", "~3\r\n:0\r\n:1\r\n:2\r\n"),
11194            ("map", "%3\r\n:0\r\n#f\r\n:1\r\n#t\r\n:2\r\n#f\r\n"),
11195            (
11196                "attrib",
11197                "|1\r\n$14\r\nkey-popularity\r\n*2\r\n$7\r\nkey:123\r\n:90\r\n\
11198                 $39\r\nSome real reply following the attribute\r\n",
11199            ),
11200            (
11201                "push",
11202                "$40\r\nSome real reply following the push reply\r\n\
11203                 >2\r\n$16\r\nserver-cpu-usage\r\n:42\r\n",
11204            ),
11205            ("verbatim", "=29\r\ntxt:This is a verbatim\nstring\r\n"),
11206            ("true", "#t\r\n"),
11207            ("false", "#f\r\n"),
11208        ] {
11209            assert_eq!(
11210                f.run(&[b"DEBUG", b"PROTOCOL", kind.as_bytes()]),
11211                want,
11212                "{kind}"
11213            );
11214        }
11215    }
11216
11217    /// A type name that is not one of the thirteen lists all thirteen.
11218    #[test]
11219    fn debug_protocol_names_every_type_when_it_is_given_none_of_them() {
11220        let mut f = Fixture::new();
11221        for kind in [&b"bogus"[..], b""] {
11222            assert_eq!(
11223                f.run(&[b"DEBUG", b"PROTOCOL", kind]),
11224                "-ERR Wrong protocol type name. Please use one of the following: \
11225                 string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false\r\n"
11226            );
11227        }
11228    }
11229
11230    /// The one sentence `DEBUG` says about everything it cannot do.
11231    ///
11232    /// A subcommand that does not exist and a subcommand handed the wrong number
11233    /// of arguments are the same case on a real server, because both fall off
11234    /// the end of the same chain of tests, and the name is echoed in the case it
11235    /// arrived in.
11236    #[test]
11237    fn debug_says_the_same_thing_about_a_bad_name_and_a_bad_count() {
11238        let mut f = Fixture::new();
11239        for parts in [
11240            &[b"DEBUG".as_slice(), b"NOSUCH"][..],
11241            &[b"DEBUG", b"PROTOCOL"],
11242            &[b"DEBUG", b"PROTOCOL", b"string", b"extra"],
11243            &[b"DEBUG", b"SLEEP"],
11244            &[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"1", b"2"],
11245            &[b"DEBUG", b"HELP", b"me"],
11246        ] {
11247            let got = f.run(parts);
11248            let name = String::from_utf8_lossy(parts[1]).to_string();
11249            assert_eq!(
11250                got,
11251                format!(
11252                    "-ERR unknown subcommand or wrong number of arguments for '{name}'. \
11253                     Try DEBUG HELP.\r\n"
11254                ),
11255                "{name}"
11256            );
11257        }
11258        assert_eq!(
11259            f.run(&[b"DEBUG"]),
11260            "-ERR wrong number of arguments for 'debug' command\r\n"
11261        );
11262    }
11263
11264    /// `DEBUG ERROR` writes the line it was given and nothing around it.
11265    #[test]
11266    fn debug_error_hands_back_whatever_it_was_given() {
11267        let mut f = Fixture::new();
11268        assert_eq!(f.run(&[b"DEBUG", b"ERROR", b"my error"]), "-my error\r\n");
11269        assert_eq!(f.run(&[b"DEBUG", b"ERROR", b""]), "-\r\n");
11270        // A code the caller made up goes out as the code, which is the whole use
11271        // of this: a client library testing that it branches on one.
11272        assert_eq!(
11273            f.run(&[b"DEBUG", b"ERROR", b"-WEIRD thing"]),
11274            "--WEIRD thing\r\n"
11275        );
11276        // And a newline in the middle cannot become a second reply.
11277        assert_eq!(
11278            f.run(&[b"DEBUG", b"ERROR", b"two\nlines"]),
11279            "-two lines\r\n"
11280        );
11281    }
11282
11283    /// `DEBUG POPULATE` fills a database and leaves what is already there.
11284    #[test]
11285    fn debug_populate_fills_and_skips_what_is_there() {
11286        let mut f = Fixture::new();
11287        assert_eq!(f.run(&[b"SET", b"key:0", b"mine"]), "+OK\r\n");
11288        assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"3"]), "+OK\r\n");
11289        assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n");
11290        assert_eq!(f.run(&[b"GET", b"key:0"]), "$4\r\nmine\r\n");
11291        assert_eq!(f.run(&[b"GET", b"key:2"]), "$7\r\nvalue:2\r\n");
11292        // A prefix is the whole of the name in front of the colon, so the colon
11293        // in a prefix that has one is not the separator and there are two.
11294        assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"p:"]), "+OK\r\n");
11295        assert_eq!(f.run(&[b"GET", b"p::0"]), "$7\r\nvalue:0\r\n");
11296        // A size pads with zero bytes, and one shorter than the name cuts it.
11297        assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"q", b"9"]), "+OK\r\n");
11298        assert_eq!(f.raw(&[b"GET", b"q:0"]), b"$9\r\nvalue:0\0\0\r\n");
11299        assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"r", b"4"]), "+OK\r\n");
11300        assert_eq!(f.run(&[b"GET", b"r:0"]), "$4\r\nvalu\r\n");
11301        // And nought is not a size of nothing, it is no size at all.
11302        assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"1", b"s", b"0"]), "+OK\r\n");
11303        assert_eq!(f.run(&[b"GET", b"s:0"]), "$7\r\nvalue:0\r\n");
11304    }
11305
11306    /// Both of `POPULATE`'s numbers complain about the range and not the digits.
11307    #[test]
11308    fn debug_populate_wants_two_numbers_that_are_not_negative() {
11309        let mut f = Fixture::new();
11310        for parts in [
11311            &[b"DEBUG".as_slice(), b"POPULATE", b"abc"][..],
11312            &[b"DEBUG", b"POPULATE", b"-1"],
11313            &[b"DEBUG", b"POPULATE", b"1.5"],
11314            &[b"DEBUG", b"POPULATE", b"1", b"p", b"-1"],
11315            &[b"DEBUG", b"POPULATE", b"1", b"p", b"x"],
11316        ] {
11317            assert_eq!(
11318                f.run(parts),
11319                "-ERR value is out of range, must be positive\r\n"
11320            );
11321        }
11322        assert_eq!(f.run(&[b"DEBUG", b"POPULATE", b"0"]), "+OK\r\n");
11323        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11324    }
11325
11326    /// The packed threshold takes a memory value up to just under four gigabytes.
11327    ///
11328    /// The error sentence says bigger than one and smaller than 4gb and neither
11329    /// half of that is what is checked, which is why the numbers here were taken
11330    /// off a running server rather than off the sentence.
11331    #[test]
11332    fn debug_quicklist_packed_threshold_takes_what_the_reference_takes() {
11333        let mut f = Fixture::new();
11334        for good in [
11335            &b"1"[..],
11336            b"2",
11337            b"1b",
11338            b"1K",
11339            b"1kb",
11340            b"1G",
11341            b"3gb",
11342            b"0",
11343            b"0b",
11344        ] {
11345            assert_eq!(
11346                f.run(&[b"DEBUG", b"QUICKLIST-PACKED-THRESHOLD", good]),
11347                "+OK\r\n",
11348                "{}",
11349                String::from_utf8_lossy(good)
11350            );
11351        }
11352        for bad in [
11353            &b"4gb"[..],
11354            b"4294967295",
11355            b"4294967296",
11356            b"abc",
11357            b"",
11358            b"+5",
11359            b"1.5",
11360        ] {
11361            assert_eq!(
11362                f.run(&[b"DEBUG", b"QUICKLIST-PACKED-THRESHOLD", bad]),
11363                "-ERR argument must be a memory value bigger than 1 and smaller than 4gb\r\n",
11364                "{}",
11365                String::from_utf8_lossy(bad)
11366            );
11367        }
11368    }
11369
11370    /// The three gates really gate, and they say `OK` to anything.
11371    #[test]
11372    fn the_debug_gates_turn_the_things_they_name_off_and_on_again() {
11373        let mut f = Fixture::new();
11374        for (sub, read) in [
11375            (&b"SET-ACTIVE-EXPIRE"[..], 0),
11376            (b"DICT-RESIZING", 1),
11377            (b"PAUSE-CRON", 2),
11378        ] {
11379            let reads: [fn(&Server) -> bool; 3] =
11380                [Server::expiring, Server::resizing, Server::cron_running];
11381            let on = reads[read];
11382            // `PAUSE-CRON` is the one whose argument means the opposite of the
11383            // gate, since it names the stopping and the gate names the running.
11384            let stop: &[u8] = if read == 2 { b"1" } else { b"0" };
11385            let go: &[u8] = if read == 2 { b"0" } else { b"1" };
11386            assert!(on(&f.server), "{}", String::from_utf8_lossy(sub));
11387            assert_eq!(f.run(&[b"DEBUG", sub, stop]), "+OK\r\n");
11388            assert!(!on(&f.server), "{}", String::from_utf8_lossy(sub));
11389            // A word is nought to `atoi`, so it turns the gate off rather than
11390            // being refused, and on `PAUSE-CRON` that means it starts the cron.
11391            assert_eq!(f.run(&[b"DEBUG", sub, b"nonsense"]), "+OK\r\n");
11392            assert_eq!(on(&f.server), read == 2);
11393            assert_eq!(f.run(&[b"DEBUG", sub, go]), "+OK\r\n");
11394            assert!(on(&f.server), "{}", String::from_utf8_lossy(sub));
11395        }
11396    }
11397
11398    /// A key past its deadline is not swept while the sweep is off.
11399    ///
11400    /// The lazy read still reports it gone, which is the same split a real
11401    /// server has: `SET-ACTIVE-EXPIRE 0` stops the background cycle and does not
11402    /// make an expired key readable.
11403    #[test]
11404    fn the_sweep_stops_when_debug_turns_it_off() {
11405        let mut f = Fixture::new();
11406        assert_eq!(f.run(&[b"SET", b"k", b"v", b"PX", b"10"]), "+OK\r\n");
11407        assert_eq!(f.run(&[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"0"]), "+OK\r\n");
11408        f.advance(50);
11409        assert_eq!(f.server.expire_slice(64), 0);
11410        assert_eq!(f.run(&[b"DEBUG", b"SET-ACTIVE-EXPIRE", b"1"]), "+OK\r\n");
11411        assert_eq!(f.server.expire_slice(64), 1);
11412    }
11413
11414    /// Five of the ten `COMMAND INFO` fields are sets once RESP3 has a set.
11415    ///
11416    /// This is every command and not just `DEBUG`, and it only shows on RESP3,
11417    /// which is why it went unnoticed until a wire compare looked at the bytes
11418    /// rather than at what a client decoded them into.
11419    #[test]
11420    fn command_info_sends_sets_where_the_reference_sends_sets() {
11421        let mut f = Fixture::new();
11422        f.out = Out::new(Proto::Resp3);
11423        assert_eq!(
11424            f.run(&[b"COMMAND", b"INFO", b"get"]),
11425            "*1\r\n*10\r\n$3\r\nget\r\n:2\r\n~2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n\
11426             ~3\r\n+@read\r\n+@string\r\n+@fast\r\n~0\r\n~1\r\n%3\r\n\
11427             $5\r\nflags\r\n~2\r\n+RO\r\n+access\r\n\
11428             $12\r\nbegin_search\r\n%2\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
11429             %1\r\n$5\r\nindex\r\n:1\r\n\
11430             $9\r\nfind_keys\r\n%2\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
11431             %3\r\n$7\r\nlastkey\r\n:0\r\n$7\r\nkeystep\r\n:1\r\n$5\r\nlimit\r\n:0\r\n\
11432             ~0\r\n"
11433        );
11434        // And RESP2, where a set is an array and nothing moved.
11435        let mut f = Fixture::new();
11436        assert_eq!(
11437            f.run(&[b"COMMAND", b"INFO", b"get"]),
11438            "*1\r\n*10\r\n$3\r\nget\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n\
11439             *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*1\r\n*6\r\n\
11440             $5\r\nflags\r\n*2\r\n+RO\r\n+access\r\n\
11441             $12\r\nbegin_search\r\n*4\r\n$4\r\ntype\r\n$5\r\nindex\r\n$4\r\nspec\r\n\
11442             *2\r\n$5\r\nindex\r\n:1\r\n\
11443             $9\r\nfind_keys\r\n*4\r\n$4\r\ntype\r\n$5\r\nrange\r\n$4\r\nspec\r\n\
11444             *6\r\n$7\r\nlastkey\r\n:0\r\n$7\r\nkeystep\r\n:1\r\n$5\r\nlimit\r\n:0\r\n\
11445             *0\r\n"
11446        );
11447    }
11448
11449    /// `DEBUG` is admin, so no monitor is ever shown one.
11450    #[test]
11451    fn debug_is_admin_and_stays_off_a_monitor_feed() {
11452        let spec = table::lookup(b"debug").expect("debug is in the table");
11453        assert!(spec.flags.contains(&"admin"));
11454        assert_eq!(spec.arity, -2);
11455        assert_eq!(spec.acl, ["@admin", "@slow", "@dangerous"]);
11456    }
11457
11458    #[test]
11459    fn the_command_counter_counts_every_command_including_the_bad_ones() {
11460        let mut f = Fixture::new();
11461        f.run(&[b"PING"]);
11462        f.run(&[b"NOPE"]);
11463        f.run(&[b"GET"]);
11464        assert_eq!(f.server.totals().commands, 3);
11465    }
11466
11467    #[test]
11468    fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
11469        let mut server = Server::new();
11470        server.set_threads(2);
11471        // A fresh server has every database on the turn's list, so start from
11472        // nothing to see the one mark arrive.
11473        server.mine().turn.store(0, Relaxed);
11474        server.locals[1].mark(1 << 9);
11475        server.collect_marks();
11476        assert!(server.mine().wanted(9));
11477        // And taken once rather than left to be taken again next turn.
11478        assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
11479    }
11480
11481    #[test]
11482    fn what_two_threads_counted_is_added_up_when_info_asks() {
11483        let mut server = Server::new();
11484        server.set_threads(2);
11485        // Written into the two sets by hand, because what is under test is the
11486        // adding up and not the claiming, and one test thread can only ever
11487        // claim one set.
11488        let ping = lookup(b"PING").expect("PING is a command");
11489        for (at, calls) in [(0, 2), (1, 3)] {
11490            let counters = &server.locals[at];
11491            for _ in 0..calls {
11492                counters.stats.commands.bump();
11493                counters.cmdstats.at(ping).calls.bump();
11494            }
11495            counters.stats.opened();
11496        }
11497        assert_eq!(server.totals().commands, 5);
11498        assert_eq!(server.totals().clients, 2);
11499        assert_eq!(server.totals().connections, 2);
11500        let rows: Vec<_> = server.command_stats().collect();
11501        assert_eq!(rows.len(), 1);
11502        assert_eq!(rows[0].0, "ping");
11503        assert_eq!(rows[0].1.calls, 5);
11504        // A reset takes the totals and leaves the open connections, which are
11505        // still open.
11506        server.reset_stats();
11507        assert_eq!(server.totals().commands, 0);
11508        assert_eq!(server.totals().connections, 0);
11509        assert_eq!(server.totals().clients, 2);
11510    }
11511
11512    #[test]
11513    fn the_parked_count_says_what_the_waiter_list_says() {
11514        let mut f = Fixture::new();
11515        assert_eq!(f.server.parked(), 0);
11516        for client in 1..=3u64 {
11517            f.session = Session::new(client);
11518            assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
11519        }
11520        assert_eq!(f.server.parked(), 3);
11521        assert_eq!(f.server.waiters().len(), 3);
11522
11523        // The three ways the list gets shorter, each of which has to move the
11524        // number with it, because a number left behind is either a walk of the
11525        // list that never happens or one that runs off the end of it.
11526        f.server.forget_waiters(2);
11527        assert_eq!(f.server.parked(), f.server.waiters().len());
11528        f.server.forget_waiters(1);
11529        assert_eq!(f.server.parked(), f.server.waiters().len());
11530        f.run(&[b"RPUSH", b"q", b"v"]);
11531        let mut out = Out::new(Proto::Resp2);
11532        assert!(f.server.serve_waiter(3, 0, &mut out));
11533        f.server.forget_waiters(3);
11534        assert_eq!(f.server.parked(), 0);
11535        assert!(f.server.waiters().is_empty());
11536    }
11537
11538    #[test]
11539    fn a_set_goes_from_bytes_to_bytes() {
11540        let mut f = Fixture::new();
11541        assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
11542        assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
11543        assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
11544        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
11545        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
11546        assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
11547        assert_eq!(
11548            f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
11549            "*3\r\n:1\r\n:0\r\n:1\r\n"
11550        );
11551        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
11552        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
11553    }
11554
11555    #[test]
11556    fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
11557        let mut f = Fixture::new();
11558        assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
11559        assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
11560        assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
11561        assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
11562        assert_eq!(
11563            f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
11564            "*2\r\n:0\r\n:0\r\n"
11565        );
11566        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
11567    }
11568
11569    #[test]
11570    fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
11571        // Not cosmetic. A RESP3 client that gets a `~` hands the caller a set
11572        // and one that gets a `*` hands it a list, without either of them being
11573        // told which command was sent.
11574        let mut f = Fixture::new();
11575        f.run(&[b"SADD", b"s", b"one"]);
11576        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
11577
11578        f.run(&[b"HELLO", b"3"]);
11579        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
11580    }
11581
11582    #[test]
11583    fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
11584        // An intset holds the number, so these digits exist for the first time
11585        // in the reply buffer.
11586        let mut f = Fixture::new();
11587        f.run(&[b"SADD", b"s", b"42"]);
11588        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
11589        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
11590        assert_eq!(
11591            f.run(&[b"SISMEMBER", b"s", b"042"]),
11592            ":0\r\n",
11593            "the member is the bytes and not the number they parse to"
11594        );
11595    }
11596
11597    #[test]
11598    fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
11599        let mut f = Fixture::new();
11600        f.run(&[b"SET", b"str", b"v"]);
11601        f.run(&[b"SADD", b"set", b"a"]);
11602
11603        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11604        assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
11605        assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
11606        assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
11607        assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
11608        assert_eq!(f.run(&[b"GET", b"set"]), wrong);
11609        assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
11610        assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
11611        assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
11612
11613        // MGET is the one that does not, because Redis gives nil for the odd
11614        // key out rather than failing the good keys next to it.
11615        assert_eq!(
11616            f.run(&[b"MGET", b"str", b"set", b"nope"]),
11617            "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
11618        );
11619        // And plain SET overwrites any type, which takes the body with it.
11620        assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
11621        assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
11622    }
11623
11624    #[test]
11625    fn a_wrongtype_leaves_nothing_half_written() {
11626        // SMISMEMBER writes an array header and then one reply per member, so
11627        // it is the first command in the server that could get a header out in
11628        // front of an error if it checked its key in the wrong order.
11629        let mut f = Fixture::new();
11630        f.run(&[b"SET", b"k", b"v"]);
11631        let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
11632        assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
11633        assert!(!reply.contains('*'), "an array header went out in front");
11634    }
11635
11636    #[test]
11637    fn emptying_a_set_takes_the_key_with_it() {
11638        let mut f = Fixture::new();
11639        f.run(&[b"SADD", b"s", b"a", b"b"]);
11640        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
11641        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
11642        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11643        assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
11644        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11645    }
11646
11647    /// Pull the cursor and the members out of one `SSCAN` reply.
11648    ///
11649    /// Crude on purpose. A test that walked a set through a real client would
11650    /// be testing the client, and what these tests are about is the shape of
11651    /// the bytes and the fact that a walk sees every member once.
11652    fn split_scan(reply: &str) -> (String, Vec<String>) {
11653        let mut lines = reply.split("\r\n");
11654        assert_eq!(lines.next(), Some("*2"), "got {reply}");
11655        lines.next().expect("the cursor header");
11656        let cursor = lines.next().expect("the cursor").to_owned();
11657        let header = lines.next().expect("the member header");
11658        let n: usize = header[1..].parse().expect("a member count");
11659        let mut members = Vec::with_capacity(n);
11660        for _ in 0..n {
11661            lines.next().expect("a member header");
11662            members.push(lines.next().expect("a member").to_owned());
11663        }
11664        (cursor, members)
11665    }
11666
11667    #[test]
11668    fn popping_takes_a_member_off_the_set_and_hands_it_back() {
11669        let mut f = Fixture::new();
11670        f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
11671
11672        let one = f.run(&[b"SPOP", b"s"]);
11673        assert!(
11674            ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
11675            "got {one}"
11676        );
11677        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
11678
11679        // A count takes that many, and the last one takes the key with it.
11680        let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
11681        assert!(rest.starts_with("*3\r\n"), "got {rest}");
11682        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11683        // And a pop at a key that is not there is a nil, not an empty bulk.
11684        assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
11685        assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
11686    }
11687
11688    #[test]
11689    fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
11690        // The one place in the server where the reply type carries something
11691        // the command name does not. SPOP's members are distinct so a RESP3
11692        // client can build a set out of them. SRANDMEMBER with a negative count
11693        // can hand back the same member three times, and a set would lose two.
11694        let mut f = Fixture::new();
11695        f.run(&[b"HELLO", b"3"]);
11696        f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
11697
11698        assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
11699        // And a positive count is an array too, since Redis makes it one.
11700        assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
11701
11702        // A negative count against a set of one is where the difference bites:
11703        // the same member three times, which is a three element reply and would
11704        // have been a one element reply if it had gone out as a set.
11705        f.run(&[b"SADD", b"one", b"z"]);
11706        assert_eq!(
11707            f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
11708            "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
11709        );
11710    }
11711
11712    #[test]
11713    fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
11714        let mut f = Fixture::new();
11715        f.run(&[b"SADD", b"s", b"only"]);
11716        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
11717        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
11718        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
11719
11720        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
11721        // The count form answers an empty array rather than a nil, which is the
11722        // pair of answers Redis gives and is not the pair it looks like.
11723        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
11724        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
11725        // Asking for more than is there answers all of it once and not padding.
11726        assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
11727    }
11728
11729    #[test]
11730    fn a_pop_count_that_is_not_a_positive_number_says_so() {
11731        let mut f = Fixture::new();
11732        f.run(&[b"SADD", b"s", b"a"]);
11733        let bad = "-ERR value is out of range, must be positive\r\n";
11734        assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
11735        assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
11736        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
11737        // Zero is allowed and is a real answer rather than an error.
11738        assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
11739        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
11740    }
11741
11742    #[test]
11743    fn a_scan_walks_a_set_of_any_size_exactly_once() {
11744        let mut f = Fixture::new();
11745        let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
11746        let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
11747            .into_iter()
11748            .chain(members.iter().map(Vec::as_slice))
11749            .collect();
11750        f.run(&args);
11751
11752        let mut seen = Vec::new();
11753        let mut cursor = "0".to_owned();
11754        loop {
11755            let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
11756            let (next, got) = split_scan(&reply);
11757            seen.extend(got);
11758            cursor = next;
11759            if cursor == "0" {
11760                break;
11761            }
11762        }
11763        seen.sort();
11764        seen.dedup();
11765        assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
11766
11767        // A set small enough to be a listpack answers in one call whatever
11768        // cursor it was handed, which is what Redis does for that encoding.
11769        f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
11770        let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
11771        assert_eq!(cursor, "0");
11772        assert_eq!(got.len(), 3);
11773        // And a key that is not there is a finished scan of nothing.
11774        assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
11775    }
11776
11777    #[test]
11778    fn a_scan_takes_match_and_count_and_refuses_anything_else() {
11779        let mut f = Fixture::new();
11780        f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
11781
11782        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
11783        let mut got = got;
11784        got.sort();
11785        assert_eq!(got, ["aa", "ab"]);
11786
11787        // An integer member has no digits stored anywhere, so MATCH is the one
11788        // place a scan pays to write some.
11789        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
11790        let mut got = got;
11791        got.sort();
11792        assert_eq!(got, ["12", "13"]);
11793
11794        assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
11795        assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
11796        assert_eq!(
11797            f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
11798            "-ERR syntax error\r\n"
11799        );
11800        // A count under one is a syntax error and not a range error, which is
11801        // the odder of Redis's two answers and the reason it is copied exactly.
11802        assert_eq!(
11803            f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
11804            "-ERR syntax error\r\n"
11805        );
11806    }
11807
11808    #[test]
11809    fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
11810        let mut f = Fixture::new();
11811        f.run(&[b"SADD", b"src", b"a", b"b"]);
11812        f.run(&[b"SADD", b"dst", b"c"]);
11813
11814        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
11815        assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
11816        assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
11817        // A member that is not in the source is a zero and moves nothing.
11818        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
11819        assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
11820
11821        // A destination that does not exist gets made, and a source that runs
11822        // out goes away.
11823        assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
11824        assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
11825        assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
11826    }
11827
11828    #[test]
11829    fn moving_checks_the_types_in_the_order_redis_checks_them() {
11830        // Not the order it looks like it should be. A source that is not there
11831        // answers zero without ever looking at the destination, so this is a
11832        // zero and not a WRONGTYPE even though the destination is a string.
11833        let mut f = Fixture::new();
11834        f.run(&[b"SET", b"str", b"v"]);
11835        f.run(&[b"SADD", b"set", b"a"]);
11836
11837        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11838        assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
11839        assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
11840        assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
11841        assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
11842        assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
11843        assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
11844        assert_eq!(
11845            f.run(&[b"SISMEMBER", b"set", b"a"]),
11846            ":1\r\n",
11847            "and none of that moved anything"
11848        );
11849    }
11850
11851    #[test]
11852    fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
11853        // SSCAN writes an outer array header before it walks, so it is the
11854        // command most likely to get bytes out in front of an error.
11855        let mut f = Fixture::new();
11856        f.run(&[b"SADD", b"s", b"a"]);
11857        for bad in [
11858            &[b"SSCAN".as_slice(), b"s", b"abc"][..],
11859            &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
11860            &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
11861        ] {
11862            let reply = f.run(bad);
11863            assert!(reply.starts_with("-ERR"), "got {reply}");
11864            assert!(!reply.contains('*'), "an array header went out in front");
11865        }
11866    }
11867
11868    #[test]
11869    fn a_hash_writes_reads_and_deletes_its_fields() {
11870        let mut f = Fixture::new();
11871        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
11872        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
11873        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
11874        assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
11875        assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
11876        assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
11877        assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
11878        assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
11879        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
11880        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
11881
11882        // The value the client sent is `9`, so HGET h b must not find the `2`
11883        // that is a value. A search with a step of one would have.
11884        assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
11885
11886        assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
11887        assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
11888        assert_eq!(
11889            f.run(&[b"EXISTS", b"h"]),
11890            ":0\r\n",
11891            "and losing the last field lost the key"
11892        );
11893    }
11894
11895    #[test]
11896    fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
11897        let mut f = Fixture::new();
11898        f.run(&[b"HSET", b"h", b"a", b"1"]);
11899        assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11900        assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
11901        assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
11902        assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
11903        assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
11904
11905        f.run(&[b"HELLO", b"3"]);
11906        assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
11907        assert_eq!(
11908            f.run(&[b"HGETALL", b"nokey"]),
11909            "%0\r\n",
11910            "a missing key is the empty hash and never a nil"
11911        );
11912        assert_eq!(
11913            f.run(&[b"HKEYS", b"h"]),
11914            "*1\r\n$1\r\na\r\n",
11915            "and the two that answer one side stay arrays"
11916        );
11917    }
11918
11919    #[test]
11920    fn hmget_answers_once_per_field_and_hmset_answers_ok() {
11921        let mut f = Fixture::new();
11922        assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
11923        assert_eq!(
11924            f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
11925            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
11926            "the reply is positional, so b is a nil and not a gap"
11927        );
11928        assert_eq!(
11929            f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
11930            "*2\r\n$-1\r\n$-1\r\n",
11931            "and a missing key is all nils rather than an empty array"
11932        );
11933
11934        assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
11935        assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
11936        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
11937    }
11938
11939    #[test]
11940    fn a_hash_counts_up_and_says_so_when_it_cannot() {
11941        let mut f = Fixture::new();
11942        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
11943        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
11944        assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
11945        assert_eq!(
11946            f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
11947            "$4\r\n10.5\r\n",
11948            "a bulk string and not a double, on both protocols"
11949        );
11950
11951        f.run(&[b"HSET", b"h", b"s", b"words"]);
11952        let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
11953        assert!(
11954            bad.starts_with("-ERR hash value is not an integer"),
11955            "{bad}"
11956        );
11957        let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
11958        assert!(
11959            bad.starts_with("-ERR value is not an integer"),
11960            "a bad argument is not yet a hash value, {bad}"
11961        );
11962        assert_eq!(
11963            f.run(&[b"HGET", b"h", b"s"]),
11964            "$5\r\nwords\r\n",
11965            "and neither of them wrote anything"
11966        );
11967    }
11968
11969    #[test]
11970    fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
11971        // Fourteen minutes under Miri at five hundred, which was the slowest
11972        // test in this crate that was not about megabytes. What the count has
11973        // to be is more than one page of the cursor, and the count below is
11974        // thirty two, so ninety six is three pages and asks the same question.
11975        let fields = if cfg!(miri) { 96 } else { 500 };
11976        let mut f = Fixture::new();
11977        for i in 0..fields {
11978            let field = format!("field-{i}");
11979            let value = format!("value-{i}");
11980            f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
11981        }
11982
11983        let mut seen: Vec<String> = Vec::new();
11984        let mut cursor = "0".to_owned();
11985        loop {
11986            let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
11987            let (next, items) = scan_reply(&reply);
11988            assert_eq!(items.len() % 2, 0, "a pair went out half written");
11989            for pair in items.chunks(2) {
11990                assert_eq!(
11991                    pair[0].strip_prefix("field-"),
11992                    pair[1].strip_prefix("value-"),
11993                    "a field came back with someone else's value"
11994                );
11995                seen.push(pair[0].clone());
11996            }
11997            cursor = next;
11998            if cursor == "0" {
11999                break;
12000            }
12001        }
12002        seen.sort();
12003        seen.dedup();
12004        assert_eq!(seen.len(), fields, "every field once and only once");
12005
12006        let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
12007        assert!(
12008            items.iter().all(|s| s.starts_with("field-")),
12009            "NOVALUES still sent the values"
12010        );
12011
12012        let last = fields - 1;
12013        let (_, one) = scan_reply(&f.run(&[
12014            b"HSCAN",
12015            b"h",
12016            b"0",
12017            b"MATCH",
12018            format!("field-{last}").as_bytes(),
12019            b"COUNT",
12020            b"1000",
12021        ]));
12022        assert_eq!(
12023            one,
12024            [format!("field-{last}"), format!("value-{last}")],
12025            "MATCH is on the field"
12026        );
12027    }
12028
12029    #[test]
12030    fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
12031        let mut f = Fixture::new();
12032        f.run(&[b"HSET", b"h", b"a", b"1"]);
12033        assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
12034        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
12035        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
12036        assert_eq!(
12037            f.run(&[b"HRANDFIELD", b"h", b"3"]),
12038            "*1\r\n$1\r\na\r\n",
12039            "a positive count is capped at the size of the hash"
12040        );
12041        assert_eq!(
12042            f.run(&[b"HRANDFIELD", b"h", b"-3"]),
12043            "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
12044            "and a negative one repeats itself"
12045        );
12046        assert_eq!(
12047            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
12048            "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
12049            "flat on RESP2"
12050        );
12051
12052        f.run(&[b"HELLO", b"3"]);
12053        assert_eq!(
12054            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
12055            "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
12056            "and nested on RESP3, but still an array and never a map"
12057        );
12058    }
12059
12060    #[test]
12061    fn every_hash_command_says_wrongtype_and_writes_nothing() {
12062        let mut f = Fixture::new();
12063        f.run(&[b"SET", b"str", b"v"]);
12064        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12065
12066        for cmd in [
12067            &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
12068            &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
12069            &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
12070            &[b"HGET".as_slice(), b"str", b"f"][..],
12071            &[b"HMGET".as_slice(), b"str", b"f"][..],
12072            &[b"HDEL".as_slice(), b"str", b"f"][..],
12073            &[b"HLEN".as_slice(), b"str"][..],
12074            &[b"HEXISTS".as_slice(), b"str", b"f"][..],
12075            &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
12076            &[b"HGETALL".as_slice(), b"str"][..],
12077            &[b"HKEYS".as_slice(), b"str"][..],
12078            &[b"HVALS".as_slice(), b"str"][..],
12079            &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
12080            &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
12081            &[b"HRANDFIELD".as_slice(), b"str"][..],
12082            &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
12083            &[b"HSCAN".as_slice(), b"str", b"0"][..],
12084        ] {
12085            let reply = f.run(cmd);
12086            assert_eq!(reply, wrong, "{:?}", cmd[0]);
12087        }
12088        assert_eq!(
12089            f.run(&[b"GET", b"str"]),
12090            "$1\r\nv\r\n",
12091            "and none of them touched the value"
12092        );
12093    }
12094
12095    #[test]
12096    fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
12097        let mut f = Fixture::new();
12098        f.run(&[b"HSET", b"h", b"f", b"v"]);
12099        for bad in [
12100            &[b"HSCAN".as_slice(), b"h", b"abc"][..],
12101            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
12102            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
12103            &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
12104        ] {
12105            let reply = f.run(bad);
12106            assert!(reply.starts_with("-ERR"), "got {reply}");
12107            assert!(!reply.contains('*'), "an array header went out in front");
12108        }
12109    }
12110
12111    #[test]
12112    fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
12113        let mut f = Fixture::new();
12114        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12115        assert_eq!(
12116            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
12117            "*1\r\n:1\r\n"
12118        );
12119        assert_eq!(
12120            f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
12121            "*3\r\n:100\r\n:-1\r\n:-2\r\n",
12122            "one answer per field, and the two sentinels are TTL's own"
12123        );
12124
12125        // The same deadline in the other three units, all of them derived from
12126        // the one number the store kept.
12127        let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
12128        assert!((99_000..=100_000).contains(&ms), "got {ms}");
12129        let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
12130        let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
12131        assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
12132        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
12133
12134        assert_eq!(
12135            f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
12136            "*3\r\n:1\r\n:-1\r\n:-2\r\n",
12137            "one for the deadline taken off, and it does not say what it was"
12138        );
12139        assert_eq!(
12140            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12141            "*1\r\n:-1\r\n"
12142        );
12143        assert_eq!(
12144            f.run(&[b"HGET", b"h", b"a"]),
12145            "$1\r\n1\r\n",
12146            "and the field is still there with the value it had"
12147        );
12148    }
12149
12150    #[test]
12151    fn a_deadline_that_has_already_gone_deletes_the_field_now() {
12152        let mut f = Fixture::new();
12153        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12154        assert_eq!(
12155            f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
12156            "*1\r\n:2\r\n",
12157            "two, and not one, because nothing was stored"
12158        );
12159        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
12160        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
12161
12162        assert_eq!(
12163            f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
12164            "*1\r\n:2\r\n"
12165        );
12166        assert_eq!(
12167            f.run(&[b"EXISTS", b"h"]),
12168            ":0\r\n",
12169            "and the last field going took the key with it"
12170        );
12171
12172        // Zero is a delete and not an error, where minus one is an error. That
12173        // is Redis's split and it is easy to get backwards.
12174        f.run(&[b"HSET", b"h", b"a", b"1"]);
12175        assert_eq!(
12176            f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
12177            "*1\r\n:2\r\n"
12178        );
12179    }
12180
12181    #[test]
12182    fn a_field_is_gone_once_its_moment_passes() {
12183        let mut f = Fixture::new();
12184        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12185        assert_eq!(
12186            f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
12187            "*1\r\n:1\r\n"
12188        );
12189        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
12190
12191        // Time moves once per turn of the event loop and nowhere else, so a
12192        // test moves it by hand rather than by sleeping. There is nothing to
12193        // sleep for: the deadline is a number and so is the clock.
12194        f.server.advance_clock_ms(60);
12195        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
12196        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
12197        assert_eq!(
12198            f.run(&[b"HGETALL", b"h"]),
12199            "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
12200            "and the walks do not hand back a field that has expired"
12201        );
12202    }
12203
12204    #[test]
12205    fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
12206        let mut f = Fixture::new();
12207        for cmd in [
12208            &[
12209                b"HEXPIRE".as_slice(),
12210                b"nokey",
12211                b"100",
12212                b"FIELDS",
12213                b"2",
12214                b"a",
12215                b"b",
12216            ][..],
12217            &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
12218            &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
12219            &[
12220                b"HEXPIRETIME".as_slice(),
12221                b"nokey",
12222                b"FIELDS",
12223                b"2",
12224                b"a",
12225                b"b",
12226            ][..],
12227            &[
12228                b"HPERSIST".as_slice(),
12229                b"nokey",
12230                b"FIELDS",
12231                b"2",
12232                b"a",
12233                b"b",
12234            ][..],
12235        ] {
12236            assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
12237        }
12238    }
12239
12240    #[test]
12241    fn writing_a_field_clears_the_deadline_that_was_on_it() {
12242        let mut f = Fixture::new();
12243        f.run(&[b"HSET", b"h", b"a", b"1"]);
12244        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
12245        f.run(&[b"HSET", b"h", b"a", b"2"]);
12246        assert_eq!(
12247            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12248            "*1\r\n:-1\r\n",
12249            "Redis has done this since 7.4, and it is why HGETEX exists"
12250        );
12251    }
12252
12253    #[test]
12254    fn the_four_conditions_reach_the_store_the_way_they_were_written() {
12255        let mut f = Fixture::new();
12256        f.run(&[b"HSET", b"h", b"a", b"1"]);
12257        assert_eq!(
12258            f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
12259            "*1\r\n:0\r\n",
12260            "XX on a field with no deadline changes nothing"
12261        );
12262        assert_eq!(
12263            f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
12264            "*1\r\n:1\r\n"
12265        );
12266        assert_eq!(
12267            f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
12268            "*1\r\n:0\r\n",
12269            "and NX will not move one that is already there"
12270        );
12271        assert_eq!(
12272            f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
12273            "*1\r\n:0\r\n"
12274        );
12275        assert_eq!(
12276            f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
12277            "*1\r\n:1\r\n"
12278        );
12279        assert_eq!(
12280            f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
12281            "*1\r\n:1\r\n"
12282        );
12283        assert_eq!(
12284            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12285            "*1\r\n:50\r\n"
12286        );
12287    }
12288
12289    #[test]
12290    fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
12291        let mut f = Fixture::new();
12292        f.run(&[b"HSET", b"h", b"a", b"1"]);
12293        for (bad, want) in [
12294            (
12295                &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
12296                "-ERR invalid expire time, must be >= 0",
12297            ),
12298            (
12299                &[
12300                    b"HEXPIRE".as_slice(),
12301                    b"h",
12302                    b"9999999999999999",
12303                    b"FIELDS",
12304                    b"1",
12305                    b"a",
12306                ][..],
12307                "-ERR invalid expire time in 'hexpire' command",
12308            ),
12309            (
12310                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
12311                "-ERR wrong number of arguments for 'hexpire' command",
12312            ),
12313            (
12314                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
12315                "-ERR Parameter `numFields` should be greater than 0",
12316            ),
12317            (
12318                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
12319                "-ERR wrong number of arguments",
12320            ),
12321            (
12322                &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
12323                "-ERR wrong number of arguments",
12324            ),
12325        ] {
12326            let reply = f.run(bad);
12327            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
12328            assert!(!reply.contains('*'), "an array header went out in front");
12329        }
12330        assert_eq!(
12331            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12332            "*1\r\n:-1\r\n",
12333            "and not one of them put a deadline on anything"
12334        );
12335    }
12336
12337    #[test]
12338    fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
12339        let mut f = Fixture::new();
12340        f.run(&[b"SET", b"str", b"v"]);
12341        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12342
12343        for cmd in [
12344            &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
12345            &[
12346                b"HPEXPIRE".as_slice(),
12347                b"str",
12348                b"100",
12349                b"FIELDS",
12350                b"1",
12351                b"f",
12352            ][..],
12353            &[
12354                b"HEXPIREAT".as_slice(),
12355                b"str",
12356                b"9999999999",
12357                b"FIELDS",
12358                b"1",
12359                b"f",
12360            ][..],
12361            &[
12362                b"HPEXPIREAT".as_slice(),
12363                b"str",
12364                b"9999999999999",
12365                b"FIELDS",
12366                b"1",
12367                b"f",
12368            ][..],
12369            &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12370            &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12371            &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12372            &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12373            &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12374        ] {
12375            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12376        }
12377        assert_eq!(
12378            f.run(&[b"GET", b"str"]),
12379            "$1\r\nv\r\n",
12380            "and none of them touched the value"
12381        );
12382    }
12383
12384    #[test]
12385    fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
12386        let mut f = Fixture::new();
12387        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
12388        assert_eq!(
12389            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
12390            "*2\r\n$1\r\n1\r\n$-1\r\n",
12391            "positional, so the field that was not there is a nil in its place"
12392        );
12393        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
12394        assert_eq!(
12395            f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
12396            "*1\r\n$-1\r\n"
12397        );
12398        assert_eq!(
12399            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
12400            "*1\r\n$1\r\n2\r\n"
12401        );
12402        assert_eq!(
12403            f.run(&[b"EXISTS", b"h"]),
12404            ":0\r\n",
12405            "and the last field took the key"
12406        );
12407    }
12408
12409    #[test]
12410    fn hgetex_reads_and_moves_the_deadline_in_one_command() {
12411        let mut f = Fixture::new();
12412        f.run(&[b"HSET", b"h", b"a", b"1"]);
12413        assert_eq!(
12414            f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
12415            "*1\r\n$1\r\n1\r\n"
12416        );
12417        assert_eq!(
12418            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12419            "*1\r\n:-1\r\n",
12420            "no option means leave it alone, which is the one place this is not GETEX"
12421        );
12422
12423        f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
12424        assert_eq!(
12425            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12426            "*1\r\n:100\r\n"
12427        );
12428        f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
12429        assert_eq!(
12430            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12431            "*1\r\n:100\r\n",
12432            "and a plain read really does leave it alone"
12433        );
12434        assert_eq!(
12435            f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
12436            "*1\r\n$1\r\n1\r\n"
12437        );
12438        assert_eq!(
12439            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12440            "*1\r\n:-1\r\n"
12441        );
12442
12443        assert_eq!(
12444            f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
12445            "*1\r\n$1\r\n1\r\n",
12446            "the value goes out before the deadline that has already gone is applied"
12447        );
12448        assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
12449        assert_eq!(
12450            f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
12451            "*1\r\n$-1\r\n"
12452        );
12453    }
12454
12455    #[test]
12456    fn hsetex_writes_all_of_it_or_none_of_it() {
12457        let mut f = Fixture::new();
12458        assert_eq!(
12459            f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
12460            ":1\r\n"
12461        );
12462        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12463        assert_eq!(
12464            f.run(&[
12465                b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
12466            ]),
12467            ":0\r\n",
12468            "FNX wants every field named to be missing"
12469        );
12470        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12471        assert_eq!(
12472            f.run(&[b"HEXISTS", b"h", b"new"]),
12473            ":0\r\n",
12474            "and none of the list was written"
12475        );
12476        assert_eq!(
12477            f.run(&[
12478                b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
12479            ]),
12480            ":0\r\n",
12481            "and FXX wants every one of them to be there"
12482        );
12483        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
12484        assert_eq!(
12485            f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
12486            ":1\r\n"
12487        );
12488        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
12489
12490        assert_eq!(
12491            f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
12492            ":0\r\n"
12493        );
12494        assert_eq!(
12495            f.run(&[b"EXISTS", b"gone"]),
12496            ":0\r\n",
12497            "a key with no fields cannot meet FXX and is not created trying"
12498        );
12499    }
12500
12501    #[test]
12502    fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
12503        let mut f = Fixture::new();
12504        f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
12505        assert_eq!(
12506            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12507            "*1\r\n:100\r\n"
12508        );
12509
12510        f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
12511        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
12512        assert_eq!(
12513            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12514            "*1\r\n:100\r\n",
12515            "KEEPTTL put back what the write cleared"
12516        );
12517
12518        f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
12519        assert_eq!(
12520            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12521            "*1\r\n:-1\r\n",
12522            "and without it a write clears the deadline the way HSET does"
12523        );
12524
12525        // Any order, because Redis reads these in a loop and not in a fixed
12526        // sequence.
12527        assert_eq!(
12528            f.run(&[
12529                b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
12530            ]),
12531            ":1\r\n"
12532        );
12533        assert_eq!(
12534            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12535            "*1\r\n:100\r\n"
12536        );
12537
12538        assert_eq!(
12539            f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
12540            ":1\r\n",
12541            "written, and not the separate code the HEXPIRE family has for this"
12542        );
12543        assert_eq!(
12544            f.run(&[b"EXISTS", b"h"]),
12545            ":0\r\n",
12546            "and storing it and then removing it emptied the hash"
12547        );
12548    }
12549
12550    #[test]
12551    fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
12552        let mut f = Fixture::new();
12553        f.run(&[b"HSET", b"h", b"a", b"1"]);
12554        for (bad, want) in [
12555            // HGETDEL has three sentences of its own for these three mistakes.
12556            (
12557                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
12558                "-ERR Number of fields must be a positive integer",
12559            ),
12560            (
12561                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
12562                "-ERR The `numfields` parameter must match the number of arguments",
12563            ),
12564            (
12565                &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
12566                "-ERR Mandatory argument FIELDS is missing or not at the right position",
12567            ),
12568            // And HGETEX and HSETEX have three different ones between them.
12569            (
12570                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
12571                "-ERR invalid number of fields",
12572            ),
12573            (
12574                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
12575                "-ERR wrong number of arguments",
12576            ),
12577            (
12578                &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
12579                "-ERR unknown argument: FIELD",
12580            ),
12581            (
12582                &[
12583                    b"HGETEX".as_slice(),
12584                    b"h",
12585                    b"KEEPTTL",
12586                    b"FIELDS",
12587                    b"1",
12588                    b"a",
12589                ][..],
12590                "-ERR unknown argument: KEEPTTL",
12591            ),
12592            (
12593                &[
12594                    b"HGETEX".as_slice(),
12595                    b"h",
12596                    b"EX",
12597                    b"100",
12598                    b"PERSIST",
12599                    b"FIELDS",
12600                    b"1",
12601                    b"a",
12602                ][..],
12603                "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
12604            ),
12605            (
12606                &[
12607                    b"HSETEX".as_slice(),
12608                    b"h",
12609                    b"EX",
12610                    b"1",
12611                    b"KEEPTTL",
12612                    b"FIELDS",
12613                    b"1",
12614                    b"a",
12615                    b"1",
12616                ][..],
12617                "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
12618            ),
12619            (
12620                &[
12621                    b"HSETEX".as_slice(),
12622                    b"h",
12623                    b"FNX",
12624                    b"FXX",
12625                    b"FIELDS",
12626                    b"1",
12627                    b"a",
12628                    b"1",
12629                ][..],
12630                "-ERR Only one of FXX or FNX arguments can be specified",
12631            ),
12632            (
12633                &[
12634                    b"HSETEX".as_slice(),
12635                    b"h",
12636                    b"FIELDS",
12637                    b"2",
12638                    b"a",
12639                    b"1",
12640                    b"b",
12641                ][..],
12642                "-ERR wrong number of arguments",
12643            ),
12644            (
12645                &[
12646                    b"HGETEX".as_slice(),
12647                    b"h",
12648                    b"EX",
12649                    b"-1",
12650                    b"FIELDS",
12651                    b"1",
12652                    b"a",
12653                ][..],
12654                "-ERR invalid expire time, must be >= 0",
12655            ),
12656            (
12657                &[
12658                    b"HGETEX".as_slice(),
12659                    b"h",
12660                    b"PXAT",
12661                    b"99999999999999",
12662                    b"FIELDS",
12663                    b"1",
12664                    b"a",
12665                ][..],
12666                "-ERR invalid expire time in 'hgetex' command",
12667            ),
12668            (
12669                &[
12670                    b"HSETEX".as_slice(),
12671                    b"h",
12672                    b"EX",
12673                    b"abc",
12674                    b"FIELDS",
12675                    b"1",
12676                    b"a",
12677                    b"1",
12678                ][..],
12679                "-ERR value is not an integer or out of range",
12680            ),
12681        ] {
12682            let reply = f.run(bad);
12683            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
12684            assert!(!reply.contains('*'), "an array header went out in front");
12685        }
12686        assert_eq!(
12687            f.run(&[b"HGET", b"h", b"a"]),
12688            "$1\r\n1\r\n",
12689            "and not one of them wrote anything"
12690        );
12691        assert_eq!(
12692            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
12693            "*1\r\n:-1\r\n"
12694        );
12695    }
12696
12697    #[test]
12698    fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
12699        let mut f = Fixture::new();
12700        f.run(&[b"SET", b"str", b"v"]);
12701        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12702        for cmd in [
12703            &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12704            &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
12705            &[
12706                b"HGETEX".as_slice(),
12707                b"str",
12708                b"EX",
12709                b"100",
12710                b"FIELDS",
12711                b"1",
12712                b"f",
12713            ][..],
12714            &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
12715        ] {
12716            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12717        }
12718        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
12719    }
12720
12721    /// The two orders `HIMPORT` juggles, which are not the same order.
12722    ///
12723    /// Values arrive in the order the fields were declared in and the hash is
12724    /// built in sorted order, so the first value is not generally the first
12725    /// field. And the sort is by length before bytes, which nothing else here
12726    /// sorts names with: `b` comes before `aa` where a plain byte comparison
12727    /// would put `aa` first. Both read off 8.10.1.
12728    #[test]
12729    fn himport_writes_declared_values_into_sorted_fields() {
12730        let mut f = Fixture::new();
12731        assert_eq!(
12732            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
12733            "+OK\r\n"
12734        );
12735        assert_eq!(
12736            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
12737            "+OK\r\n"
12738        );
12739        assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
12740        assert_eq!(
12741            f.run(&[b"HGETALL", b"k"]),
12742            bulks(&["a", "3", "b", "1", "aa", "2"])
12743        );
12744    }
12745
12746    /// It replaces the key rather than writing over it, so a field the fieldset
12747    /// does not name is gone afterwards and so is the deadline.
12748    #[test]
12749    fn himport_set_replaces_the_whole_key() {
12750        let mut f = Fixture::new();
12751        f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
12752        f.run(&[b"EXPIRE", b"k", b"100"]);
12753        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12754        assert_eq!(
12755            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
12756            "+OK\r\n"
12757        );
12758        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
12759        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
12760    }
12761
12762    /// A fieldset is connection state. `SELECT` leaves them alone and `RESET`
12763    /// throws them away, and a key built from one outlives it.
12764    #[test]
12765    fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
12766        let mut f = Fixture::new();
12767        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
12768        f.run(&[b"SELECT", b"1"]);
12769        assert_eq!(
12770            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
12771            "+OK\r\n"
12772        );
12773        f.run(&[b"SELECT", b"0"]);
12774        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
12775        assert_eq!(
12776            f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
12777            "-ERR no such fieldset\r\n"
12778        );
12779    }
12780
12781    /// Which complaint wins when a line is wrong in more than one place.
12782    ///
12783    /// The type of the key beats both of the others, so a `HIMPORT SET` against
12784    /// a string is a WRONGTYPE even when the fieldset is missing too, which is
12785    /// the ordering a real server has and not the one the argument order
12786    /// suggests.
12787    #[test]
12788    fn himport_complains_in_the_order_a_real_server_does() {
12789        let mut f = Fixture::new();
12790        f.run(&[b"SET", b"str", b"v"]);
12791        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12792        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12793        assert_eq!(
12794            f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
12795            wrong,
12796            "the type beats a missing fieldset"
12797        );
12798        assert_eq!(
12799            f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
12800            wrong,
12801            "and it beats a value count that does not fit"
12802        );
12803        assert_eq!(
12804            f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
12805            "-ERR no such fieldset\r\n"
12806        );
12807        // One sentence for too few and for too many alike.
12808        for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
12809            let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
12810            line.extend_from_slice(values);
12811            assert_eq!(
12812                f.run(&line),
12813                "-ERR value count does not match fieldset field count\r\n",
12814                "{} values into two fields",
12815                values.len()
12816            );
12817        }
12818        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
12819    }
12820
12821    /// The arity of each subcommand, and the unknown one.
12822    #[test]
12823    fn himport_checks_each_subcommand_count_under_its_own_name() {
12824        let mut f = Fixture::new();
12825        assert_eq!(
12826            f.run(&[b"HIMPORT"]),
12827            "-ERR wrong number of arguments for 'himport' command\r\n"
12828        );
12829        for (rest, name) in [
12830            (&["PREPARE"][..], "prepare"),
12831            (&["PREPARE", "fs"][..], "prepare"),
12832            (&["SET"][..], "set"),
12833            (&["SET", "k"][..], "set"),
12834            (&["SET", "k", "fs"][..], "set"),
12835            (&["DISCARD"][..], "discard"),
12836            (&["DISCARD", "a", "b"][..], "discard"),
12837            (&["DISCARDALL", "x"][..], "discardall"),
12838        ] {
12839            let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
12840            line.extend(rest.iter().map(|a| a.as_bytes()));
12841            assert_eq!(
12842                f.run(&line),
12843                format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
12844                "HIMPORT {}",
12845                rest.join(" ")
12846            );
12847        }
12848        assert_eq!(
12849            f.run(&[b"HIMPORT", b"NOPE", b"x"]),
12850            "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
12851        );
12852    }
12853
12854    /// A `PREPARE` that fails leaves the name pointing where it pointed, which
12855    /// is the answer of the two that could not be guessed from outside.
12856    #[test]
12857    fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
12858        let mut f = Fixture::new();
12859        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12860        assert_eq!(
12861            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
12862            "-ERR duplicate field name in fieldset\r\n"
12863        );
12864        assert_eq!(
12865            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
12866            "+OK\r\n"
12867        );
12868        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
12869    }
12870
12871    /// Preparing the same name twice replaces it, and the two discards count
12872    /// what they took rather than answering OK.
12873    #[test]
12874    fn himport_prepare_replaces_and_the_discards_count() {
12875        let mut f = Fixture::new();
12876        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
12877        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
12878        assert_eq!(
12879            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
12880            "+OK\r\n"
12881        );
12882        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
12883
12884        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
12885        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
12886        f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
12887        f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
12888        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
12889        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
12890    }
12891
12892    /// The one integer of a single element array reply.
12893    /// The number out of a plain integer reply.
12894    ///
12895    /// [`int_reply`] is the same thing wrapped in a one element array, which is
12896    /// the shape every hash field command answers in.
12897    fn int(reply: &str) -> i64 {
12898        let body = reply
12899            .strip_prefix(':')
12900            .and_then(|s| s.strip_suffix("\r\n"))
12901            .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
12902        body.parse().expect("an integer")
12903    }
12904
12905    fn int_reply(reply: &str) -> i64 {
12906        let body = reply
12907            .strip_prefix("*1\r\n:")
12908            .and_then(|s| s.strip_suffix("\r\n"))
12909            .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
12910        body.parse().expect("an integer")
12911    }
12912
12913    /// The cursor and the flat items of a scan reply.
12914    fn scan_reply(reply: &str) -> (String, Vec<String>) {
12915        let mut lines = reply.split("\r\n");
12916        assert_eq!(lines.next(), Some("*2"), "got {reply}");
12917        lines.next().expect("the cursor header");
12918        let cursor = lines.next().expect("a cursor").to_owned();
12919        let header = lines.next().expect("an item count");
12920        let n: usize = header[1..].parse().expect("a count");
12921        let mut items = Vec::with_capacity(n);
12922        for _ in 0..n {
12923            lines.next().expect("an item header");
12924            items.push(lines.next().expect("an item").to_owned());
12925        }
12926        (cursor, items)
12927    }
12928
12929    /// The members of a set reply, sorted, since none of these promise an
12930    /// order and a test that asserted one would be asserting an accident.
12931    fn sorted(reply: &str) -> Vec<String> {
12932        let mut lines = reply.split("\r\n");
12933        let header = lines.next().expect("a header");
12934        assert!(
12935            header.starts_with('*') || header.starts_with('~'),
12936            "got {reply}"
12937        );
12938        let n: usize = header[1..].parse().expect("a member count");
12939        let mut got = Vec::with_capacity(n);
12940        for _ in 0..n {
12941            lines.next().expect("a member header");
12942            got.push(lines.next().expect("a member").to_owned());
12943        }
12944        got.sort();
12945        got
12946    }
12947
12948    #[test]
12949    fn the_algebra_answers_what_the_sets_share_and_do_not() {
12950        let mut f = Fixture::new();
12951        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
12952        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
12953        f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
12954
12955        assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
12956        assert_eq!(
12957            sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
12958            ["1", "2", "3", "4", "5"]
12959        );
12960        assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
12961        assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
12962
12963        // A key that is not there is an empty set, which empties an
12964        // intersection and does nothing at all to a union.
12965        assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
12966        assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
12967        assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
12968        assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
12969    }
12970
12971    #[test]
12972    fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
12973        let mut f = Fixture::new();
12974        f.run(&[b"SADD", b"a", b"x"]);
12975        assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
12976        assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
12977        assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
12978
12979        f.run(&[b"HELLO", b"3"]);
12980        assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
12981        assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
12982        assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
12983        assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
12984    }
12985
12986    #[test]
12987    fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
12988        let mut f = Fixture::new();
12989        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
12990        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
12991
12992        assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
12993        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
12994        assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
12995        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
12996        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
12997        assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
12998
12999        // An empty answer deletes the destination rather than leaving an empty
13000        // set behind, and the destination may be one of the sources.
13001        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
13002        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
13003        assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
13004        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
13005
13006        // And a destination holding something else is overwritten, the same way
13007        // SET overwrites, rather than refused.
13008        f.run(&[b"SET", b"str", b"v"]);
13009        assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
13010        assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
13011    }
13012
13013    #[test]
13014    fn sintercard_counts_without_building_and_stops_at_a_limit() {
13015        let mut f = Fixture::new();
13016        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
13017        f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
13018
13019        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
13020        assert_eq!(
13021            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
13022            ":2\r\n"
13023        );
13024        assert_eq!(
13025            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
13026            ":3\r\n",
13027            "a limit of zero is no limit"
13028        );
13029        assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
13030        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
13031
13032        // The counted keys are what make its three error messages its own.
13033        assert_eq!(
13034            f.run(&[b"SINTERCARD", b"0", b"a"]),
13035            "-ERR numkeys should be greater than 0\r\n"
13036        );
13037        assert_eq!(
13038            f.run(&[b"SINTERCARD", b"abc", b"a"]),
13039            "-ERR numkeys should be greater than 0\r\n"
13040        );
13041        assert_eq!(
13042            f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
13043            "-ERR Number of keys can't be greater than number of args\r\n"
13044        );
13045        assert_eq!(
13046            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
13047            "-ERR LIMIT can't be negative\r\n"
13048        );
13049        assert_eq!(
13050            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
13051            "-ERR syntax error\r\n"
13052        );
13053        // A key really can be called LIMIT, which is why the count exists.
13054        f.run(&[b"SADD", b"LIMIT", b"2"]);
13055        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
13056    }
13057
13058    /// The two Redis 8.10 added, which are SINTERCARD's shape over a union and
13059    /// over a difference. Every number here was read off 8.10.1 first.
13060    #[test]
13061    fn sunioncard_and_sdiffcard_count_without_building() {
13062        let mut f = Fixture::new();
13063        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
13064        f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
13065
13066        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
13067        assert_eq!(
13068            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
13069            ":2\r\n"
13070        );
13071        assert_eq!(
13072            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
13073            ":6\r\n",
13074            "a limit of zero is no limit"
13075        );
13076        assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
13077        assert_eq!(
13078            f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
13079            ":4\r\n",
13080            "a missing key adds nothing to a union"
13081        );
13082
13083        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
13084        assert_eq!(
13085            f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
13086            ":1\r\n"
13087        );
13088        assert_eq!(
13089            f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
13090            ":2\r\n",
13091            "a difference is not symmetric"
13092        );
13093        assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
13094        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
13095        assert_eq!(
13096            f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
13097            ":0\r\n",
13098            "nothing taken away from nothing"
13099        );
13100
13101        // The same three messages SINTERCARD has, because the line is the same
13102        // line and is parsed once for all three.
13103        for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
13104            assert_eq!(
13105                f.run(&[name, b"0", b"a"]),
13106                "-ERR numkeys should be greater than 0\r\n"
13107            );
13108            assert_eq!(
13109                f.run(&[name, b"abc", b"a"]),
13110                "-ERR numkeys should be greater than 0\r\n"
13111            );
13112            assert_eq!(
13113                f.run(&[name, b"-1", b"a"]),
13114                "-ERR numkeys should be greater than 0\r\n"
13115            );
13116            assert_eq!(
13117                f.run(&[name, b"3", b"a", b"b"]),
13118                "-ERR Number of keys can't be greater than number of args\r\n"
13119            );
13120            assert_eq!(
13121                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
13122                "-ERR LIMIT can't be negative\r\n"
13123            );
13124            assert_eq!(
13125                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
13126                "-ERR LIMIT can't be negative\r\n",
13127                "a LIMIT that is not a number gets the negative message too"
13128            );
13129            assert_eq!(
13130                f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
13131                "-ERR syntax error\r\n"
13132            );
13133            assert_eq!(
13134                f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
13135                "-ERR syntax error\r\n"
13136            );
13137            assert_eq!(
13138                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
13139                "-ERR syntax error\r\n"
13140            );
13141        }
13142
13143        // And a key called LIMIT is a key, here as much as on SINTERCARD.
13144        f.run(&[b"SADD", b"LIMIT", b"2"]);
13145        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
13146        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
13147    }
13148
13149    #[test]
13150    fn the_algebra_answers_wrongtype_before_it_writes_anything() {
13151        let mut f = Fixture::new();
13152        f.run(&[b"SADD", b"a", b"1"]);
13153        f.run(&[b"SADD", b"d", b"old"]);
13154        f.run(&[b"SET", b"str", b"v"]);
13155
13156        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13157        for bad in [
13158            &[b"SINTER".as_slice(), b"a", b"str"][..],
13159            &[b"SUNION".as_slice(), b"str"][..],
13160            &[b"SDIFF".as_slice(), b"a", b"str"][..],
13161            &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
13162            &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
13163            &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
13164            &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
13165        ] {
13166            let reply = f.run(bad);
13167            assert_eq!(reply, wrong, "for {:?}", bad[0]);
13168        }
13169        assert_eq!(
13170            f.run(&[b"SMEMBERS", b"d"]),
13171            "*1\r\n$3\r\nold\r\n",
13172            "and the destination was left alone every time"
13173        );
13174    }
13175
13176    /// The leak a set can spring that nothing on the wire would ever show: the
13177    /// key goes, the body does not, and `DBSIZE` looks right the whole time.
13178    /// Not under Miri. What this claims is that memory does not grow over two
13179    /// hundred passes, so the passes are the claim rather than the way it
13180    /// happens to be written, and two hundred passes of a two hundred member
13181    /// collection is forty thousand trips through dispatch, which is what an
13182    /// interpreter charges for. A count small enough to run there would leave a
13183    /// server that reclaims nothing inside the bound and the test would pass on
13184    /// a leak. Nothing about memory safety goes uninterpreted either way: this
13185    /// is an accounting claim, and the same commands are run a few at a time by
13186    /// the tests around it.
13187    #[cfg_attr(miri, ignore = "the volume is the claim")]
13188    #[test]
13189    fn churning_sets_does_not_grow_the_server() {
13190        let mut f = Fixture::new();
13191        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
13192        let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
13193            .chain(std::iter::once(&b"s"[..]))
13194            .chain(members.iter().map(Vec::as_slice))
13195            .collect();
13196
13197        f.run(&args);
13198        f.run(&[b"DEL", b"s"]);
13199        f.server.compact_step();
13200        let after_first = f.server.memory_bytes();
13201
13202        for _ in 0..200 {
13203            f.run(&args);
13204            f.run(&[b"DEL", b"s"]);
13205            f.server.compact_step();
13206        }
13207        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13208        assert!(
13209            f.server.memory_bytes() <= after_first * 2,
13210            "held {} after two hundred passes against {after_first} after one",
13211            f.server.memory_bytes()
13212        );
13213    }
13214
13215    // --------------------------------------------------------------- bitmaps
13216
13217    /// The two single bit commands, and the encoding rule underneath them.
13218    ///
13219    /// A write always leaves the value `raw` and a read never re-encodes, which
13220    /// is why the `int` key here is still `int` after a `GETBIT` and is `raw`
13221    /// with its first digit changed after a `SETBIT`.
13222    #[test]
13223    fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
13224        let mut f = Fixture::new();
13225        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
13226        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
13227        assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
13228        assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
13229        assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
13230        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
13231
13232        // Writing a nought past the end still creates the key and still pads.
13233        assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
13234        assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
13235        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
13236
13237        f.run(&[b"SET", b"num", b"12345"]);
13238        assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
13239        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
13240        assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
13241        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
13242        assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
13243    }
13244
13245    /// Counting, in bytes and in bits.
13246    ///
13247    /// The `0 -5 BIT` row is 25 on a real 8.10.1 and Redis's own documentation
13248    /// says 22 for it. The server is the thing being copied here.
13249    #[test]
13250    fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
13251        let mut f = Fixture::new();
13252        f.run(&[b"SET", b"mykey", b"foobar"]);
13253        assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
13254        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
13255        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
13256        assert_eq!(
13257            f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
13258            ":6\r\n"
13259        );
13260        assert_eq!(
13261            f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
13262            ":25\r\n"
13263        );
13264        assert_eq!(
13265            f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
13266            ":17\r\n"
13267        );
13268        assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
13269
13270        // A start past the end is left where it is and the end is pulled back,
13271        // so the range comes out backwards and counts nothing.
13272        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
13273
13274        // A lone start is a syntax error here, where BITPOS allows it.
13275        assert_eq!(
13276            f.run(&[b"BITCOUNT", b"mykey", b"0"]),
13277            "-ERR syntax error\r\n"
13278        );
13279        assert_eq!(
13280            f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
13281            "-ERR syntax error\r\n"
13282        );
13283    }
13284
13285    /// Searching, and the one place a miss is not minus one.
13286    ///
13287    /// A search for a nought that runs to the end of the string answers the
13288    /// length in bits, because the string is treated as if it had noughts after
13289    /// it forever. Give it an explicit end and it answers minus one instead.
13290    #[test]
13291    fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
13292        let mut f = Fixture::new();
13293        f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
13294        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
13295        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
13296        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
13297        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
13298        assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
13299
13300        f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
13301        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
13302        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
13303        assert_eq!(
13304            f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
13305            ":8\r\n"
13306        );
13307
13308        // A missing key is all noughts, so a one is never found and a nought is
13309        // at position zero.
13310        assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
13311        assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
13312    }
13313
13314    /// The eight operations, with the answers a real server gives for them.
13315    #[test]
13316    fn the_eight_combinations_write_what_a_real_server_writes() {
13317        let mut f = Fixture::new();
13318        f.run(&[b"SET", b"a", b"abc"]);
13319        f.run(&[b"SET", b"b", b"abd"]);
13320        let cases: &[(&[u8], &str)] = &[
13321            (b"AND", "ab`"),
13322            (b"OR", "abg"),
13323            (b"XOR", "\u{0}\u{0}\u{7}"),
13324            (b"DIFF", "\u{0}\u{0}\u{3}"),
13325            (b"DIFF1", "\u{0}\u{0}\u{4}"),
13326            (b"ANDOR", "ab`"),
13327            (b"ONE", "\u{0}\u{0}\u{7}"),
13328        ];
13329        for (op, want) in cases {
13330            assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
13331            assert_eq!(
13332                f.run(&[b"GET", b"d"]),
13333                format!("$3\r\n{want}\r\n"),
13334                "{op:?}"
13335            );
13336        }
13337        // The one whose answer is not text, so it is compared as bytes.
13338        assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
13339        assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
13340
13341        // A missing source is a string of noughts as long as it needs to be, so
13342        // an AND against one writes three zero bytes rather than nothing.
13343        assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
13344        assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
13345
13346        // Every source missing is an empty result, and an empty result takes
13347        // the destination with it.
13348        f.run(&[b"SET", b"dest", b"x"]);
13349        assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
13350        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
13351    }
13352
13353    /// What `BITOP` says when it is asked for something it cannot do.
13354    #[test]
13355    fn bitop_names_the_operation_in_its_own_complaints() {
13356        let mut f = Fixture::new();
13357        f.run(&[b"SET", b"a", b"abc"]);
13358        assert_eq!(
13359            f.run(&[b"BITOP", b"nope", b"d", b"a"]),
13360            "-ERR syntax error\r\n"
13361        );
13362        assert_eq!(
13363            f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
13364            "-ERR BITOP NOT must be called with a single source key.\r\n"
13365        );
13366        for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
13367            assert_eq!(
13368                f.run(&[b"BITOP", op, b"d", b"a"]),
13369                format!(
13370                    "-ERR BITOP {} must be called with at least two source keys.\r\n",
13371                    String::from_utf8_lossy(op)
13372                )
13373            );
13374        }
13375        f.run(&[b"LPUSH", b"l", b"x"]);
13376        assert_eq!(
13377            f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
13378            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13379        );
13380    }
13381
13382    /// Packed fields, the three overflow policies and the `#` offset.
13383    #[test]
13384    fn bitfield_reads_and_writes_packed_fields() {
13385        let mut f = Fixture::new();
13386        assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
13387        assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
13388
13389        assert_eq!(
13390            f.run(&[
13391                b"BITFIELD",
13392                b"bf",
13393                b"INCRBY",
13394                b"u2",
13395                b"100",
13396                b"1",
13397                b"GET",
13398                b"u4",
13399                b"0"
13400            ]),
13401            "*2\r\n:1\r\n:0\r\n"
13402        );
13403        // The field at bit 100 is two bits wide, so it ends in the thirteenth
13404        // byte and the value grew to thirteen bytes to hold it.
13405        assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
13406
13407        // A `#` offset counts in fields rather than in bits.
13408        assert_eq!(
13409            f.run(&[
13410                b"BITFIELD",
13411                b"bf",
13412                b"SET",
13413                b"u8",
13414                b"#0",
13415                b"255",
13416                b"GET",
13417                b"u8",
13418                b"#0"
13419            ]),
13420            "*2\r\n:0\r\n:255\r\n"
13421        );
13422
13423        assert_eq!(
13424            f.run(&[
13425                b"BITFIELD",
13426                b"bf",
13427                b"OVERFLOW",
13428                b"SAT",
13429                b"INCRBY",
13430                b"i8",
13431                b"0",
13432                b"120",
13433                b"INCRBY",
13434                b"i8",
13435                b"0",
13436                b"120"
13437            ]),
13438            "*2\r\n:119\r\n:127\r\n"
13439        );
13440        assert_eq!(
13441            f.run(&[
13442                b"BITFIELD",
13443                b"bf2",
13444                b"OVERFLOW",
13445                b"FAIL",
13446                b"INCRBY",
13447                b"u2",
13448                b"0",
13449                b"5"
13450            ]),
13451            "*1\r\n$-1\r\n"
13452        );
13453        assert_eq!(
13454            f.run(&[
13455                b"BITFIELD",
13456                b"bf3",
13457                b"OVERFLOW",
13458                b"WRAP",
13459                b"INCRBY",
13460                b"u2",
13461                b"0",
13462                b"5"
13463            ]),
13464            "*1\r\n:1\r\n"
13465        );
13466        assert_eq!(
13467            f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
13468            "*1\r\n:4611686018427387904\r\n"
13469        );
13470    }
13471
13472    /// A bad subcommand anywhere in the line stops all of it.
13473    ///
13474    /// Redis checks the whole argument list before it runs any of it, so the
13475    /// `SET` in front of the bad type here never happens and the key it would
13476    /// have created is not there afterwards.
13477    #[test]
13478    fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
13479        let mut f = Fixture::new();
13480        let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
13481        assert_eq!(
13482            f.run(&[
13483                b"BITFIELD",
13484                b"bad",
13485                b"SET",
13486                b"u8",
13487                b"0",
13488                b"1",
13489                b"GET",
13490                b"u99",
13491                b"0"
13492            ]),
13493            bad_type
13494        );
13495        assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
13496        assert_eq!(
13497            f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
13498            bad_type
13499        );
13500        assert_eq!(
13501            f.run(&[b"BITFIELD", b"bad", b"GET"]),
13502            "-ERR syntax error\r\n"
13503        );
13504        assert_eq!(
13505            f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
13506            "-ERR syntax error\r\n"
13507        );
13508        assert_eq!(
13509            f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
13510            "-ERR syntax error\r\n"
13511        );
13512        assert_eq!(
13513            f.run(&[
13514                b"BITFIELD",
13515                b"bad",
13516                b"OVERFLOW",
13517                b"NOPE",
13518                b"GET",
13519                b"u8",
13520                b"0"
13521            ]),
13522            "-ERR Invalid OVERFLOW type specified\r\n"
13523        );
13524        assert_eq!(
13525            f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
13526            "-ERR value is not an integer or out of range\r\n"
13527        );
13528        for at in [&b"#-1"[..], b"abc"] {
13529            assert_eq!(
13530                f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
13531                "-ERR bit offset is not an integer or out of range\r\n"
13532            );
13533        }
13534    }
13535
13536    /// The read only twin reads, refuses to write, and creates nothing.
13537    #[test]
13538    fn bitfield_ro_answers_gets_and_refuses_the_rest() {
13539        let mut f = Fixture::new();
13540        f.run(&[b"SET", b"n", b"123"]);
13541        assert_eq!(
13542            f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
13543            "*1\r\n:49\r\n"
13544        );
13545        // A read does not unpack an int the way a write does.
13546        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
13547
13548        // An OVERFLOW word is allowed even though nothing here can overflow.
13549        assert_eq!(
13550            f.run(&[
13551                b"BITFIELD_RO",
13552                b"n",
13553                b"OVERFLOW",
13554                b"SAT",
13555                b"GET",
13556                b"u8",
13557                b"0"
13558            ]),
13559            "*1\r\n:49\r\n"
13560        );
13561        for sub in [&b"SET"[..], b"INCRBY"] {
13562            assert_eq!(
13563                f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
13564                "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
13565            );
13566        }
13567
13568        assert_eq!(
13569            f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
13570            "*1\r\n:0\r\n"
13571        );
13572        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13573    }
13574
13575    /// The offsets a bitmap command will not take.
13576    #[test]
13577    fn an_offset_off_the_end_of_the_world_is_refused() {
13578        let mut f = Fixture::new();
13579        let bad = "-ERR bit offset is not an integer or out of range\r\n";
13580        for arg in [&b"abc"[..], b"-1", b"4294967296"] {
13581            assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
13582            assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
13583        }
13584        for arg in [&b"2"[..], b"-1"] {
13585            assert_eq!(
13586                f.run(&[b"BITPOS", b"k", arg]),
13587                "-ERR The bit argument must be 1 or 0.\r\n"
13588            );
13589        }
13590        assert_eq!(
13591            f.run(&[b"BITPOS", b"k", b"abc"]),
13592            "-ERR value is not an integer or out of range\r\n"
13593        );
13594        assert_eq!(
13595            f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
13596            "-ERR value is not an integer or out of range\r\n"
13597        );
13598        let bad_bit = "-ERR bit is not an integer or out of range\r\n";
13599        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
13600        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
13601    }
13602
13603    /// Every one of the seven refuses a key that is not a string.
13604    #[test]
13605    fn every_bitmap_command_says_wrongtype() {
13606        let mut f = Fixture::new();
13607        f.run(&[b"LPUSH", b"l", b"x"]);
13608        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13609        let cases: &[&[&[u8]]] = &[
13610            &[b"SETBIT", b"l", b"0", b"1"],
13611            &[b"GETBIT", b"l", b"0"],
13612            &[b"BITCOUNT", b"l"],
13613            &[b"BITPOS", b"l", b"1"],
13614            &[b"BITOP", b"AND", b"d", b"l"],
13615            &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
13616            &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
13617        ];
13618        for case in cases {
13619            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
13620        }
13621    }
13622
13623    // --------------------------------------------------------- hyperloglogs
13624
13625    #[test]
13626    fn a_sketch_is_added_to_and_counted() {
13627        let mut f = Fixture::new();
13628        // Creating the key counts as a change, even with nothing to add.
13629        assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
13630        assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
13631        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
13632        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
13633        // And it is a string, which is not an implementation detail: a client
13634        // can `GET` a sketch out of one server and `SET` it into another.
13635        assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
13636        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
13637
13638        assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
13639        assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
13640        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
13641    }
13642
13643    #[test]
13644    fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
13645        let mut f = Fixture::new();
13646        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13647        // Not text, so it is compared as bytes.
13648        let want = b"HYLL\x01\0\0\0\0\0\0\0\0\0\0\x80\x60\xf3\x80\x50\xb1\x84\x4b\xfb\x80\x42\x5a";
13649        let mut reply = b"$27\r\n".to_vec();
13650        reply.extend_from_slice(want);
13651        reply.extend_from_slice(b"\r\n");
13652        assert_eq!(f.raw(&[b"GET", b"h"]), reply);
13653    }
13654
13655    #[test]
13656    fn counting_several_keys_counts_their_union() {
13657        let mut f = Fixture::new();
13658        f.run(&[b"PFADD", b"a", b"x", b"y"]);
13659        f.run(&[b"PFADD", b"b", b"y", b"z"]);
13660        assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
13661        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
13662        // A key that is not there is an empty sketch, not an error and not
13663        // something that gets created by being counted.
13664        assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
13665        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
13666        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13667    }
13668
13669    #[test]
13670    fn a_merge_keeps_what_the_destination_had() {
13671        let mut f = Fixture::new();
13672        f.run(&[b"PFADD", b"a", b"x", b"y"]);
13673        f.run(&[b"PFADD", b"b", b"z"]);
13674        assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
13675        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
13676        // The destination is one of the sources, so a second merge adds to it.
13677        f.run(&[b"PFADD", b"c", b"w"]);
13678        assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
13679        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
13680        // And with no sources it is a no-op that still answers OK and still
13681        // creates a destination that was not there.
13682        assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
13683        assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
13684    }
13685
13686    /// Not under Miri, and not for the number of commands: a dense sketch is
13687    /// sixteen thousand three hundred and eighty four registers and every
13688    /// command here walks all of them, so one `PFCOUNT` is more interpreted
13689    /// work than a hundred ordinary tests. The registers and the walking are in
13690    /// `yo-kv`, where fifteen tests of their own cover both encodings and where
13691    /// the interpreter does run over them. What is left here is the dispatch
13692    /// around it, which is the same dispatch every other command in this file
13693    /// goes through.
13694    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
13695    #[test]
13696    fn the_debug_forms_answer_four_different_shapes() {
13697        let mut f = Fixture::new();
13698        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13699        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
13700        assert_eq!(
13701            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
13702            "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
13703        );
13704        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
13705        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
13706        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
13707        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
13708        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
13709        // A dense sketch has no opcodes left to print.
13710        assert_eq!(
13711            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
13712            "-ERR HLL encoding is not sparse\r\n"
13713        );
13714
13715        // All 16384 registers, of which three are not nought.
13716        let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
13717        assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
13718        assert_eq!(reply.matches(":0\r\n").count(), 16381);
13719        assert_eq!(reply.matches(":1\r\n").count(), 2);
13720        assert_eq!(reply.matches(":2\r\n").count(), 1);
13721
13722        assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
13723    }
13724
13725    #[test]
13726    fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
13727        let mut f = Fixture::new();
13728        f.run(&[b"SET", b"plain", b"not a sketch"]);
13729        let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
13730        assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
13731        assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
13732        assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
13733        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
13734
13735        // A key that is not a string at all gets the ordinary sentence, and a
13736        // destination that would have been written is not created.
13737        f.run(&[b"RPUSH", b"l", b"x"]);
13738        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13739        assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
13740        assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
13741        assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
13742        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
13743        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
13744    }
13745
13746    #[test]
13747    fn pfdebug_has_its_own_complaints() {
13748        let mut f = Fixture::new();
13749        f.run(&[b"PFADD", b"h", b"a"]);
13750        // The word is quoted exactly as the client spelled it, and this is not
13751        // the "Try X HELP." sentence every other container command uses.
13752        assert_eq!(
13753            f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
13754            "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
13755        );
13756        // Where all three of the real commands take a missing key as empty.
13757        let gone = "-ERR The specified key does not exist\r\n";
13758        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
13759        assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
13760        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
13761        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
13762        assert_eq!(
13763            f.run(&[b"PFDEBUG"]),
13764            "-ERR wrong number of arguments for 'pfdebug' command\r\n"
13765        );
13766        assert_eq!(
13767            f.run(&[b"PFSELFTEST", b"x"]),
13768            "-ERR wrong number of arguments for 'pfselftest' command\r\n"
13769        );
13770    }
13771
13772    #[test]
13773    fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
13774        let mut f = Fixture::new();
13775        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
13776        // The sketch with its last byte cut off, which is still a header and a
13777        // magic and is a run length encoding that stops short of register 16384.
13778        let reply = f.raw(&[b"GET", b"h"]);
13779        let short = reply[5..reply.len() - 3].to_vec();
13780        f.run(&[b"SET", b"h", &short]);
13781        assert_eq!(
13782            f.run(&[b"PFCOUNT", b"h"]),
13783            "-INVALIDOBJ Corrupted HLL object detected\r\n"
13784        );
13785    }
13786
13787    #[test]
13788    fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
13789        let mut f = Fixture::new();
13790        // One that stays sparse and one that has gone dense, since the payload
13791        // carries the bytes and the two encodings are different lengths.
13792        f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
13793        // Ten thousand elements is what takes a sketch dense on its own, and it
13794        // is ten thousand trips through dispatch, which is what Miri charges
13795        // for. There the same sketch is taken across by hand. What this test is
13796        // about is a dense payload surviving a round trip and the encoding is
13797        // dense either way: that a sketch converts when it fills up is what
13798        // `the_debug_forms_answer_four_different_shapes` is for.
13799        if cfg!(miri) {
13800            f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
13801            f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
13802        } else {
13803            for i in 0..10_000u32 {
13804                let ele = format!("e{i}");
13805                f.run(&[b"PFADD", b"big", ele.as_bytes()]);
13806            }
13807        }
13808        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
13809        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
13810
13811        for key in [&b"small"[..], b"big"] {
13812            let mut copy = key.to_vec();
13813            copy.push(b'2');
13814            let bytes = payload(&f.raw(&[b"DUMP", key]));
13815            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
13816            // The bytes, the encoding and the estimate all come back, which is
13817            // the whole of what byte compatibility across a round trip means.
13818            assert_eq!(f.raw(&[b"GET", &copy]), f.raw(&[b"GET", key]));
13819            assert_eq!(
13820                f.run(&[b"PFDEBUG", b"ENCODING", &copy]),
13821                f.run(&[b"PFDEBUG", b"ENCODING", key])
13822            );
13823            assert_eq!(f.run(&[b"PFCOUNT", &copy]), f.run(&[b"PFCOUNT", key]));
13824        }
13825        assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
13826        assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
13827    }
13828
13829    /// One RESP2 bulk string. The JSON replies are almost all one of these and
13830    /// the text inside them has quotes in it, so writing the frame out by hand
13831    /// buries the part of the assertion that matters.
13832    fn bulk(s: &str) -> String {
13833        format!("${}\r\n{s}\r\n", s.len())
13834    }
13835
13836    /// A RESP2 array of bulk strings, which is what most of the list replies
13837    /// are and what writing them out by hand in every assertion looks like.
13838    fn bulks(parts: &[&str]) -> String {
13839        let mut s = format!("*{}\r\n", parts.len());
13840        for p in parts {
13841            s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
13842        }
13843        s
13844    }
13845
13846    #[test]
13847    fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
13848        let mut f = Fixture::new();
13849        // Each element in turn goes at the head, so the last one sent is at the
13850        // front when it is over. That reads like a bug in the client and it is
13851        // what every Redis has always done.
13852        assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
13853        assert_eq!(
13854            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13855            bulks(&["c", "b", "a"])
13856        );
13857        assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
13858        assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
13859        assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
13860        assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
13861        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
13862        assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
13863    }
13864
13865    #[test]
13866    fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
13867        let mut f = Fixture::new();
13868        assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
13869        assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
13870        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13871        f.run(&[b"RPUSH", b"k", b"a"]);
13872        assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
13873        assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
13874        assert_eq!(
13875            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13876            bulks(&["z", "a", "y"])
13877        );
13878    }
13879
13880    /// The four ways a pop can come back with nothing, which are three
13881    /// different replies and a RESP2 client can tell all of them apart.
13882    #[test]
13883    fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
13884        let mut f = Fixture::new();
13885        assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
13886        assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
13887        assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
13888        assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
13889        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13890        // A count of zero against a list that is there is an empty array and
13891        // not a null array, which is the fourth answer.
13892        assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
13893        assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
13894        // More than there is takes what there is and the key goes with it.
13895        assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
13896        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
13897    }
13898
13899    #[test]
13900    fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
13901        let mut f = Fixture::new();
13902        f.run(&[b"RPUSH", b"k", b"a"]);
13903        let range = "-ERR value is out of range, must be positive\r\n";
13904        assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
13905        assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
13906        assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
13907        // Redis calls this an arity error and not a syntax error, which is a
13908        // distinction it does not always make.
13909        assert_eq!(
13910            f.run(&[b"LPOP", b"k", b"1", b"2"]),
13911            "-ERR wrong number of arguments for 'lpop' command\r\n"
13912        );
13913        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
13914    }
13915
13916    #[test]
13917    fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
13918        let mut f = Fixture::new();
13919        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13920        assert_eq!(
13921            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13922            bulks(&["a", "b", "c"])
13923        );
13924        assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
13925        assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
13926        assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
13927        assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
13928        assert_eq!(
13929            f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
13930            bulks(&["a", "b", "c"])
13931        );
13932        // A key that is not there is an empty range and not a nil, which is the
13933        // one place a list disagrees with a set.
13934        assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
13935        assert_eq!(
13936            f.run(&[b"LRANGE", b"k", b"a", b"b"]),
13937            "-ERR value is not an integer or out of range\r\n"
13938        );
13939    }
13940
13941    #[test]
13942    fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
13943        let mut f = Fixture::new();
13944        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
13945        assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
13946        assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
13947        assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
13948        assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
13949        assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
13950        assert_eq!(
13951            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13952            bulks(&["a", "b", "z"])
13953        );
13954        // Both ways of missing are errors here rather than a nil, because a
13955        // list is never empty and there is nothing else the reply could be.
13956        assert_eq!(
13957            f.run(&[b"LSET", b"k", b"99", b"z"]),
13958            "-ERR index out of range\r\n"
13959        );
13960        assert_eq!(
13961            f.run(&[b"LSET", b"nope", b"0", b"z"]),
13962            "-ERR no such key\r\n"
13963        );
13964    }
13965
13966    #[test]
13967    fn linsert_says_three_things_with_one_signed_number() {
13968        let mut f = Fixture::new();
13969        // Zero for a key that is not there, which is not the same as minus one
13970        // for a pivot that is not in a list that is.
13971        assert_eq!(
13972            f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
13973            ":0\r\n"
13974        );
13975        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
13976        assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
13977        assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
13978        assert_eq!(
13979            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13980            bulks(&["X", "a", "b", "Y"])
13981        );
13982        assert_eq!(
13983            f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
13984            ":-1\r\n"
13985        );
13986        assert_eq!(
13987            f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
13988            "-ERR syntax error\r\n"
13989        );
13990    }
13991
13992    #[test]
13993    fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
13994        let mut f = Fixture::new();
13995        f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
13996        assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
13997        assert_eq!(
13998            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
13999            bulks(&["b", "c", "a"])
14000        );
14001        assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
14002        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
14003        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
14004        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
14005        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
14006        assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
14007    }
14008
14009    #[test]
14010    fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
14011        let mut f = Fixture::new();
14012        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
14013        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
14014        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
14015        // `LTRIM k 1 0` is the documented way to empty a list, so it has to
14016        // leave `EXISTS` answering zero rather than leaving an empty one.
14017        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
14018        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
14019        assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
14020    }
14021
14022    #[test]
14023    fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
14024        let mut f = Fixture::new();
14025        f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
14026        assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
14027        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
14028        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
14029        assert_eq!(
14030            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
14031            "*2\r\n:0\r\n:3\r\n"
14032        );
14033        assert_eq!(
14034            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
14035            "*3\r\n:6\r\n:3\r\n:0\r\n"
14036        );
14037        // MAXLEN counts elements looked at and not matches found, so three
14038        // stops after `a b c` and finds the one match in it.
14039        assert_eq!(
14040            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
14041            "*1\r\n:0\r\n"
14042        );
14043        // Nothing found is three different replies depending on how it was
14044        // asked and whether the key is there at all.
14045        assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
14046        assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
14047        assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
14048        assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
14049    }
14050
14051    #[test]
14052    fn lpos_words_its_three_mistakes_the_way_redis_does() {
14053        let mut f = Fixture::new();
14054        f.run(&[b"RPUSH", b"p", b"a"]);
14055        // The whole sentence and not a prefix, because the older wording of it
14056        // is still all over the internet and clients match on the text.
14057        assert_eq!(
14058            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
14059            "-ERR RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start from the end of the list\r\n"
14060        );
14061        assert_eq!(
14062            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
14063            "-ERR COUNT can't be negative\r\n"
14064        );
14065        assert_eq!(
14066            f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
14067            "-ERR MAXLEN can't be negative\r\n"
14068        );
14069        assert_eq!(
14070            f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
14071            "-ERR syntax error\r\n"
14072        );
14073        assert_eq!(
14074            f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
14075            "-ERR syntax error\r\n"
14076        );
14077    }
14078
14079    #[test]
14080    fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
14081        let mut f = Fixture::new();
14082        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
14083        assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
14084        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
14085        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
14086        assert_eq!(
14087            f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
14088            "$1\r\na\r\n"
14089        );
14090        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
14091        // The same key twice is the documented way to rotate a list and falls
14092        // out of taking the element before deciding where to put it.
14093        f.run(&[b"DEL", b"r"]);
14094        f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
14095        assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
14096        assert_eq!(
14097            f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
14098            bulks(&["3", "1", "2"])
14099        );
14100        assert_eq!(
14101            f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
14102            "$-1\r\n"
14103        );
14104        assert_eq!(
14105            f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
14106            "-ERR syntax error\r\n"
14107        );
14108    }
14109
14110    #[test]
14111    fn a_move_checks_the_destination_before_it_takes_anything() {
14112        let mut f = Fixture::new();
14113        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
14114        f.run(&[b"SET", b"str", b"v"]);
14115        assert_eq!(
14116            f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
14117            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
14118        );
14119        // The element is still where it was, rather than having gone nowhere.
14120        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
14121    }
14122
14123    #[test]
14124    fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
14125        // OBO is what you get from sending LMOVE that many times, BULK keeps
14126        // the source order. The two only differ when both ends are the same,
14127        // which is the whole reason the word exists.
14128        for (from, to, order, want) in [
14129            ("LEFT", "RIGHT", "OBO", ["a", "b"]),
14130            ("LEFT", "RIGHT", "BULK", ["a", "b"]),
14131            ("LEFT", "LEFT", "OBO", ["b", "a"]),
14132            ("LEFT", "LEFT", "BULK", ["a", "b"]),
14133            ("RIGHT", "LEFT", "OBO", ["d", "e"]),
14134            ("RIGHT", "LEFT", "BULK", ["d", "e"]),
14135            ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
14136            ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
14137        ] {
14138            let mut f = Fixture::new();
14139            f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
14140            let how = format!("{from} {to} {order}");
14141            let reply = f.run(&[
14142                b"LMOVEM",
14143                b"s",
14144                b"d",
14145                from.as_bytes(),
14146                to.as_bytes(),
14147                b"COUNT",
14148                b"2",
14149                order.as_bytes(),
14150            ]);
14151            assert_eq!(reply, bulks(&want), "the reply for {how}");
14152            assert_eq!(
14153                f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
14154                bulks(&want),
14155                "the destination for {how}"
14156            );
14157        }
14158    }
14159
14160    #[test]
14161    fn a_block_move_of_one_needs_no_count_at_all() {
14162        let mut f = Fixture::new();
14163        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
14164        assert_eq!(
14165            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
14166            bulks(&["a"])
14167        );
14168        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
14169        // Six and seven arguments are neither of the two forms, so the
14170        // reference calls both of them a syntax error rather than guessing.
14171        assert_eq!(
14172            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
14173            "-ERR syntax error\r\n"
14174        );
14175        assert_eq!(
14176            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
14177            "-ERR syntax error\r\n"
14178        );
14179    }
14180
14181    #[test]
14182    fn a_block_move_with_exactly_takes_all_of_them_or_none() {
14183        let mut f = Fixture::new();
14184        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
14185        // A null array and not a null bulk string, which `redis-cli` prints as
14186        // `(nil)` either way and only the raw wire tells apart. What it would
14187        // have sent is an array, so its nothing is an array's nothing.
14188        assert_eq!(
14189            f.run(&[
14190                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
14191            ]),
14192            "*-1\r\n"
14193        );
14194        assert_eq!(
14195            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
14196            bulks(&["a", "b", "c"])
14197        );
14198        // COUNT takes what there is, and an emptied source goes away.
14199        assert_eq!(
14200            f.run(&[
14201                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
14202            ]),
14203            bulks(&["a", "b", "c"])
14204        );
14205        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
14206        assert_eq!(
14207            f.run(&[
14208                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
14209            ]),
14210            "*-1\r\n"
14211        );
14212    }
14213
14214    #[test]
14215    fn a_block_move_onto_itself_rotates_by_the_count() {
14216        let mut f = Fixture::new();
14217        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
14218        assert_eq!(
14219            f.run(&[
14220                b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
14221            ]),
14222            bulks(&["a", "b"])
14223        );
14224        assert_eq!(
14225            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
14226            bulks(&["c", "a", "b"])
14227        );
14228    }
14229
14230    #[test]
14231    fn a_block_move_reads_the_count_before_the_ordering_word() {
14232        let mut f = Fixture::new();
14233        f.run(&[b"RPUSH", b"s", b"a", b"b"]);
14234        f.run(&[b"SET", b"str", b"v"]);
14235        let count = "-ERR count should be greater than 0\r\n";
14236        assert_eq!(
14237            f.run(&[
14238                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
14239            ]),
14240            count
14241        );
14242        assert_eq!(
14243            f.run(&[
14244                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
14245            ]),
14246            count
14247        );
14248        assert_eq!(
14249            f.run(&[
14250                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
14251            ]),
14252            "-ERR syntax error\r\n"
14253        );
14254        assert_eq!(
14255            f.run(&[
14256                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
14257            ]),
14258            "-ERR syntax error\r\n"
14259        );
14260        // Every argument is read before the keys are looked at, so a bad count
14261        // beats a wrong type even when the type is wrong on the source.
14262        assert_eq!(
14263            f.run(&[
14264                b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
14265            ]),
14266            count
14267        );
14268        assert_eq!(
14269            f.run(&[
14270                b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
14271            ]),
14272            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
14273        );
14274        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
14275    }
14276
14277    #[test]
14278    fn lmpop_answers_from_the_first_key_that_has_anything() {
14279        let mut f = Fixture::new();
14280        f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
14281        // The name of the key that answered comes back with the elements,
14282        // because the client cannot work out which one it was.
14283        assert_eq!(
14284            f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
14285            "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
14286        );
14287        assert_eq!(
14288            f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
14289            "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
14290        );
14291        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
14292        // A null array and not a null, even though what it stands in for is an
14293        // array holding a key name and then another array.
14294        assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
14295    }
14296
14297    #[test]
14298    fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
14299        let mut f = Fixture::new();
14300        f.run(&[b"RPUSH", b"k", b"a"]);
14301        assert_eq!(
14302            f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
14303            "-ERR numkeys should be greater than 0\r\n"
14304        );
14305        assert_eq!(
14306            f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
14307            "-ERR numkeys should be greater than 0\r\n"
14308        );
14309        assert_eq!(
14310            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
14311            "-ERR count should be greater than 0\r\n"
14312        );
14313        // A key count that eats the direction is a syntax error and not a
14314        // sentence about key counts, because the direction is simply not there.
14315        assert_eq!(
14316            f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
14317            "-ERR syntax error\r\n"
14318        );
14319        assert_eq!(
14320            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
14321            "-ERR syntax error\r\n"
14322        );
14323        assert_eq!(
14324            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
14325            "-ERR syntax error\r\n"
14326        );
14327        assert_eq!(
14328            f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
14329            "-ERR syntax error\r\n"
14330        );
14331        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
14332    }
14333
14334    #[test]
14335    fn every_list_command_says_wrongtype_and_writes_nothing() {
14336        let mut f = Fixture::new();
14337        f.run(&[b"SET", b"str", b"v"]);
14338        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14339        for cmd in [
14340            &[b"LPUSH".as_slice(), b"str", b"a"][..],
14341            &[b"RPUSH", b"str", b"a"],
14342            &[b"LPUSHX", b"str", b"a"],
14343            &[b"RPUSHX", b"str", b"a"],
14344            &[b"LPOP", b"str"],
14345            &[b"LPOP", b"str", b"2"],
14346            &[b"RPOP", b"str"],
14347            &[b"LLEN", b"str"],
14348            &[b"LRANGE", b"str", b"0", b"-1"],
14349            &[b"LINDEX", b"str", b"0"],
14350            &[b"LSET", b"str", b"0", b"a"],
14351            &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
14352            &[b"LREM", b"str", b"0", b"a"],
14353            &[b"LTRIM", b"str", b"0", b"-1"],
14354            &[b"LPOS", b"str", b"a"],
14355            &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
14356            &[b"RPOPLPUSH", b"str", b"d"],
14357            &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
14358            &[b"LMPOP", b"1", b"str", b"LEFT"],
14359        ] {
14360            assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
14361        }
14362        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
14363        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
14364    }
14365
14366    /// A timeout is not an integer and it is not an ordinary float either: the
14367    /// three sentences it can answer with are its own, and which one a given
14368    /// argument gets is not what reading the code would suggest.
14369    #[test]
14370    fn a_timeout_has_three_ways_of_being_wrong() {
14371        let mut f = Fixture::new();
14372        let not_float = "-ERR timeout is not a float or out of range\r\n";
14373        let range = "-ERR timeout is out of range\r\n";
14374        for (bad, want) in [
14375            (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
14376            (&[b"BLPOP", b"k", b"nan"], not_float),
14377            (&[b"BLPOP", b"k", b""], not_float),
14378            // Whitespace on either side, which `strtold` would take and Redis
14379            // does not.
14380            (&[b"BLPOP", b"k", b" 1"], not_float),
14381            (&[b"BLPOP", b"k", b"1 "], not_float),
14382            (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
14383            (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
14384            // These three parse, so they are not the not-a-float error, and all
14385            // three are further off than an i64 of milliseconds reaches.
14386            (&[b"BLPOP", b"k", b"1e400"], range),
14387            (&[b"BLPOP", b"k", b"inf"], range),
14388            (&[b"BLPOP", b"k", b"9999999999999999"], range),
14389            (&[b"BRPOP", b"k", b"abc"], not_float),
14390            (
14391                &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
14392                not_float,
14393            ),
14394            (
14395                &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
14396                "-ERR timeout is negative\r\n",
14397            ),
14398            (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
14399        ] {
14400            assert_eq!(f.run(bad), want, "for {bad:?}");
14401        }
14402    }
14403
14404    /// A timeout of exactly zero means no timeout, and there are two ways of
14405    /// writing exactly zero.
14406    #[test]
14407    fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
14408        let mut f = Fixture::new();
14409        for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
14410            let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
14411            assert_eq!(flow, Flow::Block, "for {timeout:?}");
14412            assert!(out.is_empty(), "for {timeout:?}");
14413        }
14414        // Positive, so it is a real deadline, and the deadline is this
14415        // millisecond. Nothing is written here either: the reply comes from the
14416        // sweep, which is the engine's and not this layer's.
14417        let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
14418        assert_eq!(flow, Flow::Block);
14419        assert!(out.is_empty());
14420    }
14421
14422    #[test]
14423    fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
14424        let mut f = Fixture::new();
14425        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
14426
14427        // The one difference from LPOP: the reply names the key that answered,
14428        // which is what makes BLPOP over several keys usable.
14429        assert_eq!(
14430            f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
14431            (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
14432        );
14433        assert_eq!(
14434            f.run(&[b"BRPOP", b"L", b"0"]),
14435            "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
14436        );
14437        assert_eq!(
14438            f.run(&[
14439                b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
14440            ]),
14441            "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14442        );
14443        assert_eq!(
14444            f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
14445            "$1\r\nd\r\n"
14446        );
14447        assert_eq!(
14448            f.run(&[b"EXISTS", b"L"]),
14449            ":0\r\n",
14450            "and the key went with it"
14451        );
14452        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
14453        // Onto itself, which is how a list is rotated and is a real thing to ask
14454        // a blocking move for.
14455        f.run(&[b"RPUSH", b"D", b"x"]);
14456        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
14457        assert_eq!(
14458            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
14459            "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
14460        );
14461    }
14462
14463    #[test]
14464    fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
14465        let mut f = Fixture::new();
14466        f.run(&[b"RPUSH", b"k", b"a"]);
14467        for (bad, want) in [
14468            (
14469                &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
14470                "-ERR numkeys should be greater than 0\r\n",
14471            ),
14472            (
14473                &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
14474                "-ERR numkeys should be greater than 0\r\n",
14475            ),
14476            // Two keys named and one given, so the word that should have been
14477            // the direction is a key and there is no direction left.
14478            (
14479                &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
14480                "-ERR syntax error\r\n",
14481            ),
14482            (
14483                &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
14484                "-ERR syntax error\r\n",
14485            ),
14486            (
14487                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
14488                "-ERR syntax error\r\n",
14489            ),
14490            (
14491                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
14492                "-ERR syntax error\r\n",
14493            ),
14494            // A count that is not a number at all gets the same sentence a zero
14495            // or a negative one gets, rather than the usual one about integers.
14496            (
14497                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
14498                "-ERR count should be greater than 0\r\n",
14499            ),
14500            (
14501                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
14502                "-ERR count should be greater than 0\r\n",
14503            ),
14504        ] {
14505            assert_eq!(f.run(bad), want, "for {bad:?}");
14506        }
14507        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
14508    }
14509
14510    #[test]
14511    fn a_blocking_move_reads_its_directions_before_its_timeout() {
14512        let mut f = Fixture::new();
14513        // Both are wrong. Redis checks the directions first, so this is the
14514        // syntax error and not a complaint about the timeout.
14515        assert_eq!(
14516            f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
14517            "-ERR syntax error\r\n"
14518        );
14519        assert_eq!(
14520            f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
14521            "-ERR syntax error\r\n"
14522        );
14523    }
14524
14525    /// `BLMOVEM` answers exactly what `LMOVEM` answers when it does not have to
14526    /// wait, which is the same relationship every other command in this file has
14527    /// with the one it wraps.
14528    #[test]
14529    fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
14530        let mut f = Fixture::new();
14531        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
14532        assert_eq!(
14533            f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
14534            (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
14535        );
14536        assert_eq!(
14537            f.run(&[
14538                b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
14539            ]),
14540            bulks(&["e", "d"])
14541        );
14542        assert_eq!(
14543            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
14544            bulks(&["a", "e", "d"])
14545        );
14546        // `EXACTLY` with enough there does not wait either.
14547        assert_eq!(
14548            f.run(&[
14549                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
14550            ]),
14551            bulks(&["b", "c"])
14552        );
14553        assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
14554    }
14555
14556    /// The one thing `BLMOVEM` decides differently from the other five: `COUNT`
14557    /// is ready as soon as there is anything and `EXACTLY` is not ready until the
14558    /// whole block has arrived.
14559    #[test]
14560    fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
14561        let mut f = Fixture::new();
14562        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
14563        // Two there and three asked for. `COUNT` takes the two.
14564        assert_eq!(
14565            f.flow(&[
14566                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
14567            ]),
14568            (Flow::Continue, bulks(&["a", "b"]))
14569        );
14570
14571        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
14572        // The same line with `EXACTLY` parks instead, and takes nothing on the
14573        // way past.
14574        assert_eq!(
14575            f.flow(&[
14576                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
14577            ])
14578            .0,
14579            Flow::Block
14580        );
14581        assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
14582    }
14583
14584    #[test]
14585    fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
14586        let mut f = Fixture::new();
14587        let syntax = "-ERR syntax error\r\n";
14588        // All three are wrong and the directions are read first.
14589        assert_eq!(
14590            f.run(&[
14591                b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
14592            ]),
14593            syntax
14594        );
14595        // Directions fine, timeout and count both wrong, so the timeout wins.
14596        assert_eq!(
14597            f.run(&[
14598                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
14599            ]),
14600            "-ERR timeout is not a float or out of range\r\n"
14601        );
14602        assert_eq!(
14603            f.run(&[
14604                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
14605            ]),
14606            "-ERR timeout is negative\r\n"
14607        );
14608        // And with the timeout fine, the count before the ordering word.
14609        assert_eq!(
14610            f.run(&[
14611                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
14612            ]),
14613            "-ERR count should be greater than 0\r\n"
14614        );
14615        assert_eq!(
14616            f.run(&[
14617                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
14618            ]),
14619            syntax
14620        );
14621        // Seven and eight arguments are neither of the two forms, the same way
14622        // six and seven are for `LMOVEM`.
14623        assert_eq!(
14624            f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
14625            syntax
14626        );
14627        assert_eq!(
14628            f.run(&[
14629                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
14630            ]),
14631            syntax
14632        );
14633    }
14634
14635    /// The four ways a blocking command sees a key of another type, and the one
14636    /// way it does not.
14637    #[test]
14638    fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
14639        let mut f = Fixture::new();
14640        f.run(&[b"SET", b"S", b"v"]);
14641        f.run(&[b"RPUSH", b"D", b"x"]);
14642        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14643
14644        assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
14645        // Every key is checked even when an earlier one would have blocked, so
14646        // an empty key in front of a string does not hide it.
14647        assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
14648        assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
14649        assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
14650        assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
14651        // The destination, which is only reached because the source has
14652        // something in it.
14653        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
14654        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
14655        assert_eq!(
14656            f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
14657            wrong
14658        );
14659        assert_eq!(
14660            f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
14661            wrong
14662        );
14663
14664        // And the one that does not: an empty source means the destination is
14665        // never looked at, so this waits rather than erroring, and on a real
14666        // server it times out.
14667        assert_eq!(
14668            f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
14669                .0,
14670            Flow::Block
14671        );
14672        // `BLMOVEM` has a second way of not being ready, and it hides the
14673        // destination just as well: the source is a list with two elements in it
14674        // and `EXACTLY` wants three, so the string never gets looked at.
14675        assert_eq!(
14676            f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
14677                .0,
14678            Flow::Block
14679        );
14680        f.run(&[b"RPUSH", b"E", b"1", b"2"]);
14681        assert_eq!(
14682            f.flow(&[
14683                b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
14684            ])
14685            .0,
14686            Flow::Block
14687        );
14688    }
14689
14690    /// The same churn the set and the string get, because a list that leaks a
14691    /// chunk per push looks exactly like one that does not until it has run for
14692    /// an afternoon.
14693    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
14694    #[cfg_attr(miri, ignore = "the volume is the claim")]
14695    #[test]
14696    fn churning_lists_does_not_grow_the_server() {
14697        let mut f = Fixture::new();
14698        let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
14699        let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
14700            .into_iter()
14701            .chain(vals.iter().map(Vec::as_slice))
14702            .collect();
14703
14704        f.run(&args);
14705        f.run(&[b"DEL", b"k"]);
14706        f.server.compact_step();
14707        let after_first = f.server.memory_bytes();
14708
14709        for _ in 0..200 {
14710            f.run(&args);
14711            f.run(&[b"LTRIM", b"k", b"1", b"0"]);
14712            f.server.compact_step();
14713        }
14714        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14715        assert!(
14716            f.server.memory_bytes() <= after_first * 2,
14717            "held {} after two hundred passes against {after_first} after one",
14718            f.server.memory_bytes()
14719        );
14720    }
14721
14722    // ------------------------------------------------------------ sorted set
14723
14724    #[test]
14725    fn a_sorted_set_takes_scores_and_gives_them_back() {
14726        let mut f = Fixture::new();
14727        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
14728        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
14729        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
14730        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
14731        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
14732        assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
14733        assert_eq!(
14734            f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
14735            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
14736        );
14737        assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
14738        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
14739        // The key goes when the last member does.
14740        assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
14741        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
14742    }
14743
14744    #[test]
14745    fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
14746        let mut f = Fixture::new();
14747        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
14748        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
14749        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
14750        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
14751
14752        f.out = Out::new(Proto::Resp3);
14753        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
14754        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
14755        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
14756        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
14757    }
14758
14759    #[test]
14760    fn the_zadd_options_gate_what_gets_written() {
14761        let mut f = Fixture::new();
14762        f.run(&[b"ZADD", b"z", b"5", b"a"]);
14763        // NX leaves a member that is there alone, XX will not create one.
14764        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
14765        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
14766        assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
14767        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
14768        // GT and LT only move a score one way.
14769        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
14770        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
14771        assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
14772        // CH counts a moved score and plain ZADD does not.
14773        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
14774        assert_eq!(
14775            f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
14776            ":2\r\n"
14777        );
14778    }
14779
14780    #[test]
14781    fn zadd_incr_answers_a_score_or_nothing_at_all() {
14782        let mut f = Fixture::new();
14783        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
14784        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
14785        // A gate that refuses is the string nil, because the reply it stands in
14786        // for is a score.
14787        assert_eq!(
14788            f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
14789            "$-1\r\n"
14790        );
14791        assert_eq!(
14792            f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
14793            "$-1\r\n"
14794        );
14795        assert_eq!(
14796            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
14797            "$-1\r\n"
14798        );
14799        assert_eq!(
14800            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
14801            "$1\r\n8\r\n"
14802        );
14803        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
14804        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
14805    }
14806
14807    #[test]
14808    fn the_two_infinities_will_not_be_added_together() {
14809        let mut f = Fixture::new();
14810        f.run(&[b"ZADD", b"z", b"inf", b"m"]);
14811        let nan = "-ERR resulting score is not a number (NaN)\r\n";
14812        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
14813        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
14814        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
14815        // And a key made for an increment that then fails does not stay behind.
14816        assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
14817    }
14818
14819    #[test]
14820    fn zadd_says_its_mistakes_the_way_redis_says_them() {
14821        let mut f = Fixture::new();
14822        // The pairs are counted before the options are looked at, so this is a
14823        // syntax error about having none and not a complaint about NX and XX.
14824        assert_eq!(
14825            f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
14826            "-ERR syntax error\r\n"
14827        );
14828        assert_eq!(
14829            f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
14830            "-ERR XX and NX options at the same time are not compatible\r\n"
14831        );
14832        let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
14833        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
14834        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
14835        assert_eq!(
14836            f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
14837            "-ERR INCR option supports a single increment-element pair\r\n"
14838        );
14839        // An odd number of arguments after the options.
14840        assert_eq!(
14841            f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
14842            "-ERR syntax error\r\n"
14843        );
14844        // Every score is read before the first is stored.
14845        assert_eq!(
14846            f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
14847            "-ERR value is not a valid float\r\n"
14848        );
14849        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
14850    }
14851
14852    #[test]
14853    fn a_rank_says_where_a_member_sits_from_either_end() {
14854        let mut f = Fixture::new();
14855        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14856        assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
14857        assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
14858        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
14859        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
14860        // WITHSCORE changes both shapes: the answer and the nothing.
14861        assert_eq!(
14862            f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
14863            "*2\r\n:1\r\n$1\r\n2\r\n"
14864        );
14865        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
14866        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
14867        assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
14868        // A bad option is a syntax error and one argument too many is an arity
14869        // error, which is Redis's split.
14870        assert_eq!(
14871            f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
14872            "-ERR syntax error\r\n"
14873        );
14874        assert_eq!(
14875            f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
14876            "-ERR wrong number of arguments for 'zrevrank' command\r\n"
14877        );
14878    }
14879
14880    #[test]
14881    fn the_two_counts_read_their_two_kinds_of_bound() {
14882        let mut f = Fixture::new();
14883        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14884        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
14885        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
14886        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
14887        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
14888        assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
14889        assert_eq!(
14890            f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
14891            "-ERR min or max is not a float\r\n"
14892        );
14893
14894        f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
14895        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
14896        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
14897        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
14898        // A bare member is not a bound, because a member can start with any
14899        // byte and there would be no way to say the bracket if it were optional.
14900        assert_eq!(
14901            f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
14902            "-ERR min or max not valid string range item\r\n"
14903        );
14904    }
14905
14906    /// The three ways `ZRANGE` can be asked for a window, forwards and back.
14907    ///
14908    /// Every byte in here was read off a real 8.10.1 rather than worked out,
14909    /// because the interesting part of this command is not what it selects, it
14910    /// is which of the two ends the client is expected to name first.
14911    #[test]
14912    fn one_range_command_selects_by_rank_or_score_or_name() {
14913        let mut f = Fixture::new();
14914        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14915        assert_eq!(
14916            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
14917            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14918        );
14919        assert_eq!(
14920            f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
14921            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14922        );
14923        assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
14924        assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
14925        // REV over ranks reverses the walk and leaves the two arguments alone,
14926        // because a rank counts from the end the walk starts at.
14927        assert_eq!(
14928            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
14929            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14930        );
14931        assert_eq!(
14932            f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
14933            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14934        );
14935        // And REV over scores does swap them, since a bound does not count from
14936        // anywhere. This is the one line of the parse that tells the two apart.
14937        assert_eq!(
14938            f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
14939            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
14940        );
14941        assert_eq!(
14942            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
14943            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
14944        );
14945        assert_eq!(
14946            f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
14947            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14948        );
14949    }
14950
14951    /// The older spellings, which are the same six windows with the mode in the
14952    /// name and the high end named first on the three that go backwards.
14953    #[test]
14954    fn the_older_range_spellings_name_their_high_end_first() {
14955        let mut f = Fixture::new();
14956        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
14957        assert_eq!(
14958            f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
14959            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
14960        );
14961        assert_eq!(
14962            f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
14963            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
14964        );
14965        assert_eq!(
14966            f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
14967            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
14968        );
14969        assert_eq!(
14970            f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
14971            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
14972        );
14973        // The two arguments the wrong way round is an empty answer and not an
14974        // error, which is what the swap being in the parse rather than in the
14975        // window buys.
14976        assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
14977        assert_eq!(
14978            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
14979            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
14980        );
14981        assert_eq!(
14982            f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
14983            "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
14984        );
14985        // BYSCORE, BYLEX and REV mean nothing to these, so they are not another
14986        // way of spelling the mode, they are a syntax error.
14987        for cmd in [
14988            &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
14989            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
14990            &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
14991        ] {
14992            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
14993        }
14994    }
14995
14996    /// `LIMIT` and `WITHSCORES`, which every one of these commands reads and
14997    /// only some of them accept.
14998    #[test]
14999    fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
15000        let mut f = Fixture::new();
15001        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15002        assert_eq!(
15003            f.run(&[
15004                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
15005            ]),
15006            "*1\r\n$1\r\nb\r\n"
15007        );
15008        // A negative offset skips past everything, a negative count is no bound.
15009        assert_eq!(
15010            f.run(&[
15011                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
15012            ]),
15013            "*0\r\n"
15014        );
15015        assert_eq!(
15016            f.run(&[
15017                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
15018            ]),
15019            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
15020        );
15021        // The two options in either order, which falls out of the parse loop.
15022        let both = "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n";
15023        assert_eq!(
15024            f.run(&[
15025                b"ZRANGEBYSCORE",
15026                b"z",
15027                b"1",
15028                b"3",
15029                b"WITHSCORES",
15030                b"LIMIT",
15031                b"0",
15032                b"2"
15033            ]),
15034            both
15035        );
15036        assert_eq!(
15037            f.run(&[
15038                b"ZRANGEBYSCORE",
15039                b"z",
15040                b"1",
15041                b"3",
15042                b"LIMIT",
15043                b"0",
15044                b"2",
15045                b"WITHSCORES"
15046            ]),
15047            both
15048        );
15049        // LIMIT on a range by rank is refused after the whole option list has
15050        // been read, so this complains about LIMIT and not about WITHSCORES.
15051        let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
15052        assert_eq!(
15053            f.run(&[
15054                b"ZREVRANGE",
15055                b"z",
15056                b"0",
15057                b"-1",
15058                b"WITHSCORES",
15059                b"LIMIT",
15060                b"0",
15061                b"1"
15062            ]),
15063            needs_by
15064        );
15065        assert_eq!(
15066            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
15067            needs_by
15068        );
15069        let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
15070        assert_eq!(
15071            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
15072            not_bylex
15073        );
15074        assert_eq!(
15075            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
15076            not_bylex
15077        );
15078        // Two modes at once, an option nobody knows, a LIMIT missing its count,
15079        // and the three number errors, which are three different sentences.
15080        for cmd in [
15081            &[
15082                b"ZRANGE".as_slice(),
15083                b"z",
15084                b"0",
15085                b"-1",
15086                b"BYSCORE",
15087                b"BYLEX",
15088            ][..],
15089            &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
15090            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
15091        ] {
15092            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15093        }
15094        assert_eq!(
15095            f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
15096            "-ERR min or max is not a float\r\n"
15097        );
15098        assert_eq!(
15099            f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
15100            "-ERR min or max not valid string range item\r\n"
15101        );
15102        assert_eq!(
15103            f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
15104            "-ERR value is not an integer or out of range\r\n"
15105        );
15106    }
15107
15108    /// `WITHSCORES` is the one place in this group where the two protocols
15109    /// disagree about the shape of the reply and not just the type of a value.
15110    #[test]
15111    fn withscores_nests_on_resp3_and_flattens_on_resp2() {
15112        let mut f = Fixture::new();
15113        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15114        assert_eq!(
15115            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
15116            "*6\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15117        );
15118        f.out = Out::new(Proto::Resp3);
15119        assert_eq!(
15120            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
15121            "*3\r\n*2\r\n$1\r\na\r\n,1\r\n*2\r\n$1\r\nb\r\n,2\r\n*2\r\n$1\r\nc\r\n,3\r\n"
15122        );
15123        assert_eq!(
15124            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
15125            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
15126        );
15127    }
15128
15129    /// The store form, which is the same parse with the destination in front.
15130    #[test]
15131    fn a_range_store_writes_the_window_into_another_key() {
15132        let mut f = Fixture::new();
15133        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15134        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
15135        // A window that selects nothing deletes the destination rather than
15136        // leaving an empty sorted set, because an empty one does not exist.
15137        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
15138        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
15139        assert_eq!(
15140            f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
15141            ":2\r\n"
15142        );
15143        assert_eq!(
15144            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
15145            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15146        );
15147        // The destination is allowed to be the source, because the result is
15148        // built whole before anything is written over.
15149        assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
15150        assert_eq!(
15151            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
15152            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15153        );
15154        // It takes every option ZRANGE takes except WITHSCORES, which is a
15155        // plain syntax error here and not the sentence about BYLEX.
15156        assert_eq!(
15157            f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
15158            "-ERR syntax error\r\n"
15159        );
15160    }
15161
15162    /// The three removals, which are the read side's window with the walk
15163    /// turned into a removal and no options at all.
15164    #[test]
15165    fn the_three_removals_share_their_window_with_the_reads() {
15166        let mut f = Fixture::new();
15167        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15168        assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
15169        assert_eq!(
15170            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
15171            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
15172        );
15173        assert_eq!(
15174            f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
15175            ":1\r\n"
15176        );
15177        assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
15178        // The last member going takes the key with it.
15179        assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
15180        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15181        assert_eq!(
15182            f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
15183            ":0\r\n"
15184        );
15185        assert_eq!(
15186            f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
15187            "-ERR value is not an integer or out of range\r\n"
15188        );
15189    }
15190
15191    /// The algebra, which is one gather and three names for it.
15192    #[test]
15193    fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
15194        let mut f = Fixture::new();
15195        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15196        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
15197        assert_eq!(
15198            f.run(&[b"ZUNION", b"2", b"z", b"y"]),
15199            "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
15200        );
15201        // The scores are added where a member is in both, and the answer comes
15202        // out in the order those combined scores put it in.
15203        assert_eq!(
15204            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
15205            "*8\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n$1\r\nb\r\n$2\r\n12\r\n$1\r\nd\r\n$2\r\n20\r\n"
15206        );
15207        assert_eq!(
15208            f.run(&[
15209                b"ZUNION",
15210                b"2",
15211                b"z",
15212                b"y",
15213                b"WEIGHTS",
15214                b"2",
15215                b"3",
15216                b"WITHSCORES"
15217            ]),
15218            "*8\r\n$1\r\na\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n6\r\n$1\r\nb\r\n$2\r\n34\r\n$1\r\nd\r\n$2\r\n60\r\n"
15219        );
15220        assert_eq!(
15221            f.run(&[
15222                b"ZUNION",
15223                b"2",
15224                b"z",
15225                b"y",
15226                b"AGGREGATE",
15227                b"MIN",
15228                b"WITHSCORES"
15229            ]),
15230            "*8\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n$1\r\nd\r\n$2\r\n20\r\n"
15231        );
15232        assert_eq!(
15233            f.run(&[
15234                b"ZUNION",
15235                b"2",
15236                b"z",
15237                b"y",
15238                b"AGGREGATE",
15239                b"MAX",
15240                b"WITHSCORES"
15241            ]),
15242            "*8\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n$1\r\nb\r\n$2\r\n10\r\n$1\r\nd\r\n$2\r\n20\r\n"
15243        );
15244        assert_eq!(
15245            f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
15246            "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
15247        );
15248        assert_eq!(
15249            f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
15250            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
15251        );
15252        assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
15253        // A plain set is an input, and it behaves as a sorted set in which
15254        // every member scores one.
15255        f.run(&[b"SADD", b"p", b"a", b"d"]);
15256        assert_eq!(
15257            f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
15258            "*8\r\n$1\r\nd\r\n$1\r\n1\r\n$1\r\na\r\n$1\r\n2\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15259        );
15260        // A difference never combines two scores, so it has nothing for either
15261        // of the two options to do and refuses both.
15262        for cmd in [
15263            &[
15264                b"ZDIFF".as_slice(),
15265                b"2",
15266                b"z",
15267                b"y",
15268                b"WEIGHTS",
15269                b"1",
15270                b"1",
15271            ][..],
15272            &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
15273        ] {
15274            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15275        }
15276    }
15277
15278    /// The count of keys, which is what lets a key be named `WEIGHTS`.
15279    #[test]
15280    fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
15281        let mut f = Fixture::new();
15282        f.run(&[b"ZADD", b"z", b"1", b"a"]);
15283        f.run(&[b"ZADD", b"y", b"2", b"b"]);
15284        // Redis names the command in this one, so each spelling says its own.
15285        assert_eq!(
15286            f.run(&[b"ZUNION", b"0", b"z"]),
15287            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
15288        );
15289        assert_eq!(
15290            f.run(&[b"ZUNION", b"-1", b"z"]),
15291            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
15292        );
15293        assert_eq!(
15294            f.run(&[b"ZINTERCARD", b"0", b"z"]),
15295            "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
15296        );
15297        // A count bigger than the line is a plain syntax error, which reads
15298        // oddly and is what Redis says.
15299        assert_eq!(
15300            f.run(&[b"ZUNION", b"3", b"z", b"y"]),
15301            "-ERR syntax error\r\n"
15302        );
15303        assert_eq!(
15304            f.run(&[b"ZUNION", b"x", b"z"]),
15305            "-ERR value is not an integer or out of range\r\n"
15306        );
15307        // A WEIGHTS list that is not one per key is a syntax error, and a
15308        // weight that is not a number gets a sentence of its own.
15309        assert_eq!(
15310            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
15311            "-ERR syntax error\r\n"
15312        );
15313        assert_eq!(
15314            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
15315            "-ERR weight value is not a float\r\n"
15316        );
15317        assert_eq!(
15318            f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
15319            "-ERR syntax error\r\n"
15320        );
15321    }
15322
15323    /// The three store forms, which answer a count and take no WITHSCORES.
15324    #[test]
15325    fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
15326        let mut f = Fixture::new();
15327        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15328        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
15329        assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
15330        assert_eq!(
15331            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
15332            "*8\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n$1\r\nb\r\n$2\r\n12\r\n$1\r\nd\r\n$2\r\n20\r\n"
15333        );
15334        assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
15335        assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
15336        // An empty result deletes the destination rather than leaving an empty
15337        // sorted set, because an empty one does not exist.
15338        assert_eq!(
15339            f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
15340            ":0\r\n"
15341        );
15342        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
15343        // The destination is allowed to name its own source.
15344        assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
15345        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
15346        for cmd in [
15347            &[
15348                b"ZUNIONSTORE".as_slice(),
15349                b"d",
15350                b"2",
15351                b"z",
15352                b"y",
15353                b"WITHSCORES",
15354            ][..],
15355            &[
15356                b"ZDIFFSTORE",
15357                b"d",
15358                b"2",
15359                b"z",
15360                b"y",
15361                b"WEIGHTS",
15362                b"1",
15363                b"1",
15364            ],
15365        ] {
15366            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15367        }
15368    }
15369
15370    /// `ZINTERCARD`, which counts without building anything.
15371    #[test]
15372    fn intercard_counts_and_stops_at_its_limit() {
15373        let mut f = Fixture::new();
15374        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15375        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
15376        assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
15377        // A limit of zero is no limit, which is Redis's reading of it.
15378        assert_eq!(
15379            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
15380            ":2\r\n"
15381        );
15382        assert_eq!(
15383            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
15384            ":1\r\n"
15385        );
15386        // A negative limit and a limit that is not a number at all get the same
15387        // sentence, which looks like a mistake in Redis and is copied as one.
15388        let bad = "-ERR LIMIT can't be negative\r\n";
15389        assert_eq!(
15390            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
15391            bad
15392        );
15393        assert_eq!(
15394            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
15395            bad
15396        );
15397        for cmd in [
15398            &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
15399            &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
15400            &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
15401        ] {
15402            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
15403        }
15404    }
15405
15406    /// `ZRANDMEMBER`, which answers two different shapes out of one name.
15407    #[test]
15408    fn a_draw_answers_one_member_or_an_array_of_them() {
15409        let mut f = Fixture::new();
15410        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15411        // No count is one member or a nil, a count is an array that may be
15412        // empty, and those are two reply types the client has to tell apart.
15413        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
15414        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
15415        assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
15416        assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
15417        // A positive count draws without replacement, so a count over the size
15418        // answers the whole set and never a member twice.
15419        let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
15420        assert!(all.starts_with("*3\r\n"), "{all}");
15421        for m in ["a", "b", "c"] {
15422            assert!(all.contains(m), "{all}");
15423        }
15424        // A negative one draws with replacement and answers exactly as many as
15425        // it was asked for, whatever the size of the set.
15426        assert!(
15427            f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
15428            "five draws with replacement"
15429        );
15430        assert!(
15431            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
15432                .starts_with("*4\r\n"),
15433            "two pairs, flat on RESP2"
15434        );
15435        f.out = Out::new(Proto::Resp3);
15436        let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
15437        assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
15438        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
15439        f.out = Out::new(Proto::Resp2);
15440        assert_eq!(
15441            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
15442            "-ERR syntax error\r\n"
15443        );
15444        assert_eq!(
15445            f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
15446            "-ERR value is not an integer or out of range\r\n"
15447        );
15448    }
15449
15450    /// `ZSCAN`, and the one sorted set reply where a score is not a double.
15451    #[test]
15452    fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
15453        let mut f = Fixture::new();
15454        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15455        let all = "*2\r\n$1\r\n0\r\n*6\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n";
15456        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
15457        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
15458        assert_eq!(
15459            f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
15460            "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
15461        );
15462        assert_eq!(
15463            f.run(&[b"ZSCAN", b"nokey", b"0"]),
15464            "*2\r\n$1\r\n0\r\n*0\r\n"
15465        );
15466        // A score stays a bulk string on RESP3, which is the one place the two
15467        // protocols agree about a score and everywhere else they do not.
15468        f.out = Out::new(Proto::Resp3);
15469        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
15470        f.out = Out::new(Proto::Resp2);
15471        assert_eq!(
15472            f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
15473            "-ERR NOVALUES option can only be used in HSCAN\r\n"
15474        );
15475        assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
15476        assert_eq!(
15477            f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
15478            "-ERR syntax error\r\n"
15479        );
15480    }
15481
15482    /// The count is what decides the shape, and its value is not.
15483    #[test]
15484    fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
15485        let mut f = Fixture::new();
15486        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15487        // No count, so one flat pair, and the score is a bulk string on RESP2.
15488        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
15489        assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
15490        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
15491        // A count, so pairs, and on RESP2 they are flattened into one run.
15492        assert_eq!(
15493            f.run(&[b"ZPOPMIN", b"z", b"2"]),
15494            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
15495        );
15496        // An empty array rather than a null, which is where a sorted set pop and
15497        // a list pop part company, and the same answer a count of zero gives.
15498        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
15499        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
15500        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
15501        // The last member takes the key with it.
15502        assert_eq!(
15503            f.run(&[b"ZPOPMIN", b"z", b"9"]),
15504            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
15505        );
15506        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15507
15508        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
15509        f.out = Out::new(Proto::Resp3);
15510        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
15511        assert_eq!(
15512            f.run(&[b"ZPOPMIN", b"z", b"1"]),
15513            "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
15514        );
15515        f.out = Out::new(Proto::Resp2);
15516        // Both of these are the range error rather than the usual sentence about
15517        // integers, which is the odd answer and so the one worth copying.
15518        let bad = "-ERR value is out of range, must be positive\r\n";
15519        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
15520        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
15521        assert_eq!(
15522            f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
15523            "-ERR syntax error\r\n"
15524        );
15525    }
15526
15527    /// `ZMPOP`, which is `LMPOP` with scores and the same parse.
15528    #[test]
15529    fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
15530        let mut f = Fixture::new();
15531        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15532        assert_eq!(
15533            f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
15534            "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
15535        );
15536        // Nested on RESP2 as well, because the key name is already in front of
15537        // the pairs and there is nothing left to flatten into.
15538        assert_eq!(
15539            f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
15540            "*2\r\n$1\r\nz\r\n*2\r\n*2\r\n$1\r\nc\r\n$1\r\n3\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
15541        );
15542        // A null array and not a null, the same as LMPOP.
15543        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
15544        f.out = Out::new(Proto::Resp3);
15545        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
15546        f.out = Out::new(Proto::Resp2);
15547        let numkeys = "-ERR numkeys should be greater than 0\r\n";
15548        for bad in [
15549            &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
15550            &[b"ZMPOP", b"-1", b"z", b"MIN"],
15551            &[b"ZMPOP", b"x", b"z", b"MIN"],
15552        ] {
15553            assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
15554        }
15555        let count = "-ERR count should be greater than 0\r\n";
15556        for bad in [
15557            &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
15558            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
15559            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
15560        ] {
15561            assert_eq!(f.run(bad), count, "{:?}", bad[5]);
15562        }
15563        let syntax = "-ERR syntax error\r\n";
15564        for bad in [
15565            // Two keys named and one given, so the word that should have been
15566            // the direction is a key and there is no direction left.
15567            &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
15568            &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
15569            &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
15570            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
15571        ] {
15572            assert_eq!(f.run(bad), syntax, "{bad:?}");
15573        }
15574    }
15575
15576    /// The three that wait, when there is something there and they do not have
15577    /// to. `BZPOPMIN` is the one reply in the group that is three flat elements.
15578    #[test]
15579    fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
15580        let mut f = Fixture::new();
15581        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
15582        assert_eq!(
15583            f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
15584            (
15585                Flow::Continue,
15586                "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
15587            )
15588        );
15589        assert_eq!(
15590            f.run(&[b"BZPOPMAX", b"z", b"0"]),
15591            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
15592        );
15593        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
15594        assert_eq!(
15595            f.run(&[
15596                b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
15597            ]),
15598            "*2\r\n$1\r\nz\r\n*2\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
15599        );
15600        f.out = Out::new(Proto::Resp3);
15601        assert_eq!(
15602            f.run(&[b"BZPOPMIN", b"z", b"0"]),
15603            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
15604        );
15605        f.out = Out::new(Proto::Resp2);
15606        // Nothing to take, so the client is parked and nothing was written.
15607        assert_eq!(
15608            f.flow(&[b"BZPOPMIN", b"z", b"0"]),
15609            (Flow::Block, String::new())
15610        );
15611        assert_eq!(
15612            f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
15613            (Flow::Block, String::new())
15614        );
15615        // The timeout is read before the key count, so this complains about the
15616        // timeout and not about the count.
15617        assert_eq!(
15618            f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
15619            "-ERR timeout is not a float or out of range\r\n"
15620        );
15621        assert_eq!(
15622            f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
15623            "-ERR numkeys should be greater than 0\r\n"
15624        );
15625        assert_eq!(
15626            f.run(&[b"BZPOPMIN", b"z", b"-1"]),
15627            "-ERR timeout is negative\r\n"
15628        );
15629    }
15630
15631    /// A parked sorted set client is served by whatever puts a member under one
15632    /// of its keys, and is not served by something of another type landing
15633    /// there.
15634    #[test]
15635    fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
15636        let mut f = Fixture::new();
15637        assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
15638        assert_eq!(f.server.parked(), 1);
15639        // A string under the key is not what it asked for, so it stays parked
15640        // rather than being handed a WRONGTYPE on a command that was accepted.
15641        f.run(&[b"SET", b"z", b"v"]);
15642        let mut out = Out::new(Proto::Resp2);
15643        assert!(!f.server.serve_waiter(7, 0, &mut out));
15644        assert!(out.as_slice().is_empty());
15645        f.run(&[b"DEL", b"z"]);
15646        f.run(&[b"ZADD", b"z", b"5", b"m"]);
15647        assert!(f.server.serve_waiter(7, 0, &mut out));
15648        assert_eq!(
15649            core::str::from_utf8(out.as_slice()).expect("ascii"),
15650            "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
15651        );
15652        // And the member is gone, which is what makes a queue of workers on a
15653        // sorted set work at all.
15654        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
15655    }
15656
15657    #[test]
15658    fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
15659        let mut f = Fixture::new();
15660        f.run(&[b"SET", b"s", b"v"]);
15661        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
15662        for cmd in [
15663            &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
15664            &[b"ZINCRBY", b"s", b"1", b"a"],
15665            &[b"ZCARD", b"s"],
15666            &[b"ZSCORE", b"s", b"a"],
15667            &[b"ZMSCORE", b"s", b"a"],
15668            &[b"ZREM", b"s", b"a"],
15669            &[b"ZRANK", b"s", b"a"],
15670            &[b"ZREVRANK", b"s", b"a"],
15671            &[b"ZCOUNT", b"s", b"1", b"2"],
15672            &[b"ZLEXCOUNT", b"s", b"-", b"+"],
15673            &[b"ZRANGE", b"s", b"0", b"-1"],
15674            &[b"ZREVRANGE", b"s", b"0", b"-1"],
15675            &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
15676            &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
15677            &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
15678            &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
15679            &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
15680            &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
15681            &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
15682            &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
15683            &[b"ZUNION", b"1", b"s"],
15684            &[b"ZINTER", b"1", b"s"],
15685            &[b"ZDIFF", b"1", b"s"],
15686            &[b"ZUNIONSTORE", b"d", b"1", b"s"],
15687            &[b"ZINTERSTORE", b"d", b"1", b"s"],
15688            &[b"ZDIFFSTORE", b"d", b"1", b"s"],
15689            &[b"ZINTERCARD", b"1", b"s"],
15690            &[b"ZRANDMEMBER", b"s"],
15691            &[b"ZSCAN", b"s", b"0"],
15692            &[b"ZPOPMIN", b"s"],
15693            &[b"ZPOPMAX", b"s", b"2"],
15694            &[b"ZMPOP", b"1", b"s", b"MIN"],
15695            &[b"BZPOPMIN", b"s", b"0"],
15696            &[b"BZPOPMAX", b"s", b"0"],
15697            &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
15698        ] {
15699            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
15700        }
15701        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
15702    }
15703
15704    /// The same churn the set, the string and the list get, because a sorted
15705    /// set that leaks a tree node per add looks exactly like one that does not
15706    /// until it has run for an afternoon.
15707    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
15708    #[cfg_attr(miri, ignore = "the volume is the claim")]
15709    #[test]
15710    fn churning_sorted_sets_does_not_grow_the_server() {
15711        let mut f = Fixture::new();
15712        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
15713        let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
15714        let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
15715        for i in 0..200 {
15716            args.push(&scores[i]);
15717            args.push(&members[i]);
15718        }
15719
15720        f.run(&args);
15721        f.run(&[b"DEL", b"z"]);
15722        f.server.compact_step();
15723        let after_first = f.server.memory_bytes();
15724
15725        for _ in 0..200 {
15726            f.run(&args);
15727            f.run(&[b"DEL", b"z"]);
15728            f.server.compact_step();
15729        }
15730        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
15731        assert!(
15732            f.server.memory_bytes() <= after_first * 2,
15733            "held {} after two hundred passes against {after_first} after one",
15734            f.server.memory_bytes()
15735        );
15736    }
15737
15738    // ------------------------------------------------------------------- geo
15739
15740    /// The three places every Redis geo example uses, and one more.
15741    ///
15742    /// Every reply this section asserts on came off a running 8.10.1 with these
15743    /// three loaded, byte for byte, including the number of digits in a
15744    /// coordinate and the four places on a distance.
15745    fn sicily(f: &mut Fixture) {
15746        f.run(&[
15747            b"GEOADD",
15748            b"Sicily",
15749            b"13.361389",
15750            b"38.115556",
15751            b"Palermo",
15752            b"15.087269",
15753            b"37.502669",
15754            b"Catania",
15755        ]);
15756        f.run(&[
15757            b"GEOADD",
15758            b"Sicily",
15759            b"13.583333",
15760            b"37.316667",
15761            b"Agrigento",
15762        ]);
15763    }
15764
15765    #[test]
15766    fn places_go_in_as_scores_and_come_back_as_positions() {
15767        let mut f = Fixture::new();
15768        assert_eq!(
15769            f.run(&[
15770                b"GEOADD",
15771                b"Sicily",
15772                b"13.361389",
15773                b"38.115556",
15774                b"Palermo",
15775                b"15.087269",
15776                b"37.502669",
15777                b"Catania"
15778            ]),
15779            ":2\r\n"
15780        );
15781        // A geo key is a sorted set and says so, which is not an implementation
15782        // detail either: a client removes a place with ZREM and counts them
15783        // with ZCARD, and the score is the number a real server stores.
15784        assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
15785        assert_eq!(
15786            f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
15787            "$16\r\n3479099956230698\r\n"
15788        );
15789        assert_eq!(
15790            f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
15791            "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
15792        );
15793        assert_eq!(
15794            f.run(&[
15795                b"GEOHASH",
15796                b"Sicily",
15797                b"Palermo",
15798                b"Catania",
15799                b"NonExisting"
15800            ]),
15801            "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
15802        );
15803        // A key that is not there is an empty one, and the two nulls are not
15804        // the same null: GEOPOS answers the array one and GEOHASH the string
15805        // one, which a RESP2 client can tell apart.
15806        assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
15807        assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
15808    }
15809
15810    #[test]
15811    fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
15812        let mut f = Fixture::new();
15813        sicily(&mut f);
15814        assert_eq!(
15815            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
15816            "$11\r\n166274.1516\r\n"
15817        );
15818        assert_eq!(
15819            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
15820            "$8\r\n166.2742\r\n"
15821        );
15822        assert_eq!(
15823            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
15824            "$8\r\n103.3182\r\n"
15825        );
15826        // A member that is not there and a key that is not there are the same
15827        // nil, and the unit is read before the key is looked up, so a bad unit
15828        // on a missing key is still an error.
15829        assert_eq!(
15830            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
15831            "$-1\r\n"
15832        );
15833        assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
15834        assert_eq!(
15835            f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
15836            "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
15837        );
15838        assert_eq!(
15839            f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
15840            "-ERR syntax error\r\n"
15841        );
15842    }
15843
15844    #[test]
15845    fn a_search_finds_what_is_inside_it_nearest_first() {
15846        let mut f = Fixture::new();
15847        sicily(&mut f);
15848        let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
15849        assert_eq!(
15850            f.run(&[
15851                b"GEOSEARCH",
15852                b"Sicily",
15853                b"FROMLONLAT",
15854                b"15",
15855                b"37",
15856                b"BYRADIUS",
15857                b"200",
15858                b"km",
15859                b"ASC"
15860            ]),
15861            all
15862        );
15863        // The older spelling of the same search, which is the same nine boxes
15864        // and the same order.
15865        assert_eq!(
15866            f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
15867            all
15868        );
15869        assert_eq!(
15870            f.run(&[
15871                b"GEORADIUS_RO",
15872                b"Sicily",
15873                b"15",
15874                b"37",
15875                b"200",
15876                b"km",
15877                b"ASC"
15878            ]),
15879            all
15880        );
15881        // A count with no ordering means the nearest ones, so DESC has to be
15882        // asked for to get the far end.
15883        assert_eq!(
15884            f.run(&[
15885                b"GEORADIUS",
15886                b"Sicily",
15887                b"15",
15888                b"37",
15889                b"200",
15890                b"km",
15891                b"DESC",
15892                b"COUNT",
15893                b"1"
15894            ]),
15895            "*1\r\n$7\r\nPalermo\r\n"
15896        );
15897        assert_eq!(
15898            f.run(&[
15899                b"GEORADIUS",
15900                b"Sicily",
15901                b"15",
15902                b"37",
15903                b"200",
15904                b"km",
15905                b"COUNT",
15906                b"1"
15907            ]),
15908            "*1\r\n$7\r\nCatania\r\n"
15909        );
15910        // Nothing inside a kilometre of that point, and nothing in a key that
15911        // is not there, and both are the empty array rather than an error.
15912        let empty = "*0\r\n";
15913        assert_eq!(
15914            f.run(&[
15915                b"GEOSEARCH",
15916                b"Sicily",
15917                b"FROMLONLAT",
15918                b"15",
15919                b"37",
15920                b"BYRADIUS",
15921                b"1",
15922                b"km"
15923            ]),
15924            empty
15925        );
15926        assert_eq!(
15927            f.run(&[
15928                b"GEOSEARCH",
15929                b"nokey",
15930                b"FROMLONLAT",
15931                b"15",
15932                b"37",
15933                b"BYRADIUS",
15934                b"1",
15935                b"km"
15936            ]),
15937            empty
15938        );
15939        assert_eq!(
15940            f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
15941            empty
15942        );
15943    }
15944
15945    #[test]
15946    fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
15947        let mut f = Fixture::new();
15948        sicily(&mut f);
15949        assert_eq!(
15950            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
15951            "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
15952        );
15953        // The member itself is nothing away from itself, which is where the
15954        // fixed point writer's zero shows up on the wire.
15955        let with_dist = "*2\r\n*2\r\n$9\r\nAgrigento\r\n$6\r\n0.0000\r\n*2\r\n$7\r\nPalermo\r\n$7\r\n90.9778\r\n";
15956        assert_eq!(
15957            f.run(&[
15958                b"GEORADIUSBYMEMBER_RO",
15959                b"Sicily",
15960                b"Agrigento",
15961                b"100",
15962                b"km",
15963                b"WITHDIST"
15964            ]),
15965            with_dist
15966        );
15967        assert_eq!(
15968            f.run(&[
15969                b"GEOSEARCH",
15970                b"Sicily",
15971                b"FROMMEMBER",
15972                b"Agrigento",
15973                b"BYRADIUS",
15974                b"100",
15975                b"km",
15976                b"ASC",
15977                b"WITHDIST"
15978            ]),
15979            with_dist
15980        );
15981        assert_eq!(
15982            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
15983            "-ERR could not decode requested zset member\r\n"
15984        );
15985    }
15986
15987    #[test]
15988    fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
15989        let mut f = Fixture::new();
15990        sicily(&mut f);
15991        // Three options asked for, so each result is a four element array of
15992        // the member, the distance, the hash and a pair. The order of the three
15993        // is Redis's and not the order they were written in the command.
15994        assert_eq!(
15995            f.run(&[
15996                b"GEOSEARCH",
15997                b"Sicily",
15998                b"FROMLONLAT",
15999                b"15",
16000                b"37",
16001                b"BYBOX",
16002                b"400",
16003                b"400",
16004                b"km",
16005                b"ASC",
16006                b"WITHCOORD",
16007                b"WITHDIST",
16008                b"WITHHASH"
16009            ]),
16010            "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
16011             $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
16012             *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
16013             $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
16014             *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
16015             $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
16016        );
16017    }
16018
16019    #[test]
16020    fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
16021        let mut f = Fixture::new();
16022        sicily(&mut f);
16023        let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
16024                      $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
16025                      $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
16026        assert_eq!(
16027            f.run(&[
16028                b"GEOSEARCHSTORE",
16029                b"dst",
16030                b"Sicily",
16031                b"FROMLONLAT",
16032                b"15",
16033                b"37",
16034                b"BYRADIUS",
16035                b"200",
16036                b"km",
16037                b"ASC"
16038            ]),
16039            ":3\r\n"
16040        );
16041        assert_eq!(
16042            f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
16043            hashes
16044        );
16045        // The same again through the older spelling, which stores the same
16046        // scores, so a key written by either is a geo key.
16047        assert_eq!(
16048            f.run(&[
16049                b"GEORADIUS",
16050                b"Sicily",
16051                b"15",
16052                b"37",
16053                b"200",
16054                b"km",
16055                b"STORE",
16056                b"dst3"
16057            ]),
16058            ":3\r\n"
16059        );
16060        assert_eq!(
16061            f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
16062            hashes
16063        );
16064        // STOREDIST stores the distance in the search unit instead, and those
16065        // are full doubles rather than the four places WITHDIST writes. The
16066        // numbers on the right are what 8.10.1 stored for this search, and they
16067        // are compared with a tolerance rather than byte for byte because the
16068        // last bit of a haversine is the platform's sin, cos and asin: this
16069        // machine and that one disagree in the sixteenth digit, and so do two
16070        // Redis builds. Everything a client actually reads back is four places
16071        // and is asserted exactly above.
16072        assert_eq!(
16073            f.run(&[
16074                b"GEOSEARCHSTORE",
16075                b"dst2",
16076                b"Sicily",
16077                b"FROMLONLAT",
16078                b"15",
16079                b"37",
16080                b"BYRADIUS",
16081                b"200",
16082                b"km",
16083                b"ASC",
16084                b"STOREDIST"
16085            ]),
16086            ":3\r\n"
16087        );
16088        for (member, want) in [
16089            ("Catania", 56.441_257_870_158_19),
16090            ("Agrigento", 130.423_487_067_147_14),
16091            ("Palermo", 190.442_429_847_757_92),
16092        ] {
16093            let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
16094            let got: f64 = reply
16095                .trim_start_matches(|c: char| c != '\n')
16096                .trim()
16097                .parse()
16098                .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
16099            assert!(
16100                (got - want).abs() < 1e-9,
16101                "{member} scored {got} not {want}"
16102            );
16103        }
16104        // The order they went in is the order the scores put them in, which is
16105        // the point of storing the distance rather than the hash.
16106        assert_eq!(
16107            f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
16108            "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
16109        );
16110        // A search that finds nothing takes the destination with it rather than
16111        // leaving what was there, and a source key that is not there is a
16112        // search that finds nothing.
16113        assert_eq!(
16114            f.run(&[
16115                b"GEOSEARCHSTORE",
16116                b"dst",
16117                b"nokey",
16118                b"FROMLONLAT",
16119                b"15",
16120                b"37",
16121                b"BYRADIUS",
16122                b"200",
16123                b"km"
16124            ]),
16125            ":0\r\n"
16126        );
16127        assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
16128    }
16129
16130    #[test]
16131    fn the_gates_on_geoadd_are_the_ones_zadd_has() {
16132        let mut f = Fixture::new();
16133        sicily(&mut f);
16134        // XX on a member that is already where it is changes nothing, and NX on
16135        // one that is there refuses to move it.
16136        assert_eq!(
16137            f.run(&[
16138                b"GEOADD",
16139                b"Sicily",
16140                b"XX",
16141                b"CH",
16142                b"13.361389",
16143                b"38.115556",
16144                b"Palermo"
16145            ]),
16146            ":0\r\n"
16147        );
16148        assert_eq!(
16149            f.run(&[
16150                b"GEOADD",
16151                b"Sicily",
16152                b"NX",
16153                b"13.361389",
16154                b"38.9",
16155                b"Palermo"
16156            ]),
16157            ":0\r\n"
16158        );
16159        assert_eq!(
16160            f.run(&[
16161                b"GEOADD",
16162                b"Sicily",
16163                b"CH",
16164                b"13.361389",
16165                b"38.9",
16166                b"Palermo"
16167            ]),
16168            ":1\r\n"
16169        );
16170        // Out of range, and nothing is stored: the whole call is refused rather
16171        // than the good pairs going in and the bad one stopping it.
16172        assert_eq!(
16173            f.run(&[
16174                b"GEOADD",
16175                b"new",
16176                b"13.361389",
16177                b"38.115556",
16178                b"here",
16179                b"181",
16180                b"38",
16181                b"there"
16182            ]),
16183            "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
16184        );
16185        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
16186        assert_eq!(
16187            f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
16188            "-ERR value is not a valid float\r\n"
16189        );
16190        // The count of triples is checked before the two gates are, and a call
16191        // with no triples at all reaches the same sentence.
16192        assert_eq!(
16193            f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
16194            "-ERR syntax error\r\n"
16195        );
16196        assert_eq!(
16197            f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
16198            "-ERR syntax error\r\n"
16199        );
16200        assert_eq!(
16201            f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
16202            "-ERR syntax error\r\n"
16203        );
16204        assert_eq!(
16205            f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
16206            "-ERR wrong number of arguments for 'geoadd' command\r\n"
16207        );
16208    }
16209
16210    /// The sentences a search answers, which are its contract as much as the
16211    /// results are.
16212    #[test]
16213    fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
16214        let mut f = Fixture::new();
16215        sicily(&mut f);
16216        let cases: &[(&[&[u8]], &str)] = &[
16217            (
16218                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
16219                "-ERR need numeric radius\r\n",
16220            ),
16221            (
16222                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
16223                "-ERR radius cannot be negative\r\n",
16224            ),
16225            (
16226                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
16227                "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
16228            ),
16229            (
16230                &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
16231                "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
16232            ),
16233            (
16234                &[
16235                    b"GEOSEARCH",
16236                    b"Sicily",
16237                    b"FROMLONLAT",
16238                    b"15",
16239                    b"37",
16240                    b"BYBOX",
16241                    b"x",
16242                    b"1",
16243                    b"km",
16244                ],
16245                "-ERR need numeric width\r\n",
16246            ),
16247            (
16248                &[
16249                    b"GEOSEARCH",
16250                    b"Sicily",
16251                    b"FROMLONLAT",
16252                    b"15",
16253                    b"37",
16254                    b"BYBOX",
16255                    b"1",
16256                    b"y",
16257                    b"km",
16258                ],
16259                "-ERR need numeric height\r\n",
16260            ),
16261            (
16262                &[
16263                    b"GEOSEARCH",
16264                    b"Sicily",
16265                    b"FROMLONLAT",
16266                    b"15",
16267                    b"37",
16268                    b"BYBOX",
16269                    b"-1",
16270                    b"1",
16271                    b"km",
16272                ],
16273                "-ERR height or width cannot be negative\r\n",
16274            ),
16275            (
16276                &[
16277                    b"GEOSEARCH",
16278                    b"Sicily",
16279                    b"FROMLONLAT",
16280                    b"15",
16281                    b"37",
16282                    b"BYRADIUS",
16283                    b"1",
16284                    b"km",
16285                    b"ANY",
16286                ],
16287                "-ERR the ANY argument requires COUNT argument\r\n",
16288            ),
16289            (
16290                &[
16291                    b"GEOSEARCH",
16292                    b"Sicily",
16293                    b"FROMLONLAT",
16294                    b"15",
16295                    b"37",
16296                    b"BYRADIUS",
16297                    b"1",
16298                    b"km",
16299                    b"COUNT",
16300                    b"0",
16301                ],
16302                "-ERR COUNT must be > 0\r\n",
16303            ),
16304            (
16305                &[
16306                    b"GEOSEARCH",
16307                    b"Sicily",
16308                    b"BYRADIUS",
16309                    b"1",
16310                    b"km",
16311                    b"BYBOX",
16312                    b"1",
16313                    b"1",
16314                    b"km",
16315                ],
16316                "-ERR syntax error\r\n",
16317            ),
16318            (
16319                &[
16320                    b"GEOSEARCH",
16321                    b"Sicily",
16322                    b"FROMMEMBER",
16323                    b"Palermo",
16324                    b"FROMLONLAT",
16325                    b"1",
16326                    b"2",
16327                    b"BYRADIUS",
16328                    b"1",
16329                    b"km",
16330                ],
16331                "-ERR syntax error\r\n",
16332            ),
16333            // The two options a GEOSEARCH cannot leave out, each with its own
16334            // sentence, and the command quoted the way the client spelled it.
16335            (
16336                &[
16337                    b"geosearch",
16338                    b"Sicily",
16339                    b"BYRADIUS",
16340                    b"1",
16341                    b"km",
16342                    b"ASC",
16343                    b"WITHDIST",
16344                ],
16345                "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
16346            ),
16347            (
16348                &[
16349                    b"GEOSEARCH",
16350                    b"Sicily",
16351                    b"FROMLONLAT",
16352                    b"15",
16353                    b"37",
16354                    b"ASC",
16355                    b"WITHDIST",
16356                ],
16357                "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
16358            ),
16359            // A store cannot also be asked for the distance, and the two
16360            // families name themselves differently in the same sentence.
16361            (
16362                &[
16363                    b"GEOSEARCHSTORE",
16364                    b"d",
16365                    b"Sicily",
16366                    b"FROMLONLAT",
16367                    b"15",
16368                    b"37",
16369                    b"BYRADIUS",
16370                    b"1",
16371                    b"km",
16372                    b"WITHCOORD",
16373                ],
16374                "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
16375            ),
16376            (
16377                &[
16378                    b"GEORADIUS",
16379                    b"Sicily",
16380                    b"15",
16381                    b"37",
16382                    b"1",
16383                    b"km",
16384                    b"WITHDIST",
16385                    b"STORE",
16386                    b"d",
16387                ],
16388                "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
16389            ),
16390            // The read only forms have no store at all, so the word is a stray
16391            // one, and GEOSEARCH's STOREDIST is only a GEOSEARCHSTORE option.
16392            (
16393                &[
16394                    b"GEORADIUS_RO",
16395                    b"Sicily",
16396                    b"15",
16397                    b"37",
16398                    b"1",
16399                    b"km",
16400                    b"STORE",
16401                    b"d",
16402                ],
16403                "-ERR syntax error\r\n",
16404            ),
16405            (
16406                &[
16407                    b"GEOSEARCH",
16408                    b"Sicily",
16409                    b"FROMLONLAT",
16410                    b"15",
16411                    b"37",
16412                    b"BYRADIUS",
16413                    b"1",
16414                    b"km",
16415                    b"STOREDIST",
16416                ],
16417                "-ERR syntax error\r\n",
16418            ),
16419        ];
16420        for (parts, want) in cases {
16421            assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
16422        }
16423    }
16424
16425    /// A wrong type wins over a bad argument, because the key is looked up
16426    /// first, and every one of the ten says the same thing about it.
16427    #[test]
16428    fn every_geo_command_says_wrongtype() {
16429        let mut f = Fixture::new();
16430        f.run(&[b"SET", b"s", b"v"]);
16431        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16432        let cases: &[&[&[u8]]] = &[
16433            &[b"GEOADD", b"s", b"13", b"38", b"m"],
16434            &[b"GEOPOS", b"s", b"m"],
16435            &[b"GEOHASH", b"s", b"m"],
16436            &[b"GEODIST", b"s", b"a", b"b"],
16437            &[
16438                b"GEOSEARCH",
16439                b"s",
16440                b"FROMLONLAT",
16441                b"15",
16442                b"37",
16443                b"BYRADIUS",
16444                b"1",
16445                b"km",
16446            ],
16447            &[
16448                b"GEOSEARCHSTORE",
16449                b"d",
16450                b"s",
16451                b"FROMLONLAT",
16452                b"15",
16453                b"37",
16454                b"BYRADIUS",
16455                b"1",
16456                b"km",
16457            ],
16458            &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
16459            &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
16460            &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
16461            &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
16462        ];
16463        for case in cases {
16464            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
16465        }
16466        // And it wins over an argument that will not parse, which is the whole
16467        // reason the lookup comes first.
16468        assert_eq!(
16469            f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
16470            wrong
16471        );
16472    }
16473
16474    // ----------------------------------------------------------------- array
16475
16476    #[test]
16477    fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
16478        let mut f = Fixture::new();
16479        // Three consecutive positions from a high index, and the reply is how
16480        // many of them were empty before rather than how many were written.
16481        assert_eq!(
16482            f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
16483            ":3\r\n"
16484        );
16485        assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
16486        assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
16487        assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
16488        // A hole and a key that is not there are the same answer.
16489        assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
16490        assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
16491        assert_eq!(
16492            f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
16493            "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
16494        );
16495        // Scattered pairs in one command, last write wins within it.
16496        assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
16497        assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
16498    }
16499
16500    /// The two numbers an array reports are not the same number, and one of
16501    /// them does not fit a signed integer.
16502    #[test]
16503    fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
16504        let mut f = Fixture::new();
16505        assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
16506        assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
16507        f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
16508        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
16509        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
16510        // Deleting in the middle leaves the high water mark where it was.
16511        assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
16512        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
16513        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
16514
16515        // The top of the space is addressable, and its length is a number with
16516        // bit sixty three set, so the reply has to be unsigned or it comes back
16517        // negative.
16518        f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
16519        assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
16520        assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
16521        // And one past it does not exist, so a write that would reach it fails
16522        // before any of it lands.
16523        assert_eq!(
16524            f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
16525            "-ERR array index overflow\r\n"
16526        );
16527        assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
16528    }
16529
16530    /// One reply per position and not one per element, which is the whole
16531    /// reason the range is capped.
16532    #[test]
16533    fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
16534        let mut f = Fixture::new();
16535        f.run(&[b"ARSET", b"a", b"1", b"x"]);
16536        assert_eq!(
16537            f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
16538            "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
16539        );
16540        // The two ends may come in either order, and the answer is reversed
16541        // rather than empty.
16542        assert_eq!(
16543            f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
16544            "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
16545        );
16546        // A key that is not there reads like an array of nothing but holes.
16547        assert_eq!(
16548            f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
16549            "*2\r\n$-1\r\n$-1\r\n"
16550        );
16551        // A range wider than a million positions is refused and not trimmed,
16552        // because against a missing key it is a request for as many nulls as
16553        // the range is wide.
16554        assert_eq!(
16555            f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
16556            "-ERR range exceeds maximum of 1000000 items\r\n"
16557        );
16558    }
16559
16560    /// Every index in the argument list is read before the key is touched, so
16561    /// a bad one at the end leaves nothing half written.
16562    #[test]
16563    fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
16564        let mut f = Fixture::new();
16565        assert_eq!(
16566            f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
16567            "-ERR invalid array index\r\n"
16568        );
16569        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
16570        f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
16571        assert_eq!(
16572            f.run(&[b"ARDEL", b"a", b"0", b"01"]),
16573            "-ERR invalid array index\r\n"
16574        );
16575        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
16576        // An index is unsigned here, so the numbers a list would take are not
16577        // the last element, they are errors.
16578        assert_eq!(
16579            f.run(&[b"ARGET", b"a", b"-1"]),
16580            "-ERR invalid array index\r\n"
16581        );
16582        // And a pair list with an odd tail is an arity error rather than a
16583        // syntax one.
16584        assert_eq!(
16585            f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
16586            "-ERR wrong number of arguments for 'armset' command\r\n"
16587        );
16588        assert_eq!(
16589            f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
16590            "-ERR wrong number of arguments for 'ardelrange' command\r\n"
16591        );
16592    }
16593
16594    #[test]
16595    fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
16596        let mut f = Fixture::new();
16597        f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
16598        assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
16599        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
16600        // Two ranges in one command, and the second one covers the whole space
16601        // without walking it.
16602        assert_eq!(
16603            f.run(&[
16604                b"ARDELRANGE",
16605                b"a",
16606                b"100",
16607                b"200",
16608                b"0",
16609                b"18446744073709551614"
16610            ]),
16611            ":2\r\n"
16612        );
16613        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
16614        assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
16615        assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
16616    }
16617
16618    /// A value goes out as the bytes it came in as, whichever of the three ways
16619    /// the array found to store it.
16620    #[test]
16621    fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
16622        let mut f = Fixture::new();
16623        let long = vec![b'v'; 200];
16624        f.run(&[
16625            b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
16626            b"short", b"5", &long, b"6", b"-0",
16627        ]);
16628        // 42 is an integer, 007 is not one because it does not print back the
16629        // same, 3.5 survives a double and 3.14 does not, and the last two are a
16630        // word packed string and a blob.
16631        assert_eq!(
16632            f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
16633            format!(
16634                "*7\r\n$2\r\n42\r\n$3\r\n007\r\n$3\r\n3.5\r\n$4\r\n3.14\r\n$5\r\nshort\r\n$200\r\n{}\r\n$2\r\n-0\r\n",
16635                String::from_utf8_lossy(&long)
16636            )
16637        );
16638    }
16639
16640    #[test]
16641    fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
16642        let mut f = Fixture::new();
16643        f.run(&[b"ARSET", b"a", b"0", b"x"]);
16644        assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
16645        assert_eq!(
16646            f.run(&[b"OBJECT", b"ENCODING", b"a"]),
16647            "$12\r\nsliced-array\r\n"
16648        );
16649        // And it is a body like any other, so the key commands work on it.
16650        assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
16651        assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
16652        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
16653        assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
16654        assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
16655        assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
16656    }
16657
16658    #[test]
16659    fn every_array_command_refuses_a_key_holding_something_else() {
16660        let mut f = Fixture::new();
16661        f.run(&[b"SET", b"s", b"v"]);
16662        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16663        for cmd in [
16664            &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
16665            &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
16666            &[b"ARGET".as_ref(), b"s", b"0"][..],
16667            &[b"ARMGET".as_ref(), b"s", b"0"][..],
16668            &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
16669            &[b"ARLEN".as_ref(), b"s"][..],
16670            &[b"ARCOUNT".as_ref(), b"s"][..],
16671            &[b"ARDEL".as_ref(), b"s", b"0"][..],
16672            &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
16673            &[b"ARINSERT".as_ref(), b"s", b"x"][..],
16674            &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
16675            &[b"ARNEXT".as_ref(), b"s"][..],
16676            &[b"ARSEEK".as_ref(), b"s", b"1"][..],
16677            &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
16678            &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
16679            &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
16680            &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
16681            &[b"ARINFO".as_ref(), b"s"][..],
16682        ] {
16683            assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
16684        }
16685    }
16686
16687    /// Two of the array commands look the key up before they read the index and
16688    /// the rest read the index first, so the same broken argument gets two
16689    /// different errors depending on which command it went to.
16690    #[test]
16691    fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
16692        let mut f = Fixture::new();
16693        f.run(&[b"SET", b"s", b"v"]);
16694        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
16695        let bad = "-ERR invalid array index\r\n";
16696        assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
16697        assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
16698        assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
16699        assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
16700        assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
16701        assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
16702        // And on a key that is an array the index is just an index.
16703        f.run(&[b"ARSET", b"a", b"0", b"x"]);
16704        assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
16705        assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
16706    }
16707
16708    #[test]
16709    fn an_append_follows_a_cursor_the_client_can_move() {
16710        let mut f = Fixture::new();
16711        assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
16712        assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
16713        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
16714        assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
16715        assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
16716
16717        // A seek says where the next one goes, and a missing key has no cursor
16718        // to move and is not created by the asking.
16719        assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
16720        assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
16721        assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
16722        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
16723        assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
16724        assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
16725        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
16726
16727        // The top of the space is the one index only ARSEEK will take, and it
16728        // leaves the cursor with nowhere to go.
16729        assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
16730        assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
16731        assert_eq!(
16732            f.run(&[b"ARINSERT", b"a", b"x"]),
16733            "-ERR insert index overflow\r\n"
16734        );
16735        assert_eq!(
16736            f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
16737            "-ERR invalid array index\r\n"
16738        );
16739    }
16740
16741    #[test]
16742    fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
16743        let mut f = Fixture::new();
16744        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
16745        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
16746        assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
16747        assert_eq!(
16748            f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
16749            "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
16750        );
16751        // Growing it after it has wrapped puts the survivors back in the order
16752        // they arrived, which is the whole point of paying for the rebuild.
16753        assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
16754        assert_eq!(
16755            f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
16756            "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
16757        );
16758        // The size is read before the key, so a bad one is a bad size wherever
16759        // it is sent.
16760        assert_eq!(
16761            f.run(&[b"ARRING", b"r", b"0", b"x"]),
16762            "-ERR size must be positive\r\n"
16763        );
16764        assert_eq!(
16765            f.run(&[b"ARRING", b"r", b"big", b"x"]),
16766            "-ERR invalid size\r\n"
16767        );
16768    }
16769
16770    #[test]
16771    fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
16772        let mut f = Fixture::new();
16773        assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
16774        f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
16775        assert_eq!(
16776            f.run(&[b"ARLASTITEMS", b"r", b"3"]),
16777            "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
16778        );
16779        assert_eq!(
16780            f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
16781            "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
16782        );
16783        assert_eq!(
16784            f.run(&[b"ARLASTITEMS", b"r", b"99"]),
16785            "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
16786            "more than there is gets what there is"
16787        );
16788        // Nothing asked for is an empty reply, and Redis answers that before it
16789        // has read the option or looked at the key.
16790        assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
16791        assert_eq!(
16792            f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
16793            "-ERR syntax error\r\n"
16794        );
16795        assert_eq!(
16796            f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
16797            "-ERR invalid COUNT\r\n"
16798        );
16799
16800        // With no cursor the tail of the array is the anchor, and a hole inside
16801        // the window is reported as one.
16802        f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
16803        assert_eq!(
16804            f.run(&[b"ARLASTITEMS", b"h", b"5"]),
16805            "*2\r\n$-1\r\n$1\r\nz\r\n"
16806        );
16807    }
16808
16809    #[test]
16810    fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
16811        let mut f = Fixture::new();
16812        assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
16813        f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
16814        // The whole index space, which ARGETRANGE refuses and this one answers
16815        // in three visits because holes cost nothing.
16816        assert_eq!(
16817            f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
16818            "*3\r\n*2\r\n:0\r\n$1\r\nx\r\n*2\r\n:7\r\n$1\r\ny\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
16819        );
16820        assert_eq!(
16821            f.run(&[
16822                b"ARSCAN",
16823                b"a",
16824                b"18446744073709551614",
16825                b"0",
16826                b"LIMIT",
16827                b"1"
16828            ]),
16829            "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
16830        );
16831        assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
16832        assert_eq!(
16833            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
16834            "-ERR LIMIT must be positive\r\n"
16835        );
16836        assert_eq!(
16837            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
16838            "-ERR syntax error\r\n"
16839        );
16840        assert_eq!(
16841            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
16842            "-ERR wrong number of arguments for 'arscan' command\r\n"
16843        );
16844    }
16845
16846    #[test]
16847    fn a_grep_answers_the_indexes_whose_elements_match() {
16848        let mut f = Fixture::new();
16849        assert_eq!(
16850            f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
16851            "*0\r\n"
16852        );
16853        f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
16854
16855        // The two bounds take the ends of the array as well as an index, and a
16856        // reversed range is walked backwards the way ARSCAN walks one.
16857        assert_eq!(
16858            f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
16859            "*3\r\n:0\r\n:1\r\n:2\r\n"
16860        );
16861        assert_eq!(
16862            f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
16863            "*3\r\n:2\r\n:1\r\n:0\r\n"
16864        );
16865        assert_eq!(
16866            f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
16867            "*2\r\n:1\r\n:2\r\n"
16868        );
16869
16870        // One test each. NOCASE reaches all four of them and it may be written
16871        // after the pattern it applies to.
16872        assert_eq!(
16873            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
16874            "*1\r\n:0\r\n"
16875        );
16876        assert_eq!(
16877            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
16878            "*2\r\n:0\r\n:3\r\n"
16879        );
16880        assert_eq!(
16881            f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
16882            "*1\r\n:2\r\n"
16883        );
16884        assert_eq!(
16885            f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
16886            "*2\r\n:1\r\n:2\r\n"
16887        );
16888
16889        // OR is the default and AND has to be asked for, and either way the
16890        // last of a repeated option wins.
16891        let both: &[&[u8]] = &[
16892            b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
16893        ];
16894        assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
16895        assert_eq!(
16896            f.run(&[
16897                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
16898            ]),
16899            "*0\r\n"
16900        );
16901        assert_eq!(
16902            f.run(&[
16903                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
16904            ]),
16905            "*2\r\n:0\r\n:1\r\n"
16906        );
16907
16908        // WITHVALUES turns each hit into a pair, and LIMIT counts the hits and
16909        // not the positions it had to look at.
16910        assert_eq!(
16911            f.run(&[
16912                b"ARGREP",
16913                b"a",
16914                b"-",
16915                b"+",
16916                b"MATCH",
16917                b"a",
16918                b"WITHVALUES",
16919                b"LIMIT",
16920                b"2"
16921            ]),
16922            "*2\r\n*2\r\n:0\r\n$5\r\nalpha\r\n*2\r\n:1\r\n$4\r\nbeta\r\n"
16923        );
16924        assert_eq!(
16925            f.run(&[
16926                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
16927            ]),
16928            "*1\r\n:3\r\n"
16929        );
16930    }
16931
16932    /// Everything ARGREP refuses, in the order it refuses it.
16933    #[test]
16934    fn a_grep_reports_a_broken_command_the_way_redis_does() {
16935        let mut f = Fixture::new();
16936        f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
16937        let syntax = "-ERR syntax error\r\n";
16938
16939        // The bounds are read before the plan, so a bad index beats a bad
16940        // predicate whichever way round the two are written.
16941        assert_eq!(
16942            f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
16943            "-ERR invalid array index\r\n"
16944        );
16945        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
16946        // A keyword with nothing after it, and a command that asks for nothing.
16947        assert_eq!(
16948            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
16949            syntax
16950        );
16951        assert_eq!(
16952            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
16953            syntax
16954        );
16955        assert_eq!(
16956            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
16957            syntax,
16958            "a command with no predicate in it at all"
16959        );
16960        assert_eq!(
16961            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
16962            "-ERR LIMIT must be positive\r\n"
16963        );
16964        assert_eq!(
16965            f.run(&[
16966                b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
16967            ]),
16968            "-ERR value is not an integer or out of range\r\n"
16969        );
16970        assert_eq!(
16971            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
16972            "-ERR regular expression is empty\r\n"
16973        );
16974        assert_eq!(
16975            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
16976            "-ERR invalid regular expression: Missing ')'\r\n"
16977        );
16978        assert_eq!(
16979            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
16980            "-ERR regular expression backreferences are not supported\r\n"
16981        );
16982        // The arity is minus six, so a predicate keyword with no pattern after
16983        // it is short by one and never reaches the parser.
16984        let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
16985        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
16986        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
16987    }
16988
16989    #[test]
16990    fn an_op_reduces_a_range_to_one_number() {
16991        let mut f = Fixture::new();
16992        f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
16993        assert_eq!(
16994            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
16995            "$4\r\n-0.5\r\n"
16996        );
16997        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
16998        assert_eq!(
16999            f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
17000            "$3\r\n2.5\r\n"
17001        );
17002        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
17003        assert_eq!(
17004            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
17005            ":1\r\n"
17006        );
17007        // An aggregate is written with seventeen significant digits, which is
17008        // Redis's own choice and not what a score comes back as.
17009        f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
17010        assert_eq!(
17011            f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
17012            "$19\r\n0.30000000000000004\r\n"
17013        );
17014        assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
17015        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
17016
17017        // Nothing to work with is a null, and a missing key is a null for the
17018        // aggregates and a zero for the two that count.
17019        f.run(&[b"ARSET", b"w", b"0", b"word"]);
17020        assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
17021        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
17022        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
17023
17024        assert_eq!(
17025            f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
17026            "-ERR unknown operation\r\n"
17027        );
17028        assert_eq!(
17029            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
17030            "-ERR MATCH requires a value argument\r\n"
17031        );
17032        assert_eq!(
17033            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
17034            "-ERR wrong number of arguments for 'arop' command\r\n"
17035        );
17036    }
17037
17038    #[test]
17039    fn the_info_is_a_map_and_a_missing_key_is_an_error() {
17040        let mut f = Fixture::new();
17041        assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
17042        f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
17043        let short = f.run(&[b"ARINFO", b"a"]);
17044        assert!(
17045            short.starts_with("*14\r\n"),
17046            "seven pairs on RESP2: {short}"
17047        );
17048        assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
17049        assert!(
17050            short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
17051            "{short}"
17052        );
17053        assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
17054        let full = f.run(&[b"ARINFO", b"a", b"full"]);
17055        assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
17056        // Two values one apart are held sparsely, so the dense count is zero and
17057        // the two dense averages have nothing to average.
17058        assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
17059        assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
17060        assert!(
17061            full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
17062            "{full}"
17063        );
17064        assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
17065
17066        // On RESP3 the same reply is a map and the averages are doubles.
17067        let mut g = Fixture::new();
17068        g.run(&[b"HELLO", b"3"]);
17069        g.run(&[b"ARINSERT", b"a", b"x"]);
17070        let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
17071        assert!(map.starts_with("%12\r\n"), "{map}");
17072        assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
17073        assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
17074    }
17075
17076    #[test]
17077    fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
17078        let mut f = Fixture::new();
17079        // Whole numbers up to two to the sixty second come back as integers,
17080        // and past that the digit generator takes over and uses an exponent.
17081        for (score, want) in [
17082            ("3", "3"),
17083            ("3.5", "3.5"),
17084            ("0.3", "0.3"),
17085            ("1e30", "1e+30"),
17086            ("1e19", "1e+19"),
17087            ("1e-7", "1e-7"),
17088            ("0.000001", "0.000001"),
17089            ("4611686018427387904", "4611686018427387904"),
17090            ("-0", "-0"),
17091        ] {
17092            f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
17093            assert_eq!(
17094                f.run(&[b"ZSCORE", b"z", b"m"]),
17095                format!("${}\r\n{want}\r\n", want.len()),
17096                "score {score}"
17097            );
17098        }
17099
17100        // The same bytes on RESP3, where the reply is a double rather than a
17101        // bulk string.
17102        let mut g = Fixture::new();
17103        g.run(&[b"HELLO", b"3"]);
17104        g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
17105        assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
17106        // The two float increments are not this printer. They go through
17107        // ld2string in its human mode, which is a fixed point conversion with
17108        // the trailing zeros taken off, so they never write an exponent, and
17109        // they reply with a bulk string on both protocols.
17110        assert_eq!(
17111            g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
17112            "$31\r\n1000000000000000000000000000000\r\n"
17113        );
17114        assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
17115        assert_eq!(
17116            g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
17117            "$20\r\n10000000000000000000\r\n"
17118        );
17119    }
17120
17121    // ----------------------------------------------------------------- graph
17122
17123    #[test]
17124    fn a_node_comes_back_with_the_fields_it_went_in_with() {
17125        let mut f = Fixture::new();
17126        assert_eq!(
17127            f.run(&[
17128                b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
17129            ]),
17130            ":1\r\n"
17131        );
17132        // The year comes back as the four bytes that were sent and not as a
17133        // number, because every property is text and there is nothing on the
17134        // wire that says which of `1815` and `"1815"` the client meant. The
17135        // fields are in the document's order, which is sorted by name, because
17136        // that is what makes a field lookup a binary search.
17137        assert_eq!(
17138            f.run(&[b"G.NGET", b"social", b"ada"]),
17139            "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
17140        );
17141        // A second write to the same id replaces the document and says so with
17142        // a zero, so an ingest can count what it created.
17143        assert_eq!(
17144            f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
17145            ":0\r\n"
17146        );
17147        assert_eq!(
17148            f.run(&[b"G.NGET", b"social", b"ada"]),
17149            "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
17150        );
17151        // A node with no properties is an empty map and not a null, which is
17152        // how a client tells an isolated node from one that is not there.
17153        assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
17154        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
17155        assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
17156        assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
17157
17158        // A field with no value creates nothing, because the pairs are checked
17159        // before the key is touched.
17160        assert_eq!(
17161            f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
17162            "-ERR syntax error\r\n"
17163        );
17164        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
17165
17166        // On RESP3 the same reply is a map.
17167        let mut g = Fixture::new();
17168        g.run(&[b"HELLO", b"3"]);
17169        g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
17170        assert_eq!(
17171            g.run(&[b"G.NGET", b"social", b"ada"]),
17172            "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
17173        );
17174    }
17175
17176    #[test]
17177    fn an_edge_creates_the_ends_it_needs() {
17178        let mut f = Fixture::new();
17179        assert_eq!(
17180            f.run(&[
17181                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
17182            ]),
17183            ":1\r\n"
17184        );
17185        // Neither end was written first and both are there, as empty nodes.
17186        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
17187        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
17188        assert_eq!(
17189            f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
17190            "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
17191        );
17192        assert_eq!(
17193            f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
17194            "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
17195        );
17196        // The same pair under the same label again updates the edge rather than
17197        // making a second one.
17198        assert_eq!(
17199            f.run(&[
17200                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
17201            ]),
17202            ":0\r\n"
17203        );
17204        assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
17205        // A different label between the same pair is a different edge.
17206        assert_eq!(
17207            f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
17208            ":1\r\n"
17209        );
17210        assert_eq!(
17211            f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
17212            ":1\r\n"
17213        );
17214
17215        assert_eq!(
17216            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
17217            ":1\r\n"
17218        );
17219        assert_eq!(
17220            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
17221            ":0\r\n"
17222        );
17223        // A label nothing has used, an end that is not there, and a key that is
17224        // not there are all a zero rather than an error.
17225        assert_eq!(
17226            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
17227            ":0\r\n"
17228        );
17229        assert_eq!(
17230            f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
17231            ":0\r\n"
17232        );
17233        assert_eq!(
17234            f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
17235            ":0\r\n"
17236        );
17237    }
17238
17239    /// A run is paged the way `SCAN` is paged, so a client that can walk one
17240    /// can walk the other.
17241    #[test]
17242    fn a_hop_answers_a_cursor_and_a_page() {
17243        let mut f = Fixture::new();
17244        for i in 0..25u32 {
17245            let dst = format!("n{i}");
17246            f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
17247        }
17248        // Ten without being asked, and the cursor is where to carry on from.
17249        let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
17250        assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
17251
17252        let mut seen = 0;
17253        let mut cursor = String::from("0");
17254        loop {
17255            let page = f.run(&[
17256                b"G.OUT",
17257                b"social",
17258                b"hub",
17259                b"FOLLOWS",
17260                b"COUNT",
17261                b"7",
17262                b"CURSOR",
17263                cursor.as_bytes(),
17264            ]);
17265            let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
17266            cursor = head
17267                .rsplit("\r\n")
17268                .next()
17269                .expect("the cursor line")
17270                .to_string();
17271            seen += rest
17272                .split_once("\r\n")
17273                .expect("the page length")
17274                .0
17275                .parse::<usize>()
17276                .expect("a length");
17277            if cursor == "0" {
17278                break;
17279            }
17280        }
17281        assert_eq!(seen, 25, "every neighbour once across the pages");
17282
17283        // A cursor past the end is an empty page and not an error, and so is a
17284        // key or a label that is not there.
17285        assert_eq!(
17286            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
17287            "*2\r\n$1\r\n0\r\n*0\r\n"
17288        );
17289        assert_eq!(
17290            f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
17291            "*2\r\n$1\r\n0\r\n*0\r\n"
17292        );
17293        assert_eq!(
17294            f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
17295            "*2\r\n$1\r\n0\r\n*0\r\n"
17296        );
17297        assert_eq!(
17298            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
17299            "-ERR COUNT must be a positive integer\r\n"
17300        );
17301        assert_eq!(
17302            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
17303            "-ERR syntax error\r\n"
17304        );
17305    }
17306
17307    #[test]
17308    fn a_degree_counts_one_way_or_both() {
17309        let mut f = Fixture::new();
17310        f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
17311        f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
17312        f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
17313        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
17314        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
17315        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
17316        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
17317        assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
17318        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
17319        assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
17320        assert_eq!(
17321            f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
17322            "-ERR syntax error\r\n"
17323        );
17324    }
17325
17326    /// A walk answers which nodes it can reach and not by how many routes, so a
17327    /// node two ways out is in the frontier once.
17328    #[test]
17329    fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
17330        let mut f = Fixture::new();
17331        for (src, dst) in [
17332            ("ada", "grace"),
17333            ("ada", "alan"),
17334            ("grace", "edsger"),
17335            ("alan", "edsger"),
17336            ("edsger", "barbara"),
17337        ] {
17338            f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
17339        }
17340        // Two hops without being asked, the start left out, and edsger once
17341        // even though both of the first hop's nodes point at it.
17342        assert_eq!(
17343            f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
17344            "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
17345        );
17346        assert_eq!(
17347            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
17348            "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
17349        );
17350        let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
17351        assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
17352        assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
17353        // COUNT stops the walk rather than trimming what it found.
17354        assert_eq!(
17355            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
17356            "*1\r\n$5\r\ngrace\r\n"
17357        );
17358        // A node nothing leaves is an empty array and not an error.
17359        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
17360        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
17361        assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
17362        assert_eq!(
17363            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
17364            "-ERR DEPTH must be a positive integer\r\n"
17365        );
17366        assert_eq!(
17367            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
17368            "-ERR syntax error\r\n"
17369        );
17370    }
17371
17372    /// The two sided search, which is the whole reason `G.PATH` is a command
17373    /// and not something a client builds out of `G.OUT`.
17374    #[test]
17375    fn a_path_is_the_shortest_one_and_goes_over_any_label() {
17376        let mut f = Fixture::new();
17377        // A chain of six, and a shortcut that makes a shorter way round under a
17378        // second label so the search has to take either kind of hop.
17379        for i in 0..6u32 {
17380            let src = format!("n{i}");
17381            let dst = format!("n{}", i + 1);
17382            f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
17383        }
17384        assert_eq!(
17385            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
17386            "*7\r\n$2\r\nn0\r\n$2\r\nn1\r\n$2\r\nn2\r\n$2\r\nn3\r\n$2\r\nn4\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
17387        );
17388        f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
17389        assert_eq!(
17390            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
17391            "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
17392        );
17393        // A node to itself is a path of one, and a depth too short to reach is
17394        // no path at all.
17395        assert_eq!(
17396            f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
17397            "*1\r\n$2\r\nn2\r\n"
17398        );
17399        assert_eq!(
17400            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
17401            "*0\r\n"
17402        );
17403        // Direction counts: the chain only goes one way.
17404        assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
17405        // An unreachable node, a node that is not there, and a key that is not
17406        // there are the same empty answer.
17407        f.run(&[b"G.NADD", b"road", b"island"]);
17408        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
17409        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
17410        assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
17411        assert_eq!(
17412            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
17413            "-ERR syntax error\r\n"
17414        );
17415    }
17416
17417    /// The point of the escape in the record tag: the keyspace owns a graph key
17418    /// the way it owns every other key, and none of these commands know a graph
17419    /// exists.
17420    #[test]
17421    fn the_keyspace_sees_a_graph_key_like_any_other() {
17422        let mut f = Fixture::new();
17423        f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
17424        assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
17425        assert_eq!(
17426            f.run(&[b"OBJECT", b"ENCODING", b"social"]),
17427            "$9\r\nadjacency\r\n"
17428        );
17429        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
17430        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
17431        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
17432        // A graph is counted against the server the way every other body is,
17433        // which is what `maxmemory` will read when this key is a million nodes.
17434        // There is no `MEMORY USAGE` command yet, so this asks the server.
17435        let held = f.server.memory_bytes();
17436        for i in 0..200u32 {
17437            let dst = format!("n{i}");
17438            f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
17439        }
17440        assert!(
17441            f.server.memory_bytes() > held,
17442            "two hundred edges cost something: {held} then {}",
17443            f.server.memory_bytes()
17444        );
17445        f.run(&[b"DEL", b"big"]);
17446
17447        // An expiry, then a rename, then a move to another database, all of
17448        // which are the keyspace moving a record it cannot look inside.
17449        assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
17450        assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
17451        assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
17452        assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
17453        assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
17454        f.run(&[b"SELECT", b"1"]);
17455        assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
17456
17457        assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
17458        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17459        f.run(&[b"G.NADD", b"g", b"n"]);
17460        assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
17461        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17462    }
17463
17464    /// Neither `COPY` nor `DUMP` has a byte shape for a graph, so both say so
17465    /// rather than answering the way they answer for a key that is not there.
17466    #[test]
17467    fn a_graph_cannot_be_copied_or_dumped() {
17468        let mut f = Fixture::new();
17469        f.run(&[b"G.NADD", b"social", b"ada"]);
17470        assert_eq!(
17471            f.run(&[b"COPY", b"social", b"other"]),
17472            "-ERR COPY is not supported for a graph\r\n"
17473        );
17474        assert_eq!(
17475            f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
17476            "-ERR COPY is not supported for a graph\r\n"
17477        );
17478        assert_eq!(
17479            f.run(&[b"DUMP", b"social"]),
17480            "-ERR DUMP is not supported for a graph\r\n"
17481        );
17482        // A refused copy leaves both keys exactly as they were.
17483        assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
17484    }
17485
17486    /// A graph key is a key, so the commands for the other types refuse it and
17487    /// the graph commands refuse theirs.
17488    #[test]
17489    fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
17490        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
17491        let mut f = Fixture::new();
17492        f.run(&[b"G.NADD", b"social", b"ada"]);
17493        assert_eq!(f.run(&[b"GET", b"social"]), wrong);
17494        assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
17495        assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
17496
17497        f.run(&[b"SET", b"str", b"v"]);
17498        for cmd in [
17499            vec![b"G.NADD".as_ref(), b"str", b"n"],
17500            vec![b"G.NGET".as_ref(), b"str", b"n"],
17501            vec![b"G.NDEL".as_ref(), b"str", b"n"],
17502            vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
17503            vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
17504            vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
17505            vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
17506            vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
17507            vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
17508            vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
17509        ] {
17510            assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
17511        }
17512    }
17513
17514    /// Every other collection here takes its key with it when its last member
17515    /// goes, and a graph is no different.
17516    #[test]
17517    fn a_graph_goes_when_its_last_node_does() {
17518        let mut f = Fixture::new();
17519        f.run(&[
17520            b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
17521        ]);
17522        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
17523        // The node and the edges that hung off it are both gone.
17524        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
17525        assert_eq!(
17526            f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
17527            ":0\r\n"
17528        );
17529        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
17530        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
17531
17532        assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
17533        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
17534        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
17535        assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
17536
17537        // The id the removed node had is not handed out again, so a client
17538        // holding an id from an earlier reply cannot have it mean another node.
17539        f.run(&[b"G.NADD", b"social", b"first"]);
17540        f.run(&[b"G.NADD", b"social", b"second"]);
17541        f.run(&[b"G.NDEL", b"social", b"first"]);
17542        f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
17543        assert_eq!(
17544            f.run(&[b"G.OUT", b"social", b"third", b"F"]),
17545            "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
17546        );
17547    }
17548
17549    // ------------------------------------------------------------------ json
17550
17551    /// The two path syntaxes answer different shapes, which is the thing a
17552    /// client is most likely to be broken by and so the thing to pin first.
17553    #[test]
17554    fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
17555        let mut f = Fixture::new();
17556        let doc = br#"{"a":1,"b":{"c":true}}"#;
17557        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
17558        // No path at all is the legacy root and not `$`, so the document comes
17559        // back as itself rather than wrapped.
17560        assert_eq!(
17561            f.run(&[b"JSON.GET", b"doc"]),
17562            bulk(r#"{"a":1,"b":{"c":true}}"#)
17563        );
17564        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
17565        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
17566        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
17567        // A path that matched nothing is an empty set on one syntax and an
17568        // error on the other, and the error does not quote the path.
17569        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
17570        assert_eq!(
17571            f.run(&[b"JSON.GET", b"doc", b".nope"]),
17572            "-ERR Path does not exist\r\n"
17573        );
17574        assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
17575        // The key is a document to the rest of the keyspace, under the name
17576        // RedisJSON registers, and every generic command works on it.
17577        assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
17578        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
17579        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
17580        assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
17581        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
17582    }
17583
17584    /// The two error lines RedisJSON sends without a prefix in front of them.
17585    ///
17586    /// Every other error this server writes starts `ERR` or `WRONGTYPE`. These
17587    /// two do not, on a real server, and a differential harness compares the
17588    /// whole line.
17589    #[test]
17590    fn the_two_json_errors_that_carry_no_prefix() {
17591        let mut f = Fixture::new();
17592        f.run(&[b"SET", b"plain", b"x"]);
17593        let wrong = "-Existing key has wrong Redis type\r\n";
17594        assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
17595        assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
17596        assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
17597        assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
17598        assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
17599
17600        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
17601        // A wildcard that matched something writes to all of it. A wildcard
17602        // that matched nothing would have to invent a place, and that is the
17603        // other unprefixed line.
17604        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
17605        assert_eq!(
17606            f.run(&[b"JSON.GET", b"doc"]),
17607            bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
17608        );
17609        assert_eq!(
17610            f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
17611            "-Err wrong static path\r\n"
17612        );
17613    }
17614
17615    /// What `JSON.SET` does with a path that named nowhere.
17616    #[test]
17617    fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
17618        let mut f = Fixture::new();
17619        // A key that is not there can only be written whole.
17620        assert_eq!(
17621            f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
17622            "-ERR new objects must be created at the root\r\n"
17623        );
17624        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17625        // The root check comes before NX and XX, which is the order a real
17626        // server checks them in.
17627        assert_eq!(
17628            f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
17629            "-ERR new objects must be created at the root\r\n"
17630        );
17631        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
17632        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
17633
17634        f.run(&[
17635            b"JSON.SET",
17636            b"doc",
17637            b"$",
17638            br#"{"o":{},"arr":[1,2],"s":"x"}"#,
17639        ]);
17640        // One step past a container that is there is a place to write.
17641        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
17642        // One step past something that is not, or past something that is not an
17643        // object, is not an error and is not a write either.
17644        assert_eq!(
17645            f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
17646            "$-1\r\n"
17647        );
17648        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
17649        // An index past the end does not append. JSON.ARRAPPEND appends.
17650        assert_eq!(
17651            f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
17652            "-ERR array index out of range\r\n"
17653        );
17654        assert_eq!(
17655            f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
17656            "-ERR array index out of range\r\n"
17657        );
17658        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
17659        // NX on a path that is there and XX on a path that is not are both a
17660        // nil and neither changes anything.
17661        assert_eq!(
17662            f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
17663            "$-1\r\n"
17664        );
17665        assert_eq!(
17666            f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
17667            "$-1\r\n"
17668        );
17669        assert_eq!(
17670            f.run(&[b"JSON.GET", b"doc"]),
17671            bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
17672        );
17673        // Text that is not JSON is refused before the key is touched. The
17674        // line has no `ERR` in front of it, which is this command's and not
17675        // every command's, and is in D-37.
17676        assert!(
17677            f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
17678                .starts_with("-this is not the start of a value")
17679        );
17680        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
17681    }
17682
17683    /// `JSON.DEL`, `JSON.TYPE`, `JSON.TOGGLE` and `JSON.CLEAR`, each of which
17684    /// answers a count or a word rather than text.
17685    #[test]
17686    fn the_json_commands_that_do_not_answer_text() {
17687        let mut f = Fixture::new();
17688        let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
17689        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17690
17691        assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
17692        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
17693        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
17694        assert_eq!(
17695            f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
17696            format!("*1\r\n{}", bulk("integer"))
17697        );
17698        // The one place a legacy path that matched nothing is a nil rather than
17699        // an error, which lines up with a key that is not there.
17700        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
17701        assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
17702
17703        // A boolean flips and answers the value it now has, as an integer on
17704        // one syntax and as the word on the other.
17705        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
17706        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
17707        // Something that is not a boolean is a hole on one syntax and one
17708        // sentence covering both cases on the other.
17709        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
17710        assert_eq!(
17711            f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
17712            "-ERR Path does not exist or not a bool\r\n"
17713        );
17714        assert_eq!(
17715            f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
17716            "-ERR Path does not exist or not a bool\r\n"
17717        );
17718        assert_eq!(
17719            f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
17720            "-ERR could not perform this operation on a key that doesn't exist\r\n"
17721        );
17722
17723        // Clearing empties containers and zeroes numbers and leaves everything
17724        // else alone, and counts only what it changed.
17725        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
17726        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
17727        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
17728        assert_eq!(
17729            f.run(&[b"JSON.GET", b"doc"]),
17730            bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
17731        );
17732
17733        // Deleting counts what it removed, and deleting the root is deleting
17734        // the key.
17735        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
17736        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
17737        // Deleting the last member of the root container deletes the key, the
17738        // same way popping the last element off a list does. It is a rule about
17739        // deleting and not about shape: a document written as an empty object
17740        // by JSON.SET stays, because nothing was removed from it.
17741        assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
17742        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
17743        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
17744        assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
17745        assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
17746        assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
17747        assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
17748        assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
17749    }
17750
17751    /// `JSON.GET` with more than one path, and with a layout.
17752    ///
17753    /// The wrapper the reply is built in is laid out too, so what a path
17754    /// matched starts one level in for a single JSONPath and two for one of
17755    /// several, and getting that wrong is the kind of thing only a byte for
17756    /// byte comparison catches.
17757    #[test]
17758    fn json_get_lays_out_the_wrapper_it_builds() {
17759        let mut f = Fixture::new();
17760        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
17761
17762        assert_eq!(
17763            f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
17764            bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
17765        );
17766        // Legacy paths are not wrapped, even when there are several of them.
17767        assert_eq!(
17768            f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
17769            bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
17770        );
17771        let fmt: &[&[u8]] = &[b"INDENT", b"  ", b"NEWLINE", b"\n", b"SPACE", b" "];
17772        let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
17773        one.extend_from_slice(fmt);
17774        one.push(b"$.b");
17775        assert_eq!(
17776            f.run(&one),
17777            bulk("[\n  [\n    1,\n    {\n      \"c\": 2\n    }\n  ]\n]")
17778        );
17779        let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
17780        two.extend_from_slice(fmt);
17781        two.push(b"$.a");
17782        two.push(b"$.nope");
17783        assert_eq!(
17784            f.run(&two),
17785            bulk("{\n  \"$.a\": [\n    1\n  ],\n  \"$.nope\": []\n}")
17786        );
17787        // The options are read before the paths and in any order, and a
17788        // document with nothing to lay out is the same either way.
17789        let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
17790        root.push(b".a");
17791        assert_eq!(f.run(&root), bulk("1"));
17792    }
17793
17794    /// `JSON.MGET`, which is the only command here that reads more than one key
17795    /// and so the only one whose answer has holes in it.
17796    #[test]
17797    fn json_mget_answers_once_per_key_whatever_is_under_them() {
17798        let mut f = Fixture::new();
17799        f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
17800        f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
17801        f.run(&[b"SET", b"plain", b"x"]);
17802        assert_eq!(
17803            f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
17804            format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
17805        );
17806        // A key that is not there and a key holding something else are both a
17807        // hole rather than an error, the way MGET treats a hash.
17808        assert_eq!(
17809            f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
17810            format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
17811        );
17812        // A legacy path that matched nothing is a hole too, because one bad
17813        // answer should not lose the others.
17814        assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
17815    }
17816
17817    /// The four commands that ask how big something is, and the four different
17818    /// sets of answers they give for the same three failures.
17819    ///
17820    /// There is no pattern in this and there is no reading it off the
17821    /// documentation either. It was read off a running RedisJSON one line at a
17822    /// time, and it is written down here because the error text is what a client
17823    /// library branches on.
17824    #[test]
17825    fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
17826        let mut f = Fixture::new();
17827        let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
17828        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
17829
17830        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
17831        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
17832        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
17833        assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
17834        assert_eq!(
17835            f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
17836            format!("*2\r\n{}{}", bulk("x"), bulk("y"))
17837        );
17838        // A JSONPath answers one entry per match and a hole for a match of the
17839        // wrong kind, which is the one shape all four agree on.
17840        assert_eq!(
17841            f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
17842            "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
17843        );
17844
17845        // A legacy path that matched nothing. Two of them are an error and two
17846        // of them are a nil, and the two errors do not use the same sentence.
17847        assert_eq!(
17848            f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
17849            "-ERR Path does not exist\r\n"
17850        );
17851        assert_eq!(
17852            f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
17853            "-ERR Path does not exist\r\n"
17854        );
17855        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
17856        // A nil bulk and not an empty array, even though the answer would have
17857        // been an array, which is what RedisJSON sends here too.
17858        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
17859        // The JSONPath spelling of the same question is an empty array, since
17860        // no match is not a failure on that syntax.
17861        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
17862
17863        // A legacy path that matched the wrong kind of value. Now two of them
17864        // are an ERR and two of them are a WRONGTYPE, and it is not the same
17865        // two.
17866        assert_eq!(
17867            f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
17868            "-ERR Path does not exist or not an array\r\n"
17869        );
17870        assert_eq!(
17871            f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
17872            "-ERR Path does not exist or not an object\r\n"
17873        );
17874        assert_eq!(
17875            f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
17876            "-WRONGTYPE wrong type of path value - expected object\r\n"
17877        );
17878        assert_eq!(
17879            f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
17880            "-WRONGTYPE wrong type of path value - expected string\r\n"
17881        );
17882
17883        // A key that is not there, where the two syntaxes swap over: the legacy
17884        // path is the quiet answer and the JSONPath is the error.
17885        assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
17886        assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
17887        assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
17888        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
17889        assert_eq!(
17890            f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
17891            "-ERR could not perform this operation on a key that doesn't exist\r\n"
17892        );
17893        // Except this one, which answers about the path instead.
17894        assert_eq!(
17895            f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
17896            "-ERR Path does not exist or not an object\r\n"
17897        );
17898    }
17899
17900    /// `JSON.ARRAPPEND`, `JSON.ARRINSERT`, `JSON.ARRTRIM` and `JSON.ARRPOP`.
17901    ///
17902    /// The four of them share one error line for a path that named something
17903    /// that is not an array, and they disagree about what an index outside the
17904    /// array means: insert refuses it and the other two clamp.
17905    #[test]
17906    fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
17907        let mut f = Fixture::new();
17908        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
17909
17910        assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
17911        assert_eq!(
17912            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
17913            "*1\r\n:6\r\n"
17914        );
17915        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
17916
17917        // A negative index counts back from the end, and the end itself is a
17918        // place to insert at, so an insert at the length is an append.
17919        assert_eq!(
17920            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
17921            ":7\r\n"
17922        );
17923        assert_eq!(
17924            f.run(&[b"JSON.GET", b"doc", b".a"]),
17925            bulk("[1,2,3,4,5,0,6]")
17926        );
17927        assert_eq!(
17928            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
17929            ":8\r\n"
17930        );
17931        // One past the end is not, and neither is one before the front.
17932        assert_eq!(
17933            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
17934            "-ERR index out of bounds\r\n"
17935        );
17936        assert_eq!(
17937            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
17938            "-ERR index out of bounds\r\n"
17939        );
17940
17941        // Trim takes both ends inclusive and clamps both of them, so a start
17942        // past the end leaves an empty array rather than an error.
17943        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
17944        assert_eq!(
17945            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
17946            ":3\r\n"
17947        );
17948        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
17949        assert_eq!(
17950            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
17951            ":2\r\n"
17952        );
17953        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
17954        assert_eq!(
17955            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
17956            ":0\r\n"
17957        );
17958        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
17959
17960        // Pop clamps as well, its default is the last element, and an empty
17961        // array pops a nil rather than failing.
17962        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
17963        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
17964        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
17965        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
17966        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
17967
17968        // One sentence covers a path that matched nothing and a path that
17969        // matched the wrong kind of value, for all four of them.
17970        for call in [
17971            &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
17972            &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
17973            &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
17974            &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
17975        ] {
17976            for path in [&b".n"[..], &b".nope"[..]] {
17977                let args: Vec<&[u8]> = call
17978                    .iter()
17979                    .map(|a| if *a == b"PATH" { path } else { *a })
17980                    .collect();
17981                assert_eq!(
17982                    f.run(&args),
17983                    "-ERR Path does not exist or not an array\r\n",
17984                    "{} {}",
17985                    String::from_utf8_lossy(call[0]),
17986                    String::from_utf8_lossy(path)
17987                );
17988            }
17989        }
17990
17991        // A key that is not there is the same sentence for all four, on either
17992        // syntax, and it is about the key and not about the path.
17993        assert_eq!(
17994            f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
17995            "-ERR could not perform this operation on a key that doesn't exist\r\n"
17996        );
17997        assert_eq!(
17998            f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
17999            "-ERR could not perform this operation on a key that doesn't exist\r\n"
18000        );
18001
18002        // The values are parsed before the key is touched, so text that is not
18003        // JSON leaves the document alone.
18004        // Text that is not JSON is refused before the key is touched, and
18005        // the line has no `ERR` in front of it, which is D-37.
18006        assert!(
18007            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
18008                .starts_with("-this is not the start of a value")
18009        );
18010        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
18011    }
18012
18013    /// `JSON.ARRINSERT` refuses the whole command when any one of the arrays a
18014    /// path matched cannot take the index, which is D-36.
18015    ///
18016    /// RedisJSON walks the matches, inserts into each one it can, and returns
18017    /// the error on the first one it cannot, leaving the earlier inserts in the
18018    /// document. A write here is one list of edits applied together, so either
18019    /// all of them happen or none of them do.
18020    #[test]
18021    fn json_arrinsert_is_all_or_nothing_across_the_matches() {
18022        let mut f = Fixture::new();
18023        let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
18024        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18025        assert_eq!(
18026            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
18027            "-ERR index out of bounds\r\n"
18028        );
18029        assert_eq!(
18030            f.run(&[b"JSON.GET", b"doc"]),
18031            bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
18032        );
18033        // Every match can take the index, so every match gets it.
18034        assert_eq!(
18035            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
18036            "*3\r\n:4\r\n:3\r\n:2\r\n"
18037        );
18038        assert_eq!(
18039            f.run(&[b"JSON.GET", b"doc"]),
18040            bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
18041        );
18042    }
18043
18044    /// `JSON.ARRINDEX`, whose stop is exclusive and whose start clamps to the
18045    /// last element rather than to one past it.
18046    ///
18047    /// Both of those read like mistakes and both are what RedisJSON does. The
18048    /// start is the one that bites: a start of five into an array of four still
18049    /// looks at the fourth, so a search that should have run out of array comes
18050    /// back with an answer.
18051    #[test]
18052    fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
18053        let mut f = Fixture::new();
18054        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
18055
18056        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
18057        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
18058        assert_eq!(
18059            f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
18060            "*1\r\n:1\r\n"
18061        );
18062
18063        // Zero as the stop means the end rather than the front, so leaving it
18064        // off and passing it are the same thing.
18065        assert_eq!(
18066            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
18067            ":3\r\n"
18068        );
18069        // The stop is exclusive, so a stop of three does not look at index
18070        // three.
18071        assert_eq!(
18072            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
18073            ":-1\r\n"
18074        );
18075
18076        // The start clamps to the last element in both directions, which is why
18077        // a start of four, five or minus one all find the 1 at index three.
18078        for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
18079            assert_eq!(
18080                f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
18081                ":3\r\n",
18082                "{}",
18083                String::from_utf8_lossy(start)
18084            );
18085        }
18086        assert_eq!(
18087            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
18088            ":0\r\n"
18089        );
18090        // An empty array is the one case that comes back with nothing, since
18091        // the stop is zero and the loop never starts.
18092        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
18093        assert_eq!(
18094            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
18095            ":-1\r\n"
18096        );
18097
18098        // The comparison is structural rather than one of the encoded bytes,
18099        // because an object in a stored document holds its keys as intern table
18100        // ids where one parsed off the wire holds them as bytes.
18101        f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
18102        assert_eq!(
18103            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
18104            ":0\r\n"
18105        );
18106        assert_eq!(
18107            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
18108            ":1\r\n"
18109        );
18110        assert_eq!(
18111            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
18112            ":-1\r\n"
18113        );
18114
18115        // Its errors are a third set again: a missing legacy path is the short
18116        // sentence, the wrong kind of value is a WRONGTYPE, and a key that is
18117        // not there is about the path on either syntax.
18118        assert_eq!(
18119            f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
18120            "-ERR Path does not exist\r\n"
18121        );
18122        assert_eq!(
18123            f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
18124            "-WRONGTYPE wrong type of path value - expected array\r\n"
18125        );
18126        assert_eq!(
18127            f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
18128            "-ERR Path does not exist\r\n"
18129        );
18130        assert_eq!(
18131            f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
18132            "-ERR Path does not exist\r\n"
18133        );
18134    }
18135
18136    /// The number family answers text and keeps an integer an integer until
18137    /// something in the sum is not one.
18138    #[test]
18139    fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
18140        let mut f = Fixture::new();
18141        let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
18142        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18143
18144        // A legacy path answers the new value as JSON text in a bulk string,
18145        // not as a number, which is the shape all three of them use.
18146        assert_eq!(
18147            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
18148            bulk("9").as_str()
18149        );
18150        // A JSONPath answers a bulk string holding a JSON array.
18151        assert_eq!(
18152            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
18153            bulk("[11]").as_str()
18154        );
18155        // Two integers stay an integer and a double anywhere in it makes the
18156        // answer a double, which the document then holds.
18157        assert_eq!(
18158            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
18159            bulk("13.0").as_str()
18160        );
18161        assert_eq!(
18162            f.run(&[b"JSON.TYPE", b"doc", b".i"]),
18163            bulk("number").as_str()
18164        );
18165        assert_eq!(
18166            f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
18167            bulk("3.0").as_str()
18168        );
18169        assert_eq!(
18170            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
18171            bulk("-8").as_str()
18172        );
18173        // A power of a half is a square root, and the square root of a negative
18174        // number is the error that says the answer is not a number.
18175        f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
18176        assert_eq!(
18177            f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
18178            bulk("1.224744871391589").as_str()
18179        );
18180        assert_eq!(
18181            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
18182            "-ERR result is not a number\r\n"
18183        );
18184        // An integer answer that does not fit is refused rather than promoted,
18185        // and a negative exponent lands in the same error because there is no
18186        // integer answer to two to the minus one.
18187        f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
18188        assert_eq!(
18189            f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
18190            "-ERR numeric overflow\r\n"
18191        );
18192        f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
18193        assert_eq!(
18194            f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
18195            "-ERR numeric overflow\r\n"
18196        );
18197        // A double that leaves the finite numbers is the other error.
18198        f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
18199        assert_eq!(
18200            f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
18201            "-ERR result is not a number\r\n"
18202        );
18203
18204        // A match that is not a number is a null inside the array on a
18205        // JSONPath, and a legacy path that found no number at all is the error
18206        // with the module's own typo in it.
18207        assert_eq!(
18208            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
18209            bulk("[null]").as_str()
18210        );
18211        assert_eq!(
18212            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
18213            bulk("[]").as_str()
18214        );
18215        assert_eq!(
18216            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
18217            "-ERR Path does not exist or does not contains a number\r\n"
18218        );
18219        assert_eq!(
18220            f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
18221            "-ERR Path does not exist or does not contains a number\r\n"
18222        );
18223        // The operand is JSON and has to be a number. Valid JSON that is not
18224        // one is a line of its own, and it goes out without a prefix.
18225        assert_eq!(
18226            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
18227            "-bad input number\r\n"
18228        );
18229        assert_eq!(
18230            f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
18231            "-ERR could not perform this operation on a key that doesn't exist\r\n"
18232        );
18233        assert_eq!(
18234            f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
18235            "-ERR could not perform this operation on a key that doesn't exist\r\n"
18236        );
18237    }
18238
18239    /// `JSON.STRAPPEND` puts its path in the middle and makes it optional,
18240    /// which nothing else in the group does.
18241    #[test]
18242    fn json_strappend_reads_its_shape_off_the_argument_count() {
18243        let mut f = Fixture::new();
18244        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
18245
18246        assert_eq!(
18247            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
18248            ":3\r\n"
18249        );
18250        assert_eq!(
18251            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
18252            "*1\r\n:4\r\n"
18253        );
18254        // The length is in bytes and not in characters, so one two byte letter
18255        // takes it up by two.
18256        assert_eq!(
18257            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
18258            ":6\r\n"
18259        );
18260        // Three arguments means the value is the last one and the path is the
18261        // root, so this appends to a document that is a string on its own.
18262        f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
18263        assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
18264        assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
18265
18266        // The value is JSON and has to be a JSON string. A number is a
18267        // WRONGTYPE about a path value even though it was the value that was
18268        // wrong, which is the module's wording and not a slip here.
18269        assert_eq!(
18270            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
18271            "-WRONGTYPE wrong type of path value - expected string\r\n"
18272        );
18273        assert_eq!(
18274            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
18275            "*1\r\n$-1\r\n"
18276        );
18277        assert_eq!(
18278            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
18279            "-ERR Path does not exist or not a string\r\n"
18280        );
18281        assert_eq!(
18282            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
18283            "*0\r\n"
18284        );
18285        assert_eq!(
18286            f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
18287            "-ERR could not perform this operation on a key that doesn't exist\r\n"
18288        );
18289    }
18290
18291    /// A legacy path can match more than one value, and which of them the one
18292    /// answer comes from is not the same choice twice.
18293    #[test]
18294    fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
18295        let mut f = Fixture::new();
18296        // Three arrays of one, two and three elements, which tells the first
18297        // match and the last match apart in a single command.
18298        let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
18299
18300        f.run(&[b"JSON.SET", b"doc", b"$", three]);
18301        assert_eq!(
18302            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
18303            ":4\r\n"
18304        );
18305        f.run(&[b"JSON.SET", b"doc", b"$", three]);
18306        assert_eq!(
18307            f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
18308            ":2\r\n"
18309        );
18310        f.run(&[b"JSON.SET", b"doc", b"$", three]);
18311        assert_eq!(
18312            f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
18313            ":1\r\n"
18314        );
18315        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
18316        assert_eq!(
18317            f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
18318            bulk("1").as_str()
18319        );
18320        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
18321        assert_eq!(
18322            f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
18323            bulk("13").as_str()
18324        );
18325        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
18326        assert_eq!(
18327            f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
18328            ":4\r\n"
18329        );
18330        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
18331        assert_eq!(
18332            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
18333            bulk("false").as_str()
18334        );
18335        // Every one of them wrote to all three matches, whichever one it chose
18336        // to answer about.
18337        assert_eq!(
18338            f.run(&[b"JSON.GET", b"doc", b".a"]),
18339            bulk("[false,true,false]").as_str()
18340        );
18341
18342        // A match of the wrong kind is skipped rather than being the answer, so
18343        // a path that found a string and then two arrays still answers.
18344        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
18345        assert_eq!(
18346            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
18347            ":3\r\n"
18348        );
18349        assert_eq!(
18350            f.run(&[b"JSON.GET", b"doc", b".a"]),
18351            bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
18352        );
18353        // Nothing of the right kind anywhere is the error, and that is the only
18354        // case that is.
18355        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
18356        assert_eq!(
18357            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
18358            "-ERR Path does not exist or not an array\r\n"
18359        );
18360        assert_eq!(
18361            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
18362            "-ERR Path does not exist or not a bool\r\n"
18363        );
18364        // The one array that was there and had nothing in it is an answer and
18365        // not a skip, so the pop answers about it rather than about the array
18366        // after it.
18367        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
18368        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
18369        assert_eq!(
18370            f.run(&[b"JSON.GET", b"doc", b".a"]),
18371            bulk("[[],[2]]").as_str()
18372        );
18373    }
18374
18375    /// A path that matched a value and something inside that value writes to
18376    /// both, which is what `$..` and a nested wildcard are for.
18377    #[test]
18378    fn a_write_reaches_a_match_that_sits_inside_another_match() {
18379        let mut f = Fixture::new();
18380        let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
18381
18382        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
18383        assert_eq!(
18384            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
18385            "*3\r\n:3\r\n:2\r\n:3\r\n"
18386        );
18387        assert_eq!(
18388            f.run(&[b"JSON.GET", b"doc", b"$"]),
18389            bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
18390        );
18391
18392        // The same for a trim, where the outer array keeps the two elements the
18393        // inner writes landed in.
18394        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
18395        assert_eq!(
18396            f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
18397            "*3\r\n:1\r\n:1\r\n:1\r\n"
18398        );
18399        assert_eq!(
18400            f.run(&[b"JSON.GET", b"doc", b"$"]),
18401            bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
18402        );
18403
18404        // And for a number, where the first match is the object the outer array
18405        // holds and only the two inside it are numbers.
18406        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
18407        assert_eq!(
18408            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
18409            bulk("[null,8,8]").as_str()
18410        );
18411    }
18412
18413    /// The value a write is given is looked at only once the path has found
18414    /// something of the right kind to use it on.
18415    #[test]
18416    fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
18417        let mut f = Fixture::new();
18418        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
18419
18420        // A string is not a number, so the path answers first and the `"x"` is
18421        // never looked at. Same for the value that is not JSON at all.
18422        assert_eq!(
18423            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
18424            bulk("[null]").as_str()
18425        );
18426        assert_eq!(
18427            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
18428            bulk("[null]").as_str()
18429        );
18430        assert_eq!(
18431            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
18432            bulk("[]").as_str()
18433        );
18434        assert_eq!(
18435            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
18436            "-ERR Path does not exist or does not contains a number\r\n"
18437        );
18438        // A number match anywhere and the value is looked at after all.
18439        assert_eq!(
18440            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
18441            "-bad input number\r\n"
18442        );
18443
18444        // JSON.STRAPPEND follows the same order with its own two answers.
18445        assert_eq!(
18446            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
18447            "*1\r\n$-1\r\n"
18448        );
18449        assert_eq!(
18450            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
18451            "-ERR Path does not exist or not a string\r\n"
18452        );
18453        assert_eq!(
18454            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
18455            "-WRONGTYPE wrong type of path value - expected string\r\n"
18456        );
18457
18458        // A key that is not there still comes before either of them.
18459        assert_eq!(
18460            f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
18461            "-ERR could not perform this operation on a key that doesn't exist\r\n"
18462        );
18463        assert_eq!(
18464            f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
18465            "-ERR could not perform this operation on a key that doesn't exist\r\n"
18466        );
18467    }
18468
18469    /// RFC 7386 in one test: a null deletes, everything else merges, and a
18470    /// patch that is not an object replaces what it lands on.
18471    #[test]
18472    fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
18473        let mut f = Fixture::new();
18474
18475        // A key that is not there is created at the root, nulls and all,
18476        // because a deletion with nothing to delete is still what the client
18477        // sent.
18478        assert_eq!(
18479            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
18480            "+OK\r\n"
18481        );
18482        assert_eq!(
18483            f.run(&[b"JSON.GET", b"doc", b"$"]),
18484            bulk(r#"[{"x":null,"y":1}]"#).as_str()
18485        );
18486
18487        // Onto something that is there, a null deletes the member of that name
18488        // and the rest is merged one level at a time.
18489        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
18490        assert_eq!(
18491            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
18492            "+OK\r\n"
18493        );
18494        assert_eq!(
18495            f.run(&[b"JSON.GET", b"doc", b"$"]),
18496            bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
18497        );
18498
18499        // A patch that is not an object replaces what it is merged onto.
18500        assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
18501        assert_eq!(
18502            f.run(&[b"JSON.GET", b"doc", b"$"]),
18503            bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
18504        );
18505
18506        // A patch object onto a value that is not an object starts from an
18507        // empty object, so this time the null has nothing to delete and is
18508        // dropped rather than stored.
18509        assert_eq!(
18510            f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
18511            "+OK\r\n"
18512        );
18513        assert_eq!(
18514            f.run(&[b"JSON.GET", b"doc", b"$"]),
18515            bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
18516        );
18517
18518        // A member one level past the end of the document is created and keeps
18519        // its nulls, two levels past it is a write that did not happen, and a
18520        // path that would have to invent where it goes is the unprefixed line.
18521        assert_eq!(
18522            f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
18523            "+OK\r\n"
18524        );
18525        assert_eq!(
18526            f.run(&[b"JSON.GET", b"doc", b"$.new"]),
18527            bulk(r#"[{"z":null}]"#).as_str()
18528        );
18529        assert_eq!(
18530            f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
18531            "$-1\r\n"
18532        );
18533        assert_eq!(
18534            f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
18535            "-Err wrong static path\r\n"
18536        );
18537
18538        // A wildcard merges every match.
18539        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
18540        assert_eq!(
18541            f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
18542            "+OK\r\n"
18543        );
18544        assert_eq!(
18545            f.run(&[b"JSON.GET", b"doc", b"$"]),
18546            bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
18547        );
18548
18549        // The three ways to get it wrong.
18550        assert_eq!(
18551            f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
18552            "-ERR syntax error\r\n"
18553        );
18554        assert_eq!(
18555            f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
18556            "-ERR new objects must be created at the root\r\n"
18557        );
18558        f.run(&[b"SET", b"str", b"x"]);
18559        assert_eq!(
18560            f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
18561            "-Existing key has wrong Redis type\r\n"
18562        );
18563    }
18564
18565    /// A descent is the one path that matches a value and something inside that
18566    /// same value, and the inner merge has to survive the outer one.
18567    #[test]
18568    fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
18569        let mut f = Fixture::new();
18570        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
18571        assert_eq!(
18572            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
18573            "+OK\r\n"
18574        );
18575        // `a`, `a.b`, `c` and `c[0]` all match. `a.b` is merged first and `a` is
18576        // merged onto the result, so the `{"m":1}` written into `a.b` is still
18577        // there. Doing it the other way round would leave `{"a":{"b":1,"m":1}}`.
18578        assert_eq!(
18579            f.run(&[b"JSON.GET", b"doc", b"$"]),
18580            bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
18581        );
18582
18583        // A deletion down the same path, which is the case where the inner
18584        // merge empties the object the outer one then copies.
18585        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
18586        assert_eq!(
18587            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
18588            "+OK\r\n"
18589        );
18590        assert_eq!(
18591            f.run(&[b"JSON.GET", b"doc", b"$"]),
18592            bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
18593        );
18594    }
18595
18596    /// A filter is a selector like any other, so every command that takes a path
18597    /// takes one, reads and writes alike.
18598    #[test]
18599    fn a_filter_path_reads_and_writes_the_members_it_keeps() {
18600        let mut f = Fixture::new();
18601        let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
18602        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18603
18604        assert_eq!(
18605            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
18606            bulk(r#"["a","c"]"#).as_str()
18607        );
18608        // `$` inside the expression is the document, so a member can be measured
18609        // against something that is not inside it.
18610        assert_eq!(
18611            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
18612            bulk(r#"["a","c"]"#).as_str()
18613        );
18614        // The legacy syntax takes one too, and answers the first match.
18615        assert_eq!(
18616            f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
18617            bulk(r#""a""#).as_str()
18618        );
18619        assert_eq!(
18620            f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
18621            "*1\r\n$6\r\nobject\r\n"
18622        );
18623
18624        // A write goes through it as far as a value that is already there. A
18625        // field that is not there yet has nowhere definite to go, which is the
18626        // same refusal a wildcard gets.
18627        assert_eq!(
18628            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
18629            bulk("[9,10]").as_str()
18630        );
18631        assert_eq!(
18632            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
18633            "+OK\r\n"
18634        );
18635        assert_eq!(
18636            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
18637            "-Err wrong static path\r\n"
18638        );
18639        assert_eq!(
18640            f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
18641            ":2\r\n"
18642        );
18643        assert_eq!(
18644            f.run(&[b"JSON.GET", b"doc", b"$"]),
18645            bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
18646        );
18647
18648        // A path that does not parse is refused before the document is read, so
18649        // a key that is not there answers the same way.
18650        assert!(
18651            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
18652                .starts_with("-ERR")
18653        );
18654        assert!(
18655            f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
18656                .starts_with("-ERR")
18657        );
18658    }
18659
18660    /// The operators past the comparisons, over the wire rather than in the
18661    /// parser's own tests, so that a client can reach all of them.
18662    #[test]
18663    fn a_filter_takes_the_membership_operators_and_the_methods_too() {
18664        let mut f = Fixture::new();
18665        let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
18666        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
18667
18668        for (path, want) in [
18669            (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
18670            (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
18671            (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
18672            (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
18673            (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
18674            (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
18675            (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
18676            (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
18677            (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
18678            (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
18679            (b"$.box[?(@.n~)].t", "[]"),
18680            (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
18681            (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
18682            (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
18683            (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
18684        ] {
18685            assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
18686        }
18687
18688        // A write goes through one of these the same way it goes through a
18689        // comparison.
18690        assert_eq!(
18691            f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
18692            "+OK\r\n"
18693        );
18694        assert_eq!(
18695            f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
18696            bulk(r#"["b"]"#).as_str()
18697        );
18698    }
18699
18700    /// D-41. RedisJSON refuses this one, and which document it refuses is
18701    /// decided by how it happens to hold an array of numbers.
18702    #[test]
18703    fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
18704        let mut f = Fixture::new();
18705        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
18706        assert_eq!(
18707            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
18708            "+OK\r\n"
18709        );
18710        assert_eq!(
18711            f.run(&[b"JSON.GET", b"doc", b"$"]),
18712            bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
18713        );
18714        // The same document with one element that is not an integer is the one
18715        // RedisJSON is happy with, and it goes the same way here.
18716        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
18717        assert_eq!(
18718            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
18719            "+OK\r\n"
18720        );
18721        assert_eq!(
18722            f.run(&[b"JSON.GET", b"doc", b"$"]),
18723            bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
18724        );
18725    }
18726
18727    /// `JSON.MSET` checks what it can before it writes anything and skips the
18728    /// one thing it cannot, which is a path with nowhere to put its value.
18729    #[test]
18730    fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
18731        let mut f = Fixture::new();
18732        assert_eq!(
18733            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
18734            "+OK\r\n"
18735        );
18736        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
18737        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
18738
18739        // A repeated key takes the last write.
18740        assert_eq!(
18741            f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
18742            "+OK\r\n"
18743        );
18744        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
18745
18746        // A triple whose path names nowhere is skipped, the others are still
18747        // written and the reply turns into a nil. Both ways round, because a
18748        // loop that gave up at the first skip would agree with this on one
18749        // order and not on the other.
18750        f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
18751        assert_eq!(
18752            f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
18753            "$-1\r\n"
18754        );
18755        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
18756        assert_eq!(
18757            f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
18758            "$-1\r\n"
18759        );
18760        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
18761
18762        // A value that is not JSON, a key holding something else and a path
18763        // that would have to create a document below its own root are all
18764        // checked before anything is written, so the good triple next to them
18765        // does not happen either.
18766        f.run(&[b"SET", b"str", b"x"]);
18767        assert_eq!(
18768            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
18769            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
18770        );
18771        assert_eq!(
18772            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
18773            "-Existing key has wrong Redis type\r\n"
18774        );
18775        assert_eq!(
18776            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
18777            "-ERR new objects must be created at the root\r\n"
18778        );
18779        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
18780
18781        // The two errors a path can be are checked up front as well, so the
18782        // triple before them is not written either. A wildcard that matched
18783        // nothing has nowhere to invent, and an index that is not in the array
18784        // is out of range, and both of them stop the whole command.
18785        assert_eq!(
18786            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
18787            "-Err wrong static path\r\n"
18788        );
18789        assert_eq!(
18790            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
18791            "-ERR array index out of range\r\n"
18792        );
18793        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
18794
18795        // Every triple is worked out against the keyspace as the command found
18796        // it, so a second triple on the same key does not see the first one and
18797        // the last write is the one that stays.
18798        f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
18799        assert_eq!(
18800            f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
18801            "+OK\r\n"
18802        );
18803        assert_eq!(
18804            f.run(&[b"JSON.GET", b"c", b"$"]),
18805            bulk(r#"[{"n":3}]"#).as_str()
18806        );
18807
18808        // An argument count that is not a run of key, path and value is the
18809        // arity error rather than a syntax one.
18810        assert_eq!(
18811            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
18812            "-ERR wrong number of arguments for 'json.mset' command\r\n"
18813        );
18814    }
18815
18816    /// `JSON.RESP` hands back RESP types, and the marker element is what tells
18817    /// an empty array and an empty object apart.
18818    #[test]
18819    fn json_resp_answers_the_document_as_resp_types() {
18820        let mut f = Fixture::new();
18821        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
18822        assert_eq!(
18823            f.run(&[b"JSON.RESP", b"doc"]),
18824            "*5\r\n+{\r\n$1\r\na\r\n:1\r\n$1\r\nb\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
18825        );
18826        // A JSONPath wraps the same answer in one more array.
18827        assert_eq!(
18828            f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
18829            "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
18830        );
18831
18832        f.run(&[
18833            b"JSON.SET",
18834            b"doc",
18835            b"$",
18836            br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
18837        ]);
18838        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
18839        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
18840        // A double goes out as its text, so a client reads the same digits
18841        // `JSON.GET` would have given it.
18842        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
18843        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
18844        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
18845
18846        // A missing legacy path is an error, a missing JSONPath is an empty
18847        // array, and a key that is not there is a nil on either.
18848        assert_eq!(
18849            f.run(&[b"JSON.RESP", b"doc", b".nope"]),
18850            "-ERR Path does not exist\r\n"
18851        );
18852        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
18853        assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
18854        assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
18855    }
18856
18857    /// `JSON.DEBUG` answers a byte count that is this encoding's, so the test
18858    /// pins the shapes and that the two syntaxes agree rather than a number
18859    /// read off another server. That is D-42.
18860    #[test]
18861    fn json_debug_answers_a_byte_count_and_its_own_help() {
18862        let mut f = Fixture::new();
18863        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
18864        let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
18865        assert!(one.starts_with(':'), "{one}");
18866        assert_eq!(
18867            f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
18868            format!("*1\r\n{one}")
18869        );
18870        let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
18871        assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
18872
18873        // A key that is not there is a zero on a legacy path and an empty set
18874        // on a JSONPath, which is the one reader here that does not answer nil
18875        // for it.
18876        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
18877        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
18878        assert_eq!(
18879            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
18880            "-ERR Path does not exist\r\n"
18881        );
18882        assert_eq!(
18883            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
18884            "*0\r\n"
18885        );
18886
18887        assert_eq!(
18888            f.run(&[b"JSON.DEBUG", b"HELP"]),
18889            "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
18890             $34\r\nHELP                - this message\r\n"
18891        );
18892        assert_eq!(
18893            f.run(&[b"JSON.DEBUG", b"NOPE"]),
18894            "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
18895        );
18896        assert_eq!(
18897            f.run(&[b"JSON.DEBUG", b"MEMORY"]),
18898            "-ERR wrong number of arguments for 'json.debug' command\r\n"
18899        );
18900    }
18901
18902    // ---------------------------------------------------------------- vector
18903
18904    /// The first `VADD` fixes the dimension and every one after it has to
18905    /// agree, because there is no create command to say it earlier.
18906    #[test]
18907    fn the_first_vadd_decides_how_wide_the_set_is() {
18908        let mut f = Fixture::new();
18909        assert_eq!(
18910            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
18911            ":1\r\n"
18912        );
18913        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
18914        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
18915        // A second vector under the same name replaces it and says so with a
18916        // zero, so an ingest can count what it created.
18917        assert_eq!(
18918            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
18919            ":0\r\n"
18920        );
18921        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
18922        // Three dimensions into a two dimensional set names both numbers, since
18923        // a client that gets this wrong needs to know which end is which.
18924        assert_eq!(
18925            f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
18926            "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
18927        );
18928        // A vector of zeros has no direction, and it is taken anyway and comes
18929        // back as the origin, because that is what a real server does with it.
18930        assert_eq!(
18931            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
18932            ":1\r\n"
18933        );
18934        assert_eq!(
18935            f.run(&[b"VEMB", b"v", b"nowhere"]),
18936            "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
18937        );
18938        // A set is made with one quantisation and keeps it, and a `VADD` that
18939        // names another is refused. Naming none names `Q8`, which is why this
18940        // set is a `Q8` one.
18941        assert_eq!(
18942            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
18943            "-ERR asked quantization mismatch with existing vector set\r\n"
18944        );
18945        // Nothing above created a key, and a set that never took a vector has
18946        // no dimension to report.
18947        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
18948        assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
18949        assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
18950    }
18951
18952    /// What a client sent comes back out, and what a client asked for is a
18953    /// similarity and not the distance underneath it.
18954    #[test]
18955    fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
18956        let mut f = Fixture::new();
18957        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
18958        // The set stored the direction and the length is multiplied back on the
18959        // way out, so this is `3 4` and not `0.6 0.8`. It is not quite `3 4`
18960        // either, because nobody named a quantisation and that means `Q8`: the
18961        // wider coordinate lands on a code exactly and the other one does not.
18962        // Both numbers are a real server's answers for the same input.
18963        assert_eq!(
18964            f.run(&[b"VEMB", b"v", b"a"]),
18965            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
18966        );
18967        // NOQUANT is the way to ask for what went in to come back out.
18968        f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
18969        assert_eq!(
18970            f.run(&[b"VEMB", b"n", b"a"]),
18971            "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
18972        );
18973        // BIN keeps the signs and nothing else, and does not multiply the
18974        // length back on, since a sign has no length in it to scale.
18975        f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
18976        assert_eq!(
18977            f.run(&[b"VEMB", b"b", b"a"]),
18978            "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
18979        );
18980        assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
18981        assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
18982
18983        // On the axes, where the unit vector is exact and so is the dot
18984        // product, both ends of the scale come out exact: the same direction is
18985        // 1 and the opposite one is 0, with a right angle at a half.
18986        let mut f = Fixture::new();
18987        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
18988        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
18989        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
18990        assert_eq!(
18991            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
18992            "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
18993             $8\r\nopposite\r\n$1\r\n0\r\n"
18994        );
18995        // A search from an element leaves that element out, since it is always
18996        // its own nearest neighbour.
18997        assert_eq!(
18998            f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
18999            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
19000        );
19001        // An element that is not there is an empty answer and not an error,
19002        // which is what a missing key gives too.
19003        assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
19004        assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
19005        // COUNT bounds it and TRUTH reads every vector rather than the codes,
19006        // which has to agree with the index on a set this small.
19007        assert_eq!(
19008            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
19009            "*1\r\n$6\r\nacross\r\n"
19010        );
19011        assert_eq!(
19012            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
19013            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
19014        );
19015        // EF widens how much of the index is read and does not change how many
19016        // answers come back, so a wide search still returns what COUNT asked
19017        // for.
19018        assert_eq!(
19019            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
19020            "*1\r\n$6\r\nacross\r\n"
19021        );
19022
19023        // On RESP3 a scored search is a map, which is what the vector set
19024        // module replies and is not what ZRANGE does here.
19025        let mut g = Fixture::new();
19026        g.run(&[b"HELLO", b"3"]);
19027        g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19028        assert_eq!(
19029            g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
19030            "%1\r\n$4\r\neast\r\n,1\r\n"
19031        );
19032    }
19033
19034    /// The attribute pair, and the one reply that means two things.
19035    #[test]
19036    fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
19037        let mut f = Fixture::new();
19038        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19039        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
19040        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
19041        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
19042        // Not parsed as JSON, because nothing reads into it yet and refusing a
19043        // write for a rule nothing enforces would be the wrong trade.
19044        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
19045        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
19046        // An empty string clears it, which is Redis's spelling of the removal.
19047        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
19048        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
19049        // An element that is not there answers zero rather than being created,
19050        // since an attribute with no vector under it is not a thing this holds.
19051        assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
19052        assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
19053        assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
19054        // A null for an element with no attribute and a null for one that is
19055        // not there. VISMEMBER is how a client tells the two apart.
19056        assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
19057        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
19058        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
19059        assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
19060
19061        // WITHATTRIBS carries it alongside the answers.
19062        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
19063        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19064        assert_eq!(
19065            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
19066            "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
19067        );
19068    }
19069
19070    /// The slot a removed element had is reused, and nothing that was beside it
19071    /// comes back with the next element to get it.
19072    #[test]
19073    fn vrem_takes_the_attribute_with_it() {
19074        let mut f = Fixture::new();
19075        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19076        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
19077        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
19078        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
19079        assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
19080        // The key went with the last element, the way every other collection
19081        // here works.
19082        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19083
19084        // The next element is given the slot the removed one had, and it comes
19085        // with no attribute on it.
19086        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19087        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
19088        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19089        f.run(&[b"VREM", b"v", b"east"]);
19090        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
19091        assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
19092    }
19093
19094    /// `VINFO` says what the index is before it says anything a client could
19095    /// mistake for a graph.
19096    #[test]
19097    fn vinfo_says_partition_first() {
19098        let mut f = Fixture::new();
19099        f.run(&[
19100            b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
19101        ]);
19102        f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
19103        let info = f.run(&[b"VINFO", b"v"]);
19104        assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
19105        // What the client asked for and not what happened to the tuning, which
19106        // is `10` section 7: M is recorded and changes nothing.
19107        assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
19108        assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
19109        assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
19110        // Nobody named a quantisation, so this set is a `Q8` one and every
19111        // element in it is stored that way.
19112        assert!(
19113            info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
19114            "{info}"
19115        );
19116        let mut f = Fixture::new();
19117        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
19118        assert!(
19119            f.run(&[b"VINFO", b"v"])
19120                .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
19121        );
19122        assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
19123    }
19124
19125    /// A set to read ranges of names out of.
19126    fn named() -> Fixture {
19127        let mut f = Fixture::new();
19128        for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
19129            .iter()
19130            .enumerate()
19131        {
19132            let x = (i + 1).to_string();
19133            f.run(&[
19134                b"VADD",
19135                b"r",
19136                b"VALUES",
19137                b"2",
19138                x.as_bytes(),
19139                b"1",
19140                name.as_bytes(),
19141            ]);
19142        }
19143        f
19144    }
19145
19146    /// `VRANGE` reads the names in the order bytes come in and pays no
19147    /// attention to where the vectors point.
19148    #[test]
19149    fn vrange_walks_the_names_and_not_the_vectors() {
19150        let mut f = named();
19151        assert_eq!(
19152            f.run(&[b"VRANGE", b"r", b"-", b"+"]),
19153            "*5\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n$5\r\ngamma\r\n"
19154        );
19155        assert_eq!(
19156            f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
19157            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
19158            "the high end is a name and not a prefix, so delta is past it"
19159        );
19160        assert_eq!(
19161            f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
19162            "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
19163        );
19164        assert_eq!(
19165            f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
19166            "*1\r\n$4\r\nbeta\r\n"
19167        );
19168        assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
19169        // Bytes and not letters, so an upper case name sorts before every lower
19170        // case one rather than beside its own spelling.
19171        f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
19172        assert_eq!(
19173            f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
19174            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
19175        );
19176        assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
19177    }
19178
19179    /// The count cuts the answer after the range is decided, and zero is not
19180    /// the same as leaving it out.
19181    #[test]
19182    fn a_vrange_count_of_zero_asks_for_nothing() {
19183        let mut f = named();
19184        assert_eq!(
19185            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
19186            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
19187        );
19188        assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
19189        assert!(
19190            f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
19191                .starts_with("*5\r\n"),
19192            "a negative count is no limit at all"
19193        );
19194    }
19195
19196    /// Both ends are read before either is placed, and the count is read before
19197    /// either end.
19198    #[test]
19199    fn vrange_says_which_end_it_could_not_read() {
19200        let mut f = named();
19201        assert_eq!(
19202            f.run(&[b"VRANGE", b"r", b"x", b"y"]),
19203            "-ERR invalid start range format\r\n"
19204        );
19205        assert_eq!(
19206            f.run(&[b"VRANGE", b"r", b"+", b"x"]),
19207            "-ERR invalid end range format\r\n",
19208            "the high end is spelled wrong, which is worth saying before the \
19209             low end being on the wrong side"
19210        );
19211        assert_eq!(
19212            f.run(&[b"VRANGE", b"r", b"+", b"-"]),
19213            "-ERR '-' can only be used as first argument, '+' only as second\r\n"
19214        );
19215        // A bracket with nothing after it is not the empty name here, though an
19216        // element really can be called that.
19217        assert_eq!(
19218            f.run(&[b"VRANGE", b"r", b"[", b"+"]),
19219            "-ERR invalid start range format\r\n"
19220        );
19221        assert_eq!(
19222            f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
19223            "-ERR invalid COUNT value\r\n"
19224        );
19225        assert_eq!(
19226            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
19227            "-ERR wrong number of arguments for 'VRANGE' command\r\n"
19228        );
19229        f.run(&[b"SET", b"s", b"x"]);
19230        assert!(
19231            f.run(&[b"VRANGE", b"s", b"-", b"+"])
19232                .starts_with("-WRONGTYPE")
19233        );
19234    }
19235
19236    /// The option that asks for something this index does not have says so
19237    /// rather than doing something else quietly.
19238    #[test]
19239    fn reduce_is_refused_and_not_ignored() {
19240        let mut f = Fixture::new();
19241        let reduce = f.run(&[
19242            b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
19243        ]);
19244        assert!(
19245            reduce.starts_with("-ERR REDUCE is not supported."),
19246            "{reduce}"
19247        );
19248        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19249    }
19250
19251    /// A filtered search answers with the nearest elements that match, and an
19252    /// expression that is not one is an error before the key is looked at.
19253    #[test]
19254    fn vsim_filter_reads_the_attributes() {
19255        let mut f = Fixture::new();
19256        for (name, x, y, attr) in [
19257            ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
19258            ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
19259            ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
19260            ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
19261        ] {
19262            f.run(&[
19263                b"VADD",
19264                b"v",
19265                b"VALUES",
19266                b"2",
19267                x.as_bytes(),
19268                y.as_bytes(),
19269                name.as_bytes(),
19270                b"SETATTR",
19271                attr.as_bytes(),
19272            ]);
19273        }
19274        // `b` is the nearest to the query and is the one the filter drops, so
19275        // this is the answer a filter applied afterwards would have got wrong.
19276        assert_eq!(
19277            f.run(&[
19278                b"VSIM",
19279                b"v",
19280                b"VALUES",
19281                b"2",
19282                b"9",
19283                b"1",
19284                b"COUNT",
19285                b"2",
19286                b"FILTER",
19287                b".lang == \"en\"",
19288            ]),
19289            "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
19290        );
19291        // A number is compared as a number, and the two halves of an `and` both
19292        // have to hold.
19293        assert_eq!(
19294            f.run(&[
19295                b"VSIM",
19296                b"v",
19297                b"VALUES",
19298                b"2",
19299                b"9",
19300                b"1",
19301                b"FILTER",
19302                b".lang == 'en' and .year > 1980",
19303            ]),
19304            "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
19305        );
19306        // A list, and a field an element does not have.
19307        assert_eq!(
19308            f.run(&[
19309                b"VSIM",
19310                b"v",
19311                b"VALUES",
19312                b"2",
19313                b"9",
19314                b"1",
19315                b"FILTER",
19316                b".lang in ['fr', 'de']",
19317            ]),
19318            "*1\r\n$1\r\nb\r\n"
19319        );
19320        assert_eq!(
19321            f.run(&[
19322                b"VSIM",
19323                b"v",
19324                b"VALUES",
19325                b"2",
19326                b"9",
19327                b"1",
19328                b"FILTER",
19329                b".rating > 3"
19330            ]),
19331            "*0\r\n"
19332        );
19333        // TRUTH measures every vector, and the filter still decides which ones
19334        // are measured.
19335        assert_eq!(
19336            f.run(&[
19337                b"VSIM",
19338                b"v",
19339                b"VALUES",
19340                b"2",
19341                b"9",
19342                b"1",
19343                b"TRUTH",
19344                b"FILTER",
19345                b".year < 1980",
19346            ]),
19347            "*1\r\n$1\r\nc\r\n"
19348        );
19349        // VSETATTR moves an element in and out of a filter, which means the tag
19350        // beside its code was rewritten and not just the string.
19351        f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
19352        assert_eq!(
19353            f.run(&[
19354                b"VSIM",
19355                b"v",
19356                b"VALUES",
19357                b"2",
19358                b"9",
19359                b"1",
19360                b"COUNT",
19361                b"1",
19362                b"FILTER",
19363                b".lang == \"en\"",
19364            ]),
19365            "*1\r\n$1\r\nb\r\n"
19366        );
19367        // And a VADD that replaces the vector keeps the attribute and the tag,
19368        // which is the same rewrite from the other end.
19369        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
19370        assert_eq!(
19371            f.run(&[
19372                b"VSIM",
19373                b"v",
19374                b"VALUES",
19375                b"2",
19376                b"9",
19377                b"1",
19378                b"COUNT",
19379                b"1",
19380                b"FILTER",
19381                b".lang == \"en\"",
19382            ]),
19383            "*1\r\n$1\r\nb\r\n"
19384        );
19385
19386        // The expression is parsed before the key is read, so a bad one is an
19387        // error whether or not the key is there.
19388        let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
19389        assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
19390        assert_eq!(
19391            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
19392            "-ERR invalid FILTER expression\r\n"
19393        );
19394        // FILTER-EF raises the effort rather than capping it, and zero is
19395        // Redis's word for no limit, so neither is an error.
19396        assert_eq!(
19397            f.run(&[
19398                b"VSIM",
19399                b"v",
19400                b"VALUES",
19401                b"2",
19402                b"9",
19403                b"1",
19404                b"COUNT",
19405                b"1",
19406                b"FILTER-EF",
19407                b"500",
19408                b"FILTER",
19409                b".lang == 'en'",
19410            ]),
19411            "*1\r\n$1\r\nb\r\n"
19412        );
19413        assert_eq!(
19414            f.run(&[
19415                b"VSIM",
19416                b"v",
19417                b"VALUES",
19418                b"2",
19419                b"9",
19420                b"1",
19421                b"COUNT",
19422                b"1",
19423                b"FILTER-EF",
19424                b"0"
19425            ]),
19426            "*1\r\n$1\r\nb\r\n"
19427        );
19428        assert_eq!(
19429            f.run(&[
19430                b"VSIM",
19431                b"v",
19432                b"VALUES",
19433                b"2",
19434                b"9",
19435                b"1",
19436                b"FILTER-EF",
19437                b"lots"
19438            ]),
19439            "-ERR EF must be a positive integer\r\n"
19440        );
19441    }
19442
19443    /// A vector set key is a key, so the keyspace owns it the way it owns every
19444    /// other one and none of those commands know what is inside it.
19445    #[test]
19446    fn the_keyspace_sees_a_vector_set_key_like_any_other() {
19447        let mut f = Fixture::new();
19448        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19449        assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
19450        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
19451        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
19452        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
19453        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
19454        assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
19455        assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
19456        assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
19457        assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
19458        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
19459
19460        // And the wrong type is the wrong type in both directions.
19461        f.run(&[b"SET", b"s", b"1"]);
19462        assert_eq!(
19463            f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
19464            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19465        );
19466        assert_eq!(
19467            f.run(&[b"VCARD", b"s"]),
19468            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19469        );
19470        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19471        assert_eq!(
19472            f.run(&[b"GET", b"v"]),
19473            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19474        );
19475        // A graph and a vector set share the escape in the record tag and are
19476        // still two different types, which is the case the tag alone cannot
19477        // decide.
19478        f.run(&[b"G.NADD", b"social", b"ada"]);
19479        assert_eq!(
19480            f.run(&[b"VCARD", b"social"]),
19481            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19482        );
19483        assert_eq!(
19484            f.run(&[b"G.NGET", b"v", b"ada"]),
19485            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19486        );
19487    }
19488
19489    /// `VRANDMEMBER` is `SRANDMEMBER` over the element names, in both of its
19490    /// shapes, off the database's own generator.
19491    #[test]
19492    fn vrandmember_has_the_two_shapes_srandmember_has() {
19493        let mut f = Fixture::new();
19494        for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
19495            let x = (i + 1).to_string();
19496            f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
19497        }
19498        // One element is a bulk string and not an array of one.
19499        let one = f.run(&[b"VRANDMEMBER", b"v"]);
19500        assert!(one.starts_with("$1\r\n"), "{one}");
19501        // A positive count is distinct and stops at the size of the set.
19502        let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
19503        assert!(all.starts_with("*3\r\n"), "{all}");
19504        for name in ["a", "b", "c"] {
19505            assert!(all.contains(name), "{all} is missing {name}");
19506        }
19507        all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
19508        assert!(all.starts_with("*2\r\n"), "{all}");
19509        // A negative one draws that many and allows repeats.
19510        let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
19511        assert!(many.starts_with("*5\r\n"), "{many}");
19512        // A key that is not there answers the shape that was asked for.
19513        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
19514        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
19515    }
19516
19517    /// `VLINKS` answers about the index that is here rather than the graph that
19518    /// is not, which is D-2.
19519    #[test]
19520    fn vlinks_reports_one_layer_of_partition_neighbours() {
19521        let mut f = Fixture::new();
19522        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
19523        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
19524        // One layer deep, because the index is one layer deep, so a client
19525        // walking layers gets a short list and not a shape it cannot parse.
19526        assert_eq!(
19527            f.run(&[b"VLINKS", b"v", b"east"]),
19528            "*1\r\n*1\r\n$5\r\nnorth\r\n"
19529        );
19530        assert_eq!(
19531            f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
19532            "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
19533        );
19534        assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
19535        assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
19536    }
19537
19538    /// A vector arrives either as digits or as bytes, and the two have to mean
19539    /// the same thing.
19540    #[test]
19541    fn fp32_and_values_are_the_same_vector() {
19542        let mut f = Fixture::new();
19543        let mut blob = Vec::new();
19544        for x in [3.0f32, 4.0] {
19545            blob.extend_from_slice(&x.to_le_bytes());
19546        }
19547        assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
19548        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
19549        assert_eq!(
19550            f.run(&[b"VEMB", b"v", b"a"]),
19551            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
19552        );
19553        // RAW is the stored bytes and the numbers that turn them back into the
19554        // client's vector, which for `Q8` is a code a coordinate, the length the
19555        // vector arrived with and the scale the codes are measured against. The
19556        // name of the form is a simple string, which is a real server's shape,
19557        // and all four of these are a real server's answers.
19558        assert_eq!(
19559            f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
19560            "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
19561        );
19562        // A blob that is not a whole number of floats is not a vector.
19563        assert_eq!(
19564            f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
19565            "-ERR invalid vector specification\r\n"
19566        );
19567        // Neither is a count that promises more than arrived.
19568        assert_eq!(
19569            f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
19570            "-ERR syntax error\r\n"
19571        );
19572        assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
19573    }
19574
19575    // ----------------------------------------------------------------- bloom
19576
19577    /// The filter a client gets when it does not describe one, and the two
19578    /// answers an add can give.
19579    #[test]
19580    fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
19581        let mut f = Fixture::new();
19582        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
19583        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
19584        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
19585        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
19586        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
19587        // The defaults are the module's configs and not anything the command
19588        // said, which is 100 entries at a hundredth and a growth of 2.
19589        assert_eq!(
19590            f.run(&[b"BF.INFO", b"b"]),
19591            "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
19592             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
19593             +Expansion rate\r\n:2\r\n"
19594        );
19595        assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
19596        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
19597        // A key that is not there has no filter to report on, and answers two
19598        // different ways about it depending on which command asked.
19599        assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
19600        assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
19601    }
19602
19603    /// `BF.EXISTS` on a key holding something else answers a miss, and
19604    /// everything else in the family answers `WRONGTYPE`.
19605    ///
19606    /// The two halves of a check and set disagree about what that key is, which
19607    /// is the module's behaviour and not a decision taken here.
19608    #[test]
19609    fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
19610        let mut f = Fixture::new();
19611        f.run(&[b"SET", b"s", b"text"]);
19612        assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
19613        assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
19614        for cmd in [
19615            vec![&b"BF.ADD"[..], b"s", b"x"],
19616            vec![&b"BF.MADD"[..], b"s", b"x"],
19617            vec![&b"BF.CARD"[..], b"s"],
19618            vec![&b"BF.INFO"[..], b"s"],
19619            vec![&b"BF.DEBUG"[..], b"s"],
19620            vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
19621        ] {
19622            let name = String::from_utf8_lossy(cmd[0]).into_owned();
19623            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
19624        }
19625        // The arguments are read before the key is, so a reserve with a bad
19626        // error rate complains about the rate and never learns about the string.
19627        assert_eq!(
19628            f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
19629            "-ERR bad error rate\r\n"
19630        );
19631        assert!(
19632            f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
19633                .starts_with("-WRONGTYPE")
19634        );
19635    }
19636
19637    /// A chain grows by its expansion factor and each link is half as wrong as
19638    /// the one before, which is what makes the whole filter hold its rate.
19639    #[test]
19640    fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
19641        let mut f = Fixture::new();
19642        assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
19643        for i in 0..10u32 {
19644            assert_eq!(
19645                f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
19646                ":1\r\n"
19647            );
19648        }
19649        assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
19650        assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
19651        assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
19652        // Capacity is the sum of every link and not the number that was asked
19653        // for, so it is 10 and then 10 plus 20.
19654        assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
19655        assert_eq!(
19656            f.run(&[b"BF.DEBUG", b"g"]),
19657            "*3\r\n$7\r\nsize:11\r\n\
19658             $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
19659             $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
19660        );
19661
19662        // The same filter told not to grow fills instead.
19663        assert_eq!(
19664            f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
19665            "+OK\r\n"
19666        );
19667        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
19668        assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
19669        assert_eq!(
19670            f.run(&[b"BF.ADD", b"n", b"c"]),
19671            "-ERR non scaling filter is full\r\n"
19672        );
19673        // And an item that is already in it still answers, because membership
19674        // is checked before fullness.
19675        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
19676        // A filter that will not grow has no expansion rate to report, in
19677        // either of the two spellings that make one.
19678        assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
19679        f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
19680        assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
19681        // Asking for both at once is refused, which is one of the module's
19682        // errors that carries no prefix at all.
19683        assert_eq!(
19684            f.run(&[
19685                b"BF.RESERVE",
19686                b"q",
19687                b"0.01",
19688                b"2",
19689                b"NONSCALING",
19690                b"EXPANSION",
19691                b"2"
19692            ]),
19693            "-Nonscaling filters cannot expand\r\n"
19694        );
19695    }
19696
19697    /// A multi add stops where the filter did, so the reply can be shorter than
19698    /// the argument list.
19699    #[test]
19700    fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
19701        let mut f = Fixture::new();
19702        f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
19703        assert_eq!(
19704            f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
19705            "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
19706        );
19707        assert_eq!(
19708            f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
19709            "*2\r\n:1\r\n:0\r\n"
19710        );
19711    }
19712
19713    /// `BF.INSERT` describes a filter and fills it in one command, with its own
19714    /// spelling of every complaint.
19715    #[test]
19716    fn insert_is_a_reserve_and_a_madd_with_different_errors() {
19717        let mut f = Fixture::new();
19718        assert_eq!(
19719            f.run(&[
19720                b"BF.INSERT",
19721                b"i",
19722                b"CAPACITY",
19723                b"50",
19724                b"ERROR",
19725                b"0.001",
19726                b"ITEMS",
19727                b"a",
19728                b"b"
19729            ]),
19730            "*2\r\n:1\r\n:1\r\n"
19731        );
19732        assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
19733        // NOCREATE is the only way to add without making the key.
19734        assert_eq!(
19735            f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
19736            "-ERR not found\r\n"
19737        );
19738        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
19739        // The same mistakes as BF.RESERVE, in the sentences this command uses
19740        // for them, and one sentence where BF.RESERVE has two.
19741        assert_eq!(
19742            f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
19743            "-Bad capacity\r\n"
19744        );
19745        assert_eq!(
19746            f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
19747            "-Bad error rate\r\n"
19748        );
19749        assert_eq!(
19750            f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
19751            "-Bad expansion\r\n"
19752        );
19753        // An option is matched on its first letter and not on the word, so a
19754        // token nobody meant as an option is one anyway if it starts with the
19755        // right letter. NOSUCHTHING is NONSCALING here, and the filter it
19756        // builds says so.
19757        assert_eq!(
19758            f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
19759            "*1\r\n:1\r\n"
19760        );
19761        assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
19762        // Only E and N need a second look, one for ERROR against EXPANSION and
19763        // the other for NOCREATE against NONSCALING, and both stop as soon as
19764        // they can tell the two apart.
19765        assert_eq!(
19766            f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
19767            "*1\r\n:1\r\n"
19768        );
19769        assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
19770        assert_eq!(
19771            f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
19772            "*1\r\n:1\r\n"
19773        );
19774        assert_eq!(
19775            f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
19776            "-ERR not found\r\n"
19777        );
19778        // A letter that starts nothing is the one case that is refused.
19779        assert_eq!(
19780            f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
19781            "-Unknown argument received\r\n"
19782        );
19783        // Everything after ITEMS is an item, even when it spells an option.
19784        assert_eq!(
19785            f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
19786            "*1\r\n:1\r\n"
19787        );
19788        // And ITEMS with nothing after it is the same as leaving it out.
19789        assert!(
19790            f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
19791                .contains("wrong number of arguments")
19792        );
19793    }
19794
19795    /// A filter dumped a chunk at a time and put back into another key is the
19796    /// same filter.
19797    #[test]
19798    fn a_dump_replays_into_a_filter_that_answers_the_same() {
19799        let mut f = Fixture::new();
19800        f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
19801        for i in 0..25u32 {
19802            f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
19803        }
19804        assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
19805
19806        // Iterator zero asks for the header and every one after it is a running
19807        // byte offset, and a chunk never spans two links.
19808        let mut iter = b"0".to_vec();
19809        let mut chunks = 0;
19810        loop {
19811            let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
19812            let text = String::from_utf8_lossy(&raw).into_owned();
19813            let next = text
19814                .split("\r\n")
19815                .nth(1)
19816                .and_then(|n| n.strip_prefix(':'))
19817                .expect("a two element reply of an iterator and a chunk")
19818                .to_owned();
19819            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
19820            let data = &body[body
19821                .windows(2)
19822                .position(|w| w == b"\r\n")
19823                .expect("a length line")
19824                + 2..body.len() - 2];
19825            if next == "0" {
19826                assert!(data.is_empty(), "the last chunk is empty");
19827                break;
19828            }
19829            let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
19830            assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
19831            iter = next.into_bytes();
19832            chunks += 1;
19833        }
19834        assert_eq!(chunks, 3, "a header and one chunk per link");
19835
19836        assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
19837        assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
19838        for i in 0..25u32 {
19839            assert_eq!(
19840                f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
19841                ":1\r\n"
19842            );
19843        }
19844
19845        // A header on top of a filter is refused rather than merged, and so is
19846        // one that no filter wrote.
19847        assert_eq!(
19848            f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
19849            "-ERR received bad data\r\n"
19850        );
19851        assert_eq!(
19852            f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
19853            "-ERR received bad data\r\n"
19854        );
19855        // An offset past the end of the filter names itself.
19856        assert_eq!(
19857            f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
19858            "-ERR invalid offset - no link found\r\n"
19859        );
19860        assert_eq!(
19861            f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
19862            "-ERR Second argument must be numeric\r\n"
19863        );
19864        // The same complaint without the prefix on the way out, which is the
19865        // module's inconsistency and not a slip here.
19866        assert_eq!(
19867            f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
19868            "-Second argument must be numeric\r\n"
19869        );
19870    }
19871
19872    /// The argument checks, which have a sentence each and read numbers the way
19873    /// Redis reads them everywhere else.
19874    #[test]
19875    fn reserve_reads_its_numbers_the_way_string2ll_does() {
19876        let mut f = Fixture::new();
19877        for (args, want) in [
19878            (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
19879            (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
19880            (
19881                vec![&b"0"[..], b"10"],
19882                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19883            ),
19884            (
19885                vec![&b"1"[..], b"10"],
19886                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19887            ),
19888            (
19889                vec![&b"inf"[..], b"10"],
19890                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
19891            ),
19892            (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
19893            (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
19894            (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
19895            (
19896                vec![&b"0.01"[..], b"0"],
19897                "-ERR capacity must be in the range [1, 1073741824]\r\n",
19898            ),
19899            (
19900                vec![&b"0.01"[..], b"1073741825"],
19901                "-ERR capacity must be in the range [1, 1073741824]\r\n",
19902            ),
19903        ] {
19904            let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
19905            cmd.extend(args.iter().copied());
19906            assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
19907        }
19908        assert_eq!(
19909            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
19910            "-ERR no expansion\r\n"
19911        );
19912        assert_eq!(
19913            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
19914            "-ERR bad expansion\r\n"
19915        );
19916        assert_eq!(
19917            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
19918            "-ERR expansion must be in the range [0, 32768]\r\n"
19919        );
19920        // Trailing rubbish after the capacity is ignored rather than refused.
19921        assert_eq!(
19922            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
19923            "+OK\r\n"
19924        );
19925        assert_eq!(
19926            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
19927            "-ERR item exists\r\n"
19928        );
19929        assert_eq!(
19930            f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
19931            "-Invalid information value\r\n"
19932        );
19933        assert!(
19934            f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
19935                .contains("wrong number of arguments")
19936        );
19937    }
19938
19939    /// The RESP3 shapes, which are where this family differs most from RESP2.
19940    #[test]
19941    fn the_bloom_family_answers_in_resp3_spelling_too() {
19942        let mut f = Fixture::new();
19943        f.out.set_proto(Proto::Resp3);
19944        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
19945        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
19946        assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
19947        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
19948        assert_eq!(
19949            f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
19950            "*2\r\n#t\r\n#f\r\n"
19951        );
19952        // The count stays an integer, because it counts rather than answers.
19953        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
19954        assert_eq!(
19955            f.run(&[b"BF.INFO", b"b"]),
19956            "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
19957             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
19958             +Expansion rate\r\n:2\r\n"
19959        );
19960        // One field is a map of one here and a bare array of one on RESP2, so
19961        // this is the reply where the two protocols carry different facts.
19962        assert_eq!(
19963            f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
19964            "%1\r\n+Capacity\r\n:100\r\n"
19965        );
19966    }
19967
19968    // ---------------------------------------------------------------- cuckoo
19969
19970    /// A dump header, which is the four counts and the three widths a filter
19971    /// writes in front of its fingerprints.
19972    ///
19973    /// Written by hand rather than taken from a `CF.SCANDUMP`, because what the
19974    /// tests below want out of it is the states a filter cannot be put into
19975    /// from the wire.
19976    fn cf_header(
19977        items: u64,
19978        buckets: u64,
19979        deletes: u64,
19980        filters: u64,
19981        geometry: [u16; 3],
19982    ) -> Vec<u8> {
19983        let mut out = Vec::with_capacity(38);
19984        for n in [items, buckets, deletes, filters] {
19985            out.extend_from_slice(&n.to_le_bytes());
19986        }
19987        for n in geometry {
19988            out.extend_from_slice(&n.to_le_bytes());
19989        }
19990        out
19991    }
19992
19993    /// The filter a client gets when it does not describe one, and the thing a
19994    /// cuckoo filter does that a Bloom filter cannot, which is count copies and
19995    /// take them out again.
19996    #[test]
19997    fn cf_add_makes_the_filter_and_counts_the_copies() {
19998        let mut f = Fixture::new();
19999        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
20000        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
20001        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
20002        // The NX form is the one that looks first, which is why it is a command
20003        // of its own rather than an option.
20004        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
20005        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
20006        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
20007        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
20008        assert_eq!(
20009            f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
20010            "*2\r\n:1\r\n:0\r\n"
20011        );
20012        // The defaults are the module's configs: 1024 entries over buckets of
20013        // two, twenty kicks and a chain that grows by one.
20014        assert_eq!(
20015            f.run(&[b"CF.INFO", b"d"]),
20016            "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
20017             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
20018             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
20019             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20020        );
20021        assert_eq!(
20022            f.run(&[b"CF.DEBUG", b"d"]),
20023            "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
20024             max_iterations:20 expansion:1\r\n"
20025        );
20026        assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
20027        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
20028
20029        // A delete takes one copy, so the same item goes twice and then stops.
20030        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
20031        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
20032        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
20033        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
20034        assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
20035
20036        // A key with no filter under it gets three different sentences and one
20037        // plain miss, depending on which command asked.
20038        assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
20039        assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
20040        assert_eq!(
20041            f.run(&[b"CF.COMPACT", b"gone"]),
20042            "-Cuckoo filter was not found\r\n"
20043        );
20044        assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
20045        // And `CF.COMPACT` is declared as taking any number of keys and takes
20046        // exactly one, which is the module's own arity being wrong rather than
20047        // this table's.
20048        assert!(
20049            f.run(&[b"CF.COMPACT", b"a", b"b"])
20050                .contains("wrong number of arguments")
20051        );
20052    }
20053
20054    /// The four that only read fingerprints treat a key holding something else
20055    /// as a key with no filter, and everything else answers `WRONGTYPE`.
20056    #[test]
20057    fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
20058        let mut f = Fixture::new();
20059        f.run(&[b"SET", b"s", b"text"]);
20060        assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
20061        assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
20062        assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
20063        // `CF.DEL` writes and is still in that group, and `CF.COMPACT` writes
20064        // and is declared read only, so neither of the two halves of the family
20065        // is the same set as the flags say.
20066        assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
20067        assert_eq!(
20068            f.run(&[b"CF.COMPACT", b"s"]),
20069            "-Cuckoo filter was not found\r\n"
20070        );
20071        for cmd in [
20072            vec![&b"CF.ADD"[..], b"s", b"x"],
20073            vec![&b"CF.ADDNX"[..], b"s", b"x"],
20074            vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
20075            vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
20076            vec![&b"CF.INFO"[..], b"s"],
20077            vec![&b"CF.DEBUG"[..], b"s"],
20078            vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
20079            vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
20080            vec![&b"CF.RESERVE"[..], b"s", b"64"],
20081        ] {
20082            let name = String::from_utf8_lossy(cmd[0]).into_owned();
20083            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
20084        }
20085    }
20086
20087    /// `CF.RESERVE` reads its options by name in an order of its own, and the
20088    /// first pair with a given name is the only one it looks at.
20089    #[test]
20090    fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
20091        let mut f = Fixture::new();
20092        assert_eq!(
20093            f.run(&[
20094                b"CF.RESERVE",
20095                b"r",
20096                b"64",
20097                b"BUCKETSIZE",
20098                b"1",
20099                b"MAXITERATIONS",
20100                b"7",
20101                b"EXPANSION",
20102                b"4"
20103            ]),
20104            "+OK\r\n"
20105        );
20106        assert_eq!(
20107            f.run(&[b"CF.DEBUG", b"r"]),
20108            "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
20109             max_iterations:7 expansion:4\r\n"
20110        );
20111        assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
20112
20113        assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
20114        assert_eq!(
20115            f.run(&[b"CF.RESERVE", b"q", b"1"]),
20116            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
20117        );
20118        // The range is the bucket size's and not a constant, so a capacity that
20119        // was fine at two slots a bucket is not at four.
20120        assert_eq!(
20121            f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
20122            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
20123        );
20124        assert_eq!(
20125            f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
20126            "+OK\r\n"
20127        );
20128
20129        // The capacity is checked last, so a command that is wrong twice
20130        // answers about the option. Which option it answers about is the order
20131        // the module looks for them in and not the order they were written, so
20132        // a bad kick budget wins over a bad bucket size wherever the two sit.
20133        assert_eq!(
20134            f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
20135            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
20136        );
20137        assert_eq!(
20138            f.run(&[
20139                b"CF.RESERVE",
20140                b"q2",
20141                b"64",
20142                b"EXPANSION",
20143                b"xx",
20144                b"BUCKETSIZE",
20145                b"0"
20146            ]),
20147            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
20148        );
20149        assert_eq!(
20150            f.run(&[
20151                b"CF.RESERVE",
20152                b"q2",
20153                b"64",
20154                b"MAXITERATIONS",
20155                b"0",
20156                b"BUCKETSIZE",
20157                b"0"
20158            ]),
20159            "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
20160        );
20161        // A second pair with a name that has already been read is not looked at
20162        // at all, so this one is a filter with buckets of one rather than an
20163        // error about a bucket size of zero.
20164        assert_eq!(
20165            f.run(&[
20166                b"CF.RESERVE",
20167                b"q3",
20168                b"64",
20169                b"BUCKETSIZE",
20170                b"1",
20171                b"BUCKETSIZE",
20172                b"0"
20173            ]),
20174            "+OK\r\n"
20175        );
20176        // A pair nobody knows is dropped, which is the opposite of what
20177        // `CF.INSERT` does with the same mistake.
20178        assert_eq!(
20179            f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
20180            "+OK\r\n"
20181        );
20182        assert_eq!(
20183            f.run(&[b"CF.DEBUG", b"q4"]),
20184            "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
20185             max_iterations:20 expansion:1\r\n"
20186        );
20187        // And an option with nothing after it leaves an odd number of them,
20188        // which is an arity error rather than a complaint about the option.
20189        assert!(
20190            f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
20191                .contains("wrong number of arguments")
20192        );
20193    }
20194
20195    /// `CF.INSERT` is a reserve and a multi add, with a grammar that agrees
20196    /// with `CF.RESERVE` about nothing.
20197    #[test]
20198    fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
20199        let mut f = Fixture::new();
20200        assert_eq!(
20201            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
20202            "*2\r\n:1\r\n:1\r\n"
20203        );
20204        assert_eq!(
20205            f.run(&[b"CF.DEBUG", b"i"]),
20206            "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
20207             max_iterations:20 expansion:1\r\n"
20208        );
20209        // The NX form has three answers rather than two, which is why it stays
20210        // integers on both protocols.
20211        assert_eq!(
20212            f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
20213            "*2\r\n:0\r\n:1\r\n"
20214        );
20215        assert_eq!(
20216            f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
20217            "-ERR not found\r\n"
20218        );
20219        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20220
20221        assert_eq!(
20222            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
20223            "-Bad capacity\r\n"
20224        );
20225        // The bucket size cannot be given here, so the range names the config
20226        // that holds it instead of the option `CF.RESERVE` names.
20227        assert_eq!(
20228            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
20229            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
20230        );
20231        // Every occurrence is checked, which is where this differs from
20232        // `CF.RESERVE`: the second `CAPACITY` is an error even though the first
20233        // one is the one that would have been used.
20234        assert_eq!(
20235            f.run(&[
20236                b"CF.INSERT",
20237                b"i",
20238                b"CAPACITY",
20239                b"8",
20240                b"CAPACITY",
20241                b"2",
20242                b"ITEMS",
20243                b"a"
20244            ]),
20245            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
20246        );
20247        // An option is one letter and not a word, so `NOSUCH` is `NOCREATE` and
20248        // `ITEMSXYZ` is `ITEMS`, and only a letter that starts nothing is
20249        // refused.
20250        assert_eq!(
20251            f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
20252            "*1\r\n:1\r\n"
20253        );
20254        assert_eq!(
20255            f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
20256            "*1\r\n:1\r\n"
20257        );
20258        assert_eq!(
20259            f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
20260            "-Unknown argument received\r\n"
20261        );
20262        // Everything after ITEMS is an item, even when it spells an option.
20263        assert_eq!(
20264            f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
20265            "*1\r\n:1\r\n"
20266        );
20267        // And the two ways of sending no items at all are the same complaint.
20268        assert!(
20269            f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
20270                .contains("wrong number of arguments")
20271        );
20272        assert!(
20273            f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
20274                .contains("wrong number of arguments")
20275        );
20276    }
20277
20278    /// The two walls a filter can hit, which say different things and are not
20279    /// the same wall.
20280    #[test]
20281    fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
20282        let mut f = Fixture::new();
20283        f.run(&[
20284            b"CF.RESERVE",
20285            b"s",
20286            b"4",
20287            b"BUCKETSIZE",
20288            b"1",
20289            b"EXPANSION",
20290            b"0",
20291        ]);
20292        for i in 0..4u32 {
20293            assert_eq!(
20294                f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
20295                ":1\r\n"
20296            );
20297        }
20298        assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
20299        assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
20300        // The add commands say it in a sentence and the insert commands say it
20301        // in the array, one value per item, and the array is never short.
20302        assert_eq!(
20303            f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
20304            "*2\r\n:-1\r\n:-1\r\n"
20305        );
20306        assert_eq!(
20307            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
20308            "*2\r\n:0\r\n:-1\r\n"
20309        );
20310
20311        // A chain that is allowed to grow stops for a different reason, and the
20312        // count it stops at is the filter limit rather than the room: this one
20313        // gives up with three slots free. Loading a chain that already has
20314        // every filter it is allowed shows why, since it refuses an item
20315        // straight into an empty one.
20316        let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
20317        assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
20318        assert_eq!(
20319            f.run(&[b"CF.ADD", b"g", b"q"]),
20320            "-Maximum expansions reached\r\n"
20321        );
20322        assert_eq!(
20323            f.run(&[b"CF.INFO", b"g"]),
20324            "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
20325             +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
20326             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
20327             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20328        );
20329    }
20330
20331    /// A filter dumped a chunk at a time and put back under another key is the
20332    /// same filter, and the headers that describe one nobody could build are
20333    /// refused on the way in.
20334    #[test]
20335    fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
20336        let mut f = Fixture::new();
20337        f.run(&[
20338            b"CF.RESERVE",
20339            b"src",
20340            b"8",
20341            b"BUCKETSIZE",
20342            b"2",
20343            b"EXPANSION",
20344            b"2",
20345        ]);
20346        for i in 0..40u32 {
20347            f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
20348        }
20349        // Position zero asks for the header and every one after it is a byte
20350        // offset across every filter laid end to end, and the walk ends on a
20351        // zero and a nil rather than an empty chunk.
20352        let mut pos = b"0".to_vec();
20353        let mut chunks = 0;
20354        loop {
20355            let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
20356            let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
20357            let next = head
20358                .split("\r\n")
20359                .nth(1)
20360                .and_then(|n| n.strip_prefix(':'))
20361                .expect("a two element reply of a position and a chunk")
20362                .to_owned();
20363            if next == "0" {
20364                assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
20365                break;
20366            }
20367            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
20368            let at = body
20369                .windows(2)
20370                .position(|w| w == b"\r\n")
20371                .expect("a length line")
20372                + 2;
20373            let data = &body[at..body.len() - 2];
20374            assert_eq!(
20375                f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
20376                "+OK\r\n",
20377                "loading chunk {chunks}"
20378            );
20379            pos = next.into_bytes();
20380            chunks += 1;
20381        }
20382        assert!(chunks >= 2, "a header and at least one chunk");
20383
20384        assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
20385        assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
20386        for i in 0..40u32 {
20387            assert_eq!(
20388                f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
20389                ":1\r\n"
20390            );
20391        }
20392
20393        // A filter with nothing in it hands out no header at all, so a client
20394        // that dumps one has nothing to load back.
20395        f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
20396        assert_eq!(
20397            f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
20398            "*2\r\n:0\r\n$-1\r\n"
20399        );
20400
20401        // The positions this end will not take, which are not the same set at
20402        // both ends: a dump refuses a negative one and a load takes it as an
20403        // offset and fails to find anything there.
20404        assert_eq!(
20405            f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
20406            "-Invalid position\r\n"
20407        );
20408        assert_eq!(
20409            f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
20410            "-Invalid position\r\n"
20411        );
20412        assert_eq!(
20413            f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
20414            "-Invalid position\r\n"
20415        );
20416        assert_eq!(
20417            f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
20418            "-Couldn't load chunk!\r\n"
20419        );
20420        // A header on top of a filter is refused rather than merged.
20421        let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
20422        assert_eq!(
20423            f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
20424            "-ERR item exists\r\n"
20425        );
20426        // A chunk that is not the size of a header where a header should have
20427        // been is one sentence, and one that is the size of a header and
20428        // describes a filter nobody could build is another.
20429        assert_eq!(
20430            f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
20431            "-Invalid header\r\n"
20432        );
20433        for (why, bad) in [
20434            ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
20435            ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
20436            (
20437                "a bucket count that is not a power of two",
20438                cf_header(0, 3, 0, 1, [2, 20, 1]),
20439            ),
20440            ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
20441            ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
20442            (
20443                "a growth nobody could reach",
20444                cf_header(0, 8, 0, 1, [2, 20, 32769]),
20445            ),
20446            (
20447                "a chain that cannot grow and did",
20448                cf_header(0, 8, 0, 2, [2, 20, 0]),
20449            ),
20450            // The count is written in eight bytes and read into two, so a
20451            // number that is a multiple of the second arrives as none.
20452            (
20453                "a filter count that wraps",
20454                cf_header(0, 8, 0, 65_536, [2, 20, 1]),
20455            ),
20456        ] {
20457            assert_eq!(
20458                f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
20459                "-Couldn't create filter!\r\n",
20460                "{why}"
20461            );
20462        }
20463    }
20464
20465    /// The RESP3 shapes, which are where this family differs most from RESP2
20466    /// and where one of its answers stops being readable.
20467    #[test]
20468    fn the_cuckoo_family_answers_in_resp3_spelling_too() {
20469        let mut f = Fixture::new();
20470        f.out.set_proto(Proto::Resp3);
20471        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
20472        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
20473        assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
20474        assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
20475        assert_eq!(
20476            f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
20477            "*2\r\n#t\r\n#f\r\n"
20478        );
20479        assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
20480        assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
20481        // The count stays an integer, because it counts rather than answers.
20482        assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
20483        assert_eq!(
20484            f.run(&[b"CF.INFO", b"c"]),
20485            "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
20486             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
20487             +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
20488             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
20489        );
20490
20491        // `CF.INSERT` writes a boolean per item here and an integer per item on
20492        // RESP2, and minus one has nowhere to go in a boolean, so a RESP3
20493        // client cannot tell an item that did not fit from one that is already
20494        // there. `CF.INSERTNX` keeps its integers for exactly that reason.
20495        f.run(&[
20496            b"CF.RESERVE",
20497            b"s",
20498            b"4",
20499            b"BUCKETSIZE",
20500            b"1",
20501            b"EXPANSION",
20502            b"0",
20503        ]);
20504        assert_eq!(
20505            f.run(&[
20506                b"CF.INSERT",
20507                b"s",
20508                b"ITEMS",
20509                b"a",
20510                b"b",
20511                b"c",
20512                b"d",
20513                b"e",
20514                b"f"
20515            ]),
20516            "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
20517        );
20518        assert_eq!(
20519            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
20520            "*2\r\n:0\r\n:-1\r\n"
20521        );
20522        assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
20523        // The end of a dump is a nil and not an empty chunk, which is one
20524        // underscore here and a negative length on RESP2.
20525        assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
20526    }
20527
20528    // ------------------------------------------------------------------- cms
20529
20530    /// A sketch is made from either end, and both constructors look at the key
20531    /// before they look at their arguments.
20532    #[test]
20533    fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
20534        let mut f = Fixture::new();
20535        assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
20536        assert_eq!(
20537            f.run(&[b"CMS.INFO", b"d"]),
20538            "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
20539        );
20540        assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
20541        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
20542        // Two over the error rounded up, and the log of the probability over the
20543        // log of a half rounded up, which for these two is 200 by 6.
20544        assert_eq!(
20545            f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
20546            "+OK\r\n"
20547        );
20548        assert_eq!(
20549            f.run(&[b"CMS.INFO", b"p"]),
20550            "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
20551        );
20552        // The key is checked first, so a width of zero at a key that is already
20553        // there is about the key and not about the width.
20554        assert_eq!(
20555            f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
20556            "-CMS: key already exists\r\n"
20557        );
20558        assert_eq!(
20559            f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
20560            "-CMS: invalid width\r\n"
20561        );
20562        assert_eq!(
20563            f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
20564            "-CMS: invalid depth\r\n"
20565        );
20566        assert_eq!(
20567            f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
20568            "-CMS: invalid overestimation value\r\n"
20569        );
20570        assert_eq!(
20571            f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
20572            "-CMS: invalid prob value\r\n"
20573        );
20574        // A probability whose float conversion is zero has no depth, and a width
20575        // past a signed sixty four bit integer has no width, and both are the
20576        // same sentence.
20577        assert_eq!(
20578            f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
20579            "-CMS: invalid init arguments\r\n"
20580        );
20581        // And a sketch bigger than a gibibyte of counters is refused here where
20582        // the reference reserves address space nobody has touched, which is
20583        // D-47.
20584        assert_eq!(
20585            f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
20586            "-CMS: Insufficient memory to create the key\r\n"
20587        );
20588        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
20589    }
20590
20591    /// Every pair is parsed before any of them lands, the counters saturate,
20592    /// and the count is a signed total of what was asked for.
20593    #[test]
20594    fn increments_are_parsed_whole_and_the_counters_saturate() {
20595        let mut f = Fixture::new();
20596        f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
20597        assert_eq!(
20598            f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
20599            "*2\r\n:3\r\n:4\r\n"
20600        );
20601        // An item that is incremented twice in one call sees its own first
20602        // increment in the reply to the second.
20603        assert_eq!(
20604            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
20605            "*2\r\n:4\r\n:5\r\n"
20606        );
20607        // A bad number anywhere means nothing at all is applied.
20608        assert_eq!(
20609            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
20610            "-CMS: Cannot parse number\r\n"
20611        );
20612        assert_eq!(
20613            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
20614            "-CMS: Number cannot be negative\r\n"
20615        );
20616        assert_eq!(
20617            f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
20618            "*2\r\n:5\r\n:4\r\n"
20619        );
20620        // The counters stop at four billion and the item that stopped says so in
20621        // its own slot while the one beside it answers a number.
20622        f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
20623        assert_eq!(
20624            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
20625            "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
20626        );
20627        assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
20628        // The count is what was asked for rather than what landed, and it is
20629        // signed, so a big enough total comes back negative.
20630        f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
20631        f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
20632        f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
20633        assert_eq!(
20634            f.run(&[b"CMS.INFO", b"w"]),
20635            "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
20636        );
20637        // An odd number of arguments after the key is an arity error and not a
20638        // syntax one.
20639        assert!(
20640            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
20641                .contains("wrong number of arguments")
20642        );
20643        assert_eq!(
20644            f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
20645            "-CMS: key does not exist\r\n"
20646        );
20647        assert_eq!(
20648            f.run(&[b"CMS.QUERY", b"nope", b"a"]),
20649            "-CMS: key does not exist\r\n"
20650        );
20651    }
20652
20653    /// A merge overwrites its destination, and it is worked out in full before
20654    /// any of it is written.
20655    #[test]
20656    fn a_merge_lands_whole_or_not_at_all() {
20657        let mut f = Fixture::new();
20658        for name in [&b"m1"[..], b"m2", b"dst"] {
20659            f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
20660        }
20661        f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
20662        f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
20663        assert_eq!(
20664            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
20665            "+OK\r\n"
20666        );
20667        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
20668        // Overwritten and not added to, so the same merge twice is the same
20669        // answer twice.
20670        assert_eq!(
20671            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
20672            "+OK\r\n"
20673        );
20674        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
20675        assert_eq!(
20676            f.run(&[
20677                b"CMS.MERGE",
20678                b"dst",
20679                b"2",
20680                b"m1",
20681                b"m2",
20682                b"WEIGHTS",
20683                b"2",
20684                b"3"
20685            ]),
20686            "+OK\r\n"
20687        );
20688        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
20689        // A cell times a weight is checked wide rather than wrapped, so this is
20690        // a refusal and the destination is left exactly as it was.
20691        assert_eq!(
20692            f.run(&[
20693                b"CMS.MERGE",
20694                b"dst",
20695                b"1",
20696                b"m1",
20697                b"WEIGHTS",
20698                b"4611686018427387904"
20699            ]),
20700            "-CMS: MERGE overflow\r\n"
20701        );
20702        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
20703        // The destination comes first, then the count, then the layout, then the
20704        // weights, then the sources one at a time.
20705        f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
20706        assert_eq!(
20707            f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
20708            "-CMS: key does not exist\r\n"
20709        );
20710        assert_eq!(
20711            f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
20712            "-CMS: Number of keys must be positive\r\n"
20713        );
20714        assert_eq!(
20715            f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
20716            "-CMS: wrong number of keys\r\n"
20717        );
20718        assert_eq!(
20719            f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
20720            "-CMS: wrong number of keys/weights\r\n"
20721        );
20722        assert_eq!(
20723            f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
20724            "-CMS: width/depth is not equal\r\n"
20725        );
20726        assert_eq!(
20727            f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
20728            "-CMS: key does not exist\r\n"
20729        );
20730    }
20731
20732    /// A key holding anything else is `WRONGTYPE` to all six, and a key holding
20733    /// a sketch is refused by the two commands that would have to serialise it.
20734    #[test]
20735    fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
20736        let mut f = Fixture::new();
20737        f.run(&[b"SET", b"s", b"text"]);
20738        for cmd in [
20739            vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
20740            vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
20741            vec![&b"CMS.QUERY"[..], b"s", b"a"],
20742            vec![&b"CMS.INFO"[..], b"s"],
20743            vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
20744        ] {
20745            let name = String::from_utf8_lossy(cmd[0]).into_owned();
20746            let reply = f.run(&cmd);
20747            // The two constructors see the key before anything else and say so
20748            // in the module's own words, and the rest are `WRONGTYPE`.
20749            assert!(
20750                reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
20751                "{name}: {reply}"
20752            );
20753        }
20754        f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
20755        // Redis refuses to copy a module key that has no copy callback, and
20756        // these are its words rather than ours. `DUMP` is the other half of
20757        // D-48: the reference has a payload for one of these and we do not.
20758        assert_eq!(
20759            f.run(&[b"COPY", b"c", b"c2"]),
20760            "-ERR not supported for this module key\r\n"
20761        );
20762        assert_eq!(
20763            f.run(&[b"DUMP", b"c"]),
20764            "-ERR DUMP is not supported for this module key\r\n"
20765        );
20766        // A graph is nobody's module and keeps its own sentence.
20767        f.run(&[b"G.NADD", b"g", b"a"]);
20768        assert_eq!(
20769            f.run(&[b"COPY", b"g", b"g2"]),
20770            "-ERR COPY is not supported for a graph\r\n"
20771        );
20772        assert_eq!(
20773            f.run(&[b"DUMP", b"g"]),
20774            "-ERR DUMP is not supported for a graph\r\n"
20775        );
20776        // Everything that does not need a byte shape works on a sketch key the
20777        // way it works on any other.
20778        assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
20779        assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
20780        assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
20781        assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
20782        assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
20783    }
20784
20785    // ------------------------------------------------------------------ topk
20786
20787    /// `TOPK.RESERVE` takes three arguments or six, and looks at the key before
20788    /// it looks at any of them.
20789    #[test]
20790    fn a_reserve_takes_three_arguments_or_six() {
20791        let mut f = Fixture::new();
20792        assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
20793        assert_eq!(
20794            f.run(&[b"TOPK.INFO", b"t"]),
20795            "*8\r\n+k\r\n:5\r\n+width\r\n:8\r\n+depth\r\n:7\r\n+decay\r\n$3\r\n0.9\r\n"
20796        );
20797        // Four arguments and five are an arity error rather than a defaulted
20798        // depth or decay.
20799        for cmd in [
20800            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
20801            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
20802        ] {
20803            assert!(f.run(&cmd).contains("wrong number of arguments"));
20804        }
20805        assert_eq!(
20806            f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
20807            "+OK\r\n"
20808        );
20809        // The key is checked first, so a reserve with nothing else right at a
20810        // key that is taken still says the key is taken.
20811        assert_eq!(
20812            f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
20813            "-TopK: key already exists\r\n"
20814        );
20815        assert_eq!(
20816            f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
20817            "-TopK: invalid k\r\n"
20818        );
20819        assert_eq!(
20820            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
20821            "-TopK: invalid width\r\n"
20822        );
20823        assert_eq!(
20824            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
20825            "-TopK: invalid depth\r\n"
20826        );
20827        // Zero is out and one is in, which is the module's `> 0` and `<= 1`.
20828        assert_eq!(
20829            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
20830            "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
20831        );
20832        assert_eq!(
20833            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
20834            "+OK\r\n"
20835        );
20836        // Past the cap, with the one sentence in the family that has a prefix.
20837        assert_eq!(
20838            f.run(&[
20839                b"TOPK.RESERVE",
20840                b"w",
20841                b"1",
20842                b"4294967295",
20843                b"4294967295",
20844                b"0.9"
20845            ]),
20846            "-ERR Insufficient memory to create topk data structure\r\n"
20847        );
20848    }
20849
20850    /// What the sketch keeps, and the three ways of asking about it.
20851    #[test]
20852    fn the_kept_set_is_what_query_and_list_answer_from() {
20853        let mut f = Fixture::new();
20854        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
20855        // A null an item while there is room, then the name of whatever was
20856        // pushed out.
20857        assert_eq!(
20858            f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
20859            "*2\r\n$-1\r\n$-1\r\n"
20860        );
20861        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
20862        // Two slots are full and `c` arrives with a count of one, which is not
20863        // under the smallest kept count, so it takes that slot straight away.
20864        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
20865        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
20866        assert_eq!(
20867            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
20868            "*3\r\n:1\r\n:0\r\n:1\r\n"
20869        );
20870        // The table still counts what the kept set let go of.
20871        assert_eq!(
20872            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
20873            "*3\r\n:11\r\n:1\r\n:6\r\n"
20874        );
20875        assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
20876        assert_eq!(
20877            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
20878            "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
20879        );
20880        // Any prefix of the keyword turns the counts on, the empty string
20881        // included, and only a longer word or a different one is refused.
20882        assert_eq!(
20883            f.run(&[b"TOPK.LIST", b"t", b"w"]),
20884            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
20885        );
20886        assert_eq!(
20887            f.run(&[b"TOPK.LIST", b"t", b""]),
20888            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
20889        );
20890        assert_eq!(
20891            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
20892            "-WITHCOUNT keyword expected\r\n"
20893        );
20894        // And the keyword is looked at before the key, so a missing key with a
20895        // bad keyword complains about the keyword.
20896        assert_eq!(
20897            f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
20898            "-WITHCOUNT keyword expected\r\n"
20899        );
20900        assert_eq!(
20901            f.run(&[b"TOPK.LIST", b"missing"]),
20902            "-TopK: key does not exist\r\n"
20903        );
20904        // An item counted zero times is kept and not listed.
20905        f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
20906        assert_eq!(
20907            f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
20908            "*1\r\n$-1\r\n"
20909        );
20910        assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
20911        assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
20912    }
20913
20914    /// `TOPK.INCRBY` applies as it goes, so a bad increment leaves everything
20915    /// before it counted, and the reply counts what it wrote.
20916    #[test]
20917    fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
20918        let mut f = Fixture::new();
20919        f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
20920        // Three pairs, the middle one bad: two elements come back, one of them
20921        // the error, and the array header says two rather than three. That last
20922        // part is D-51 and it is why a client here stays in step.
20923        assert_eq!(
20924            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
20925            format!(
20926                "*2\r\n$-1\r\n-{}\r\n",
20927                "TopK: increment must be an integer greater or equal to 0                            and smaller or equal to 100,000"
20928            )
20929        );
20930        assert_eq!(
20931            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
20932            "*3\r\n:3\r\n:0\r\n:0\r\n"
20933        );
20934        // A hundred thousand is in and one more is out.
20935        assert_eq!(
20936            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
20937            "*1\r\n$-1\r\n"
20938        );
20939        assert!(
20940            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
20941                .contains("smaller or equal to 100,000")
20942        );
20943        // Pairs have to be pairs.
20944        assert!(
20945            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
20946                .contains("wrong number of arguments")
20947        );
20948        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
20949    }
20950
20951    /// The RESP3 shapes, which are the two the protocols disagree about.
20952    #[test]
20953    fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
20954        let mut f = Fixture::new();
20955        f.run(&[b"HELLO", b"3"]);
20956        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
20957        f.run(&[b"TOPK.ADD", b"t", b"a"]);
20958        assert_eq!(
20959            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
20960            "*2\r\n#t\r\n#f\r\n"
20961        );
20962        // The count stays an integer on both protocols.
20963        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
20964        assert_eq!(
20965            f.run(&[b"TOPK.INFO", b"t"]),
20966            "%4\r\n+k\r\n:2\r\n+width\r\n:8\r\n+depth\r\n:7\r\n+decay\r\n,0.5\r\n"
20967        );
20968        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
20969    }
20970
20971    /// A top k key answers the module sentences the other sketch families
20972    /// answer, and its own word for its type.
20973    #[test]
20974    fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
20975        let mut f = Fixture::new();
20976        f.run(&[b"SET", b"s", b"text"]);
20977        for cmd in [
20978            vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
20979            vec![&b"TOPK.ADD"[..], b"s", b"a"],
20980            vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
20981            vec![&b"TOPK.QUERY"[..], b"s", b"a"],
20982            vec![&b"TOPK.COUNT"[..], b"s", b"a"],
20983            vec![&b"TOPK.LIST"[..], b"s"],
20984            vec![&b"TOPK.INFO"[..], b"s"],
20985        ] {
20986            let name = String::from_utf8_lossy(cmd[0]).into_owned();
20987            let reply = f.run(&cmd);
20988            assert!(
20989                reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
20990                "{name}: {reply}"
20991            );
20992        }
20993        f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
20994        assert_eq!(
20995            f.run(&[b"COPY", b"t", b"t2"]),
20996            "-ERR not supported for this module key\r\n"
20997        );
20998        assert_eq!(
20999            f.run(&[b"DUMP", b"t"]),
21000            "-ERR DUMP is not supported for this module key\r\n"
21001        );
21002        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
21003        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
21004        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
21005        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
21006        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
21007        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
21008        // Every one of the six that is not the constructor says the same thing
21009        // about a key that is not there.
21010        assert_eq!(
21011            f.run(&[b"TOPK.INFO", b"t3"]),
21012            "-TopK: key does not exist\r\n"
21013        );
21014    }
21015
21016    // --------------------------------------------------------------- tdigest
21017
21018    /// `TDIGEST.CREATE` takes two arguments or four, and the keyword search is a
21019    /// search rather than a lookup.
21020    #[test]
21021    fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
21022        let mut f = Fixture::new();
21023        assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
21024        // A hundred is the default and the capacity is six times it plus ten.
21025        assert_eq!(
21026            f.run(&[b"TDIGEST.INFO", b"t"]),
21027            "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
21028             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
21029             +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
21030        );
21031        assert_eq!(
21032            f.run(&[b"TDIGEST.CREATE", b"t"]),
21033            "-ERR T-Digest: key already exists\r\n"
21034        );
21035        // Three arguments is an arity error and not a missing keyword.
21036        assert!(
21037            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
21038                .contains("wrong number of arguments")
21039        );
21040        assert_eq!(
21041            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
21042            "+OK\r\n"
21043        );
21044        assert_eq!(
21045            f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
21046            "+OK\r\n"
21047        );
21048        // The word is looked for across both trailing arguments and the number
21049        // is then read out of the last one whatever was found, so this looks for
21050        // a number inside the word `COMPRESSION` and does not find one.
21051        assert_eq!(
21052            f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
21053            "-ERR T-Digest: error parsing compression parameter\r\n"
21054        );
21055        assert_eq!(
21056            f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
21057            "-ERR T-Digest: wrong keyword\r\n"
21058        );
21059        assert_eq!(
21060            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
21061            "-ERR T-Digest: error parsing compression parameter\r\n"
21062        );
21063        assert_eq!(
21064            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
21065            "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
21066        );
21067        // The reference's own ceiling, which is where the capacity stops fitting
21068        // in an int, and one past it.
21069        assert_eq!(
21070            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
21071            "-ERR T-Digest: allocation failed\r\n"
21072        );
21073        // And ours, which is a gibibyte of centroids and is D-52.
21074        assert_eq!(
21075            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
21076            "-ERR T-Digest: allocation failed\r\n"
21077        );
21078        // The key is checked before the arguments, so a bad compression at a key
21079        // that is already a digest still says the key is taken.
21080        assert_eq!(
21081            f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
21082            "-ERR T-Digest: key already exists\r\n"
21083        );
21084    }
21085
21086    /// The four samples every note about this family is written against, and the
21087    /// answers a real 8.10.1 gives for them.
21088    #[test]
21089    fn the_quantile_family_answers_what_the_module_answers() {
21090        let mut f = Fixture::new();
21091        f.run(&[b"TDIGEST.CREATE", b"s"]);
21092        assert_eq!(
21093            f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
21094            "+OK\r\n"
21095        );
21096        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
21097        assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
21098        // The cdf of a sample is the weight below it plus half its own.
21099        assert_eq!(
21100            f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
21101            "*4\r\n$5\r\n0.125\r\n$5\r\n0.375\r\n$5\r\n0.625\r\n$5\r\n0.875\r\n"
21102        );
21103        assert_eq!(
21104            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
21105            "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
21106        );
21107        // Out of order, the walk restarts, and 0.5 answers 3 either way while
21108        // the two after it are read from the front again.
21109        assert_eq!(
21110            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
21111            "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
21112        );
21113        assert_eq!(
21114            f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
21115            "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
21116        );
21117        assert_eq!(
21118            f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
21119            "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
21120        );
21121        assert_eq!(
21122            f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
21123            "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
21124        );
21125        assert_eq!(
21126            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
21127            "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
21128        );
21129        assert_eq!(
21130            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
21131            "$3\r\n2.5\r\n"
21132        );
21133        assert_eq!(
21134            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
21135            "$3\r\n2.5\r\n"
21136        );
21137        // The ranges, which are separate sentences from the parse failures.
21138        assert_eq!(
21139            f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
21140            "-ERR T-Digest: quantile should be in [0,1]\r\n"
21141        );
21142        assert_eq!(
21143            f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
21144            "-ERR T-Digest: error parsing quantile\r\n"
21145        );
21146        assert_eq!(
21147            f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
21148            "-ERR T-Digest: error parsing cdf\r\n"
21149        );
21150        assert_eq!(
21151            f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
21152            "-ERR T-Digest: error parsing value\r\n"
21153        );
21154        assert_eq!(
21155            f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
21156            "-ERR T-Digest: rank needs to be non negative\r\n"
21157        );
21158        assert_eq!(
21159            f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
21160            "-ERR T-Digest: error parsing rank\r\n"
21161        );
21162        // Both cuts have their own parse sentence and share the range one, and
21163        // equal cuts are refused rather than answering nothing.
21164        assert_eq!(
21165            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
21166            "-ERR T-Digest: error parsing low_cut_percentile\r\n"
21167        );
21168        assert_eq!(
21169            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
21170            "-ERR T-Digest: error parsing high_cut_percentile\r\n"
21171        );
21172        assert_eq!(
21173            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
21174            "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
21175        );
21176        assert_eq!(
21177            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
21178            "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
21179        );
21180    }
21181
21182    /// An empty digest answers every question, and answers most of them with
21183    /// something that is not a number.
21184    #[test]
21185    fn an_empty_digest_has_an_answer_for_everything() {
21186        let mut f = Fixture::new();
21187        f.run(&[b"TDIGEST.CREATE", b"e"]);
21188        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
21189        assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
21190        assert_eq!(
21191            f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
21192            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
21193        );
21194        assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
21195        assert_eq!(
21196            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
21197            "$3\r\nnan\r\n"
21198        );
21199        // Minus two, which is a number no rank on a digest with samples in it
21200        // can ever be.
21201        assert_eq!(
21202            f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
21203            "*2\r\n:-2\r\n:-2\r\n"
21204        );
21205        assert_eq!(
21206            f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
21207            "*2\r\n:-2\r\n:-2\r\n"
21208        );
21209        assert_eq!(
21210            f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
21211            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
21212        );
21213        // A reset puts a digest with samples back into exactly this state.
21214        f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
21215        assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
21216        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
21217        // Down to the compression count, so a reset digest and a fresh one of
21218        // the same compression report the same nine numbers.
21219        f.run(&[b"TDIGEST.CREATE", b"e2"]);
21220        assert_eq!(
21221            f.run(&[b"TDIGEST.INFO", b"e"]),
21222            f.run(&[b"TDIGEST.INFO", b"e2"])
21223        );
21224    }
21225
21226    /// The double parser is Redis's and not this engine's, and the two disagree
21227    /// at both ends of the range.
21228    #[test]
21229    fn a_sample_is_read_the_way_redis_reads_a_double() {
21230        let mut f = Fixture::new();
21231        f.run(&[b"TDIGEST.CREATE", b"a"]);
21232        // Overflow and underflow are parse failures rather than an infinity and
21233        // a zero, which is where this parts company with the rest of the engine.
21234        for bad in [
21235            &b"nan"[..],
21236            b"1e400",
21237            b"-1e400",
21238            b"1e309",
21239            b"1e-400",
21240            b"",
21241            b" 1",
21242            b"1 ",
21243            b"1e",
21244            b"--1",
21245        ] {
21246            assert_eq!(
21247                f.run(&[b"TDIGEST.ADD", b"a", bad]),
21248                "-ERR T-Digest: error parsing val parameter\r\n",
21249                "{}",
21250                String::from_utf8_lossy(bad)
21251            );
21252        }
21253        // An infinity spelled out parses and is then refused for being one, with
21254        // a different sentence.
21255        for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
21256            assert_eq!(
21257                f.run(&[b"TDIGEST.ADD", b"a", word]),
21258                "-ERR T-Digest: val parameter needs to be a finite number\r\n",
21259                "{}",
21260                String::from_utf8_lossy(word)
21261            );
21262        }
21263        // These all parse: hex, a bare point either side, and the smallest
21264        // subnormal the reference will take.
21265        for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
21266            assert_eq!(
21267                f.run(&[b"TDIGEST.ADD", b"a", good]),
21268                "+OK\r\n",
21269                "{}",
21270                String::from_utf8_lossy(good)
21271            );
21272        }
21273        // Nothing landed from the failures, so six samples is what there is.
21274        assert!(
21275            f.run(&[b"TDIGEST.INFO", b"a"])
21276                .contains("Observations\r\n:6\r\n")
21277        );
21278        // Every value is parsed before any is added, so this whole command is a
21279        // no op.
21280        assert_eq!(
21281            f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
21282            "-ERR T-Digest: error parsing val parameter\r\n"
21283        );
21284        assert!(
21285            f.run(&[b"TDIGEST.INFO", b"a"])
21286                .contains("Observations\r\n:6\r\n")
21287        );
21288    }
21289
21290    /// What a merge does to its destination, to its inputs and to the buffer
21291    /// split `TDIGEST.INFO` reports.
21292    #[test]
21293    fn a_merge_sweeps_the_destination_between_its_inputs() {
21294        let mut f = Fixture::new();
21295        f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
21296        f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
21297        f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
21298        f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
21299        assert_eq!(
21300            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
21301            "+OK\r\n"
21302        );
21303        // The destination did not exist, so the compression is the largest of
21304        // the inputs. The three from the first input were swept in before the
21305        // three from the second arrived, which is the one visible effect of the
21306        // reference folding one input at a time.
21307        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
21308        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
21309        assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
21310        assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
21311        assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
21312        assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
21313        assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
21314        // Reading a source sweeps it too, so a merge writes to keys it only
21315        // reads from.
21316        assert!(
21317            f.run(&[b"TDIGEST.INFO", b"m1"])
21318                .contains("Merged nodes\r\n:3\r\n")
21319        );
21320        // Without OVERRIDE the destination joins its own inputs, so this takes
21321        // it to nine observations and keeps its own compression.
21322        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
21323        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
21324        assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
21325        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
21326        // With OVERRIDE the old destination is dropped and the compression goes
21327        // back to the largest of the inputs.
21328        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
21329        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
21330        assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
21331        assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
21332        // And COMPRESSION beats both.
21333        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
21334        assert!(
21335            f.run(&[b"TDIGEST.INFO", b"d"])
21336                .contains("Compression\r\n:500\r\n")
21337        );
21338        // Naming the destination as a source folds it in twice.
21339        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
21340        assert!(
21341            f.run(&[b"TDIGEST.INFO", b"d"])
21342                .contains("Observations\r\n:12\r\n")
21343        );
21344        // The arguments, in the order the reference checks them.
21345        assert_eq!(
21346            f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
21347            "-ERR T-Digest: error parsing numkeys\r\n"
21348        );
21349        assert_eq!(
21350            f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
21351            "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
21352        );
21353        assert!(
21354            f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
21355                .contains("wrong number of arguments")
21356        );
21357        assert!(
21358            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
21359                .contains("wrong number of arguments")
21360        );
21361        assert_eq!(
21362            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
21363            "-ERR T-Digest: wrong keyword\r\n"
21364        );
21365        // A source that is not there stops the whole thing, and the destination
21366        // is left as it was.
21367        assert_eq!(
21368            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
21369            "-ERR T-Digest: key does not exist\r\n"
21370        );
21371        assert!(
21372            f.run(&[b"TDIGEST.INFO", b"d"])
21373                .contains("Observations\r\n:12\r\n")
21374        );
21375        // A destination that is not there and is also named as a source is the
21376        // same sentence rather than an empty merge.
21377        assert_eq!(
21378            f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
21379            "-ERR T-Digest: key does not exist\r\n"
21380        );
21381    }
21382
21383    /// The RESP3 shapes, which are the two the protocols disagree about.
21384    #[test]
21385    fn a_digest_answers_doubles_and_a_map_on_resp3() {
21386        let mut f = Fixture::new();
21387        f.run(&[b"HELLO", b"3"]);
21388        f.run(&[b"TDIGEST.CREATE", b"s"]);
21389        f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
21390        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
21391        assert_eq!(
21392            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
21393            "*2\r\n,1\r\n,4\r\n"
21394        );
21395        assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
21396        // The two infinities and the NaN go out as the bare words.
21397        assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
21398        assert_eq!(
21399            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
21400            "*1\r\n,-inf\r\n"
21401        );
21402        f.run(&[b"TDIGEST.CREATE", b"e"]);
21403        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
21404        // The ranks stay integers on both protocols.
21405        assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
21406        // Every question above swept the buffer in, so the four samples are all
21407        // merged by now and the compression count says it happened once.
21408        assert_eq!(
21409            f.run(&[b"TDIGEST.INFO", b"s"]),
21410            "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
21411             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
21412             +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
21413        );
21414    }
21415
21416    /// A t digest key answers the module sentences the other sketch families
21417    /// answer, and its own word for its type.
21418    #[test]
21419    fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
21420        let mut f = Fixture::new();
21421        f.run(&[b"SET", b"s", b"text"]);
21422        for cmd in [
21423            vec![&b"TDIGEST.CREATE"[..], b"s"],
21424            vec![&b"TDIGEST.RESET"[..], b"s"],
21425            vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
21426            vec![&b"TDIGEST.MIN"[..], b"s"],
21427            vec![&b"TDIGEST.MAX"[..], b"s"],
21428            vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
21429            vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
21430            vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
21431            vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
21432            vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
21433            vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
21434            vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
21435            vec![&b"TDIGEST.INFO"[..], b"s"],
21436        ] {
21437            let name = String::from_utf8_lossy(cmd[0]).into_owned();
21438            let reply = f.run(&cmd);
21439            assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
21440        }
21441        // The merge checks its destination the same way, and its sources too.
21442        f.run(&[b"TDIGEST.CREATE", b"t"]);
21443        assert!(
21444            f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
21445                .starts_with("-WRONGTYPE")
21446        );
21447        assert!(
21448            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
21449                .starts_with("-WRONGTYPE")
21450        );
21451        assert_eq!(
21452            f.run(&[b"COPY", b"t", b"t2"]),
21453            "-ERR not supported for this module key\r\n"
21454        );
21455        assert_eq!(
21456            f.run(&[b"DUMP", b"t"]),
21457            "-ERR DUMP is not supported for this module key\r\n"
21458        );
21459        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
21460        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
21461        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
21462        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
21463        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
21464        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
21465        // An empty digest is still a key, so the twelve that are not the
21466        // constructor all say the same thing once it is gone.
21467        assert_eq!(
21468            f.run(&[b"TDIGEST.INFO", b"t3"]),
21469            "-ERR T-Digest: key does not exist\r\n"
21470        );
21471        // The key is looked at before the arguments, so a bad argument at a key
21472        // that is not there still says the key is not there.
21473        assert_eq!(
21474            f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
21475            "-ERR T-Digest: key does not exist\r\n"
21476        );
21477    }
21478
21479    // -------------------------------------------------------------------- ts
21480
21481    /// A `TS.INFO` reply with the memory usage taken out of it.
21482    ///
21483    /// That number is what a series costs here rather than what one costs in the
21484    /// module, which is D-53, and it moves whenever the layout of a chunk does.
21485    /// Everything either side of it is the wire contract and is worth pinning
21486    /// down exactly, so the tests below check the whole reply with the one
21487    /// number lifted out.
21488    fn without_memory(reply: &str) -> String {
21489        let head = "+memoryUsage\r\n:";
21490        let at = reply.find(head).expect("every TS.INFO reports memory");
21491        let rest = &reply[at + head.len()..];
21492        let end = rest.find("\r\n").expect("and it is a whole number");
21493        format!("{}{}", &reply[..at + head.len()], &rest[end..])
21494    }
21495
21496    /// A series is made empty and still says it has a chunk, and the options are
21497    /// read before the key is looked at.
21498    #[test]
21499    fn a_series_is_made_empty_and_reports_on_itself() {
21500        let mut f = Fixture::new();
21501        assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
21502        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
21503        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
21504        // Fourteen fields, so twenty eight elements. An empty series reports one
21505        // chunk and zero at both ends, and neither the chunk type nor the
21506        // duplicate policy is ever a nil.
21507        assert_eq!(
21508            without_memory(&f.run(&[b"TS.INFO", b"t"])),
21509            "*28\r\n\
21510             +totalSamples\r\n:0\r\n\
21511             +memoryUsage\r\n:\r\n\
21512             +firstTimestamp\r\n:0\r\n\
21513             +lastTimestamp\r\n:0\r\n\
21514             +retentionTime\r\n:0\r\n\
21515             +chunkCount\r\n:1\r\n\
21516             +chunkSize\r\n:4096\r\n\
21517             +chunkType\r\n+compressed\r\n\
21518             +duplicatePolicy\r\n+block\r\n\
21519             +labels\r\n*0\r\n\
21520             +sourceKey\r\n$-1\r\n\
21521             +rules\r\n*0\r\n\
21522             +ignoreMaxTimeDiff\r\n:0\r\n\
21523             +ignoreMaxValDiff\r\n$1\r\n0\r\n"
21524        );
21525        // A key that is already there is about the key whatever it holds, and
21526        // the existence is what is checked rather than the type.
21527        assert_eq!(
21528            f.run(&[b"TS.CREATE", b"t"]),
21529            "-ERR TSDB: key already exists\r\n"
21530        );
21531        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21532        assert_eq!(
21533            f.run(&[b"TS.CREATE", b"str"]),
21534            "-ERR TSDB: key already exists\r\n"
21535        );
21536        // But the arguments are read first, so a bad one at a key that is there
21537        // answers about the argument.
21538        assert_eq!(
21539            f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
21540            "-ERR TSDB: Couldn't parse RETENTION\r\n"
21541        );
21542        // The seven that will not make a series say WRONGTYPE about a key
21543        // holding something else, where the two that would say a sentence.
21544        // The word is inside the sentence and not in front of it, because the
21545        // module writes its own error text and Redis puts ERR on the front of
21546        // anything a module writes.
21547        let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
21548        assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
21549        assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
21550        assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
21551        assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
21552        assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
21553        assert_eq!(
21554            f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
21555            "-ERR TSDB: the key is not a TSDB key\r\n"
21556        );
21557        // And the ones that will not make one say so about a key that is gone.
21558        assert_eq!(
21559            f.run(&[b"TS.INFO", b"nope"]),
21560            "-ERR TSDB: the key does not exist\r\n"
21561        );
21562        assert_eq!(
21563            f.run(&[b"TS.GET", b"nope"]),
21564            "-ERR TSDB: the key does not exist\r\n"
21565        );
21566        assert_eq!(
21567            f.run(&[b"TS.ALTER", b"nope"]),
21568            "-ERR TSDB: the key does not exist\r\n"
21569        );
21570        assert_eq!(
21571            f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
21572            "-ERR TSDB: the key does not exist\r\n"
21573        );
21574    }
21575
21576    /// Every option word, including the ones that are wrong, and the scan that
21577    /// finds them.
21578    #[test]
21579    fn the_options_are_a_keyword_scan_and_not_a_grammar() {
21580        let mut f = Fixture::new();
21581        assert_eq!(
21582            f.run(&[
21583                b"TS.CREATE",
21584                b"t",
21585                b"RETENTION",
21586                b"5000",
21587                b"ENCODING",
21588                b"UNCOMPRESSED",
21589                b"CHUNK_SIZE",
21590                b"128",
21591                b"DUPLICATE_POLICY",
21592                b"LAST",
21593                b"IGNORE",
21594                b"10",
21595                b"0.5",
21596                b"LABELS",
21597                b"room",
21598                b"kitchen"
21599            ]),
21600            "+OK\r\n"
21601        );
21602        let info = f.run(&[b"TS.INFO", b"t"]);
21603        assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
21604        assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
21605        assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
21606        assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
21607        assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
21608        // A plain double here, where a sample value out of TS.GET is the
21609        // shortest digits that read back as the same number.
21610        assert!(
21611            info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
21612            "{info}"
21613        );
21614        assert!(
21615            info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
21616            "{info}"
21617        );
21618
21619        // A word that is not an option is read past rather than refused.
21620        assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
21621        // LABELS eats everything after it in pairs, and the later scans still
21622        // look inside what it ate, so this sets a retention and stores a label
21623        // called RETENTION at the same time.
21624        assert_eq!(
21625            f.run(&[
21626                b"TS.CREATE",
21627                b"g",
21628                b"LABELS",
21629                b"a",
21630                b"b",
21631                b"RETENTION",
21632                b"5"
21633            ]),
21634            "+OK\r\n"
21635        );
21636        let greedy = f.run(&[b"TS.INFO", b"g"]);
21637        assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
21638        assert!(
21639            greedy.contains("*2\r\n$1\r\na\r\n$1\r\nb\r\n*2\r\n$9\r\nRETENTION\r\n$1\r\n5\r\n"),
21640            "{greedy}"
21641        );
21642
21643        // Every way an option can be wrong, in the order the module reads them.
21644        assert_eq!(
21645            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
21646            "-ERR TSDB: Couldn't parse LABELS\r\n"
21647        );
21648        assert_eq!(
21649            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
21650            "-ERR TSDB: Couldn't parse LABELS\r\n"
21651        );
21652        assert_eq!(
21653            f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
21654            "-ERR TSDB: Couldn't parse RETENTION\r\n"
21655        );
21656        // A retention below zero is one of the two the module writes with no
21657        // ERR in front of it, where one that is not a number gets one.
21658        assert_eq!(
21659            f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
21660            "-TSDB: Couldn't parse RETENTION\r\n"
21661        );
21662        assert_eq!(
21663            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
21664            "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
21665        );
21666        assert_eq!(
21667            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
21668            "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
21669        );
21670        assert_eq!(
21671            f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
21672            "-ERR TSDB: unknown ENCODING parameter\r\n"
21673        );
21674        // And an ENCODING with nothing behind it is an arity error where every
21675        // other keyword in the same spot is a sentence.
21676        assert!(
21677            f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
21678                .contains("wrong number of arguments for 'ts.create' command")
21679        );
21680        assert_eq!(
21681            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
21682            "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
21683        );
21684        assert_eq!(
21685            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
21686            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
21687        );
21688        assert_eq!(
21689            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
21690            "-ERR TSDB: Couldn't parse IGNORE\r\n"
21691        );
21692        assert_eq!(
21693            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
21694            "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
21695        );
21696        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
21697
21698        // An alter changes what was named and leaves the rest alone, and reads
21699        // an encoding only far enough to refuse a bad one.
21700        assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
21701        let after = f.run(&[b"TS.INFO", b"t"]);
21702        assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
21703        assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
21704        assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
21705        assert_eq!(
21706            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
21707            "-ERR TSDB: unknown ENCODING parameter\r\n"
21708        );
21709        // An encoding it does take is still not applied.
21710        assert_eq!(
21711            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
21712            "+OK\r\n"
21713        );
21714        assert!(
21715            f.run(&[b"TS.INFO", b"t"])
21716                .contains("+chunkType\r\n+uncompressed\r\n")
21717        );
21718    }
21719
21720    /// Samples go in, come back out and are refused for the reasons the module
21721    /// refuses them.
21722    #[test]
21723    fn samples_land_where_they_are_put_and_the_newest_comes_back() {
21724        let mut f = Fixture::new();
21725        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
21726        // The series was made on the way in.
21727        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
21728        assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
21729        // A sample value goes out as a simple string of the shortest digits
21730        // that read back as the same number.
21731        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
21732        assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
21733        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
21734        // An empty series has no newest sample and answers an empty array
21735        // rather than a nil.
21736        assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
21737        assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
21738
21739        // The value is read before the key, so a bad one against a key holding
21740        // a string is about the value.
21741        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21742        assert_eq!(
21743            f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
21744            "-ERR TSDB: invalid value\r\n"
21745        );
21746        // The grammar is tighter than the one a number argument usually gets:
21747        // no leading plus, no bare fraction, no infinity and nothing that does
21748        // not fit.
21749        for bad in [
21750            &b".5"[..],
21751            b"1.",
21752            b"+1",
21753            b" 1",
21754            b"0x10",
21755            b"inf",
21756            b"1e400",
21757            b"--1",
21758            b"1e",
21759        ] {
21760            assert_eq!(
21761                f.run(&[b"TS.ADD", b"v", b"1", bad]),
21762                "-ERR TSDB: invalid value\r\n",
21763                "{}",
21764                String::from_utf8_lossy(bad)
21765            );
21766        }
21767        // And a reading that is not a number is one of three words.
21768        assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
21769
21770        // A timestamp that is not a number, and one that is and is below zero,
21771        // are two different sentences.
21772        assert_eq!(
21773            f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
21774            "-ERR TSDB: invalid timestamp\r\n"
21775        );
21776        assert_eq!(
21777            f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
21778            "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
21779        );
21780
21781        // A repeated timestamp is blocked by default, and ON_DUPLICATE on the
21782        // command beats what the series was told.
21783        assert_eq!(
21784            f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
21785            "-ERR TSDB: Error at upsert, update is not supported when DUPLICATE_POLICY is set to BLOCK mode, or either current or new value is NaN and DUPLICATE_POLICY is MAX/MIN/SUM\r\n"
21786        );
21787        assert_eq!(
21788            f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
21789            ":300\r\n"
21790        );
21791        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
21792        // ON_DUPLICATE is only read when the key was already there, which is
21793        // why a policy word that is not a policy passes on a fresh key.
21794        assert_eq!(
21795            f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
21796            ":1\r\n"
21797        );
21798        assert_eq!(
21799            f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
21800            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
21801        );
21802
21803        // Retention is exact and it is checked before anything else happens, so
21804        // a sample landing behind the window is refused rather than trimmed.
21805        assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
21806        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
21807        assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
21808        assert_eq!(
21809            f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
21810            "-ERR TSDB: Timestamp is older than retention\r\n"
21811        );
21812        // And the window trims as it moves.
21813        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
21814        assert!(
21815            f.run(&[b"TS.INFO", b"r"])
21816                .contains("+totalSamples\r\n:1\r\n")
21817        );
21818
21819        // An ignore window drops a sample close enough to the newest one to be
21820        // uninteresting, and answers the newest timestamp so a client can tell.
21821        assert_eq!(
21822            f.run(&[
21823                b"TS.CREATE",
21824                b"i",
21825                b"DUPLICATE_POLICY",
21826                b"LAST",
21827                b"IGNORE",
21828                b"10",
21829                b"0.5"
21830            ]),
21831            "+OK\r\n"
21832        );
21833        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
21834        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
21835        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
21836    }
21837
21838    /// Every triple in a `TS.MADD` is answered on its own, and none of them
21839    /// makes a series.
21840    #[test]
21841    fn a_madd_answers_each_triple_and_creates_nothing() {
21842        let mut f = Fixture::new();
21843        assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
21844        assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
21845        assert_eq!(
21846            f.run(&[
21847                b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
21848            ]),
21849            "*3\r\n:100\r\n:100\r\n:200\r\n"
21850        );
21851        // A key that is not a series is an error in its own slot and the ones
21852        // after it still land.
21853        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21854        assert_eq!(
21855            f.run(&[
21856                b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
21857            ]),
21858            "*3\r\n\
21859             -ERR TSDB: the key is not a TSDB key\r\n\
21860             -ERR TSDB: the key is not a TSDB key\r\n\
21861             :300\r\n"
21862        );
21863        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
21864        // A bad value and a bad timestamp are answered in their slots too.
21865        assert_eq!(
21866            f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
21867            "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
21868        );
21869        // And a list that is not made of triples is an arity error.
21870        assert!(
21871            f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
21872                .contains("wrong number of arguments for 'ts.madd' command")
21873        );
21874    }
21875
21876    /// The two increments, which only ever write forwards.
21877    #[test]
21878    fn an_increment_walks_the_newest_value_up_and_down() {
21879        let mut f = Fixture::new();
21880        assert_eq!(
21881            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
21882            ":100\r\n"
21883        );
21884        assert_eq!(
21885            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
21886            ":100\r\n"
21887        );
21888        // Two on one timestamp add up rather than collide, because the sample
21889        // goes in under the last policy whatever the series says.
21890        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
21891        assert_eq!(
21892            f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
21893            ":200\r\n"
21894        );
21895        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
21896        // A timestamp behind the newest sample is the other of the two errors
21897        // the module writes with no ERR in front of it.
21898        assert_eq!(
21899            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
21900            "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
21901        );
21902        // The increment goes through the ordinary number reader, so it takes
21903        // what a sample value will not and refuses a NaN that a sample value
21904        // takes.
21905        assert_eq!(
21906            f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
21907            ":1\r\n"
21908        );
21909        assert_eq!(
21910            f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
21911            ":1\r\n"
21912        );
21913        assert_eq!(
21914            f.run(&[b"TS.INCRBY", b"t", b"nan"]),
21915            "-ERR TSDB: invalid increase/decrease value\r\n"
21916        );
21917        assert_eq!(
21918            f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
21919            "-ERR TSDB: invalid increase/decrease value\r\n"
21920        );
21921        // A key holding something else is WRONGTYPE and is answered before the
21922        // number is looked at.
21923        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
21924        assert_eq!(
21925            f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
21926            "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21927        );
21928        // A TIMESTAMP keyword with nothing behind it is about the timestamp.
21929        // The reference reads one past the end of its own arguments here and
21930        // answers whatever was in that memory, so there is nothing to copy and
21931        // this answers the same thing every time.
21932        assert_eq!(
21933            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
21934            "-ERR TSDB: invalid timestamp\r\n"
21935        );
21936        // And one behind a LABELS is a label name rather than the keyword, so
21937        // this lands at the clock rather than at 5.
21938        assert_eq!(
21939            f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
21940            format!(":{}\r\n", f.server.now_ms())
21941        );
21942        // Adding to a series whose newest value is not a number has no answer.
21943        assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
21944        assert_eq!(
21945            f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
21946            "-ERR TSDB: cannot increment/decrement NaN value\r\n"
21947        );
21948    }
21949
21950    /// Deleting a span, both ends included.
21951    #[test]
21952    fn deleting_takes_out_a_span_and_answers_how_many_went() {
21953        let mut f = Fixture::new();
21954        for at in [b"100".as_slice(), b"200", b"300", b"400"] {
21955            f.run(&[b"TS.ADD", b"t", at, b"1"]);
21956        }
21957        assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
21958        assert!(
21959            f.run(&[b"TS.INFO", b"t"])
21960                .contains("+totalSamples\r\n:2\r\n")
21961        );
21962        // Ends the wrong way round take nothing out rather than being an error.
21963        assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
21964        // The two open ends.
21965        assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
21966        // A series everything has been deleted from keeps its chunk and reports
21967        // zero at both ends again.
21968        let empty = f.run(&[b"TS.INFO", b"t"]);
21969        assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
21970        assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
21971        assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
21972        assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
21973        assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
21974        // The two ends have their own sentences.
21975        assert_eq!(
21976            f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
21977            "-ERR TSDB: wrong fromTimestamp\r\n"
21978        );
21979        assert_eq!(
21980            f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
21981            "-ERR TSDB: wrong toTimestamp\r\n"
21982        );
21983        assert_eq!(
21984            f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
21985            "-ERR TSDB: wrong fromTimestamp\r\n"
21986        );
21987    }
21988
21989    /// What RESP3 changes, which is the two places a number is written and the
21990    /// shape of `TS.INFO`.
21991    #[test]
21992    fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
21993        let mut f = Fixture::new();
21994        f.out = Out::new(Proto::Resp3);
21995        assert_eq!(
21996            f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
21997            "+OK\r\n"
21998        );
21999        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
22000        // A double rather than the simple string RESP2 gets.
22001        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
22002        assert_eq!(
22003            without_memory(&f.run(&[b"TS.INFO", b"t"])),
22004            "%14\r\n\
22005             +totalSamples\r\n:1\r\n\
22006             +memoryUsage\r\n:\r\n\
22007             +firstTimestamp\r\n:100\r\n\
22008             +lastTimestamp\r\n:100\r\n\
22009             +retentionTime\r\n:0\r\n\
22010             +chunkCount\r\n:1\r\n\
22011             +chunkSize\r\n:4096\r\n\
22012             +chunkType\r\n+compressed\r\n\
22013             +duplicatePolicy\r\n+block\r\n\
22014             +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
22015             +sourceKey\r\n_\r\n\
22016             +rules\r\n%0\r\n\
22017             +ignoreMaxTimeDiff\r\n:0\r\n\
22018             +ignoreMaxValDiff\r\n,0\r\n"
22019        );
22020    }
22021
22022    /// Reading a span back, both ways round, with the two ends and the three
22023    /// things that trim what comes out.
22024    #[test]
22025    fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
22026        let mut f = Fixture::new();
22027        for (at, v) in [
22028            (b"100".as_slice(), b"1".as_slice()),
22029            (b"200", b"2"),
22030            (b"300", b"3"),
22031            (b"400", b"4"),
22032        ] {
22033            f.run(&[b"TS.ADD", b"t", at, v]);
22034        }
22035        assert_eq!(
22036            f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
22037            "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
22038             *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
22039        );
22040        // Both ends are included.
22041        assert_eq!(
22042            f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
22043            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
22044        );
22045        // Backwards, and the count takes from the front of what comes out, so
22046        // backwards it takes the newest.
22047        assert_eq!(
22048            f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
22049            "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
22050        );
22051        // Ends the wrong way round are empty rather than an error.
22052        assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
22053        // The two filters.
22054        assert_eq!(
22055            f.run(&[
22056                b"TS.RANGE",
22057                b"t",
22058                b"-",
22059                b"+",
22060                b"FILTER_BY_VALUE",
22061                b"2",
22062                b"3"
22063            ]),
22064            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
22065        );
22066        assert_eq!(
22067            f.run(&[
22068                b"TS.RANGE",
22069                b"t",
22070                b"-",
22071                b"+",
22072                b"FILTER_BY_TS",
22073                b"100",
22074                b"400"
22075            ]),
22076            "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
22077        );
22078        // A word that is not an option is ignored wherever it sits.
22079        assert_eq!(
22080            f.run(&[
22081                b"TS.RANGE",
22082                b"t",
22083                b"-",
22084                b"+",
22085                b"ZZZ",
22086                b"FILTER_BY_TS",
22087                b"400"
22088            ]),
22089            "*1\r\n*2\r\n:400\r\n+4\r\n"
22090        );
22091        // `LATEST` means nothing until there is a compaction rule to follow.
22092        assert_eq!(
22093            f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
22094            "*1\r\n*2\r\n:100\r\n+1\r\n"
22095        );
22096    }
22097
22098    /// The bucketing, which is one column a reduction and a flat row.
22099    #[test]
22100    fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
22101        let mut f = Fixture::new();
22102        for (at, v) in [
22103            (b"100".as_slice(), b"1".as_slice()),
22104            (b"200", b"2"),
22105            (b"300", b"3"),
22106            (b"400", b"4"),
22107        ] {
22108            f.run(&[b"TS.ADD", b"t", at, v]);
22109        }
22110        assert_eq!(
22111            f.run(&[
22112                b"TS.RANGE",
22113                b"t",
22114                b"-",
22115                b"+",
22116                b"AGGREGATION",
22117                b"avg",
22118                b"200"
22119            ]),
22120            "*3\r\n*2\r\n:0\r\n+1\r\n*2\r\n:200\r\n+2.5\r\n*2\r\n:400\r\n+4\r\n"
22121        );
22122        // Three reductions is a row of four and not a row of two with a nested
22123        // three in it.
22124        assert_eq!(
22125            f.run(&[
22126                b"TS.RANGE",
22127                b"t",
22128                b"-",
22129                b"+",
22130                b"AGGREGATION",
22131                b"min,max,count",
22132                b"200"
22133            ]),
22134            "*3\r\n\
22135             *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
22136             *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
22137             *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
22138        );
22139        // The timestamp a bucket is reported under.
22140        assert_eq!(
22141            f.run(&[
22142                b"TS.RANGE",
22143                b"t",
22144                b"-",
22145                b"+",
22146                b"AGGREGATION",
22147                b"avg",
22148                b"200",
22149                b"BUCKETTIMESTAMP",
22150                b"+"
22151            ]),
22152            "*3\r\n*2\r\n:200\r\n+1\r\n*2\r\n:400\r\n+2.5\r\n*2\r\n:600\r\n+4\r\n"
22153        );
22154        // An alignment moves where the bucket edges land.
22155        assert_eq!(
22156            f.run(&[
22157                b"TS.RANGE",
22158                b"t",
22159                b"100",
22160                b"400",
22161                b"ALIGN",
22162                b"100",
22163                b"AGGREGATION",
22164                b"sum",
22165                b"200"
22166            ]),
22167            "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
22168        );
22169        // A `COUNT` sitting where the reduction name belongs is that name, and
22170        // the scan for a real one starts again two words later.
22171        assert_eq!(
22172            f.run(&[
22173                b"TS.RANGE",
22174                b"t",
22175                b"-",
22176                b"+",
22177                b"AGGREGATION",
22178                b"count",
22179                b"200"
22180            ]),
22181            "*3\r\n*2\r\n:0\r\n+1\r\n*2\r\n:200\r\n+2\r\n*2\r\n:400\r\n+1\r\n"
22182        );
22183        assert_eq!(
22184            f.run(&[
22185                b"TS.RANGE",
22186                b"t",
22187                b"-",
22188                b"+",
22189                b"AGGREGATION",
22190                b"count",
22191                b"200",
22192                b"COUNT",
22193                b"1"
22194            ]),
22195            "*1\r\n*2\r\n:0\r\n+1\r\n"
22196        );
22197    }
22198
22199    /// `EMPTY` fills the gaps between readings and nothing else, and `last`
22200    /// carries two different things depending on which kind of empty it is.
22201    #[test]
22202    fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
22203        let mut f = Fixture::new();
22204        for (at, v) in [
22205            (b"0".as_slice(), b"1".as_slice()),
22206            (b"100", b"2"),
22207            (b"500", b"nan"),
22208            (b"600", b"3"),
22209        ] {
22210            f.run(&[b"TS.ADD", b"g", at, v]);
22211        }
22212        // Without `EMPTY` the buckets with nothing in them are not there at all,
22213        // and neither is the one holding only a reading that is not a number.
22214        assert_eq!(
22215            f.run(&[
22216                b"TS.RANGE",
22217                b"g",
22218                b"-",
22219                b"+",
22220                b"AGGREGATION",
22221                b"avg",
22222                b"100"
22223            ]),
22224            "*3\r\n*2\r\n:0\r\n+1\r\n*2\r\n:100\r\n+2\r\n*2\r\n:600\r\n+3\r\n"
22225        );
22226        // The sum of nothing is zero rather than not a number.
22227        assert_eq!(
22228            f.run(&[
22229                b"TS.RANGE",
22230                b"g",
22231                b"-",
22232                b"+",
22233                b"AGGREGATION",
22234                b"sum",
22235                b"100",
22236                b"EMPTY"
22237            ]),
22238            "*7\r\n*2\r\n:0\r\n+1\r\n*2\r\n:100\r\n+2\r\n*2\r\n:200\r\n+0\r\n\
22239             *2\r\n:300\r\n+0\r\n*2\r\n:400\r\n+0\r\n*2\r\n:500\r\n+0\r\n\
22240             *2\r\n:600\r\n+3\r\n"
22241        );
22242        // Buckets 200 through 400 have no readings at all and carry the reading
22243        // before the gap either way round. Bucket 500 has a reading that is not
22244        // a number, so it carries whatever the bucket before it in the reading
22245        // direction answered, which is 2 forwards and 3 backwards.
22246        assert_eq!(
22247            f.run(&[
22248                b"TS.RANGE",
22249                b"g",
22250                b"-",
22251                b"+",
22252                b"AGGREGATION",
22253                b"last",
22254                b"100",
22255                b"EMPTY"
22256            ]),
22257            "*7\r\n*2\r\n:0\r\n+1\r\n*2\r\n:100\r\n+2\r\n*2\r\n:200\r\n+2\r\n\
22258             *2\r\n:300\r\n+2\r\n*2\r\n:400\r\n+2\r\n*2\r\n:500\r\n+2\r\n\
22259             *2\r\n:600\r\n+3\r\n"
22260        );
22261        assert_eq!(
22262            f.run(&[
22263                b"TS.REVRANGE",
22264                b"g",
22265                b"-",
22266                b"+",
22267                b"AGGREGATION",
22268                b"last",
22269                b"100",
22270                b"EMPTY"
22271            ]),
22272            "*7\r\n*2\r\n:600\r\n+3\r\n*2\r\n:500\r\n+3\r\n*2\r\n:400\r\n+2\r\n\
22273             *2\r\n:300\r\n+2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:100\r\n+2\r\n\
22274             *2\r\n:0\r\n+1\r\n"
22275        );
22276        // And a window that opens on that bucket has nothing in range before it
22277        // to carry, so it answers not a number.
22278        assert_eq!(
22279            f.run(&[
22280                b"TS.RANGE",
22281                b"g",
22282                b"500",
22283                b"600",
22284                b"AGGREGATION",
22285                b"last",
22286                b"100",
22287                b"EMPTY"
22288            ]),
22289            "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
22290        );
22291    }
22292
22293    /// The sentences a read answers when its options do not add up, which are
22294    /// the module's own word for word.
22295    #[test]
22296    fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
22297        let mut f = Fixture::new();
22298        f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
22299        f.run(&[b"SET", b"str", b"x"]);
22300        let cases: &[(&[&[u8]], &str)] = &[
22301            (
22302                &[b"TS.RANGE", b"t"],
22303                "-ERR wrong number of arguments for 'ts.range' command\r\n",
22304            ),
22305            // The key is resolved before a single option is read.
22306            (
22307                &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
22308                "-ERR TSDB: the key does not exist\r\n",
22309            ),
22310            (
22311                &[b"TS.RANGE", b"str", b"-", b"+"],
22312                "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
22313            ),
22314            (
22315                &[b"TS.RANGE", b"t", b"abc", b"+"],
22316                "-ERR TSDB: wrong fromTimestamp\r\n",
22317            ),
22318            (
22319                &[b"TS.RANGE", b"t", b"-", b"abc"],
22320                "-ERR TSDB: wrong toTimestamp\r\n",
22321            ),
22322            (
22323                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
22324                "-ERR TSDB: COUNT argument is missing\r\n",
22325            ),
22326            (
22327                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
22328                "-ERR TSDB: Couldn't parse COUNT\r\n",
22329            ),
22330            (
22331                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
22332                "-ERR TSDB: Invalid COUNT value\r\n",
22333            ),
22334            (
22335                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
22336                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22337            ),
22338            (
22339                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
22340                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22341            ),
22342            (
22343                &[
22344                    b"TS.RANGE",
22345                    b"t",
22346                    b"-",
22347                    b"+",
22348                    b"AGGREGATION",
22349                    b"nope",
22350                    b"100",
22351                ],
22352                "-ERR TSDB: Unknown aggregation type\r\n",
22353            ),
22354            (
22355                &[
22356                    b"TS.RANGE",
22357                    b"t",
22358                    b"-",
22359                    b"+",
22360                    b"AGGREGATION",
22361                    b"avg,,min",
22362                    b"100",
22363                ],
22364                "-ERR TSDB: Empty aggregation type in list\r\n",
22365            ),
22366            // The list of names is read before the width is looked at.
22367            (
22368                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
22369                "-ERR TSDB: Unknown aggregation type\r\n",
22370            ),
22371            (
22372                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
22373                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
22374            ),
22375            (
22376                &[
22377                    b"TS.RANGE",
22378                    b"t",
22379                    b"-",
22380                    b"+",
22381                    b"AGGREGATION",
22382                    b"avg",
22383                    b"100",
22384                    b"X",
22385                    b"EMPTY",
22386                ],
22387                "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
22388            ),
22389            (
22390                &[
22391                    b"TS.RANGE",
22392                    b"t",
22393                    b"-",
22394                    b"+",
22395                    b"AGGREGATION",
22396                    b"avg",
22397                    b"100",
22398                    b"BUCKETTIMESTAMP",
22399                    b"z",
22400                ],
22401                "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
22402            ),
22403            (
22404                &[
22405                    b"TS.RANGE",
22406                    b"t",
22407                    b"-",
22408                    b"+",
22409                    b"AGGREGATION",
22410                    b"avg",
22411                    b"100",
22412                    b"X",
22413                    b"Y",
22414                    b"BUCKETTIMESTAMP",
22415                    b"-",
22416                ],
22417                "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
22418                 AGGREGATION flag\r\n",
22419            ),
22420            (
22421                &[
22422                    b"TS.RANGE",
22423                    b"t",
22424                    b"-",
22425                    b"+",
22426                    b"ALIGN",
22427                    b"z",
22428                    b"AGGREGATION",
22429                    b"avg",
22430                    b"100",
22431                ],
22432                "-ERR TSDB: unknown ALIGN parameter\r\n",
22433            ),
22434            (
22435                &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
22436                "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
22437            ),
22438            (
22439                &[
22440                    b"TS.RANGE",
22441                    b"t",
22442                    b"-",
22443                    b"+",
22444                    b"ALIGN",
22445                    b"-",
22446                    b"AGGREGATION",
22447                    b"avg",
22448                    b"100",
22449                ],
22450                "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
22451            ),
22452            (
22453                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
22454                "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
22455            ),
22456            (
22457                &[
22458                    b"TS.RANGE",
22459                    b"t",
22460                    b"-",
22461                    b"+",
22462                    b"FILTER_BY_VALUE",
22463                    b"x",
22464                    b"2",
22465                ],
22466                "-ERR TSDB: Couldn't parse MIN\r\n",
22467            ),
22468            (
22469                &[
22470                    b"TS.RANGE",
22471                    b"t",
22472                    b"-",
22473                    b"+",
22474                    b"FILTER_BY_VALUE",
22475                    b"1",
22476                    b"y",
22477                ],
22478                "-ERR TSDB: Couldn't parse MAX\r\n",
22479            ),
22480            (
22481                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
22482                "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
22483            ),
22484        ];
22485        for (argv, want) in cases {
22486            let got = f.run(argv);
22487            assert_eq!(&got, want, "{:?}", argv.last());
22488        }
22489        // The one sentence here that is yo's own rather than the module's, which
22490        // is D-54. A read that would build more rows than yo will build is
22491        // refused instead of attempted.
22492        f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
22493        f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
22494        assert_eq!(
22495            f.run(&[
22496                b"TS.RANGE",
22497                b"wide",
22498                b"-",
22499                b"+",
22500                b"AGGREGATION",
22501                b"avg",
22502                b"1",
22503                b"EMPTY"
22504            ]),
22505            "-ERR TSDB: the requested range holds too many empty buckets\r\n"
22506        );
22507    }
22508
22509    /// What RESP3 changes on a read, which is only how a number is written.
22510    #[test]
22511    fn resp3_writes_a_read_value_as_a_double() {
22512        let mut f = Fixture::new();
22513        f.out = Out::new(Proto::Resp3);
22514        for (at, v) in [
22515            (b"0".as_slice(), b"1".as_slice()),
22516            (b"100", b"2"),
22517            (b"500", b"nan"),
22518            (b"600", b"3"),
22519        ] {
22520            f.run(&[b"TS.ADD", b"g", at, v]);
22521        }
22522        assert_eq!(
22523            f.run(&[
22524                b"TS.RANGE",
22525                b"g",
22526                b"0",
22527                b"100",
22528                b"AGGREGATION",
22529                b"avg,min",
22530                b"200"
22531            ]),
22532            "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
22533        );
22534        assert_eq!(
22535            f.run(&[
22536                b"TS.RANGE",
22537                b"g",
22538                b"500",
22539                b"600",
22540                b"AGGREGATION",
22541                b"last",
22542                b"100",
22543                b"EMPTY"
22544            ]),
22545            "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
22546        );
22547    }
22548
22549    /// Two series with an overlap and a gap each, plus a third holding nothing,
22550    /// which is what the joined reads are measured against.
22551    fn joined() -> Fixture {
22552        let mut f = Fixture::new();
22553        f.run(&[b"TS.CREATE", b"z"]);
22554        for (at, v) in [
22555            (b"10".as_slice(), b"1".as_slice()),
22556            (b"20", b"2"),
22557            (b"40", b"4"),
22558            (b"50", b"5"),
22559        ] {
22560            f.run(&[b"TS.ADD", b"x", at, v]);
22561        }
22562        for (at, v) in [
22563            (b"20".as_slice(), b"20".as_slice()),
22564            (b"30", b"30"),
22565            (b"50", b"50"),
22566            (b"60", b"60"),
22567        ] {
22568            f.run(&[b"TS.ADD", b"y", at, v]);
22569        }
22570        f
22571    }
22572
22573    /// The joined read lines its keys up on the timestamp and writes a row as
22574    /// the timestamp and then a nested array of the columns, which is the one
22575    /// shape in the family that is not the flat pair.
22576    #[test]
22577    fn an_nrange_joins_its_keys_on_the_timestamp() {
22578        let mut f = joined();
22579        // One key still nests, so the shape does not depend on the count.
22580        assert_eq!(
22581            f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
22582            "*4\r\n*2\r\n:10\r\n*1\r\n+1\r\n*2\r\n:20\r\n*1\r\n+2\r\n\
22583             *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
22584        );
22585        // A key with no reading where another key has one writes NaN there.
22586        assert_eq!(
22587            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
22588            "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
22589             *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
22590             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
22591             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
22592             *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
22593             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22594        );
22595        // A series holding nothing is a column of NaN and never a row of its
22596        // own, and the same key twice answers twice.
22597        assert_eq!(
22598            f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
22599            "*2\r\n*2\r\n:20\r\n*2\r\n+2\r\n+NaN\r\n*2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n"
22600        );
22601        assert_eq!(
22602            f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
22603            "*2\r\n*2\r\n:40\r\n*2\r\n+4\r\n+4\r\n*2\r\n:50\r\n*2\r\n+5\r\n+5\r\n"
22604        );
22605        // COUNT is applied to the joined rows and not to each key, so backwards
22606        // it gives the newest joined row rather than the newest of each.
22607        assert_eq!(
22608            f.run(&[
22609                b"TS.NREVRANGE",
22610                b"2",
22611                b"x",
22612                b"y",
22613                b"-",
22614                b"+",
22615                b"COUNT",
22616                b"1"
22617            ]),
22618            "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22619        );
22620        assert_eq!(
22621            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
22622            "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
22623        );
22624        // The two sample filters are settled a key at a time, before the join.
22625        assert_eq!(
22626            f.run(&[
22627                b"TS.NRANGE",
22628                b"2",
22629                b"x",
22630                b"y",
22631                b"-",
22632                b"+",
22633                b"FILTER_BY_VALUE",
22634                b"2",
22635                b"30"
22636            ]),
22637            "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
22638             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
22639             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
22640             *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
22641        );
22642    }
22643
22644    /// The aggregation on a joined read names one reduction a key and then the
22645    /// one bucket width, and each name may be a comma list, so a row can be
22646    /// wider than the key count.
22647    #[test]
22648    fn an_nrange_aggregation_names_one_reduction_a_key() {
22649        let mut f = joined();
22650        assert_eq!(
22651            f.run(&[
22652                b"TS.NRANGE",
22653                b"2",
22654                b"x",
22655                b"y",
22656                b"-",
22657                b"+",
22658                b"AGGREGATION",
22659                b"sum",
22660                b"sum",
22661                b"20"
22662            ]),
22663            "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
22664             *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
22665             *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
22666             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
22667        );
22668        // A comma list on the first key widens the row to three columns.
22669        assert_eq!(
22670            f.run(&[
22671                b"TS.NRANGE",
22672                b"2",
22673                b"x",
22674                b"y",
22675                b"-",
22676                b"+",
22677                b"AGGREGATION",
22678                b"sum,count",
22679                b"avg",
22680                b"20"
22681            ]),
22682            "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
22683             *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
22684             *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
22685             *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
22686        );
22687        // Everything behind the width moves along with it, so BUCKETTIMESTAMP
22688        // sits one or two past the width whatever the key count is.
22689        assert_eq!(
22690            f.run(&[
22691                b"TS.NRANGE",
22692                b"2",
22693                b"x",
22694                b"y",
22695                b"-",
22696                b"+",
22697                b"AGGREGATION",
22698                b"avg",
22699                b"sum",
22700                b"100",
22701                b"EMPTY",
22702                b"BUCKETTIMESTAMP",
22703                b"end"
22704            ]),
22705            "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
22706        );
22707        // A COUNT landing in one of the name slots is a reduction name and not
22708        // the keyword, and the read then has no count at all.
22709        assert_eq!(
22710            f.run(&[
22711                b"TS.NRANGE",
22712                b"2",
22713                b"x",
22714                b"y",
22715                b"-",
22716                b"+",
22717                b"AGGREGATION",
22718                b"avg",
22719                b"COUNT",
22720                b"100"
22721            ]),
22722            "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
22723        );
22724    }
22725
22726    /// The sentences a joined read answers when it does not add up, which are
22727    /// the module's own and come out in the module's own order.
22728    #[test]
22729    fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
22730        let mut f = joined();
22731        f.run(&[b"SET", b"str", b"hi"]);
22732        let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
22733        let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
22734                       must be equal to numkeys\r\n";
22735        let cases: &[(&[&[u8]], &str)] = &[
22736            (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
22737            (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
22738            (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
22739            // Not enough words behind the count for the keys and both ends of
22740            // the span, which is an arity error however many keys were named.
22741            (
22742                &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
22743                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
22744            ),
22745            (
22746                &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
22747                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
22748            ),
22749            // The reduction names are read before the two ends of the span,
22750            // which no other option is.
22751            (
22752                &[
22753                    b"TS.NRANGE",
22754                    b"2",
22755                    b"x",
22756                    b"y",
22757                    b"abc",
22758                    b"+",
22759                    b"AGGREGATION",
22760                    b"nope",
22761                    b"sum",
22762                    b"100",
22763                ],
22764                "-ERR TSDB: Unknown aggregation type\r\n",
22765            ),
22766            (
22767                &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
22768                "-ERR TSDB: wrong fromTimestamp\r\n",
22769            ),
22770            (
22771                &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
22772                "-ERR TSDB: wrong toTimestamp\r\n",
22773            ),
22774            // A name slot that is missing or holds a number is the count
22775            // sentence, and a width slot that is itself a reduction name is
22776            // that sentence as well.
22777            (
22778                &[
22779                    b"TS.NRANGE",
22780                    b"2",
22781                    b"x",
22782                    b"y",
22783                    b"-",
22784                    b"+",
22785                    b"AGGREGATION",
22786                    b"avg",
22787                ],
22788                numkeys,
22789            ),
22790            (
22791                &[
22792                    b"TS.NRANGE",
22793                    b"2",
22794                    b"x",
22795                    b"y",
22796                    b"-",
22797                    b"+",
22798                    b"AGGREGATION",
22799                    b"100",
22800                    b"sum",
22801                    b"100",
22802                ],
22803                numkeys,
22804            ),
22805            (
22806                &[
22807                    b"TS.NRANGE",
22808                    b"2",
22809                    b"x",
22810                    b"y",
22811                    b"-",
22812                    b"+",
22813                    b"AGGREGATION",
22814                    b"avg",
22815                    b"sum",
22816                    b"sum",
22817                    b"100",
22818                ],
22819                numkeys,
22820            ),
22821            (
22822                &[
22823                    b"TS.NRANGE",
22824                    b"2",
22825                    b"x",
22826                    b"y",
22827                    b"-",
22828                    b"+",
22829                    b"AGGREGATION",
22830                    b"avg",
22831                    b"sum",
22832                    b"abc",
22833                ],
22834                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22835            ),
22836            (
22837                &[
22838                    b"TS.NRANGE",
22839                    b"2",
22840                    b"x",
22841                    b"y",
22842                    b"-",
22843                    b"+",
22844                    b"AGGREGATION",
22845                    b"avg",
22846                    b"sum",
22847                    b"0",
22848                ],
22849                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
22850            ),
22851            // With one key none of that applies and the plain parser runs, so a
22852            // lone width is a missing width rather than a count mismatch.
22853            (
22854                &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
22855                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
22856            ),
22857            (
22858                &[
22859                    b"TS.NRANGE",
22860                    b"1",
22861                    b"x",
22862                    b"-",
22863                    b"+",
22864                    b"AGGREGATION",
22865                    b"100",
22866                    b"200",
22867                ],
22868                "-ERR TSDB: Unknown aggregation type\r\n",
22869            ),
22870            // The keys come last and in the order they were named.
22871            (
22872                &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
22873                "-ERR TSDB: the key does not exist\r\n",
22874            ),
22875            (
22876                &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
22877                "-ERR WRONGTYPE Operation against a key \
22878                 holding the wrong kind of value\r\n",
22879            ),
22880        ];
22881        for (argv, want) in cases {
22882            let got = f.run(argv);
22883            assert_eq!(&got, want, "{argv:?}");
22884        }
22885    }
22886
22887    /// `TS.READ`, which is a key, one timestamp and everything from there on.
22888    #[test]
22889    fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
22890        let mut f = joined();
22891        assert_eq!(
22892            f.run(&[b"TS.READ", b"x", b"-"]),
22893            "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
22894             *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
22895        );
22896        // A plus is the last sample on its own, and a timestamp between two
22897        // samples starts at the one behind it.
22898        assert_eq!(
22899            f.run(&[b"TS.READ", b"x", b"+"]),
22900            "*1\r\n*2\r\n:50\r\n+5\r\n"
22901        );
22902        assert_eq!(
22903            f.run(&[b"TS.READ", b"x", b"25"]),
22904            "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
22905        );
22906        // Past the end, a series holding nothing and a key that is not there
22907        // are all the empty array rather than an error.
22908        assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
22909        assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
22910        assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
22911        assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
22912        // The timestamp refusal goes out with nothing in front of it, and a key
22913        // holding something else answers the bare WRONGTYPE rather than the
22914        // module's prefixed one, both unlike the rest of the family.
22915        assert_eq!(
22916            f.run(&[b"TS.READ", b"x", b"abc"]),
22917            "-TSDB: invalid timestamp\r\n"
22918        );
22919        assert_eq!(
22920            f.run(&[b"TS.READ", b"x", b"-1"]),
22921            "-TSDB: invalid timestamp\r\n"
22922        );
22923        f.run(&[b"SET", b"str", b"hi"]);
22924        assert_eq!(
22925            f.run(&[b"TS.READ", b"str", b"-"]),
22926            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22927        );
22928        // Anything other than exactly three words is an arity error, so there
22929        // is nowhere to put an option even though the table says minus three.
22930        assert_eq!(
22931            f.run(&[b"TS.READ", b"x"]),
22932            "-ERR wrong number of arguments for 'ts.read' command\r\n"
22933        );
22934        assert_eq!(
22935            f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
22936            "-ERR wrong number of arguments for 'ts.read' command\r\n"
22937        );
22938    }
22939
22940    /// The keys of a joined read sit behind a count, so `COMMAND GETKEYS` has
22941    /// to read the count to find them.
22942    #[test]
22943    fn getkeys_reads_the_count_of_a_joined_read() {
22944        let mut f = Fixture::new();
22945        assert_eq!(
22946            f.run(&[
22947                b"COMMAND",
22948                b"GETKEYS",
22949                b"TS.NRANGE",
22950                b"2",
22951                b"a",
22952                b"b",
22953                b"-",
22954                b"+"
22955            ]),
22956            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
22957        );
22958        assert_eq!(
22959            f.run(&[
22960                b"COMMAND",
22961                b"GETKEYS",
22962                b"TS.NREVRANGE",
22963                b"1",
22964                b"a",
22965                b"-",
22966                b"+"
22967            ]),
22968            "*1\r\n$1\r\na\r\n"
22969        );
22970        // A count of zero, or one too large for the words that follow it, is
22971        // the server's own refusal and not the module's.
22972        for n in [b"0".as_slice(), b"9", b"abc"] {
22973            assert_eq!(
22974                f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
22975                "-ERR Invalid arguments specified for command\r\n"
22976            );
22977        }
22978    }
22979
22980    /// The five series every test of the label surface works against.
22981    fn labelled() -> Fixture {
22982        let mut f = Fixture::new();
22983        f.run(&[
22984            b"TS.CREATE",
22985            b"a",
22986            b"LABELS",
22987            b"room",
22988            b"kitchen",
22989            b"x",
22990            b"1",
22991        ]);
22992        f.run(&[
22993            b"TS.CREATE",
22994            b"b",
22995            b"LABELS",
22996            b"room",
22997            b"bedroom",
22998            b"x",
22999            b"2",
23000        ]);
23001        f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
23002        f.run(&[b"TS.CREATE", b"d"]);
23003        f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
23004        f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
23005        f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
23006        f
23007    }
23008
23009    /// The filter grammar, which is four steps and a `strtok` rather than a
23010    /// grammar, and which every command that searches on labels shares.
23011    #[test]
23012    fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
23013        let mut f = labelled();
23014        let cases: &[(&[&[u8]], &str)] = &[
23015            // The plain forms, and the order the answer comes back in, which is
23016            // by key name and not by anything the series remembers.
23017            (
23018                &[b"TS.QUERYINDEX", b"room=kitchen"],
23019                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
23020            ),
23021            (
23022                &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
23023                "*1\r\n$1\r\na\r\n",
23024            ),
23025            (
23026                &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
23027                "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
23028            ),
23029            // An empty list still counts as something that says which series to
23030            // take, it just never takes any.
23031            (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
23032            // Absent and present, neither of which stands on its own.
23033            (
23034                &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
23035                "*1\r\n$1\r\nc\r\n",
23036            ),
23037            (
23038                &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
23039                "*1\r\n$1\r\na\r\n",
23040            ),
23041            (
23042                &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
23043                "-ERR TSDB: please provide at least one matcher\r\n",
23044            ),
23045            // A run of separators is one separator and everything past the
23046            // second field is dropped, so all three of these ask one question.
23047            (
23048                &[b"TS.QUERYINDEX", b"room==kitchen"],
23049                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
23050            ),
23051            (
23052                &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
23053                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
23054            ),
23055            (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
23056            // A bracket is only a list when it sits straight behind the
23057            // separator, and then the label in front of it has to be there.
23058            (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
23059            (
23060                &[b"TS.QUERYINDEX", b"=(1)"],
23061                "-ERR TSDB: failed parsing labels\r\n",
23062            ),
23063            (
23064                &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
23065                "-ERR TSDB: failed parsing labels\r\n",
23066            ),
23067            (
23068                &[b"TS.QUERYINDEX", b"room=(kitchen"],
23069                "-ERR TSDB: failed parsing labels\r\n",
23070            ),
23071            (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
23072            (
23073                &[b"TS.QUERYINDEX", b"nonsense"],
23074                "-ERR TSDB: failed parsing labels\r\n",
23075            ),
23076            // Nothing here says which series to take.
23077            (
23078                &[b"TS.QUERYINDEX", b"room!=kitchen"],
23079                "-ERR TSDB: please provide at least one matcher\r\n",
23080            ),
23081            // Names and values are both compared byte for byte.
23082            (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
23083            (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
23084            (
23085                &[b"TS.QUERYINDEX"],
23086                "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
23087            ),
23088        ];
23089        for (argv, want) in cases {
23090            let got = f.run(argv);
23091            assert_eq!(&got, want, "{:?}", argv.last());
23092        }
23093    }
23094
23095    /// `TS.QUERYLABELS`, whose filter is the one that is allowed to be missing.
23096    #[test]
23097    fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
23098        let mut f = labelled();
23099        let cases: &[(&[&[u8]], &str)] = &[
23100            (
23101                &[b"TS.QUERYLABELS", b"LABELS"],
23102                "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
23103            ),
23104            (
23105                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
23106                "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
23107            ),
23108            (
23109                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
23110                "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
23111            ),
23112            // The series wearing `r` twice contributes the smaller of the two
23113            // here, which is not the one it was written down as first.
23114            (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
23115            (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
23116            (
23117                &[b"TS.QUERYLABELS", b"VALUES"],
23118                "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
23119            ),
23120            (
23121                &[b"TS.QUERYLABELS", b"ZZZ"],
23122                "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
23123            ),
23124            (
23125                &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
23126                "-ERR TSDB: unknown argument, expected FILTER\r\n",
23127            ),
23128            (
23129                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
23130                "-ERR TSDB: FILTER given with no filter expressions\r\n",
23131            ),
23132            // With no filter at all every series is taken, which is why the
23133            // first case here answers about `r` as well. A filter that is there
23134            // still has to say which series to take.
23135            (
23136                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
23137                "-ERR TSDB: please provide at least one matcher\r\n",
23138            ),
23139            (
23140                &[
23141                    b"TS.QUERYLABELS",
23142                    b"LABELS",
23143                    b"FILTER",
23144                    b"room=kitchen",
23145                    b"x=",
23146                ],
23147                "*1\r\n$4\r\nroom\r\n",
23148            ),
23149        ];
23150        for (argv, want) in cases {
23151            let got = f.run(argv);
23152            assert_eq!(&got, want, "{:?}", argv.last());
23153        }
23154    }
23155
23156    /// `TS.MGET`, the newest sample of every series a filter takes, and the two
23157    /// ways of asking for the labels back alongside it.
23158    #[test]
23159    fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
23160        let mut f = labelled();
23161        let cases: &[(&[&[u8]], &str)] = &[
23162            // A series with no samples writes an empty array where the sample
23163            // goes rather than dropping out of the reply.
23164            (
23165                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
23166                "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
23167                 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
23168            ),
23169            (
23170                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
23171                "*2\r\n*3\r\n$1\r\na\r\n*2\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23172                 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
23173                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
23174            ),
23175            // A selected label the series does not wear is a nil, not a gap.
23176            (
23177                &[
23178                    b"TS.MGET",
23179                    b"SELECTED_LABELS",
23180                    b"x",
23181                    b"FILTER",
23182                    b"room=kitchen",
23183                ],
23184                "*2\r\n*3\r\n$1\r\na\r\n*1\r\n*2\r\n$1\r\nx\r\n$1\r\n1\r\n\
23185                 *2\r\n:100\r\n+1.5\r\n\
23186                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n*0\r\n",
23187            ),
23188            // The other half of the duplicated name rule. This one takes the
23189            // first written down where `TS.QUERYLABELS` takes the smallest.
23190            (
23191                &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
23192                "*1\r\n*3\r\n$1\r\ne\r\n*1\r\n*2\r\n$1\r\nr\r\n$2\r\nbb\r\n*0\r\n",
23193            ),
23194            (
23195                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
23196                "*1\r\n*3\r\n$1\r\ne\r\n*2\r\n*2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
23197                 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
23198            ),
23199            // A word that is not an option is ignored, but a missing `FILTER`
23200            // is an arity error whatever else was written.
23201            (
23202                &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
23203                "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
23204            ),
23205            (
23206                &[b"TS.MGET", b"a", b"b", b"c"],
23207                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
23208            ),
23209            (
23210                &[b"TS.MGET", b"FILTER"],
23211                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
23212            ),
23213            // Both keyword checks happen before the filter is read, and the two
23214            // sentences spell the second keyword without its `ED`.
23215            (
23216                &[
23217                    b"TS.MGET",
23218                    b"WITHLABELS",
23219                    b"SELECTED_LABELS",
23220                    b"x",
23221                    b"FILTER",
23222                    b"bad",
23223                ],
23224                "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
23225            ),
23226            (
23227                &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
23228                "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
23229            ),
23230        ];
23231        for (argv, want) in cases {
23232            let got = f.run(argv);
23233            assert_eq!(&got, want, "{:?}", argv.last());
23234        }
23235    }
23236
23237    /// What RESP3 changes across the label surface, which is a set where there
23238    /// was an array and a map where there was a pair of them.
23239    #[test]
23240    fn resp3_writes_the_label_surface_as_sets_and_maps() {
23241        let mut f = labelled();
23242        f.out = Out::new(Proto::Resp3);
23243        let cases: &[(&[&[u8]], &str)] = &[
23244            (
23245                &[b"TS.QUERYINDEX", b"room=kitchen"],
23246                "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
23247            ),
23248            (
23249                &[b"TS.QUERYLABELS", b"LABELS"],
23250                "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
23251            ),
23252            (
23253                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
23254                "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
23255            ),
23256            // The key stops being the first of three and becomes the map key,
23257            // and the labels stop being pairs and become a map of their own.
23258            (
23259                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
23260                "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
23261                 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
23262            ),
23263            (
23264                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
23265                "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23266                 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
23267                 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
23268            ),
23269            (
23270                &[
23271                    b"TS.MGET",
23272                    b"SELECTED_LABELS",
23273                    b"x",
23274                    b"FILTER",
23275                    b"room=kitchen",
23276                ],
23277                "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
23278                 *2\r\n:100\r\n,1.5\r\n\
23279                 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
23280            ),
23281            // A map with a name in it twice, which is what a series wearing one
23282            // label name twice turns into.
23283            (
23284                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
23285                "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
23286                 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
23287            ),
23288        ];
23289        for (argv, want) in cases {
23290            let got = f.run(argv);
23291            assert_eq!(&got, want, "{:?}", argv.last());
23292        }
23293    }
23294
23295    /// The same five series with enough samples in them for a group to have
23296    /// something to fold.
23297    fn spanned() -> Fixture {
23298        let mut f = labelled();
23299        f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
23300        f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
23301        f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
23302        f
23303    }
23304
23305    /// A span read out of every series a filter takes, with and without a group
23306    /// over the top of it.
23307    #[test]
23308    fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
23309        let mut f = spanned();
23310        let cases: &[(&[&[u8]], &str)] = &[
23311            (
23312                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
23313                "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
23314                 *3\r\n$1\r\nc\r\n*0\r\n*2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
23315            ),
23316            // Newest first is applied to each series before anything else sees
23317            // the rows.
23318            (
23319                &[
23320                    b"TS.MREVRANGE",
23321                    b"-",
23322                    b"+",
23323                    b"WITHLABELS",
23324                    b"FILTER",
23325                    b"room=kitchen",
23326                ],
23327                "*2\r\n*3\r\n$1\r\na\r\n*2\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23328                 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n*2\r\n:200\r\n+2.5\r\n*2\r\n:100\r\n+1.5\r\n\
23329                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23330                 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
23331            ),
23332            // A label a series does not wear comes back against a nil rather
23333            // than being left out.
23334            (
23335                &[
23336                    b"TS.MRANGE",
23337                    b"-",
23338                    b"+",
23339                    b"SELECTED_LABELS",
23340                    b"x",
23341                    b"FILTER",
23342                    b"room=kitchen",
23343                ],
23344                "*2\r\n*3\r\n$1\r\na\r\n*1\r\n*2\r\n$1\r\nx\r\n$1\r\n1\r\n\
23345                 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
23346                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
23347                 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
23348            ),
23349            // The fold: 100 is in both series and adds up, the other two are in
23350            // one each and are still rows.
23351            (
23352                &[
23353                    b"TS.MRANGE",
23354                    b"-",
23355                    b"+",
23356                    b"FILTER",
23357                    b"room=kitchen",
23358                    b"GROUPBY",
23359                    b"room",
23360                    b"REDUCE",
23361                    b"sum",
23362                ],
23363                "*1\r\n*3\r\n$12\r\nroom=kitchen\r\n*0\r\n*3\r\n*2\r\n:100\r\n+11.5\r\n\
23364                 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
23365            ),
23366            // RESP2 has nowhere to put the reducer and the member keys, so a
23367            // group wearing labels writes them as two more labels.
23368            (
23369                &[
23370                    b"TS.MRANGE",
23371                    b"-",
23372                    b"+",
23373                    b"WITHLABELS",
23374                    b"FILTER",
23375                    b"room=kitchen",
23376                    b"GROUPBY",
23377                    b"room",
23378                    b"REDUCE",
23379                    b"max",
23380                ],
23381                "*1\r\n*3\r\n$12\r\nroom=kitchen\r\n*3\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23382                 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
23383                 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
23384                 *3\r\n*2\r\n:100\r\n+10\r\n*2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
23385            ),
23386            // A count is applied to each member and then again to the fold.
23387            (
23388                &[
23389                    b"TS.MREVRANGE",
23390                    b"-",
23391                    b"+",
23392                    b"COUNT",
23393                    b"1",
23394                    b"FILTER",
23395                    b"room=kitchen",
23396                    b"GROUPBY",
23397                    b"room",
23398                    b"REDUCE",
23399                    b"count",
23400                ],
23401                "*1\r\n*3\r\n$12\r\nroom=kitchen\r\n*0\r\n*1\r\n*2\r\n:300\r\n+1\r\n",
23402            ),
23403            // Nothing wears the label, so nothing is in any group.
23404            (
23405                &[
23406                    b"TS.MRANGE",
23407                    b"-",
23408                    b"+",
23409                    b"FILTER",
23410                    b"room=kitchen",
23411                    b"GROUPBY",
23412                    b"nope",
23413                    b"REDUCE",
23414                    b"sum",
23415                ],
23416                "*0\r\n",
23417            ),
23418            (
23419                &[
23420                    b"TS.MRANGE",
23421                    b"-",
23422                    b"+",
23423                    b"AGGREGATION",
23424                    b"sum,avg",
23425                    b"100",
23426                    b"FILTER",
23427                    b"room=bedroom",
23428                ],
23429                "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*1\r\n*3\r\n:200\r\n+2\r\n+2\r\n",
23430            ),
23431            // The errors, in the order they are looked for.
23432            (
23433                &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
23434                "-ERR TSDB: missing FILTER argument\r\n",
23435            ),
23436            (
23437                &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
23438                "-ERR TSDB: missing labels for filter argument\r\n",
23439            ),
23440            (
23441                &[
23442                    b"TS.MRANGE",
23443                    b"-",
23444                    b"+",
23445                    b"GROUPBY",
23446                    b"room",
23447                    b"REDUCE",
23448                    b"sum",
23449                    b"FILTER",
23450                    b"room=kitchen",
23451                ],
23452                "-ERR TSDB: GROUPBY should always come after filter\r\n",
23453            ),
23454            // The group is four words from the end here, so the length is what
23455            // is wrong with it.
23456            (
23457                &[
23458                    b"TS.MRANGE",
23459                    b"-",
23460                    b"+",
23461                    b"FILTER",
23462                    b"room=kitchen",
23463                    b"GROUPBY",
23464                    b"room",
23465                    b"REDUCE",
23466                    b"sum",
23467                    b"x",
23468                ],
23469                "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
23470            ),
23471            // And here it is not, so its words are filters and answer first.
23472            (
23473                &[
23474                    b"TS.MRANGE",
23475                    b"-",
23476                    b"+",
23477                    b"FILTER",
23478                    b"nope",
23479                    b"GROUPBY",
23480                    b"room",
23481                    b"REDUCE",
23482                    b"sum",
23483                    b"x",
23484                ],
23485                "-ERR TSDB: failed parsing labels\r\n",
23486            ),
23487            (
23488                &[
23489                    b"TS.MRANGE",
23490                    b"-",
23491                    b"+",
23492                    b"FILTER",
23493                    b"room=kitchen",
23494                    b"GROUPBY",
23495                    b"room",
23496                    b"REDUCE",
23497                    b"twa",
23498                ],
23499                "-ERR TSDB: Invalid reducer type\r\n",
23500            ),
23501            (
23502                &[
23503                    b"TS.MRANGE",
23504                    b"-",
23505                    b"+",
23506                    b"AGGREGATION",
23507                    b"sum,avg",
23508                    b"100",
23509                    b"FILTER",
23510                    b"room=kitchen",
23511                    b"GROUPBY",
23512                    b"room",
23513                    b"REDUCE",
23514                    b"sum",
23515                ],
23516                "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
23517            ),
23518            // The label list ends at a keyword, so this is a `COUNT` with a
23519            // `FILTER` where its number should be.
23520            (
23521                &[
23522                    b"TS.MRANGE",
23523                    b"-",
23524                    b"+",
23525                    b"SELECTED_LABELS",
23526                    b"COUNT",
23527                    b"FILTER",
23528                    b"room=kitchen",
23529                ],
23530                "-ERR TSDB: Couldn't parse COUNT\r\n",
23531            ),
23532        ];
23533        for (argv, want) in cases {
23534            let got = f.run(argv);
23535            assert_eq!(&got, want, "{argv:?}");
23536        }
23537    }
23538
23539    /// The multi key reads on RESP3, where the key becomes a map key and the
23540    /// reducer and the member keys become fields of their own.
23541    #[test]
23542    fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
23543        let mut f = spanned();
23544        f.out = Out::new(Proto::Resp3);
23545        let cases: &[(&[&[u8]], &str)] = &[
23546            (
23547                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
23548                "%1\r\n$1\r\nb\r\n*3\r\n%0\r\n%1\r\n$11\r\naggregators\r\n*0\r\n\
23549                 *1\r\n*2\r\n:200\r\n,2\r\n",
23550            ),
23551            // The reductions a read asked for, which RESP2 has no room for at
23552            // all and which is empty on a read that asked for none.
23553            (
23554                &[
23555                    b"TS.MRANGE",
23556                    b"-",
23557                    b"+",
23558                    b"AGGREGATION",
23559                    b"sum,avg",
23560                    b"100",
23561                    b"FILTER",
23562                    b"room=bedroom",
23563                ],
23564                "%1\r\n$1\r\nb\r\n*3\r\n%0\r\n%1\r\n$11\r\naggregators\r\n*2\r\n$3\r\nsum\r\n\
23565                 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
23566            ),
23567            (
23568                &[
23569                    b"TS.MRANGE",
23570                    b"-",
23571                    b"+",
23572                    b"FILTER",
23573                    b"room=kitchen",
23574                    b"GROUPBY",
23575                    b"room",
23576                    b"REDUCE",
23577                    b"sum",
23578                ],
23579                "%1\r\n$12\r\nroom=kitchen\r\n*4\r\n%0\r\n%1\r\n$8\r\nreducers\r\n*1\r\n\
23580                 $3\r\nsum\r\n%1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
23581                 *3\r\n*2\r\n:100\r\n,11.5\r\n*2\r\n:200\r\n,2.5\r\n*2\r\n:300\r\n,30\r\n",
23582            ),
23583            // The labels hold only the pair the group was made on, because the
23584            // reducer and the sources have somewhere else to go.
23585            (
23586                &[
23587                    b"TS.MRANGE",
23588                    b"-",
23589                    b"+",
23590                    b"WITHLABELS",
23591                    b"FILTER",
23592                    b"room=kitchen",
23593                    b"GROUPBY",
23594                    b"room",
23595                    b"REDUCE",
23596                    b"max",
23597                ],
23598                "%1\r\n$12\r\nroom=kitchen\r\n*4\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
23599                 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
23600                 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
23601                 *3\r\n*2\r\n:100\r\n,10\r\n*2\r\n:200\r\n,2.5\r\n*2\r\n:300\r\n,30\r\n",
23602            ),
23603            (
23604                &[
23605                    b"TS.MRANGE",
23606                    b"-",
23607                    b"+",
23608                    b"FILTER",
23609                    b"room=kitchen",
23610                    b"GROUPBY",
23611                    b"nope",
23612                    b"REDUCE",
23613                    b"sum",
23614                ],
23615                "%0\r\n",
23616            ),
23617        ];
23618        for (argv, want) in cases {
23619            let got = f.run(argv);
23620            assert_eq!(&got, want, "{argv:?}");
23621        }
23622    }
23623
23624    /// `TS.CREATERULE`, whose refusals come in an order of their own.
23625    #[test]
23626    fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
23627        let mut f = Fixture::new();
23628        f.run(&[b"TS.CREATE", b"src"]);
23629        f.run(&[b"TS.CREATE", b"dst"]);
23630        f.run(&[b"SET", b"plain", b"v"]);
23631        let cases: &[(&[&[u8]], &str)] = &[
23632            // The width is read before the reduction, the reduction before the
23633            // width being above zero, and all three before either key is looked
23634            // at, so a command that is wrong twice complains about the first.
23635            (
23636                &[
23637                    b"TS.CREATERULE",
23638                    b"src",
23639                    b"dst",
23640                    b"AGGREGATION",
23641                    b"nope",
23642                    b"x",
23643                ],
23644                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
23645            ),
23646            (
23647                &[
23648                    b"TS.CREATERULE",
23649                    b"src",
23650                    b"dst",
23651                    b"AGGREGATION",
23652                    b"nope",
23653                    b"10",
23654                ],
23655                "-ERR TSDB: Unknown aggregation type\r\n",
23656            ),
23657            (
23658                &[
23659                    b"TS.CREATERULE",
23660                    b"src",
23661                    b"dst",
23662                    b"AGGREGATION",
23663                    b"avg",
23664                    b"0",
23665                ],
23666                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
23667            ),
23668            (
23669                &[
23670                    b"TS.CREATERULE",
23671                    b"src",
23672                    b"dst",
23673                    b"AGGREGATION",
23674                    b"avg",
23675                    b"10",
23676                    b"x",
23677                ],
23678                "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
23679            ),
23680            (
23681                &[
23682                    b"TS.CREATERULE",
23683                    b"src",
23684                    b"src",
23685                    b"AGGREGATION",
23686                    b"avg",
23687                    b"10",
23688                ],
23689                "-ERR TSDB: the source key and destination key should be different\r\n",
23690            ),
23691            // A key holding something else answers the same as a key that is not
23692            // there at all, because the source is looked up first and neither of
23693            // them is a series.
23694            (
23695                &[
23696                    b"TS.CREATERULE",
23697                    b"nope",
23698                    b"plain",
23699                    b"AGGREGATION",
23700                    b"avg",
23701                    b"10",
23702                ],
23703                "-ERR TSDB: the key does not exist\r\n",
23704            ),
23705            (
23706                &[
23707                    b"TS.CREATERULE",
23708                    b"src",
23709                    b"nope",
23710                    b"AGGREGATION",
23711                    b"avg",
23712                    b"10",
23713                ],
23714                "-ERR TSDB: the key does not exist\r\n",
23715            ),
23716            // A keyword other than AGGREGATION is an arity error rather than a
23717            // syntax one, because the arity is all that is checked.
23718            (
23719                &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
23720                "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
23721            ),
23722            (
23723                &[
23724                    b"TS.CREATERULE",
23725                    b"src",
23726                    b"dst",
23727                    b"AGGREGATION",
23728                    b"avg",
23729                    b"10",
23730                ],
23731                "+OK\r\n",
23732            ),
23733            // The link is now in place, so the same rule again is refused from
23734            // the destination's end.
23735            (
23736                &[
23737                    b"TS.CREATERULE",
23738                    b"src",
23739                    b"dst",
23740                    b"AGGREGATION",
23741                    b"avg",
23742                    b"10",
23743                ],
23744                "-ERR TSDB: the destination key already has a src rule\r\n",
23745            ),
23746            // A source that is already someone's destination, and a destination
23747            // that is already someone's source, are two different sentences.
23748            (
23749                &[
23750                    b"TS.CREATERULE",
23751                    b"dst",
23752                    b"src",
23753                    b"AGGREGATION",
23754                    b"avg",
23755                    b"10",
23756                ],
23757                "-ERR TSDB: the source key already has a source rule\r\n",
23758            ),
23759            (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
23760            (
23761                &[b"TS.DELETERULE", b"src", b"dst"],
23762                "-ERR TSDB: compaction rule does not exist\r\n",
23763            ),
23764            // The source is looked up and the destination is not, so a missing
23765            // destination is a missing rule and a missing source is a missing
23766            // key, which is the other way round from `TS.CREATERULE`.
23767            (
23768                &[b"TS.DELETERULE", b"src", b"nope"],
23769                "-ERR TSDB: compaction rule does not exist\r\n",
23770            ),
23771            (
23772                &[b"TS.DELETERULE", b"nope", b"dst"],
23773                "-ERR TSDB: the key does not exist\r\n",
23774            ),
23775        ];
23776        for (argv, want) in cases {
23777            let got = f.run(argv);
23778            assert_eq!(&got, want, "{argv:?}");
23779        }
23780    }
23781
23782    /// What a rule writes, which is every bucket but the one it is filling.
23783    #[test]
23784    fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
23785        let mut f = Fixture::new();
23786        f.run(&[b"TS.CREATE", b"src"]);
23787        f.run(&[b"TS.CREATE", b"dst"]);
23788        // The readings written before the rule was made are not folded, so the
23789        // destination is still empty after the first two.
23790        f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
23791        f.run(&[
23792            b"TS.CREATERULE",
23793            b"src",
23794            b"dst",
23795            b"AGGREGATION",
23796            b"sum",
23797            b"100",
23798        ]);
23799        f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
23800        assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
23801        // The bucket the rule is filling holds only what it was given, so it is
23802        // 2 rather than 3, and it is written when a reading lands past it.
23803        assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
23804        f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
23805        assert_eq!(
23806            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23807            "*1\r\n*2\r\n:0\r\n+2\r\n"
23808        );
23809        // A reading into a bucket that has already been written works that
23810        // bucket out again over everything the source now holds.
23811        f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
23812        assert_eq!(
23813            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23814            "*1\r\n*2\r\n:0\r\n+11\r\n"
23815        );
23816        // Deleting from the source works the buckets it touched out again and
23817        // reopens the newest one, so `LATEST` starts from the whole bucket.
23818        assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
23819        assert_eq!(
23820            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
23821            "*1\r\n*2\r\n:0\r\n+8\r\n"
23822        );
23823        assert_eq!(
23824            f.run(&[b"TS.GET", b"dst", b"LATEST"]),
23825            "*2\r\n:100\r\n+4\r\n"
23826        );
23827        // The link shows on both ends, and dropping either key takes it down.
23828        assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
23829        f.run(&[b"DEL", b"dst"]);
23830        assert_eq!(
23831            f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
23832            "-ERR TSDB: compaction rule does not exist\r\n"
23833        );
23834    }
23835
23836    /// The three shapes an `XADD` id can take, and the one rule behind all of
23837    /// them.
23838    #[test]
23839    fn xadd_ids_only_ever_go_up() {
23840        let mut f = Fixture::new();
23841        // A bare millisecond is that millisecond and sequence zero.
23842        assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
23843        // And `5-*` is the next free sequence inside it.
23844        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
23845        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
23846        assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
23847        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
23848
23849        assert!(
23850            f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
23851                .contains("equal or smaller")
23852        );
23853        assert!(
23854            f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
23855                .contains("must be greater than 0-0")
23856        );
23857        assert!(
23858            f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
23859                .contains("Invalid stream ID")
23860        );
23861        // The pairs have to be pairs, and Redis calls an odd one an arity error
23862        // rather than a syntax error even though the table has already passed.
23863        assert!(
23864            f.run(&[b"XADD", b"s", b"*", b"a"])
23865                .contains("wrong number of arguments")
23866        );
23867
23868        // `NOMKSTREAM` on a key that is not there is a null and not a zero, so a
23869        // producer can tell nobody is consuming this yet from the write landed.
23870        assert_eq!(
23871            f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
23872            "$-1\r\n"
23873        );
23874        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
23875        assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
23876        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
23877    }
23878
23879    /// The trim options, which are three keywords that disagree about how many
23880    /// arguments they take.
23881    #[test]
23882    fn trimming_reads_its_options_the_way_redis_does() {
23883        let mut f = Fixture::new();
23884        for i in 1..=10u32 {
23885            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
23886        }
23887        assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
23888        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
23889        assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
23890        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23891
23892        // One argument after the keyword and the `~` is read as the threshold,
23893        // which is what a real server does and is the reason this is a number
23894        // complaint and not a syntax one.
23895        assert!(
23896            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
23897                .contains("not an integer")
23898        );
23899        assert!(
23900            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
23901                .contains("MAXLEN argument must be >= 0")
23902        );
23903        // The strategy check runs before the approximation check, so a LIMIT
23904        // with neither is told about the missing strategy.
23905        assert!(
23906            f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
23907                .contains("without specifying a trimming strategy")
23908        );
23909        assert!(
23910            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
23911                .contains("without the special ~ option")
23912        );
23913        assert!(
23914            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
23915                .contains("at the same time are not compatible")
23916        );
23917        // NOMKSTREAM is XADD's and XTRIM does not take it.
23918        assert!(
23919            f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
23920                .contains("syntax error")
23921        );
23922        assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
23923    }
23924
23925    /// `XRANGE`, whose two kinds of nothing are the thing worth pinning.
23926    #[test]
23927    fn xrange_looks_the_key_up_before_it_reads_the_count() {
23928        let mut f = Fixture::new();
23929        f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
23930        f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
23931
23932        assert_eq!(
23933            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
23934            "*2\r\n*2\r\n$3\r\n5-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n\
23935             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23936        );
23937        assert_eq!(
23938            f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
23939            "*1\r\n*2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23940        );
23941        // The exclusive bound is stepped after the missing sequence is filled
23942        // in, so `(6` is `6-` and the largest sequence there is, minus one, and
23943        // `6-1` is still in the range.
23944        assert_eq!(
23945            f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
23946            "*2\r\n*2\r\n$3\r\n5-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n\
23947             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23948        );
23949        assert_eq!(
23950            f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
23951            "*1\r\n*2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
23952        );
23953        assert!(
23954            f.run(&[b"XRANGE", b"s", b"(-", b"+"])
23955                .contains("Invalid stream ID")
23956        );
23957
23958        // The two kinds of nothing. A key that is not there is an empty array
23959        // and a key that is there with a count of zero is a null array, because
23960        // the lookup happens first.
23961        assert_eq!(
23962            f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
23963            "*0\r\n"
23964        );
23965        assert_eq!(
23966            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
23967            "*-1\r\n"
23968        );
23969        f.run(&[b"SET", b"str", b"v"]);
23970        assert!(
23971            f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
23972                .starts_with("-WRONGTYPE")
23973        );
23974        // The count is read in a loop, so the last one wins.
23975        assert_eq!(
23976            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
23977            "*1\r\n*2\r\n$3\r\n5-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
23978        );
23979    }
23980
23981    /// `XDEL` and `XACK` check every id before they touch any of them.
23982    #[test]
23983    fn a_bad_id_late_in_the_list_stops_the_whole_command() {
23984        let mut f = Fixture::new();
23985        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
23986        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
23987        assert!(
23988            f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
23989                .contains("Invalid stream ID")
23990        );
23991        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
23992        assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
23993        assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
23994        assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
23995        assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
23996    }
23997
23998    /// `XGROUP`, and the two different complaints it makes about arguments.
23999    #[test]
24000    fn xgroup_has_an_arity_per_subcommand() {
24001        let mut f = Fixture::new();
24002        assert!(
24003            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
24004                .contains("requires the key")
24005        );
24006        assert_eq!(
24007            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
24008            "+OK\r\n"
24009        );
24010        // A second CREATE is BUSYGROUP and not an ordinary error, because a
24011        // client racing another one to make a group branches on the prefix.
24012        assert!(
24013            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
24014                .starts_with("-BUSYGROUP")
24015        );
24016        assert_eq!(
24017            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
24018            ":1\r\n"
24019        );
24020        assert_eq!(
24021            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
24022            ":0\r\n"
24023        );
24024        assert_eq!(
24025            f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
24026            ":0\r\n"
24027        );
24028
24029        // Below the subcommand's own arity is an arity error naming the pair.
24030        let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
24031        assert!(
24032            short.contains("wrong number of arguments for 'xgroup|destroy' command"),
24033            "{short}"
24034        );
24035        // At or above it in a shape the handler will not take is the other one.
24036        let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
24037        assert!(
24038            odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
24039            "{odd}"
24040        );
24041        assert!(
24042            f.run(&[b"XGROUP", b"NOSUCH", b"s"])
24043                .contains("Try XGROUP HELP")
24044        );
24045
24046        assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
24047        assert!(
24048            f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
24049                .starts_with("-NOGROUP")
24050        );
24051        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
24052        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
24053        assert!(
24054            f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
24055                .contains("requires the key")
24056        );
24057    }
24058
24059    /// A group read, an acknowledgement, and what is left in between.
24060    #[test]
24061    fn xreadgroup_hands_out_and_xack_takes_back() {
24062        let mut f = Fixture::new();
24063        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24064        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24065        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24066
24067        let first = f.run(&[
24068            b"XREADGROUP",
24069            b"GROUP",
24070            b"g",
24071            b"c1",
24072            b"COUNT",
24073            b"1",
24074            b"STREAMS",
24075            b"s",
24076            b">",
24077        ]);
24078        assert_eq!(
24079            first,
24080            "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
24081        );
24082        // A history read names its stream even with nothing to show, which is
24083        // the difference between it and a `>` read that found nothing.
24084        assert_eq!(
24085            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
24086            "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
24087        );
24088        assert_eq!(
24089            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
24090            "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
24091        );
24092
24093        assert_eq!(
24094            f.run(&[b"XPENDING", b"s", b"g"]),
24095            "*4\r\n:1\r\n$3\r\n1-1\r\n$3\r\n1-1\r\n*1\r\n*2\r\n$2\r\nc1\r\n$1\r\n1\r\n"
24096        );
24097        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
24098        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
24099        // Empty is four nulls and not a zero with three empty things.
24100        assert_eq!(
24101            f.run(&[b"XPENDING", b"s", b"g"]),
24102            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
24103        );
24104
24105        // A history read of an entry that has since been deleted is the id with
24106        // a null beside it, so the consumer can still acknowledge it.
24107        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24108        f.run(&[b"XDEL", b"s", b"2-1"]);
24109        assert_eq!(
24110            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
24111            "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n2-1\r\n$-1\r\n"
24112        );
24113
24114        // The group lookup runs before the id parse, so a `+` at a stream with
24115        // no such group is told about the group and not about the id.
24116        assert!(
24117            f.run(&[
24118                b"XREADGROUP",
24119                b"GROUP",
24120                b"nope",
24121                b"c",
24122                b"STREAMS",
24123                b"s",
24124                b"+"
24125            ])
24126            .starts_with("-NOGROUP")
24127        );
24128        assert!(
24129            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
24130                .contains("meaningless in the context of XREADGROUP")
24131        );
24132        assert!(
24133            f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
24134                .contains("only supported by XREADGROUP")
24135        );
24136        assert!(
24137            f.run(&[
24138                b"XREADGROUP",
24139                b"GROUP",
24140                b"g",
24141                b"c",
24142                b"STREAMS",
24143                b"s",
24144                b"a",
24145                b"b"
24146            ])
24147            .contains("Unbalanced 'xreadgroup' list of streams")
24148        );
24149    }
24150
24151    /// `XREAD` without `BLOCK`, which answers now and takes nothing for an
24152    /// answer.
24153    #[test]
24154    fn xread_with_no_block_writes_the_null_itself() {
24155        let mut f = Fixture::new();
24156        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24157        assert_eq!(
24158            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
24159            "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
24160        );
24161        // Nothing new is a null array and not an empty one, and a stream with
24162        // nothing new is left out rather than sent with an empty list.
24163        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
24164        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
24165        f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
24166        assert_eq!(
24167            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
24168            "*1\r\n*2\r\n$5\r\nother\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
24169        );
24170        // `$` is the last id, so nothing that is already there comes back.
24171        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
24172        // And `+` is the last entry, whatever COUNT says.
24173        assert_eq!(
24174            f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
24175            "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
24176        );
24177        // A count of zero means unlimited here, which is the opposite of what it
24178        // means to XRANGE.
24179        assert_eq!(
24180            f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
24181            "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
24182        );
24183        // Milliseconds as a whole number, where BLPOP takes seconds as a float.
24184        assert!(
24185            f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
24186                .contains("not an integer")
24187        );
24188        assert!(
24189            f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
24190                .contains("timeout is negative")
24191        );
24192        assert!(
24193            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
24194                .contains("Unbalanced 'xread' list of streams")
24195        );
24196    }
24197
24198    /// A blocked reader, and the two ways it stops being blocked.
24199    #[test]
24200    fn a_blocked_xread_wakes_on_the_next_entry() {
24201        let mut f = Fixture::new();
24202        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24203        let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
24204        assert_eq!(flow, Flow::Block);
24205        assert!(reply.is_empty());
24206
24207        // Everybody parked on the stream gets the entry, because a read takes
24208        // nothing away. That is the difference between this and BLPOP. Two
24209        // clients rather than one twice, since a client that is waiting is not
24210        // reading and cannot block again.
24211        f.session = Session::new(8);
24212        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
24213        assert_eq!(flow, Flow::Block);
24214        assert_eq!(f.server.parked(), 2);
24215
24216        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24217        let want = "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n2-1\r\n*2\r\n$1\r\na\r\n$1\r\n2\r\n";
24218        for client in [7, 8] {
24219            let mut out = Out::new(Proto::Resp2);
24220            assert!(f.server.serve_waiter(client, 0, &mut out));
24221            assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
24222        }
24223
24224        // And a deadline that runs out is a null array, the same as a plain
24225        // XREAD that found nothing.
24226        f.server.forget_waiters(7);
24227        f.server.forget_waiters(8);
24228        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
24229        assert_eq!(flow, Flow::Block);
24230        let mut out = Out::new(Proto::Resp2);
24231        assert!(!f.server.serve_waiter(8, 0, &mut out));
24232        assert!(out.as_slice().is_empty());
24233        assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
24234        assert_eq!(
24235            core::str::from_utf8(out.as_slice()).expect("ascii"),
24236            "*-1\r\n"
24237        );
24238    }
24239
24240    /// A blocked group reader whose group is destroyed under it.
24241    #[test]
24242    fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
24243        let mut f = Fixture::new();
24244        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24245        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
24246        let (flow, _) = f.flow(&[
24247            b"XREADGROUP",
24248            b"GROUP",
24249            b"g",
24250            b"c",
24251            b"BLOCK",
24252            b"0",
24253            b"STREAMS",
24254            b"s",
24255            b">",
24256        ]);
24257        assert_eq!(flow, Flow::Block);
24258
24259        f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
24260        let mut out = Out::new(Proto::Resp2);
24261        assert!(f.server.serve_waiter(7, 0, &mut out));
24262        // The ordinary sentence and not a special one about having been parked,
24263        // which is what a running 8.10 sends.
24264        assert_eq!(
24265            core::str::from_utf8(out.as_slice()).expect("ascii"),
24266            "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
24267        );
24268    }
24269
24270    /// `XCLAIM`, whose argument shape is the odd one in the group.
24271    #[test]
24272    fn xclaim_reads_ids_until_one_will_not_parse() {
24273        let mut f = Fixture::new();
24274        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24275        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24276        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24277        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24278
24279        // Everything after the first argument that is not an id is an option, so
24280        // a `-` is an unrecognised option and not a bad id.
24281        assert!(
24282            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
24283                .contains("Unrecognized XCLAIM option '-'")
24284        );
24285        assert_eq!(
24286            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
24287            "*1\r\n$3\r\n1-1\r\n"
24288        );
24289        // An id that is pending but whose entry has gone is an empty answer, and
24290        // it leaves the pending list on the way past.
24291        f.run(&[b"XDEL", b"s", b"2-1"]);
24292        assert_eq!(
24293            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
24294            "*0\r\n"
24295        );
24296        assert!(
24297            f.run(&[b"XPENDING", b"s", b"g"])
24298                .starts_with("*4\r\n:1\r\n")
24299        );
24300        assert!(
24301            f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
24302                .starts_with("-NOGROUP")
24303        );
24304        assert!(
24305            f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
24306                .contains("Invalid min-idle-time argument for XCLAIM")
24307        );
24308    }
24309
24310    /// `XAUTOCLAIM`, and the third value nobody expects.
24311    #[test]
24312    fn xautoclaim_reports_what_it_dropped() {
24313        let mut f = Fixture::new();
24314        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24315        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24316        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24317        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24318        f.run(&[b"XDEL", b"s", b"1-1"]);
24319
24320        // The cursor, what was claimed, and what was dropped for no longer being
24321        // in the stream. The third one is what makes a sweep converge.
24322        assert_eq!(
24323            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
24324            "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n2-1\r\n*1\r\n$3\r\n1-1\r\n"
24325        );
24326        assert!(
24327            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
24328                .contains("COUNT must be > 0")
24329        );
24330        assert!(
24331            f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
24332                .starts_with("-NOGROUP")
24333        );
24334    }
24335
24336    /// `XDELEX`, which is `XDEL` with a say in what the groups keep.
24337    #[test]
24338    fn xdelex_answers_one_integer_an_id() {
24339        let mut f = Fixture::new();
24340        for i in 1..=4 {
24341            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
24342        }
24343        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24344        f.run(&[
24345            b"XREADGROUP",
24346            b"GROUP",
24347            b"g",
24348            b"c",
24349            b"COUNT",
24350            b"2",
24351            b"STREAMS",
24352            b"s",
24353            b">",
24354        ]);
24355
24356        // One means gone and minus one means it was not there to start with.
24357        assert_eq!(
24358            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
24359            "*2\r\n:1\r\n:-1\r\n"
24360        );
24361        // `KEEPREF` leaves the pending entry behind, so the group still counts
24362        // the one it was handed even though the entry has gone.
24363        assert!(
24364            f.run(&[b"XPENDING", b"s", b"g"])
24365                .starts_with("*4\r\n:2\r\n")
24366        );
24367        // `DELREF` takes it out of every pending list on the way past.
24368        assert_eq!(
24369            f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
24370            "*1\r\n:1\r\n"
24371        );
24372        // `1-1` is still in the list, because the delete before it said KEEPREF.
24373        assert_eq!(
24374            f.run(&[b"XPENDING", b"s", b"g"]),
24375            "*4\r\n:1\r\n$3\r\n1-1\r\n$3\r\n1-1\r\n*1\r\n*2\r\n$1\r\nc\r\n$1\r\n1\r\n"
24376        );
24377
24378        // Two means somebody still wants it, and the question is wider than the
24379        // name: the group's bookmark is at `2-1`, so `4-1` is above it and is
24380        // refused even though no consumer has ever been handed it.
24381        assert_eq!(
24382            f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
24383            "*2\r\n:2\r\n:2\r\n"
24384        );
24385
24386        // A key that is not there answers minus ones without reading the IDs.
24387        assert_eq!(
24388            f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
24389            "*2\r\n:-1\r\n:-1\r\n"
24390        );
24391        // A key that is there validates every ID before deleting any of them.
24392        assert!(
24393            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
24394                .starts_with("-ERR Invalid stream ID")
24395        );
24396        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24397
24398        assert!(
24399            f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
24400                .contains("Number of IDs must be a positive integer")
24401        );
24402        assert!(
24403            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
24404                .contains("The `numids` parameter must match the number of arguments")
24405        );
24406        // The condition is one word, so a second one is a syntax error, and so
24407        // is one ID more than the count promised.
24408        assert!(
24409            f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
24410                .starts_with("-ERR syntax error")
24411        );
24412        assert!(
24413            f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
24414                .starts_with("-ERR syntax error")
24415        );
24416        // The key is looked up first, so the wrong type beats the syntax.
24417        f.run(&[b"SET", b"str", b"v"]);
24418        assert!(
24419            f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
24420                .starts_with("-WRONGTYPE")
24421        );
24422    }
24423
24424    /// `XACKDEL`, whose reply is about the pending list and not about the log.
24425    #[test]
24426    fn xackdel_reports_what_the_group_was_holding() {
24427        let mut f = Fixture::new();
24428        for i in 1..=3 {
24429            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
24430        }
24431        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24432        f.run(&[
24433            b"XREADGROUP",
24434            b"GROUP",
24435            b"g",
24436            b"c",
24437            b"COUNT",
24438            b"1",
24439            b"STREAMS",
24440            b"s",
24441            b">",
24442        ]);
24443
24444        // Minus one is not about the stream: `2-1` is sitting there unread and
24445        // still answers minus one, because the group was not holding it. It also
24446        // stays, since only an ID that was acknowledged is deleted.
24447        assert_eq!(
24448            f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
24449            "*2\r\n:1\r\n:-1\r\n"
24450        );
24451        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24452
24453        // A missing group is minus one an ID and not a NOGROUP.
24454        assert_eq!(
24455            f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
24456            "*1\r\n:-1\r\n"
24457        );
24458        assert_eq!(
24459            f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
24460            "*1\r\n:-1\r\n"
24461        );
24462
24463        // The acknowledgement happens whatever the condition says, so an ACKED
24464        // that answers two has still emptied the pending list.
24465        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
24466        f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
24467        assert_eq!(
24468            f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
24469            "*1\r\n:2\r\n"
24470        );
24471        assert_eq!(
24472            f.run(&[b"XPENDING", b"s", b"g"]),
24473            "*4\r\n:1\r\n$3\r\n3-1\r\n$3\r\n3-1\r\n*1\r\n*2\r\n$1\r\nc\r\n$1\r\n1\r\n"
24474        );
24475        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
24476    }
24477
24478    /// `XNACK`, which hands an entry back to nobody.
24479    #[test]
24480    fn xnack_releases_an_entry_for_the_next_claim() {
24481        let mut f = Fixture::new();
24482        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24483        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24484        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24485        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24486        // Twice, so the delivery count is two and the words have something to
24487        // do with it.
24488        f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
24489
24490        assert_eq!(
24491            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
24492            ":1\r\n"
24493        );
24494        // No owner, no idle time, and the count left where it was. A released
24495        // entry reads as idle for longer than any min-idle-time, which is what
24496        // puts it at the front of the next claim.
24497        assert_eq!(
24498            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
24499            "*2\r\n*4\r\n$3\r\n1-1\r\n$0\r\n\r\n:-1\r\n:2\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
24500        );
24501        // The consumer no longer holds it, so a filtered XPENDING skips it.
24502        assert_eq!(
24503            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
24504            "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
24505        );
24506        // The bookmark did not move, so a `>` read will not hand it out again.
24507        assert_eq!(
24508            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
24509            "*-1\r\n"
24510        );
24511        // A claim at any min-idle-time takes it.
24512        assert_eq!(
24513            f.run(&[
24514                b"XAUTOCLAIM",
24515                b"s",
24516                b"g",
24517                b"c2",
24518                b"99999999",
24519                b"-",
24520                b"JUSTID"
24521            ]),
24522            "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
24523        );
24524
24525        // `SILENT` takes one off the count rather than putting it back to zero,
24526        // which only shows on an entry that has been handed out more than once.
24527        // It was delivered and then claimed, so it is on two and goes to one.
24528        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24529        assert!(
24530            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24531                .contains(":-1\r\n:1\r\n")
24532        );
24533        // And it stops at zero rather than wrapping.
24534        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24535        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
24536        assert!(
24537            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24538                .contains(":-1\r\n:0\r\n")
24539        );
24540        // `FATAL` puts it at the ceiling, and `RETRYCOUNT` wins over the word.
24541        f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
24542        assert!(
24543            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24544                .contains(":9223372036854775807\r\n")
24545        );
24546        f.run(&[
24547            b"XNACK",
24548            b"s",
24549            b"g",
24550            b"FATAL",
24551            b"IDS",
24552            b"1",
24553            b"1-1",
24554            b"RETRYCOUNT",
24555            b"3",
24556        ]);
24557        assert!(
24558            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24559                .contains(":-1\r\n:3\r\n")
24560        );
24561
24562        // Releasing something the group is not holding is zero, and `FORCE`
24563        // makes the pending entry rather than answering zero. A forced entry
24564        // starts at zero, since there was no earlier count to keep.
24565        f.run(&[b"XACK", b"s", b"g", b"2-1"]);
24566        assert_eq!(
24567            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
24568            ":0\r\n"
24569        );
24570        assert_eq!(
24571            f.run(&[
24572                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
24573            ]),
24574            ":1\r\n"
24575        );
24576        assert!(
24577            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
24578                .contains(":-1\r\n:0\r\n")
24579        );
24580        // `FORCE` on an ID the stream does not have is still zero.
24581        assert_eq!(
24582            f.run(&[
24583                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
24584            ]),
24585            ":0\r\n"
24586        );
24587
24588        // The group is looked up before the mode word, and it raises rather
24589        // than answering per ID the way the two delete commands do.
24590        assert_eq!(
24591            f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
24592            "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
24593        );
24594        assert!(
24595            f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
24596                .starts_with("-ERR")
24597        );
24598        // Its own sentences, which are not the ones XDELEX uses.
24599        assert!(
24600            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
24601                .contains("numids must be a positive integer")
24602        );
24603        assert!(
24604            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
24605                .contains("number of IDs doesn't match numids")
24606        );
24607        // Everything past the counted IDs is an option, so one too many is an
24608        // option nobody recognises and not a count that does not add up.
24609        assert!(
24610            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
24611                .contains("Unrecognized XNACK option '2-1'")
24612        );
24613    }
24614
24615    /// `XINFO`, which is where the shape of the storage shows through.
24616    #[test]
24617    fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
24618        let mut f = Fixture::new();
24619        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24620        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
24621        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24622        f.run(&[
24623            b"XREADGROUP",
24624            b"GROUP",
24625            b"g",
24626            b"c1",
24627            b"COUNT",
24628            b"1",
24629            b"STREAMS",
24630            b"s",
24631            b">",
24632        ]);
24633
24634        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
24635        // Ten pairs, since the six idempotency fields have nothing behind them
24636        // here and a zero would claim they had. That is D-27.
24637        assert!(info.starts_with("*20\r\n"), "{info}");
24638        assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
24639        assert!(
24640            info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
24641            "{info}"
24642        );
24643        assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
24644        assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
24645
24646        let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
24647        assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
24648        assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
24649        assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
24650        assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
24651
24652        // A consumer that has never been given anything reports minus one for
24653        // inactive rather than the moment it turned up, which is what tells a
24654        // worker that is stuck from one that has nothing to do.
24655        f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
24656        let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
24657        assert!(consumers.starts_with("*2\r\n"), "{consumers}");
24658        assert!(
24659            consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
24660            "{consumers}"
24661        );
24662        // And in name order, which the storage does not hold them in.
24663        let c1 = consumers.find("c1").unwrap();
24664        let c2 = consumers.find("c2").unwrap();
24665        assert!(c1 < c2, "{consumers}");
24666
24667        let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
24668        assert!(full.starts_with("*18\r\n"), "{full}");
24669        assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
24670        assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
24671
24672        assert!(
24673            f.run(&[b"XINFO", b"STREAM", b"missing"])
24674                .contains("no such key")
24675        );
24676        assert!(
24677            f.run(&[b"XINFO", b"GROUPS", b"missing"])
24678                .contains("no such key")
24679        );
24680        assert!(
24681            f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
24682                .starts_with("-NOGROUP")
24683        );
24684        assert!(
24685            f.run(&[b"XINFO", b"NOSUCH", b"s"])
24686                .contains("Try XINFO HELP")
24687        );
24688        assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
24689        assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
24690    }
24691
24692    /// `XPENDING`'s long form, which reads its arguments by counting them.
24693    #[test]
24694    fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
24695        let mut f = Fixture::new();
24696        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24697        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
24698        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
24699
24700        let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
24701        assert_eq!(list, "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n");
24702        assert_eq!(
24703            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
24704            "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
24705        );
24706        // A consumer nobody has heard of holds nothing rather than erroring.
24707        assert_eq!(
24708            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
24709            "*0\r\n"
24710        );
24711        assert_eq!(
24712            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
24713            list
24714        );
24715        // IDLE is only read at position three.
24716        assert!(
24717            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
24718                .contains("syntax error")
24719        );
24720        assert!(
24721            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
24722                .contains("syntax error")
24723        );
24724        assert_eq!(
24725            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
24726            "*0\r\n"
24727        );
24728        assert!(
24729            f.run(&[b"XPENDING", b"missing", b"g"])
24730                .starts_with("-NOGROUP")
24731        );
24732    }
24733
24734    /// `XSETID`, which is three counters and two refusals.
24735    #[test]
24736    fn xsetid_will_not_go_below_what_is_there() {
24737        let mut f = Fixture::new();
24738        f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
24739        assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
24740        assert_eq!(
24741            f.run(&[
24742                b"XSETID",
24743                b"s",
24744                b"10-1",
24745                b"ENTRIESADDED",
24746                b"7",
24747                b"MAXDELETEDID",
24748                b"9-1"
24749            ]),
24750            "+OK\r\n"
24751        );
24752        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
24753        assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
24754        assert!(
24755            info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
24756            "{info}"
24757        );
24758
24759        assert!(
24760            f.run(&[b"XSETID", b"s", b"1-1"])
24761                .contains("smaller than the target stream top item")
24762        );
24763        assert!(
24764            f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
24765                .contains("entries_added must be positive")
24766        );
24767        assert!(
24768            f.run(&[b"XSETID", b"missing", b"1-1"])
24769                .contains("no such key")
24770        );
24771    }
24772
24773    /// RESP3, where the two reads answer a map and the entries stay an array.
24774    #[test]
24775    fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
24776        let mut f = Fixture::new();
24777        f.run(&[b"HELLO", b"3"]);
24778        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
24779        // A map header and then the key and the entries side by side, with no
24780        // two element array wrapping the pair.
24781        assert_eq!(
24782            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
24783            "%1\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
24784        );
24785        // The fields are still one flat array and not a map, which is Redis's
24786        // shape and is what every consumer written before RESP3 expects.
24787        assert_eq!(
24788            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
24789            "*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
24790        );
24791        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
24792    }
24793
24794    /// A store to migrate values into, so a test can watch the inversion.
24795    ///
24796    /// A vector rather than a file for the same reason the tier's own tests use
24797    /// one: the file work has not attached a real store yet, and what this is
24798    /// checking is the policy above the store rather than the store.
24799    struct Mem {
24800        blobs: Vec<Vec<u8>>,
24801    }
24802
24803    impl yo_kv::cold::Blocks for Mem {
24804        fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
24805            self.blobs.push(bytes.to_vec());
24806            Ok(yo_common::Addr::new(
24807                yo_common::Space::Log,
24808                (self.blobs.len() - 1) as u64,
24809            ))
24810        }
24811
24812        fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
24813            self.blobs
24814                .get(at.offset() as usize)
24815                .map(Vec::as_slice)
24816                .ok_or_else(|| {
24817                    yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
24818                })
24819        }
24820
24821        fn bytes(&self) -> u64 {
24822            self.blobs.iter().map(|b| b.len() as u64).sum()
24823        }
24824    }
24825
24826    /// A server holding several segments of strings, with somewhere to put them.
24827    ///
24828    /// Answers the fixture and what it was holding when it stopped filling.
24829    /// The three tests that call this are the ones Miri is not run over.
24830    ///
24831    /// What they are about is the regime a database is in once the arena has
24832    /// several segments, and a segment is two megabytes, so there is no smaller
24833    /// version of the question: twenty four thousand keys is already the least
24834    /// that gets there. Interpreted, each of them sat for over forty minutes
24835    /// and was still going. The arena's own segment handling is interpreted in
24836    /// full in its own crate, and the policy these three check is ordinary
24837    /// bookkeeping with no unsafe block anywhere in it.
24838    fn filled(attach: bool) -> (Fixture, usize) {
24839        let mut f = Fixture::new();
24840        if attach {
24841            f.server
24842                .striped(0)
24843                .hold_stripe(0)
24844                .attach(Box::new(Mem { blobs: Vec::new() }));
24845        }
24846        let val = vec![b'v'; 256];
24847        for i in 0..24000u32 {
24848            let k = format!("key:{i:08}");
24849            f.run(&[b"SET", k.as_bytes(), &val]);
24850        }
24851        let full = f.server.memory_bytes();
24852        assert!(full > 3 * 1024 * 1024, "the arena is several segments");
24853        (f, full)
24854    }
24855
24856    /// Write until the server is under `limit` or the writes run out.
24857    ///
24858    /// The same shape the eviction test uses. A memory limit is enforced in
24859    /// front of a command, so nothing happens until something is written, and
24860    /// the budget means one command does not do the whole job.
24861    fn press(f: &mut Fixture, limit: usize) {
24862        let val = vec![b'v'; 256];
24863        for i in 0..3000u32 {
24864            let k = format!("new:{i:08}");
24865            assert_eq!(
24866                f.run(&[b"SET", k.as_bytes(), &val]),
24867                "+OK\r\n",
24868                "write {i} was refused"
24869            );
24870            f.server.refresh_memory();
24871            if f.server.memory_bytes() <= limit {
24872                return;
24873            }
24874        }
24875        panic!(
24876            "it never got under: {} against {limit}",
24877            f.server.memory_bytes()
24878        );
24879    }
24880
24881    #[test]
24882    fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
24883        let mut f = Fixture::new();
24884        assert_eq!(
24885            f.run(&[b"CONFIG", b"GET", b"maxstore"]),
24886            "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
24887            "no limit is the default"
24888        );
24889        // The same memory value parser `maxmemory` uses, and the same trap in
24890        // it, plus the one spelling that means no limit at all.
24891        for (typed, bytes) in [
24892            (&b"0"[..], "0"),
24893            (b"1024", "1024"),
24894            (b"1k", "1000"),
24895            (b"1gb", "1073741824"),
24896            (b"-1", "-1"),
24897        ] {
24898            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
24899            assert_eq!(
24900                f.run(&[b"CONFIG", b"GET", b"maxstore"]),
24901                format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
24902                "set {}",
24903                String::from_utf8_lossy(typed)
24904            );
24905        }
24906        for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
24907            assert_eq!(
24908                f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
24909                "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
24910                "refused {}",
24911                String::from_utf8_lossy(bad)
24912            );
24913        }
24914        // Nothing is attached, so the answer to a memory limit is still Redis's.
24915        let info = f.run(&[b"INFO", b"memory"]);
24916        assert!(info.contains("maxstore:-1"), "{info}");
24917        assert!(info.contains("yo_memory_regime:evict"), "{info}");
24918        assert!(info.contains("yo_store_bytes:0"), "{info}");
24919    }
24920
24921    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24922    #[test]
24923    fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
24924        // The inversion. The same pressure that makes a Redis server throw keys
24925        // away makes this one move values to the file, and afterwards every key
24926        // is still there and still answers with what was stored in it.
24927        let (mut f, full) = filled(true);
24928        let keys = f.run(&[b"DBSIZE"]);
24929        assert!(
24930            f.run(&[b"INFO", b"memory"])
24931                .contains("yo_memory_regime:migrate"),
24932            "a database with somewhere to put values migrates"
24933        );
24934
24935        let limit = full - 2 * 1024 * 1024;
24936        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
24937        f.run(&[
24938            b"CONFIG",
24939            b"SET",
24940            b"maxmemory",
24941            limit.to_string().as_bytes(),
24942        ]);
24943        press(&mut f, limit);
24944
24945        assert!(
24946            f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
24947            "nothing was thrown away"
24948        );
24949        let after: usize = f.run(&[b"DBSIZE"])[1..]
24950            .trim_end()
24951            .parse()
24952            .expect("a count");
24953        let before: usize = keys[1..].trim_end().parse().expect("a count");
24954        assert!(after > before, "the keys that came in are all still here");
24955        assert!(
24956            f.server.store_bytes() > 0,
24957            "and what came out of memory went to the file"
24958        );
24959        // And the values read back, which is the part that makes it a migration
24960        // rather than a loss.
24961        let val = format!("$256\r\n{}\r\n", "v".repeat(256));
24962        assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
24963        assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
24964    }
24965
24966    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24967    #[test]
24968    fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
24969        // The documented setting for a drop in cache. A file that may hold
24970        // nothing cannot be migrated to, so eviction is all that is left, and
24971        // the server behaves exactly as it did before any of this existed.
24972        let (mut f, full) = filled(true);
24973        f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
24974        assert!(
24975            f.run(&[b"INFO", b"memory"])
24976                .contains("yo_memory_regime:evict"),
24977            "nothing may go to the file"
24978        );
24979
24980        let limit = full - 2 * 1024 * 1024;
24981        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
24982        f.run(&[
24983            b"CONFIG",
24984            b"SET",
24985            b"maxmemory",
24986            limit.to_string().as_bytes(),
24987        ]);
24988        press(&mut f, limit);
24989
24990        assert!(
24991            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
24992            "keys were thrown away, which is what was asked for"
24993        );
24994        assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
24995    }
24996
24997    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
24998    #[test]
24999    fn a_full_file_goes_back_to_evicting() {
25000        // A storage limit reached is a storage limit, and eviction is the right
25001        // answer to one. The budget here is a few kilobytes, so the first round
25002        // of migration fills it and everything after that is evicted.
25003        let (mut f, full) = filled(true);
25004        f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
25005        let limit = full - 2 * 1024 * 1024;
25006        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
25007        f.run(&[
25008            b"CONFIG",
25009            b"SET",
25010            b"maxmemory",
25011            limit.to_string().as_bytes(),
25012        ]);
25013        press(&mut f, limit);
25014
25015        assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
25016        assert!(
25017            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
25018            "and then it started evicting"
25019        );
25020        assert!(
25021            f.run(&[b"INFO", b"memory"])
25022                .contains("yo_memory_regime:evict"),
25023            "and it says so"
25024        );
25025    }
25026    // ------------------------------------------------------------- stripes
25027
25028    /// Every string command, run twice: once on a database that is one keyspace
25029    /// and once on a database that is eight, with the same commands in the same
25030    /// order and the replies compared byte for byte.
25031    ///
25032    /// This is the whole claim the striping rests on. A key belongs to one
25033    /// stripe and to no other, so the answer to a command cannot depend on how
25034    /// many stripes there are, and the way to check that is to ask the same
25035    /// question of two servers that differ in nothing else.
25036    ///
25037    /// The keys are chosen to land on different stripes rather than to look
25038    /// tidy. `MSET a 1 b 2 c 3` over eight stripes is only a test of anything if
25039    /// those three keys are not all on the same one, and at eight stripes three
25040    /// keys land together about one time in fifty.
25041    #[test]
25042    fn the_string_group_answers_the_same_however_many_stripes_there_are() {
25043        let script: &[&[&[u8]]] = &[
25044            // The single key commands, which are the ones that get handed one
25045            // stripe at the dispatch site.
25046            &[b"SET", b"k1", b"v1"],
25047            &[b"SET", b"k2", b"v2"],
25048            &[b"GET", b"k1"],
25049            &[b"GET", b"nothing"],
25050            &[b"GETSET", b"k1", b"v1b"],
25051            &[b"SETNX", b"k1", b"no"],
25052            &[b"SETNX", b"k3", b"yes"],
25053            &[b"APPEND", b"k3", b"!"],
25054            &[b"STRLEN", b"k3"],
25055            &[b"SETRANGE", b"k3", b"1", b"XY"],
25056            &[b"GETRANGE", b"k3", b"0", b"-1"],
25057            &[b"INCR", b"n1"],
25058            &[b"INCRBY", b"n1", b"41"],
25059            &[b"DECRBY", b"n1", b"2"],
25060            &[b"INCRBYFLOAT", b"f1", b"1.5"],
25061            &[b"SETEX", b"e1", b"100", b"v"],
25062            &[b"PSETEX", b"e2", b"100000", b"v"],
25063            &[b"GETEX", b"e1", b"PERSIST"],
25064            &[b"GETDEL", b"k2"],
25065            &[b"GET", b"k2"],
25066            &[b"DIGEST", b"k1"],
25067            &[b"DELEX", b"k3"],
25068            // The five that name more than one key, which are the ones that
25069            // cannot be handed one stripe at all.
25070            &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
25071            &[b"MGET", b"a", b"b", b"c", b"missing"],
25072            &[b"MSETNX", b"d", b"4", b"e", b"5"],
25073            &[b"MSETNX", b"e", b"6", b"f", b"7"],
25074            &[b"MGET", b"d", b"e", b"f"],
25075            &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
25076            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
25077            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
25078            &[b"MGET", b"g", b"h"],
25079            &[b"SET", b"s1", b"ohmytext"],
25080            &[b"SET", b"s2", b"mynewtext"],
25081            &[b"LCS", b"s1", b"s2"],
25082            &[b"LCS", b"s1", b"s2", b"LEN"],
25083            &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
25084            &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
25085            &[b"LCS", b"s1", b"gone"],
25086            // And the errors, which have to be the same errors.
25087            &[b"MSET", b"odd"],
25088            &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
25089            &[b"MGET"],
25090        ];
25091
25092        let mut one = Fixture::new();
25093        let mut many = Fixture::striped(8);
25094        for parts in script {
25095            let a = one.run(parts);
25096            let b = many.run(parts);
25097            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25098        }
25099    }
25100
25101    /// The keys of an `MSET` really do end up on different stripes.
25102    ///
25103    /// Without this the test above could pass on a server whose stripe number
25104    /// happened to be a constant, which is a striped database in name only.
25105    #[test]
25106    fn a_striped_database_spreads_the_keys_it_is_given() {
25107        let mut f = Fixture::striped(8);
25108        for i in 0..256 {
25109            let key = format!("key:{i}");
25110            f.run(&[b"SET", key.as_bytes(), b"v"]);
25111        }
25112        assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
25113    }
25114
25115    /// A wrong type stops an `MGET` no more than it does on one stripe: the key
25116    /// that is not a string comes back nil and the rest of the reply is intact.
25117    #[test]
25118    fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
25119        let mut one = Fixture::new();
25120        let mut many = Fixture::striped(8);
25121        for f in [&mut one, &mut many] {
25122            f.run(&[b"SET", b"str", b"v"]);
25123            // Planted rather than pushed. `RPUSH` belongs to the list group,
25124            // which has not been taught about stripes yet and would refuse the
25125            // wide server. What is under test is what `MGET` does when it walks
25126            // onto a key that is not a string, and that does not care how the
25127            // key got there.
25128            f.server
25129                .striped(0)
25130                .hold(b"list")
25131                .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
25132                .expect("a new list");
25133        }
25134        assert_eq!(
25135            one.run(&[b"MGET", b"str", b"list", b"gone"]),
25136            many.run(&[b"MGET", b"str", b"list", b"gone"])
25137        );
25138    }
25139
25140    /// The same claim for the keyspace group, and the same way of checking it.
25141    ///
25142    /// `SORT` is not in the script because it is the one command in that file
25143    /// that has not been taught about stripes, and `SCAN`, `KEYS` and
25144    /// `RANDOMKEY` are not in it either, because those three do not promise an
25145    /// order and comparing two replies byte for byte would be asserting one.
25146    /// They get tests of their own below.
25147    #[test]
25148    fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
25149        let script: &[&[&[u8]]] = &[
25150            &[b"SET", b"k1", b"v1"],
25151            &[b"SET", b"k2", b"v2"],
25152            &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
25153            &[b"TYPE", b"k1"],
25154            &[b"TYPE", b"gone"],
25155            &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
25156            &[b"EXPIRE", b"k1", b"100"],
25157            &[b"TTL", b"k1"],
25158            &[b"EXPIRE", b"k1", b"200", b"NX"],
25159            &[b"PERSIST", b"k1"],
25160            &[b"TTL", b"k1"],
25161            &[b"PEXPIREAT", b"k2", b"1900000000000"],
25162            &[b"EXPIRETIME", b"k2"],
25163            &[b"PEXPIRETIME", b"k2"],
25164            &[b"PERSIST", b"k2"],
25165            &[b"OBJECT", b"ENCODING", b"k1"],
25166            &[b"OBJECT", b"REFCOUNT", b"k1"],
25167            &[b"OBJECT", b"IDLETIME", b"k1"],
25168            &[b"OBJECT", b"FREQ", b"k1"],
25169            &[b"OBJECT", b"ENCODING", b"gone"],
25170            &[b"OBJECT", b"HELP"],
25171            &[b"RENAME", b"k1", b"k9"],
25172            &[b"GET", b"k9"],
25173            &[b"RENAME", b"gone", b"x"],
25174            &[b"RENAMENX", b"k9", b"k2"],
25175            &[b"RENAMENX", b"k9", b"k8"],
25176            &[b"GET", b"k8"],
25177            &[b"COPY", b"k8", b"c1"],
25178            &[b"COPY", b"k8", b"c1"],
25179            &[b"COPY", b"k8", b"c1", b"REPLACE"],
25180            &[b"COPY", b"k8", b"k8"],
25181            &[b"COPY", b"gone", b"c2"],
25182            &[b"COPY", b"k8", b"k8", b"DB", b"1"],
25183            &[b"COPY", b"k8", b"c9", b"DB", b"9"],
25184            &[b"MOVE", b"c1", b"1"],
25185            &[b"MOVE", b"c1", b"1"],
25186            &[b"MOVE", b"k8", b"0"],
25187            &[b"DEL", b"k2", b"gone"],
25188            &[b"UNLINK", b"k8", b"k8"],
25189            &[b"DBSIZE"],
25190        ];
25191
25192        let mut one = Fixture::new();
25193        let mut many = Fixture::striped(8);
25194        for parts in script {
25195            let a = one.run(parts);
25196            let b = many.run(parts);
25197            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25198        }
25199
25200        // `RESTORE` needs bytes a client would have got from a `DUMP`, so the
25201        // payload is taken from the store rather than parsed back out of a
25202        // reply that is not text. Both servers dump the same key and the bytes
25203        // are the same bytes, which is the first half of what is being checked
25204        // here.
25205        for f in [&mut one, &mut many] {
25206            f.run(&[b"SET", b"d1", b"payload"]);
25207            let payload = f
25208                .server
25209                .striped(0)
25210                .hold(b"d1")
25211                .dump(b"d1")
25212                .expect("a key that is there");
25213            assert!(
25214                f.run(&[b"DUMP", b"d1"])
25215                    .starts_with(&format!("${}", payload.len())),
25216                "a payload of the length the store gave"
25217            );
25218            assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
25219            assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
25220            assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
25221            assert_eq!(
25222                f.run(&[b"RESTORE", b"d2", b"0", &payload]),
25223                "-BUSYKEY Target key name already exists.\r\n"
25224            );
25225            assert_eq!(
25226                f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
25227                "-ERR DUMP payload version or checksum are wrong\r\n"
25228            );
25229        }
25230    }
25231
25232    /// A `SCAN` of a database of eight stripes comes back with all of it.
25233    ///
25234    /// The cursor is the thing under test. It has to carry the stripe as well
25235    /// as the place in it, so a client that stops at one stripe and comes back
25236    /// carries on in that stripe and not at the top of the database, and the
25237    /// walk has to end once rather than eight times.
25238    #[test]
25239    fn a_scan_of_a_striped_database_walks_all_of_it() {
25240        // Eight stripes and a COUNT of ten, so eighty keys is already more than
25241        // one page on every stripe and the cursor has to carry which stripe it
25242        // was on, which is the thing being checked.
25243        let n = if cfg!(miri) { 80 } else { 500 };
25244        let mut f = Fixture::striped(8);
25245        for i in 0..n {
25246            let key = format!("key:{i}");
25247            f.run(&[b"SET", key.as_bytes(), b"v"]);
25248        }
25249
25250        let mut seen = Vec::new();
25251        let mut cursor = "0".to_owned();
25252        let mut calls = 0;
25253        loop {
25254            let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
25255            let (next, keys) = scan_reply(&reply);
25256            seen.extend(keys);
25257            cursor = next;
25258            calls += 1;
25259            assert!(calls < 5_000, "a scan that will not finish");
25260            if cursor == "0" {
25261                break;
25262            }
25263        }
25264        seen.sort();
25265        assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
25266        assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
25267
25268        // And the options still work when the walk is over several stripes,
25269        // since a `MATCH` is applied to keys a stripe handed up and a `TYPE` is
25270        // applied by each stripe on the way.
25271        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
25272        let (_, keys) = scan_reply(&reply);
25273        assert_eq!(keys.len(), 10, "key:40 through key:49");
25274        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
25275        let (_, keys) = scan_reply(&reply);
25276        assert!(keys.is_empty(), "nothing here is a list");
25277    }
25278
25279    /// `RANDOMKEY` on a striped database answers a key from any of the stripes.
25280    ///
25281    /// The draw picks the stripe first, so the thing that can go wrong is that
25282    /// it always picks the same one, and two hundred draws over eight stripes
25283    /// would make that obvious.
25284    #[test]
25285    fn a_random_key_can_come_from_any_stripe() {
25286        let mut f = Fixture::striped(8);
25287        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
25288        for i in 0..200 {
25289            let key = format!("key:{i}");
25290            f.run(&[b"SET", key.as_bytes(), b"v"]);
25291        }
25292        let mut homes = std::collections::HashSet::new();
25293        for _ in 0..200 {
25294            let got = f.run(&[b"RANDOMKEY"]);
25295            let key = got.split("\r\n").nth(1).expect("a key").to_owned();
25296            assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
25297            homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
25298        }
25299        assert_eq!(homes.len(), 8, "some stripe was never drawn from");
25300    }
25301
25302    /// Two keys that are not on the same stripe, which is what `RENAME` and
25303    /// `COPY` have to cope with and what a test has to arrange rather than
25304    /// hope for.
25305    fn apart(f: &mut Fixture, src: &str) -> String {
25306        let home = f.server.striped(0).stripe_of(src.as_bytes());
25307        for i in 0..1_000 {
25308            let dst = format!("dst:{i}");
25309            if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
25310                return dst;
25311            }
25312        }
25313        panic!("eight stripes and a thousand keys all landed in one place");
25314    }
25315
25316    /// A rename whose two keys are on two stripes moves the value, the deadline
25317    /// and, for a collection, the body itself.
25318    #[test]
25319    fn a_rename_across_stripes_takes_everything_with_it() {
25320        let mut f = Fixture::striped(8);
25321        let dst = apart(&mut f, "src");
25322        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
25323
25324        f.run(&[b"SET", src, b"v"]);
25325        f.run(&[b"EXPIRE", src, b"100"]);
25326        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
25327        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
25328        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
25329        assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
25330
25331        // A list, because a string lives in its record and a collection lives
25332        // in a slab, and the second of those is the one that can be left
25333        // behind. Planted through the store, since the list group has not been
25334        // taught about stripes yet.
25335        f.server
25336            .striped(0)
25337            .hold(src)
25338            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
25339            .expect("a new list");
25340        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
25341        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
25342        assert_eq!(
25343            f.server.striped(0).hold(dst).llen(dst).expect("a list"),
25344            2,
25345            "the members are on the stripe the key moved to"
25346        );
25347
25348        // And `RENAMENX` still refuses a destination that is taken, which is
25349        // the one answer the cross stripe path has to work out for itself.
25350        f.run(&[b"SET", src, b"v"]);
25351        assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
25352        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
25353        assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
25354    }
25355
25356    /// And a copy across two stripes leaves both keys behind it.
25357    #[test]
25358    fn a_copy_across_stripes_leaves_the_source_where_it_was() {
25359        let mut f = Fixture::striped(8);
25360        let dst = apart(&mut f, "src");
25361        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
25362
25363        f.run(&[b"SET", src, b"v"]);
25364        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
25365        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
25366        assert_eq!(
25367            f.run(&[b"COPY", src, dst]),
25368            ":0\r\n",
25369            "the destination is taken"
25370        );
25371        f.run(&[b"SET", src, b"w"]);
25372        assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
25373        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
25374
25375        // A collection is cloned rather than moved, so both keys have a body of
25376        // their own afterwards and writing to one does not show up in the
25377        // other.
25378        f.run(&[b"DEL", src, dst]);
25379        f.server
25380            .striped(0)
25381            .hold(src)
25382            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
25383            .expect("a new list");
25384        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
25385        f.server
25386            .striped(0)
25387            .hold(src)
25388            .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
25389            .expect("a list that is there");
25390        assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
25391        assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
25392    }
25393
25394    /// Every bitmap command, on one stripe and on eight, replies compared byte
25395    /// for byte.
25396    ///
25397    /// `BITOP` is the one that names more than one key and it is where the work
25398    /// went. The rest are single key commands that now find their own stripe,
25399    /// and they are here because the cheapest way to be sure the routing is
25400    /// right is to ask.
25401    #[test]
25402    fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
25403        let script: &[&[&[u8]]] = &[
25404            &[b"SET", b"k1", b"foobar"],
25405            &[b"SETBIT", b"b1", b"7", b"1"],
25406            &[b"SETBIT", b"b1", b"7", b"0"],
25407            &[b"GETBIT", b"k1", b"6"],
25408            &[b"GETBIT", b"k1", b"100"],
25409            &[b"BITCOUNT", b"k1"],
25410            &[b"BITCOUNT", b"k1", b"0", b"0"],
25411            &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
25412            &[b"BITPOS", b"k1", b"1"],
25413            &[b"BITPOS", b"k1", b"0", b"2"],
25414            &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
25415            &[
25416                b"BITFIELD",
25417                b"bf",
25418                b"SET",
25419                b"u8",
25420                b"0",
25421                b"255",
25422                b"GET",
25423                b"u8",
25424                b"0",
25425            ],
25426            &[
25427                b"BITFIELD",
25428                b"bf",
25429                b"OVERFLOW",
25430                b"SAT",
25431                b"INCRBY",
25432                b"u8",
25433                b"0",
25434                b"10",
25435            ],
25436            &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
25437            // The multi key one, over sources that are not on one stripe unless
25438            // eight stripes have folded into one.
25439            &[b"SET", b"s1", b"abc"],
25440            &[b"SET", b"s2", b"abd"],
25441            &[b"SET", b"s3", b"a"],
25442            &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
25443            &[b"GET", b"d1"],
25444            &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
25445            &[b"GET", b"d2"],
25446            &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
25447            &[b"STRLEN", b"d3"],
25448            &[b"BITOP", b"NOT", b"d4", b"s1"],
25449            &[b"STRLEN", b"d4"],
25450            &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
25451            &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
25452            &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
25453            &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
25454            // A source that is not there reads as empty, and a result with
25455            // nothing in it deletes the destination rather than writing one.
25456            &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
25457            &[b"EXISTS", b"d1"],
25458            &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
25459            &[b"GET", b"d9"],
25460            // And the errors, which have to be the same errors. The key that
25461            // is not a string is planted below rather than pushed here, since
25462            // the list group has not been taught about stripes yet.
25463            &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
25464            &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
25465            &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
25466            &[b"BITOP", b"DIFF", b"d1", b"s1"],
25467            &[b"BITOP", b"NOPE", b"d1", b"s1"],
25468            &[b"BITCOUNT", b"list"],
25469            &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
25470        ];
25471
25472        let mut one = Fixture::new();
25473        let mut many = Fixture::striped(8);
25474        for f in [&mut one, &mut many] {
25475            plant_list(f, b"list");
25476        }
25477        for parts in script {
25478            let a = one.run(parts);
25479            let b = many.run(parts);
25480            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25481        }
25482    }
25483
25484    /// A list under `key`, put there through the store.
25485    ///
25486    /// What a test does when it wants a key of the wrong type on a striped
25487    /// server, because the command that would make one is in a group that has
25488    /// not been taught about stripes yet.
25489    fn plant_list(f: &mut Fixture, key: &[u8]) {
25490        f.server
25491            .striped(0)
25492            .hold(key)
25493            .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
25494            .expect("a new list");
25495    }
25496
25497    /// A `BITOP` whose keys are on two stripes reads both of them.
25498    ///
25499    /// The test above spreads its keys by hashing and would still pass if one
25500    /// stripe were doing all the work, since the answers would be the same. This
25501    /// one puts the destination and the two sources where they are known not to
25502    /// share a stripe.
25503    #[test]
25504    fn a_bitop_across_stripes_reads_every_source() {
25505        let mut f = Fixture::striped(8);
25506        let other = apart(&mut f, "src");
25507        let (src, far) = (b"src".as_slice(), other.as_bytes());
25508        assert_ne!(
25509            f.server.striped(0).stripe_of(src),
25510            f.server.striped(0).stripe_of(far),
25511            "the two keys are the point of the test"
25512        );
25513
25514        f.run(&[b"SET", src, b"abc"]);
25515        f.run(&[b"SET", far, b"abd"]);
25516        assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
25517        assert_eq!(
25518            f.run(&[b"GET", far]),
25519            "$3\r\nab`\r\n",
25520            "a destination that is also a source"
25521        );
25522        f.run(&[b"SET", far, b"abd"]);
25523        assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
25524        assert_eq!(
25525            f.run(&[b"GET", src]),
25526            "$3\r\n\0\0\x07\r\n",
25527            "and the other way round"
25528        );
25529
25530        // A result of nothing deletes a destination on whatever stripe it is
25531        // on, and a source of the wrong type is refused before anything is
25532        // written.
25533        f.run(&[b"SET", src, b"abc"]);
25534        f.run(&[b"DEL", far]);
25535        assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
25536        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
25537        f.run(&[b"SET", src, b"abc"]);
25538        f.run(&[b"DEL", far]);
25539        plant_list(&mut f, far);
25540        assert_eq!(
25541            f.run(&[b"BITOP", b"OR", b"out", src, far]),
25542            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25543        );
25544        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
25545    }
25546
25547    /// Every HyperLogLog command, on one stripe and on eight.
25548    ///
25549    /// Not under Miri, for the reason on
25550    /// `the_debug_forms_answer_four_different_shapes`, and twice over here
25551    /// because the script is run against both shapes of server.
25552    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
25553    #[test]
25554    fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
25555        let script: &[&[&[u8]]] = &[
25556            &[b"PFADD", b"h1", b"a", b"b", b"c"],
25557            &[b"PFADD", b"h1", b"a"],
25558            &[b"PFADD", b"h2"],
25559            &[b"PFADD", b"h2", b"c", b"d", b"e"],
25560            &[b"PFCOUNT", b"h1"],
25561            &[b"PFCOUNT", b"h2"],
25562            &[b"PFCOUNT", b"missing"],
25563            // The two that name more than one key.
25564            &[b"PFCOUNT", b"h1", b"h2"],
25565            &[b"PFCOUNT", b"h1", b"missing"],
25566            &[b"PFMERGE", b"m", b"h1", b"h2"],
25567            &[b"PFCOUNT", b"m"],
25568            &[b"STRLEN", b"m"],
25569            &[b"PFMERGE", b"m"],
25570            &[b"PFCOUNT", b"m"],
25571            &[b"PFMERGE", b"m2", b"missing"],
25572            &[b"PFCOUNT", b"m2"],
25573            // The debugging ones, which are single key and change what they
25574            // look at.
25575            &[b"PFDEBUG", b"ENCODING", b"h1"],
25576            &[b"PFDEBUG", b"DECODE", b"h1"],
25577            &[b"PFDEBUG", b"TODENSE", b"h1"],
25578            &[b"PFDEBUG", b"ENCODING", b"h1"],
25579            &[b"PFDEBUG", b"TODENSE", b"h1"],
25580            &[b"PFCOUNT", b"h1", b"h2"],
25581            &[b"PFSELFTEST"],
25582            // And the errors.
25583            &[b"SET", b"plain", b"not a sketch at all"],
25584            &[b"PFADD", b"plain", b"a"],
25585            &[b"PFCOUNT", b"plain"],
25586            &[b"PFCOUNT", b"h1", b"plain"],
25587            &[b"PFMERGE", b"plain", b"h1"],
25588            &[b"PFMERGE", b"m", b"plain"],
25589            &[b"PFDEBUG", b"ENCODING", b"gone"],
25590            &[b"PFDEBUG", b"NOPE", b"h1"],
25591        ];
25592
25593        let mut one = Fixture::new();
25594        let mut many = Fixture::striped(8);
25595        for parts in script {
25596            let a = one.run(parts);
25597            let b = many.run(parts);
25598            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25599        }
25600    }
25601
25602    /// Every set command, on one stripe and on eight.
25603    ///
25604    /// The commands that answer members answer them in whatever order the set
25605    /// or the table they were built in holds them, so those replies are
25606    /// compared as sets. Everything else is compared byte for byte. Two servers
25607    /// agreeing on the order would be a fact about the tables and not about the
25608    /// answer, and asserting it would make this test fail for a reason nobody
25609    /// cares about.
25610    #[test]
25611    fn the_set_group_answers_the_same_however_many_stripes_there_are() {
25612        const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
25613        let script: &[&[&[u8]]] = &[
25614            &[b"SADD", b"s1", b"a", b"b", b"c"],
25615            &[b"SADD", b"s1", b"a"],
25616            &[b"SADD", b"s2", b"b", b"c", b"d"],
25617            &[b"SADD", b"ints", b"1", b"2", b"3"],
25618            &[b"SCARD", b"s1"],
25619            &[b"SISMEMBER", b"s1", b"a"],
25620            &[b"SISMEMBER", b"s1", b"z"],
25621            &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
25622            &[b"SMEMBERS", b"s1"],
25623            &[b"SREM", b"s1", b"c"],
25624            &[b"SADD", b"s1", b"c"],
25625            &[b"SSCAN", b"s1", b"0"],
25626            &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
25627            // The two draws, on a set of one member, which is the only shape
25628            // whose answer two servers have to agree on.
25629            &[b"SADD", b"one", b"m"],
25630            &[b"SRANDMEMBER", b"one"],
25631            &[b"SRANDMEMBER", b"one", b"-3"],
25632            &[b"SRANDMEMBER", b"gone"],
25633            &[b"SPOP", b"one"],
25634            &[b"SPOP", b"one"],
25635            &[b"SPOP", b"gone", b"2"],
25636            // The one that names two keys.
25637            &[b"SMOVE", b"s1", b"s2", b"a"],
25638            &[b"SMOVE", b"s1", b"s2", b"zzz"],
25639            &[b"SMOVE", b"gone", b"s2", b"a"],
25640            &[b"SMEMBERS", b"s1"],
25641            &[b"SMEMBERS", b"s2"],
25642            // The algebra.
25643            &[b"SINTER", b"s1", b"s2"],
25644            &[b"SUNION", b"s1", b"s2"],
25645            &[b"SDIFF", b"s2", b"s1"],
25646            &[b"SINTER", b"s1", b"gone"],
25647            &[b"SUNION", b"s1", b"gone"],
25648            &[b"SDIFF", b"gone", b"s1"],
25649            &[b"SINTER", b"ints", b"s1"],
25650            &[b"SINTERCARD", b"2", b"s1", b"s2"],
25651            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
25652            &[b"SUNIONCARD", b"2", b"s1", b"s2"],
25653            &[b"SDIFFCARD", b"2", b"s2", b"s1"],
25654            &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
25655            &[b"SMEMBERS", b"d1"],
25656            &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
25657            &[b"SCARD", b"d2"],
25658            &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
25659            &[b"SCARD", b"d3"],
25660            // An empty result deletes the destination rather than storing a
25661            // set with nothing in it.
25662            &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
25663            &[b"EXISTS", b"d4"],
25664            // And a destination that is also a source.
25665            &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
25666            &[b"SCARD", b"s2"],
25667            // The errors, which have to be the same errors.
25668            &[b"SET", b"str", b"v"],
25669            &[b"SADD", b"str", b"a"],
25670            &[b"SINTER", b"s1", b"str"],
25671            &[b"SINTERSTORE", b"d5", b"s1", b"str"],
25672            &[b"EXISTS", b"d5"],
25673            &[b"SMOVE", b"str", b"s2", b"a"],
25674            &[b"SMOVE", b"s1", b"str", b"b"],
25675            &[b"SMOVE", b"gone", b"str", b"b"],
25676            &[b"SINTERCARD", b"0", b"s1"],
25677            &[b"SINTERCARD", b"3", b"s1", b"s2"],
25678            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
25679            &[b"SPOP", b"s1", b"-1"],
25680        ];
25681
25682        let mut one = Fixture::new();
25683        let mut many = Fixture::striped(8);
25684        for parts in script {
25685            let a = one.run(parts);
25686            let b = many.run(parts);
25687            let name = String::from_utf8_lossy(parts[0]).to_uppercase();
25688            if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
25689                assert_eq!(sorted(&a), sorted(&b), "{name}");
25690            } else {
25691                assert_eq!(a, b, "{name}");
25692            }
25693        }
25694    }
25695
25696    /// The algebra over sets that are known to be on different stripes.
25697    #[test]
25698    fn a_set_operation_across_stripes_reads_every_set() {
25699        let mut f = Fixture::striped(8);
25700        let second = apart(&mut f, "s1");
25701        let third = apart(&mut f, &second);
25702        let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
25703
25704        f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
25705        f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
25706        assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
25707        assert_eq!(
25708            sorted(&f.run(&[b"SUNION", s1, s2])),
25709            ["a", "b", "c", "d"],
25710            "a union of two stripes is both of them"
25711        );
25712        assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
25713        assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
25714        assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
25715        assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
25716
25717        // A destination on a third stripe, and then one that is also a source.
25718        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
25719        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
25720        assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
25721        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
25722        assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
25723        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
25724
25725        // An empty result deletes a destination wherever it is, and a key of
25726        // the wrong type stops the command before the destination is touched.
25727        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
25728        assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
25729        f.run(&[b"SET", s3, b"v"]);
25730        assert_eq!(
25731            f.run(&[b"SINTER", s1, s3]),
25732            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25733        );
25734        assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
25735    }
25736
25737    /// An `SMOVE` whose two keys are on two stripes.
25738    #[test]
25739    fn a_move_across_stripes_takes_the_member_with_it() {
25740        let mut f = Fixture::striped(8);
25741        let other = apart(&mut f, "src");
25742        let (src, dst) = (b"src".as_slice(), other.as_bytes());
25743
25744        f.run(&[b"SADD", src, b"a", b"b"]);
25745        f.run(&[b"SADD", dst, b"c"]);
25746        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
25747        assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
25748        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
25749        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
25750
25751        // A destination that is not there is created on its own stripe, and a
25752        // source that loses its last member is deleted from its own.
25753        f.run(&[b"DEL", dst]);
25754        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
25755        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
25756        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
25757
25758        // And a source that is not there answers zero without ever asking what
25759        // the destination holds, which is Redis's order and not the obvious
25760        // one.
25761        f.run(&[b"SET", dst, b"v"]);
25762        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
25763        f.run(&[b"SADD", src, b"b"]);
25764        assert_eq!(
25765            f.run(&[b"SMOVE", src, dst, b"b"]),
25766            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
25767        );
25768    }
25769
25770    /// A count and a merge over sketches that are known to be on two stripes.
25771    #[test]
25772    fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
25773        let mut f = Fixture::striped(8);
25774        let other = apart(&mut f, "src");
25775        let (src, far) = (b"src".as_slice(), other.as_bytes());
25776
25777        for i in 0..150 {
25778            let ele = format!("e:{i}");
25779            f.run(&[b"PFADD", src, ele.as_bytes()]);
25780        }
25781        for i in 150..200 {
25782            let ele = format!("e:{i}");
25783            f.run(&[b"PFADD", far, ele.as_bytes()]);
25784        }
25785        // The three numbers a real server gives for these elements, which are
25786        // the numbers the single stripe tests in the keyspace crate check too.
25787        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
25788        assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
25789        assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
25790
25791        // A merge whose destination is on a third stripe, and then one that
25792        // writes into a source.
25793        let dest = apart(&mut f, &other);
25794        assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
25795        assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
25796        assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
25797        assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
25798        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
25799    }
25800
25801    /// Every sorted set command, on one stripe and on eight.
25802    ///
25803    /// Every reply here is compared byte for byte, unlike the set group, because
25804    /// a sorted set answers in rank order and members sharing a score come out
25805    /// in the order of their bytes. There is nothing left for the table the
25806    /// answer was built in to decide.
25807    #[test]
25808    fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
25809        let script: &[&[&[u8]]] = &[
25810            &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
25811            &[b"ZADD", b"z1", b"NX", b"9", b"a"],
25812            &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
25813            &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
25814            &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
25815            &[b"ZINCRBY", b"z1", b"1.5", b"b"],
25816            &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
25817            &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
25818            &[b"ZADD", b"one", b"1", b"m"],
25819            &[b"ZCARD", b"z1"],
25820            &[b"ZCARD", b"gone"],
25821            &[b"ZSCORE", b"z1", b"a"],
25822            &[b"ZSCORE", b"z1", b"zz"],
25823            &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
25824            &[b"ZRANK", b"z1", b"c"],
25825            &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
25826            &[b"ZREVRANK", b"z1", b"c"],
25827            &[b"ZRANK", b"z1", b"gone"],
25828            &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
25829            &[b"ZCOUNT", b"z1", b"(1", b"3"],
25830            &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
25831            // The range commands, which are one parse and one walk.
25832            &[b"ZRANGE", b"z1", b"0", b"-1"],
25833            &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
25834            &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
25835            &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
25836            &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
25837            &[b"ZREVRANGE", b"z1", b"0", b"-1"],
25838            &[
25839                b"ZRANGEBYSCORE",
25840                b"z1",
25841                b"-inf",
25842                b"+inf",
25843                b"LIMIT",
25844                b"1",
25845                b"1",
25846            ],
25847            &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
25848            &[b"ZSCAN", b"z1", b"0"],
25849            &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
25850            // The draw, on a sorted set of one member, which is the only shape
25851            // whose answer two servers have to agree on.
25852            &[b"ZRANDMEMBER", b"one"],
25853            &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
25854            &[b"ZRANDMEMBER", b"gone"],
25855            // The one that copies a window into another key.
25856            &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
25857            &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
25858            &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
25859            &[b"EXISTS", b"d0"],
25860            // The algebra, in both its shapes.
25861            &[b"ZUNION", b"2", b"z1", b"z2"],
25862            &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
25863            &[
25864                b"ZUNION",
25865                b"2",
25866                b"z1",
25867                b"z2",
25868                b"WEIGHTS",
25869                b"2",
25870                b"3",
25871                b"AGGREGATE",
25872                b"MAX",
25873                b"WITHSCORES",
25874            ],
25875            &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
25876            &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
25877            &[b"ZDIFF", b"2", b"gone", b"z1"],
25878            &[b"ZINTERCARD", b"2", b"z1", b"z2"],
25879            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
25880            &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
25881            &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
25882            &[
25883                b"ZINTERSTORE",
25884                b"d2",
25885                b"2",
25886                b"z1",
25887                b"z2",
25888                b"AGGREGATE",
25889                b"MIN",
25890            ],
25891            &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
25892            &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
25893            &[b"ZCARD", b"d3"],
25894            // An empty result deletes the destination rather than storing a
25895            // sorted set with nothing in it.
25896            &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
25897            &[b"EXISTS", b"d4"],
25898            // A plain set is a sorted set where every score is one, so it is a
25899            // legal input to all of these.
25900            &[b"SADD", b"plain", b"a", b"x"],
25901            &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
25902            &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
25903            // And a destination that is also a source.
25904            &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
25905            &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
25906            // The three removals and the two pops.
25907            &[b"ZREM", b"d5", b"x", b"nothere"],
25908            &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
25909            &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
25910            &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
25911            &[b"ZPOPMIN", b"z1"],
25912            &[b"ZPOPMAX", b"z1", b"2"],
25913            &[b"ZPOPMIN", b"gone"],
25914            &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
25915            &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
25916            // The errors, which have to be the same errors.
25917            &[b"SET", b"str", b"v"],
25918            &[b"ZADD", b"str", b"1", b"a"],
25919            &[b"ZSCORE", b"str", b"a"],
25920            &[b"ZADD", b"z1", b"nan", b"a"],
25921            &[b"ZUNION", b"2", b"z1", b"str"],
25922            &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
25923            &[b"EXISTS", b"d6"],
25924            &[b"ZINTERCARD", b"0", b"z1"],
25925            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
25926            &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
25927            &[b"ZMPOP", b"1", b"str", b"MIN"],
25928            &[b"ZPOPMIN", b"z1", b"-1"],
25929        ];
25930
25931        let mut one = Fixture::new();
25932        let mut many = Fixture::striped(8);
25933        for parts in script {
25934            let a = one.run(parts);
25935            let b = many.run(parts);
25936            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
25937        }
25938    }
25939
25940    /// The algebra over sorted sets that are known to be on different stripes.
25941    #[test]
25942    fn a_sorted_set_operation_across_stripes_reads_every_input() {
25943        let mut f = Fixture::striped(8);
25944        let second = apart(&mut f, "z1");
25945        let third = apart(&mut f, &second);
25946        let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
25947
25948        f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
25949        f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
25950        // a is 1, c is 4, b is 2 and 3 added together, which is the order they
25951        // come out in and the answer that says both stripes were read.
25952        assert_eq!(
25953            f.run(&[b"ZUNION", b"2", z1, z2]),
25954            "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
25955        );
25956        assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
25957        assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
25958        assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
25959        assert_eq!(
25960            f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
25961            ":1\r\n"
25962        );
25963
25964        // A destination on a third stripe, and the weights and the aggregate
25965        // reaching every input.
25966        assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
25967        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
25968        assert_eq!(
25969            f.run(&[
25970                b"ZUNIONSTORE",
25971                z3,
25972                b"2",
25973                z1,
25974                z2,
25975                b"WEIGHTS",
25976                b"2",
25977                b"3",
25978                b"AGGREGATE",
25979                b"MAX"
25980            ]),
25981            ":3\r\n"
25982        );
25983        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
25984        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
25985        assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
25986        assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
25987        assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
25988
25989        // A pop over keys on several stripes takes from the first one that has
25990        // anything, which is what makes the order of the keys matter.
25991        let popped = format!(
25992            "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
25993            second.len()
25994        );
25995        assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
25996        f.run(&[b"ZADD", z2, b"3", b"b"]);
25997
25998        // An empty result deletes a destination wherever it is, and an input of
25999        // the wrong type stops the command before the destination is touched.
26000        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
26001        assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
26002        f.run(&[b"SET", z3, b"v"]);
26003        assert_eq!(
26004            f.run(&[b"ZUNION", b"2", z1, z3]),
26005            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26006        );
26007        assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
26008
26009        // And a destination that is also a source works across stripes for the
26010        // reason it works on one: the whole result is built before anything is
26011        // written.
26012        assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
26013        assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
26014        assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
26015    }
26016
26017    /// A `ZRANGESTORE` whose two keys are on two stripes.
26018    #[test]
26019    fn a_range_store_across_stripes_copies_the_window() {
26020        let mut f = Fixture::striped(8);
26021        let other = apart(&mut f, "src");
26022        let third = apart(&mut f, &other);
26023        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
26024
26025        f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
26026        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
26027        assert_eq!(
26028            f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
26029            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
26030        );
26031        assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
26032
26033        // A window walked backwards takes the other end of the sorted set and
26034        // still stores what it took in score order.
26035        assert_eq!(
26036            f.run(&[
26037                b"ZRANGESTORE",
26038                dst,
26039                src,
26040                b"+inf",
26041                b"-inf",
26042                b"BYSCORE",
26043                b"REV",
26044                b"LIMIT",
26045                b"0",
26046                b"2"
26047            ]),
26048            ":2\r\n"
26049        );
26050        assert_eq!(
26051            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
26052            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
26053        );
26054
26055        // An empty window deletes the destination on its own stripe, and a
26056        // source of the wrong type is refused before the destination is touched.
26057        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
26058        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
26059        f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
26060        f.run(&[b"SET", plain, b"v"]);
26061        assert_eq!(
26062            f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
26063            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26064        );
26065        assert_eq!(
26066            f.run(&[b"ZCARD", dst]),
26067            ":3\r\n",
26068            "and left the destination"
26069        );
26070    }
26071
26072    /// Every list command, on one stripe and on eight.
26073    ///
26074    /// The blocking six are in here too, both when they can be answered on the
26075    /// spot and when they cannot, since a command that parks its client writes
26076    /// nothing at all and two servers have to agree about that as much as they
26077    /// agree about a reply.
26078    #[test]
26079    fn the_list_group_answers_the_same_however_many_stripes_there_are() {
26080        let script: &[&[&[u8]]] = &[
26081            &[b"RPUSH", b"l1", b"a", b"b", b"c"],
26082            &[b"LPUSH", b"l1", b"z"],
26083            &[b"RPUSHX", b"l1", b"d"],
26084            &[b"LPUSHX", b"gone", b"x"],
26085            &[b"RPUSHX", b"gone", b"x"],
26086            &[b"LLEN", b"l1"],
26087            &[b"LLEN", b"gone"],
26088            &[b"LRANGE", b"l1", b"0", b"-1"],
26089            &[b"LRANGE", b"l1", b"1", b"2"],
26090            &[b"LRANGE", b"l1", b"5", b"9"],
26091            &[b"LINDEX", b"l1", b"0"],
26092            &[b"LINDEX", b"l1", b"-1"],
26093            &[b"LINDEX", b"l1", b"99"],
26094            &[b"LSET", b"l1", b"0", b"y"],
26095            &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
26096            &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
26097            &[b"LPOS", b"l1", b"b"],
26098            &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
26099            &[b"LPOS", b"l1", b"nothere"],
26100            &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
26101            &[b"LREM", b"l1", b"1", b"aa"],
26102            &[b"LTRIM", b"l1", b"0", b"3"],
26103            &[b"LRANGE", b"l1", b"0", b"-1"],
26104            &[b"LPOP", b"l1"],
26105            &[b"RPOP", b"l1"],
26106            &[b"LPOP", b"l1", b"2"],
26107            &[b"LPOP", b"gone"],
26108            &[b"LPOP", b"gone", b"2"],
26109            &[b"EXISTS", b"l1"],
26110            // The ones that name two keys, and the one that takes a block of
26111            // elements rather than the one on the end.
26112            &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
26113            &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
26114            &[b"RPOPLPUSH", b"src", b"dst"],
26115            &[b"LRANGE", b"dst", b"0", b"-1"],
26116            &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
26117            &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
26118            &[
26119                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
26120            ],
26121            &[
26122                b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
26123            ],
26124            &[b"LRANGE", b"dst", b"0", b"-1"],
26125            &[
26126                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
26127            ],
26128            &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
26129            &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
26130            &[b"LMPOP", b"1", b"gone", b"LEFT"],
26131            // The blocking ones, first with something there to answer them and
26132            // then with nothing, which parks the client and writes nothing.
26133            &[b"RPUSH", b"q", b"a", b"b", b"c"],
26134            &[b"BLPOP", b"gone", b"q", b"0"],
26135            &[b"BRPOP", b"q", b"0"],
26136            &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
26137            &[b"RPUSH", b"q", b"x", b"y", b"z"],
26138            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
26139            &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
26140            &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
26141            &[b"BLPOP", b"q", b"0"],
26142            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
26143            // The errors, which have to be the same errors.
26144            &[b"SET", b"plain", b"v"],
26145            &[b"LPUSH", b"plain", b"a"],
26146            &[b"LLEN", b"plain"],
26147            &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
26148            &[b"LRANGE", b"dst", b"0", b"-1"],
26149            &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
26150            &[b"LSET", b"gone", b"0", b"v"],
26151            &[b"LSET", b"dst", b"99", b"v"],
26152            &[b"LPOP", b"dst", b"-1"],
26153            &[b"LMPOP", b"0", b"dst", b"LEFT"],
26154            &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
26155        ];
26156
26157        let mut one = Fixture::new();
26158        let mut many = Fixture::striped(8);
26159        for parts in script {
26160            let a = one.run(parts);
26161            let b = many.run(parts);
26162            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26163        }
26164    }
26165
26166    /// An `LMOVE` and an `LMOVEM` whose two keys are on two stripes.
26167    #[test]
26168    fn a_list_move_across_stripes_takes_the_elements_with_it() {
26169        let mut f = Fixture::striped(8);
26170        let other = apart(&mut f, "src");
26171        let third = apart(&mut f, &other);
26172        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
26173
26174        f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
26175        assert_eq!(
26176            f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
26177            "$1\r\na\r\n"
26178        );
26179        assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
26180        assert_eq!(
26181            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
26182            "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
26183            "one went on each end of the destination"
26184        );
26185        assert_eq!(
26186            f.run(&[b"LRANGE", src, b"0", b"-1"]),
26187            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
26188        );
26189
26190        // A block of them, which under BULK arrives in the order it left.
26191        assert_eq!(
26192            f.run(&[
26193                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
26194            ]),
26195            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
26196        );
26197        assert_eq!(
26198            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
26199            "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
26200        );
26201        assert_eq!(
26202            f.run(&[b"EXISTS", src]),
26203            ":0\r\n",
26204            "and the source is gone with its last element"
26205        );
26206
26207        // An `EXACTLY` the source cannot fill moves nothing, and a source that
26208        // is not there at all is the two kinds of nothing the two commands have.
26209        f.run(&[b"RPUSH", src, b"e", b"f"]);
26210        assert_eq!(
26211            f.run(&[
26212                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
26213            ]),
26214            "*-1\r\n"
26215        );
26216        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
26217        assert_eq!(
26218            f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
26219            "$-1\r\n"
26220        );
26221        assert_eq!(
26222            f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
26223            "*-1\r\n"
26224        );
26225
26226        // A destination of the wrong type is refused before anything is taken,
26227        // which is the order that matters most here, since an element already
26228        // out of the source would have nowhere to go back to.
26229        f.run(&[b"SET", plain, b"v"]);
26230        assert_eq!(
26231            f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
26232            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26233        );
26234        assert_eq!(
26235            f.run(&[b"LLEN", src]),
26236            ":2\r\n",
26237            "and left the source alone"
26238        );
26239        assert_eq!(
26240            f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
26241            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26242        );
26243        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
26244    }
26245
26246    /// A parked client served by a push that landed on another stripe.
26247    ///
26248    /// A waiter remembers the database and not the stripe, which is the point:
26249    /// serving it runs the same attempt the command ran, and the attempt finds
26250    /// the stripe each of its keys is on for itself.
26251    #[test]
26252    fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
26253        let mut f = Fixture::striped(8);
26254        let other = apart(&mut f, "q");
26255        let (q, far) = (b"q".as_slice(), other.as_bytes());
26256
26257        assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
26258        assert_eq!(f.server.parked(), 1);
26259        f.run(&[b"RPUSH", far, b"v"]);
26260        let mut out = Out::new(Proto::Resp2);
26261        assert!(f.server.serve_waiter(7, 0, &mut out));
26262        let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
26263        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
26264        assert_eq!(
26265            f.run(&[b"EXISTS", far]),
26266            ":0\r\n",
26267            "and it took the element with it"
26268        );
26269
26270        // And a move across two stripes is served the same way, by the push
26271        // that fills its source.
26272        f.server.forget_waiters(7);
26273        assert_eq!(
26274            f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
26275            Flow::Block
26276        );
26277        f.run(&[b"RPUSH", q, b"w"]);
26278        let mut out = Out::new(Proto::Resp2);
26279        assert!(f.server.serve_waiter(7, 0, &mut out));
26280        assert_eq!(
26281            core::str::from_utf8(out.as_slice()).expect("ascii"),
26282            "$1\r\nw\r\n"
26283        );
26284        assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
26285    }
26286
26287    /// Every stream command, on one stripe and on eight.
26288    ///
26289    /// Every ID is written out rather than left to the clock, so the two servers
26290    /// are being compared on what they store and not on how long the test took
26291    /// to get from one of them to the other.
26292    #[test]
26293    fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
26294        let script: &[&[&[u8]]] = &[
26295            &[b"XADD", b"s", b"1-1", b"a", b"1"],
26296            &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
26297            &[b"XADD", b"s", b"3-1", b"d", b"4"],
26298            &[b"XADD", b"s", b"1-1", b"e", b"5"],
26299            &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
26300            &[b"XLEN", b"s"],
26301            &[b"XLEN", b"gone"],
26302            &[b"XRANGE", b"s", b"-", b"+"],
26303            &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
26304            &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
26305            &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
26306            &[b"XREVRANGE", b"s", b"+", b"-"],
26307            &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
26308            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
26309            &[b"XREAD", b"STREAMS", b"s", b"$"],
26310            // The groups, which is where most of the state is.
26311            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
26312            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
26313            &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
26314            &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
26315            &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
26316            &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
26317            &[
26318                b"XREADGROUP",
26319                b"GROUP",
26320                b"g",
26321                b"c1",
26322                b"COUNT",
26323                b"1",
26324                b"STREAMS",
26325                b"s",
26326                b"0",
26327            ],
26328            &[
26329                b"XREADGROUP",
26330                b"GROUP",
26331                b"nope",
26332                b"c1",
26333                b"STREAMS",
26334                b"s",
26335                b">",
26336            ],
26337            &[b"XPENDING", b"s", b"g"],
26338            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
26339            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
26340            &[b"XPENDING", b"s", b"nope"],
26341            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
26342            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
26343            &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
26344            &[b"XACK", b"s", b"g", b"1-1"],
26345            &[b"XACK", b"s", b"g", b"1-1"],
26346            &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
26347            &[b"XPENDING", b"s", b"g"],
26348            &[b"XINFO", b"STREAM", b"s"],
26349            &[b"XINFO", b"GROUPS", b"s"],
26350            &[b"XINFO", b"CONSUMERS", b"s", b"g"],
26351            &[b"XINFO", b"STREAM", b"gone"],
26352            // Deleting, trimming and moving the ID on.
26353            &[b"XDEL", b"s", b"3-1"],
26354            &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
26355            &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
26356            &[b"XADD", b"s", b"9-1", b"z", b"9"],
26357            &[b"XTRIM", b"s", b"MAXLEN", b"1"],
26358            &[b"XTRIM", b"s", b"MINID", b"9"],
26359            &[b"XSETID", b"s", b"99-1"],
26360            &[b"XSETID", b"s", b"1-1"],
26361            &[b"XLEN", b"s"],
26362            &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
26363            &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
26364            &[b"XGROUP", b"DESTROY", b"s", b"g"],
26365            &[b"XGROUP", b"DESTROY", b"s", b"g"],
26366            // And the errors.
26367            &[b"SET", b"plain", b"v"],
26368            &[b"XADD", b"plain", b"1-1", b"a", b"1"],
26369            &[b"XLEN", b"plain"],
26370            &[b"XREAD", b"STREAMS", b"plain", b"0"],
26371            &[b"XRANGE", b"s", b"bogus", b"+"],
26372            &[b"XADD", b"s", b"1-1", b"a"],
26373            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
26374            &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
26375        ];
26376
26377        let mut one = Fixture::new();
26378        let mut many = Fixture::striped(8);
26379        for parts in script {
26380            let a = one.run(parts);
26381            let b = many.run(parts);
26382            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26383        }
26384    }
26385
26386    /// An `XREAD` and an `XREADGROUP` naming two keys on two stripes.
26387    ///
26388    /// Nothing is shared between the two streams, so the only thing this can go
26389    /// wrong at is looking both of them up, which is exactly what a read that
26390    /// held one database and walked it would get wrong.
26391    #[test]
26392    fn a_stream_read_across_stripes_reads_every_key() {
26393        let mut f = Fixture::striped(8);
26394        let other = apart(&mut f, "s1");
26395        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
26396
26397        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
26398        f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
26399        let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
26400        assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
26401        assert!(got.contains("1-1"), "the first one is in there: {got}");
26402        assert!(got.contains("2-1"), "and so is the second: {got}");
26403
26404        // A group read looks its group up on every key before it reads any of
26405        // them, so a group that is missing on the far key stops the near one.
26406        f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
26407        let got = f.run(&[
26408            b"XREADGROUP",
26409            b"GROUP",
26410            b"g",
26411            b"c",
26412            b"STREAMS",
26413            s1,
26414            s2,
26415            b">",
26416            b">",
26417        ]);
26418        assert!(got.starts_with("-NOGROUP"), "{got}");
26419        assert_eq!(
26420            f.run(&[b"XPENDING", s1, b"g"]),
26421            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
26422            "and read nothing from the key that did have the group"
26423        );
26424
26425        f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
26426        let got = f.run(&[
26427            b"XREADGROUP",
26428            b"GROUP",
26429            b"g",
26430            b"c",
26431            b"STREAMS",
26432            s1,
26433            s2,
26434            b">",
26435            b">",
26436        ]);
26437        assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
26438    }
26439
26440    /// A client parked on an `XREAD` woken by an entry on another stripe.
26441    #[test]
26442    fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
26443        let mut f = Fixture::striped(8);
26444        let other = apart(&mut f, "s1");
26445        let (s1, far) = (b"s1".as_slice(), other.as_bytes());
26446        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
26447        f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
26448
26449        assert_eq!(
26450            f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
26451                .0,
26452            Flow::Block
26453        );
26454        f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
26455        let mut out = Out::new(Proto::Resp2);
26456        assert!(f.server.serve_waiter(7, 0, &mut out));
26457        let want = format!(
26458            "*1\r\n*2\r\n${}\r\n{other}\r\n*1\r\n*2\r\n$3\r\n2-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
26459            other.len()
26460        );
26461        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
26462    }
26463
26464    /// Every JSON command, on one stripe and on eight.
26465    #[test]
26466    fn the_json_group_answers_the_same_however_many_stripes_there_are() {
26467        let script: &[&[&[u8]]] = &[
26468            &[
26469                b"JSON.SET",
26470                b"d",
26471                b"$",
26472                br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
26473            ],
26474            &[b"JSON.SET", b"d", b"$.a", b"2"],
26475            &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
26476            &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
26477            &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
26478            &[b"JSON.GET", b"d"],
26479            &[b"JSON.GET", b"d", b"$.b"],
26480            &[b"JSON.GET", b"gone", b"$"],
26481            &[b"JSON.TYPE", b"d", b"$.b"],
26482            &[b"JSON.TYPE", b"d", b"$.s"],
26483            &[b"JSON.TOGGLE", b"d", b"$.t"],
26484            &[b"JSON.ARRLEN", b"d", b"$.b"],
26485            &[b"JSON.OBJLEN", b"d", b"$"],
26486            &[b"JSON.OBJKEYS", b"d", b"$"],
26487            &[b"JSON.STRLEN", b"d", b"$.s"],
26488            &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
26489            &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
26490            &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
26491            &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
26492            &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
26493            &[b"JSON.ARRPOP", b"d", b"$.b"],
26494            &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
26495            &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
26496            &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
26497            &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
26498            &[b"JSON.RESP", b"d", b"$.b"],
26499            &[b"JSON.DEBUG", b"MEMORY", b"d"],
26500            &[b"JSON.CLEAR", b"d", b"$.b"],
26501            &[b"JSON.DEL", b"d", b"$.m"],
26502            &[b"JSON.FORGET", b"d", b"$.nothere"],
26503            // The two that name more than one key.
26504            &[
26505                b"JSON.MSET",
26506                b"m1",
26507                b"$",
26508                b"1",
26509                b"m2",
26510                b"$",
26511                b"2",
26512                b"m3",
26513                b"$",
26514                b"3",
26515            ],
26516            &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
26517            &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
26518            &[b"JSON.GET", b"m1", b"$"],
26519            &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
26520            &[b"JSON.GET", b"m2", b"$"],
26521            // And the errors.
26522            &[b"SET", b"plain", b"v"],
26523            &[b"JSON.GET", b"plain", b"$"],
26524            &[b"JSON.SET", b"plain", b"$", b"1"],
26525            &[b"JSON.MGET", b"m1", b"plain", b"$"],
26526            &[b"JSON.SET", b"d", b"$.b", b"["],
26527            &[b"JSON.DEL", b"plain"],
26528        ];
26529
26530        let mut one = Fixture::new();
26531        let mut many = Fixture::striped(8);
26532        for parts in script {
26533            let a = one.run(parts);
26534            let b = many.run(parts);
26535            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26536        }
26537    }
26538
26539    /// A `JSON.MSET` and a `JSON.MGET` whose keys are on several stripes.
26540    ///
26541    /// `JSON.MSET` works every triple out against the keyspace as it was before
26542    /// the command and writes nothing until all of them are known to work, so
26543    /// the thing to check is that a triple that cannot be written stops the
26544    /// ones on other stripes as well as the ones on its own.
26545    #[test]
26546    fn a_json_multi_write_across_stripes_reaches_every_key() {
26547        let mut f = Fixture::striped(8);
26548        let second = apart(&mut f, "m1");
26549        let third = apart(&mut f, &second);
26550        let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
26551
26552        assert_eq!(
26553            f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
26554            "+OK\r\n"
26555        );
26556        assert_eq!(
26557            f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
26558            "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
26559        );
26560
26561        // A value that is not JSON is refused before anything is written, and
26562        // the key on the far stripe keeps what it had.
26563        assert_eq!(
26564            f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
26565            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
26566        );
26567        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
26568
26569        // A path that names nowhere is not an error. That triple is skipped,
26570        // the ones on the other stripes are still written, and the reply is a
26571        // nil rather than OK.
26572        assert_eq!(
26573            f.run(&[
26574                b"JSON.MSET",
26575                m1,
26576                b"$",
26577                b"9",
26578                m2,
26579                b"$.deep",
26580                b"9",
26581                m3,
26582                b"$",
26583                b"7"
26584            ]),
26585            "$-1\r\n"
26586        );
26587        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
26588        assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
26589        assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
26590    }
26591
26592    /// Every geospatial command, on one stripe and on eight.
26593    #[test]
26594    fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
26595        let script: &[&[&[u8]]] = &[
26596            &[
26597                b"GEOADD",
26598                b"g",
26599                b"13.361389",
26600                b"38.115556",
26601                b"palermo",
26602                b"15.087269",
26603                b"37.502669",
26604                b"catania",
26605            ],
26606            &[
26607                b"GEOADD",
26608                b"g",
26609                b"NX",
26610                b"13.361389",
26611                b"38.115556",
26612                b"palermo",
26613            ],
26614            &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
26615            &[b"GEOPOS", b"g", b"palermo", b"nothere"],
26616            &[b"GEOHASH", b"g", b"palermo", b"catania"],
26617            &[b"GEODIST", b"g", b"palermo", b"catania"],
26618            &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
26619            &[b"GEODIST", b"g", b"palermo", b"nothere"],
26620            &[
26621                b"GEOSEARCH",
26622                b"g",
26623                b"FROMLONLAT",
26624                b"15",
26625                b"37",
26626                b"BYRADIUS",
26627                b"200",
26628                b"KM",
26629                b"ASC",
26630                b"WITHCOORD",
26631                b"WITHDIST",
26632                b"WITHHASH",
26633            ],
26634            &[
26635                b"GEOSEARCH",
26636                b"g",
26637                b"FROMMEMBER",
26638                b"palermo",
26639                b"BYBOX",
26640                b"400",
26641                b"400",
26642                b"KM",
26643                b"DESC",
26644            ],
26645            &[
26646                b"GEORADIUS",
26647                b"g",
26648                b"15",
26649                b"37",
26650                b"200",
26651                b"KM",
26652                b"COUNT",
26653                b"1",
26654            ],
26655            &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
26656            &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
26657            &[
26658                b"GEOSEARCHSTORE",
26659                b"dst",
26660                b"g",
26661                b"FROMLONLAT",
26662                b"15",
26663                b"37",
26664                b"BYRADIUS",
26665                b"200",
26666                b"KM",
26667            ],
26668            &[b"ZRANGE", b"dst", b"0", b"-1"],
26669            &[
26670                b"GEOSEARCHSTORE",
26671                b"dst",
26672                b"g",
26673                b"FROMLONLAT",
26674                b"15",
26675                b"37",
26676                b"BYRADIUS",
26677                b"1",
26678                b"M",
26679                b"STOREDIST",
26680            ],
26681            &[b"EXISTS", b"dst"],
26682            &[
26683                b"GEORADIUS",
26684                b"g",
26685                b"15",
26686                b"37",
26687                b"200",
26688                b"KM",
26689                b"STORE",
26690                b"dst",
26691            ],
26692            &[b"ZCARD", b"dst"],
26693            // And the errors.
26694            &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
26695            &[b"SET", b"plain", b"v"],
26696            &[b"GEOPOS", b"plain", b"a"],
26697            &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
26698            &[
26699                b"GEOSEARCHSTORE",
26700                b"dst",
26701                b"g",
26702                b"FROMLONLAT",
26703                b"15",
26704                b"37",
26705                b"BYRADIUS",
26706                b"200",
26707                b"KM",
26708                b"WITHCOORD",
26709            ],
26710        ];
26711
26712        let mut one = Fixture::new();
26713        let mut many = Fixture::striped(8);
26714        for parts in script {
26715            let a = one.run(parts);
26716            let b = many.run(parts);
26717            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26718        }
26719    }
26720
26721    /// A `GEOSEARCHSTORE` whose two keys are on two stripes.
26722    #[test]
26723    fn a_geo_search_store_across_stripes_writes_what_it_found() {
26724        let mut f = Fixture::striped(8);
26725        let other = apart(&mut f, "g");
26726        let third = apart(&mut f, &other);
26727        let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
26728
26729        f.run(&[
26730            b"GEOADD",
26731            g,
26732            b"13.361389",
26733            b"38.115556",
26734            b"palermo",
26735            b"15.087269",
26736            b"37.502669",
26737            b"catania",
26738        ]);
26739        assert_eq!(
26740            f.run(&[
26741                b"GEOSEARCHSTORE",
26742                dst,
26743                g,
26744                b"FROMLONLAT",
26745                b"15",
26746                b"37",
26747                b"BYRADIUS",
26748                b"200",
26749                b"KM",
26750                b"ASC",
26751            ]),
26752            ":2\r\n"
26753        );
26754        assert_eq!(
26755            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
26756            "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
26757            "the geohash is the score, so the order is not the search order"
26758        );
26759        assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
26760
26761        // `STOREDIST` stores the distance in the unit the search was asked in,
26762        // which is the destination stripe's sorted set and not the source's.
26763        assert_eq!(
26764            f.run(&[
26765                b"GEOSEARCHSTORE",
26766                dst,
26767                g,
26768                b"FROMMEMBER",
26769                b"palermo",
26770                b"BYRADIUS",
26771                b"200",
26772                b"KM",
26773                b"STOREDIST",
26774            ]),
26775            ":2\r\n"
26776        );
26777        assert_eq!(
26778            f.run(&[b"ZSCORE", dst, b"palermo"]),
26779            "$1\r\n0\r\n",
26780            "the centre is nought away from itself"
26781        );
26782
26783        // A search that found nothing deletes the destination on its own
26784        // stripe, and a source of the wrong type is refused with the
26785        // destination left alone.
26786        assert_eq!(
26787            f.run(&[
26788                b"GEOSEARCHSTORE",
26789                dst,
26790                g,
26791                b"FROMLONLAT",
26792                b"0",
26793                b"0",
26794                b"BYRADIUS",
26795                b"1",
26796                b"M",
26797            ]),
26798            ":0\r\n"
26799        );
26800        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
26801        f.run(&[
26802            b"GEOSEARCHSTORE",
26803            dst,
26804            g,
26805            b"FROMLONLAT",
26806            b"15",
26807            b"37",
26808            b"BYRADIUS",
26809            b"200",
26810            b"KM",
26811        ]);
26812        f.run(&[b"SET", plain, b"v"]);
26813        assert_eq!(
26814            f.run(&[
26815                b"GEOSEARCHSTORE",
26816                dst,
26817                plain,
26818                b"FROMLONLAT",
26819                b"15",
26820                b"37",
26821                b"BYRADIUS",
26822                b"200",
26823                b"KM",
26824            ]),
26825            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
26826        );
26827        assert_eq!(
26828            f.run(&[b"ZCARD", dst]),
26829            ":2\r\n",
26830            "and left the destination"
26831        );
26832    }
26833
26834    /// Every time series command, on one stripe and on eight.
26835    ///
26836    /// Every timestamp is written out rather than left to the clock, so the two
26837    /// servers are compared on the samples they hold and not on how long the
26838    /// test took to get from one of them to the other.
26839    #[test]
26840    fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
26841        let script: &[&[&[u8]]] = &[
26842            &[
26843                b"TS.CREATE",
26844                b"ts:a",
26845                b"LABELS",
26846                b"sensor",
26847                b"a",
26848                b"room",
26849                b"1",
26850            ],
26851            &[b"TS.CREATE", b"ts:a"],
26852            &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
26853            &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
26854            &[
26855                b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
26856            ],
26857            &[
26858                b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
26859            ],
26860            &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
26861            &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
26862            &[b"TS.GET", b"ts:a"],
26863            &[b"TS.GET", b"gone"],
26864            &[b"TS.RANGE", b"ts:a", b"-", b"+"],
26865            &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
26866            &[
26867                b"TS.RANGE",
26868                b"ts:a",
26869                b"-",
26870                b"+",
26871                b"AGGREGATION",
26872                b"avg",
26873                b"2000",
26874            ],
26875            &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
26876            &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
26877            &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
26878            &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
26879            &[b"TS.READ", b"ts:a", b"0"],
26880            &[b"TS.READ", b"ts:a", b"+"],
26881            // The filters, which are the ones that have to walk every stripe.
26882            &[b"TS.QUERYINDEX", b"sensor=a"],
26883            &[b"TS.QUERYINDEX", b"room=1"],
26884            &[b"TS.QUERYINDEX", b"room=9"],
26885            &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
26886            &[
26887                b"TS.QUERYLABELS",
26888                b"VALUES",
26889                b"sensor",
26890                b"FILTER",
26891                b"room=1",
26892            ],
26893            &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
26894            &[
26895                b"TS.MGET",
26896                b"SELECTED_LABELS",
26897                b"sensor",
26898                b"FILTER",
26899                b"sensor=a",
26900            ],
26901            &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
26902            &[
26903                b"TS.MREVRANGE",
26904                b"-",
26905                b"+",
26906                b"WITHLABELS",
26907                b"FILTER",
26908                b"sensor=a",
26909            ],
26910            &[
26911                b"TS.MRANGE",
26912                b"-",
26913                b"+",
26914                b"FILTER",
26915                b"room=1",
26916                b"GROUPBY",
26917                b"room",
26918                b"REDUCE",
26919                b"max",
26920            ],
26921            &[b"TS.INFO", b"ts:a"],
26922            // And a rule, which is the one thing here that names two keys.
26923            &[
26924                b"TS.CREATERULE",
26925                b"ts:a",
26926                b"ts:down",
26927                b"AGGREGATION",
26928                b"avg",
26929                b"1000",
26930            ],
26931            &[b"TS.CREATE", b"ts:down"],
26932            &[
26933                b"TS.CREATERULE",
26934                b"ts:a",
26935                b"ts:down",
26936                b"AGGREGATION",
26937                b"avg",
26938                b"1000",
26939            ],
26940            &[b"TS.ADD", b"ts:a", b"5000", b"4"],
26941            &[b"TS.ADD", b"ts:a", b"6000", b"5"],
26942            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
26943            &[b"TS.GET", b"ts:down", b"LATEST"],
26944            &[b"TS.INFO", b"ts:down"],
26945            &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
26946            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
26947            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
26948            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
26949            &[b"TS.DEL", b"ts:a", b"0", b"1000"],
26950            // And the errors.
26951            &[b"SET", b"plain", b"v"],
26952            &[b"TS.ADD", b"plain", b"1", b"1"],
26953            &[b"TS.GET", b"plain"],
26954            &[b"TS.READ", b"plain", b"0"],
26955            &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
26956            &[b"TS.RANGE", b"gone", b"-", b"+"],
26957            &[b"TS.INFO", b"gone"],
26958        ];
26959
26960        let mut one = Fixture::new();
26961        let mut many = Fixture::striped(8);
26962        for parts in script {
26963            let a = one.run(parts);
26964            let b = many.run(parts);
26965            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26966        }
26967    }
26968
26969    /// A compaction rule whose two ends are on two stripes.
26970    ///
26971    /// This is the one thing in the family that walks from a key to another key,
26972    /// and it walks it in both directions: a sample on the source closes a
26973    /// bucket on the destination, a `LATEST` read on the destination folds the
26974    /// bucket the source is still filling, and a delete on the source rewrites
26975    /// what the destination already held. The same script is run against a
26976    /// server one stripe wide, where the two keys share a store, and against one
26977    /// eight stripes wide, where they do not.
26978    #[test]
26979    fn a_compaction_rule_across_stripes_reaches_both_ends() {
26980        let mut many = Fixture::striped(8);
26981        let other = apart(&mut many, "src");
26982        let (src, dst) = (b"src".as_slice(), other.as_bytes());
26983        let mut one = Fixture::new();
26984        let mut both = |parts: &[&[u8]]| {
26985            let a = one.run(parts);
26986            let b = many.run(parts);
26987            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
26988            a
26989        };
26990
26991        both(&[b"TS.CREATE", src]);
26992        both(&[b"TS.CREATE", dst]);
26993        assert_eq!(
26994            both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
26995            "+OK\r\n"
26996        );
26997        both(&[b"TS.ADD", src, b"1000", b"1"]);
26998        both(&[b"TS.ADD", src, b"1500", b"3"]);
26999        // The bucket the source is filling is not written down yet, and asking
27000        // for it works it out off the source.
27001        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
27002        let open = both(&[b"TS.GET", dst, b"LATEST"]);
27003        assert!(open.contains(":1000"), "the open bucket is folded: {open}");
27004
27005        // A sample past the bucket closes it, which is the write that has to
27006        // land on the other stripe.
27007        both(&[b"TS.ADD", src, b"2000", b"5"]);
27008        let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
27009        assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
27010        assert!(got.contains(":1000"), "{got}");
27011
27012        // And a delete on the source takes it away again.
27013        both(&[b"TS.DEL", src, b"1000", b"1999"]);
27014        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
27015
27016        // Both ends still know about each other, and the link comes apart from
27017        // the source.
27018        assert!(
27019            both(&[b"TS.INFO", dst]).contains("src"),
27020            "the source is named"
27021        );
27022        assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
27023        assert_eq!(
27024            both(&[b"TS.DELETERULE", src, dst]),
27025            "-ERR TSDB: compaction rule does not exist\r\n"
27026        );
27027    }
27028
27029    /// A label filter takes the series it names wherever they landed.
27030    #[test]
27031    fn a_label_query_across_stripes_finds_every_series() {
27032        let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
27033        let mut many = Fixture::striped(8);
27034        let mut homes: Vec<usize> = names
27035            .iter()
27036            .map(|name| many.server.striped(0).stripe_of(name))
27037            .collect();
27038        homes.sort_unstable();
27039        homes.dedup();
27040        assert!(homes.len() > 1, "the six keys are not all on one stripe");
27041
27042        let mut one = Fixture::new();
27043        let mut both = |parts: &[&[u8]]| {
27044            let a = one.run(parts);
27045            let b = many.run(parts);
27046            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27047            a
27048        };
27049        for name in &names {
27050            both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
27051            both(&[b"TS.ADD", name, b"1000", b"1"]);
27052        }
27053
27054        let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
27055        assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
27056        assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
27057        assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
27058        assert_eq!(
27059            both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
27060            "*1\r\n$4\r\nroom\r\n"
27061        );
27062    }
27063
27064    /// Every hash command, and the field import beside it, on one stripe and on
27065    /// eight.
27066    ///
27067    /// `HRANDFIELD` with a count draws from the stripe's own generator and two
27068    /// stripes do not draw the same numbers, so the only draw here is off a hash
27069    /// holding one field, where every generator gives the same answer.
27070    #[test]
27071    fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
27072        let script: &[&[&[u8]]] = &[
27073            &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
27074            &[b"HMSET", b"h", b"c", b"3"],
27075            &[b"HSETNX", b"h", b"a", b"9"],
27076            &[b"HSETNX", b"h", b"d", b"4"],
27077            &[b"HGET", b"h", b"a"],
27078            &[b"HGET", b"h", b"nope"],
27079            &[b"HMGET", b"h", b"a", b"nope"],
27080            &[b"HLEN", b"h"],
27081            &[b"HEXISTS", b"h", b"a"],
27082            &[b"HSTRLEN", b"h", b"a"],
27083            &[b"HGETALL", b"h"],
27084            &[b"HKEYS", b"h"],
27085            &[b"HVALS", b"h"],
27086            &[b"HINCRBY", b"h", b"a", b"5"],
27087            &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
27088            &[b"HSCAN", b"h", b"0"],
27089            &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
27090            &[b"HSCAN", b"h", b"0", b"NOVALUES"],
27091            &[b"HDEL", b"h", b"d"],
27092            &[b"HSET", b"one", b"f", b"v"],
27093            &[b"HRANDFIELD", b"one"],
27094            &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
27095            // The field deadlines.
27096            &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
27097            &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
27098            &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
27099            &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
27100            &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
27101            &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
27102            &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
27103            &[b"HGET", b"h", b"b"],
27104            // The three that came later and word everything their own way.
27105            &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
27106            &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
27107            &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
27108            &[b"HGET", b"h", b"e"],
27109            // And the import, whose key is the third word.
27110            &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
27111            &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
27112            &[b"HGETALL", b"imp"],
27113            &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
27114            &[b"HIMPORT", b"DISCARD", b"fs"],
27115            // And the errors.
27116            &[b"SET", b"plain", b"v"],
27117            &[b"HSET", b"plain", b"a", b"1"],
27118            &[b"HGETALL", b"plain"],
27119            &[b"HGET", b"gone", b"a"],
27120            &[b"HINCRBY", b"h", b"a", b"nan"],
27121        ];
27122
27123        let mut one = Fixture::new();
27124        let mut many = Fixture::striped(8);
27125        // The field deadlines are absolute milliseconds worked out from the
27126        // clock, so both servers are put on the same one rather than left to
27127        // read the wall a moment apart.
27128        one.server.set_clock_ms(1_700_000_000_000);
27129        many.server.set_clock_ms(1_700_000_000_000);
27130        for parts in script {
27131            let a = one.run(parts);
27132            let b = many.run(parts);
27133            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27134        }
27135    }
27136
27137    /// Every array command, on one stripe and on eight.
27138    #[test]
27139    fn the_array_group_answers_the_same_however_many_stripes_there_are() {
27140        let script: &[&[&[u8]]] = &[
27141            &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
27142            &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
27143            &[b"ARGET", b"a", b"1"],
27144            &[b"ARGET", b"a", b"99"],
27145            &[b"ARMGET", b"a", b"0", b"5", b"99"],
27146            &[b"ARGETRANGE", b"a", b"0", b"7"],
27147            &[b"ARLEN", b"a"],
27148            &[b"ARCOUNT", b"a"],
27149            &[b"ARINSERT", b"a", b"m", b"n"],
27150            &[b"ARSCAN", b"a", b"0", b"20"],
27151            &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
27152            &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
27153            &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
27154            &[b"ARLASTITEMS", b"a", b"2"],
27155            &[b"ARLASTITEMS", b"a", b"2", b"REV"],
27156            &[b"ARNEXT", b"a"],
27157            &[b"ARSEEK", b"a", b"3"],
27158            &[b"AROP", b"a", b"0", b"20", b"USED"],
27159            &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
27160            &[b"ARINFO", b"a"],
27161            &[b"ARINFO", b"a", b"FULL"],
27162            &[b"ARDEL", b"a", b"0"],
27163            &[b"ARDELRANGE", b"a", b"1", b"2"],
27164            &[b"ARCOUNT", b"a"],
27165            &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
27166            &[b"ARGETRANGE", b"r", b"0", b"9"],
27167            // And the errors.
27168            &[b"SET", b"plain", b"v"],
27169            &[b"ARGET", b"plain", b"0"],
27170            &[b"ARSET", b"plain", b"0", b"v"],
27171            &[b"ARGET", b"gone", b"0"],
27172            &[b"ARSET", b"a", b"bad", b"v"],
27173        ];
27174
27175        let mut one = Fixture::new();
27176        let mut many = Fixture::striped(8);
27177        for parts in script {
27178            let a = one.run(parts);
27179            let b = many.run(parts);
27180            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27181        }
27182    }
27183
27184    /// Every graph and vector set command, on one stripe and on eight.
27185    ///
27186    /// `VRANDMEMBER` is not in here for the reason `HRANDFIELD` with a count is
27187    /// not: it draws from the stripe's generator, and the stripes do not share
27188    /// one.
27189    #[test]
27190    fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
27191        let script: &[&[&[u8]]] = &[
27192            &[b"G.NADD", b"g", b"n1", b"name", b"one"],
27193            &[b"G.NADD", b"g", b"n2", b"name", b"two"],
27194            &[b"G.NADD", b"g", b"n3"],
27195            &[b"G.NGET", b"g", b"n1"],
27196            &[b"G.NGET", b"g", b"gone"],
27197            &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
27198            &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
27199            &[b"G.OUT", b"g", b"n1", b"knows"],
27200            &[b"G.IN", b"g", b"n2", b"knows"],
27201            &[b"G.DEG", b"g", b"n1", b"knows"],
27202            &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
27203            &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
27204            &[b"G.PATH", b"g", b"n1", b"n3"],
27205            &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
27206            &[b"G.NDEL", b"g", b"n3"],
27207            &[b"G.NGET", b"g", b"n3"],
27208            // The vector set, which is one index under one key.
27209            &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
27210            &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
27211            &[b"VCARD", b"v"],
27212            &[b"VDIM", b"v"],
27213            &[b"VEMB", b"v", b"e1"],
27214            &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
27215            &[b"VSIM", b"v", b"ELE", b"e1"],
27216            &[b"VISMEMBER", b"v", b"e1"],
27217            &[b"VISMEMBER", b"v", b"gone"],
27218            &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
27219            &[b"VGETATTR", b"v", b"e1"],
27220            &[b"VRANGE", b"v", b"-", b"+"],
27221            &[b"VLINKS", b"v", b"e1"],
27222            &[b"VINFO", b"v"],
27223            &[b"VREM", b"v", b"e2"],
27224            &[b"VCARD", b"v"],
27225            // And the errors.
27226            &[b"SET", b"plain", b"v"],
27227            &[b"G.NGET", b"plain", b"n1"],
27228            &[b"VCARD", b"plain"],
27229            &[b"G.NADD", b"gone2", b"n"],
27230            &[b"VEMB", b"gone3", b"e"],
27231        ];
27232
27233        let mut one = Fixture::new();
27234        let mut many = Fixture::striped(8);
27235        for parts in script {
27236            let a = one.run(parts);
27237            let b = many.run(parts);
27238            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27239        }
27240    }
27241
27242    /// Every bloom filter, cuckoo filter, count min sketch, top k and t digest
27243    /// command, on one stripe and on eight.
27244    #[test]
27245    fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
27246        let script: &[&[&[u8]]] = &[
27247            // The bloom filter.
27248            &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
27249            &[b"BF.ADD", b"bf", b"a"],
27250            &[b"BF.ADD", b"bf", b"a"],
27251            &[b"BF.MADD", b"bf", b"b", b"c"],
27252            &[b"BF.EXISTS", b"bf", b"a"],
27253            &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
27254            &[b"BF.CARD", b"bf"],
27255            &[b"BF.INFO", b"bf"],
27256            &[b"BF.INFO", b"bf", b"CAPACITY"],
27257            &[b"BF.DEBUG", b"bf"],
27258            &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
27259            &[b"BF.EXISTS", b"made", b"x"],
27260            &[b"BF.SCANDUMP", b"bf", b"0"],
27261            // The cuckoo filter.
27262            &[b"CF.RESERVE", b"cf", b"100"],
27263            &[b"CF.ADD", b"cf", b"a"],
27264            &[b"CF.ADDNX", b"cf", b"a"],
27265            &[b"CF.COUNT", b"cf", b"a"],
27266            &[b"CF.EXISTS", b"cf", b"a"],
27267            &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
27268            &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
27269            &[b"CF.DEL", b"cf", b"a"],
27270            &[b"CF.COMPACT", b"cf"],
27271            &[b"CF.INFO", b"cf"],
27272            &[b"CF.DEBUG", b"cf"],
27273            &[b"CF.SCANDUMP", b"cf", b"0"],
27274            // The count min sketch.
27275            &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
27276            &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
27277            &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
27278            &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
27279            &[b"CMS.INFO", b"cms"],
27280            // The top k sketch.
27281            &[b"TOPK.RESERVE", b"tk", b"3"],
27282            &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
27283            &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
27284            &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
27285            &[b"TOPK.COUNT", b"tk", b"a", b"c"],
27286            &[b"TOPK.LIST", b"tk"],
27287            &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
27288            &[b"TOPK.INFO", b"tk"],
27289            // The t digest.
27290            &[b"TDIGEST.CREATE", b"td"],
27291            &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
27292            &[b"TDIGEST.MIN", b"td"],
27293            &[b"TDIGEST.MAX", b"td"],
27294            &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
27295            &[b"TDIGEST.CDF", b"td", b"3"],
27296            &[b"TDIGEST.RANK", b"td", b"3"],
27297            &[b"TDIGEST.REVRANK", b"td", b"3"],
27298            &[b"TDIGEST.BYRANK", b"td", b"0"],
27299            &[b"TDIGEST.BYREVRANK", b"td", b"0"],
27300            &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
27301            &[b"TDIGEST.INFO", b"td"],
27302            &[b"TDIGEST.RESET", b"td"],
27303            &[b"TDIGEST.MIN", b"td"],
27304            // And the errors.
27305            &[b"SET", b"plain", b"v"],
27306            &[b"BF.ADD", b"plain", b"a"],
27307            &[b"CF.ADD", b"plain", b"a"],
27308            &[b"CMS.QUERY", b"plain", b"a"],
27309            &[b"TOPK.ADD", b"plain", b"a"],
27310            &[b"TDIGEST.ADD", b"plain", b"1"],
27311            &[b"CMS.INFO", b"gone"],
27312            &[b"TOPK.INFO", b"gone"],
27313            &[b"TDIGEST.INFO", b"gone"],
27314        ];
27315
27316        let mut one = Fixture::new();
27317        let mut many = Fixture::striped(8);
27318        for parts in script {
27319            let a = one.run(parts);
27320            let b = many.run(parts);
27321            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27322        }
27323    }
27324
27325    /// The two sketch merges, with their sources on stripes of their own.
27326    ///
27327    /// These are the only two commands in the ten groups that name more than one
27328    /// key, and both read a run of sources and write a destination, so both go
27329    /// wrong in the same way if a merge holds one store and looks every source up
27330    /// in it.
27331    #[test]
27332    fn a_sketch_merge_across_stripes_reads_every_source() {
27333        let mut many = Fixture::striped(8);
27334        let other = apart(&mut many, "s1");
27335        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
27336        let mut one = Fixture::new();
27337        let mut both = |parts: &[&[u8]]| {
27338            let a = one.run(parts);
27339            let b = many.run(parts);
27340            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27341            a
27342        };
27343
27344        // The count min sketch. The destination has to be the sources' shape,
27345        // and it is named first, so all three keys are read before anything is
27346        // written.
27347        for key in [b"cd".as_slice(), s1, s2] {
27348            both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
27349        }
27350        both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
27351        both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
27352        assert_eq!(
27353            both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
27354            "+OK\r\n",
27355            "the merge took both sources"
27356        );
27357        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
27358        // And with weights, which are read against the sources in order.
27359        both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
27360        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
27361        // A source that is not a sketch is answered before anything is written.
27362        both(&[b"SET", b"plain", b"v"]);
27363        assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
27364        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
27365
27366        // The t digest, which builds its destination and then puts it in place.
27367        // The two source keys are used again here, so what they held goes first.
27368        both(&[b"FLUSHALL"]);
27369        both(&[b"TDIGEST.CREATE", b"td"]);
27370        both(&[b"TDIGEST.CREATE", s1]);
27371        both(&[b"TDIGEST.CREATE", s2]);
27372        both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
27373        both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
27374        assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
27375        assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
27376        assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
27377    }
27378
27379    /// Every shape of `SORT`, on one stripe and on eight.
27380    ///
27381    /// The key it sorts, the keys a `BY` names, the keys a `GET` names and the
27382    /// destination are four different names and nothing lines them up, so on
27383    /// eight stripes this script is reading and writing all over the database
27384    /// while on one it is doing what it always did.
27385    #[test]
27386    fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
27387        let script: &[&[&[u8]]] = &[
27388            &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
27389            &[b"SORT", b"l"],
27390            &[b"SORT", b"l", b"DESC"],
27391            &[b"SORT", b"l", b"ALPHA"],
27392            &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
27393            &[b"SORT_RO", b"l"],
27394            // A weight per element, so the order comes off keys the command
27395            // never named.
27396            &[
27397                b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
27398            ],
27399            &[b"SORT", b"l", b"BY", b"w_*"],
27400            &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
27401            &[b"DEL", b"w_2"],
27402            &[b"SORT", b"l", b"BY", b"w_*"],
27403            // And the answer off another set of keys again, with `#` mixed in
27404            // so the rows are not all lookups.
27405            &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
27406            &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
27407            // A pattern that reaches into a hash, which is another key again.
27408            &[b"HSET", b"h_1", b"f", b"9"],
27409            &[b"HSET", b"h_2", b"f", b"8"],
27410            &[b"HSET", b"h_3", b"f", b"7"],
27411            &[b"HSET", b"h_10", b"f", b"6"],
27412            &[b"SORT", b"l", b"BY", b"h_*->f"],
27413            &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
27414            // The destination, which is a fourth place to land.
27415            &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
27416            &[b"LRANGE", b"out", b"0", b"-1"],
27417            &[b"SORT", b"l", b"STORE", b"l"],
27418            &[b"LRANGE", b"l", b"0", b"-1"],
27419            // An empty result takes the destination away rather than leaving a
27420            // list of nothing behind.
27421            &[b"SORT", b"missing", b"STORE", b"out"],
27422            &[b"EXISTS", b"out"],
27423            // A set and a sorted set sort the same way a list does, and a set
27424            // written to a destination is sorted even when nothing asked.
27425            &[b"SADD", b"s", b"c", b"a", b"b"],
27426            &[b"SORT", b"s", b"ALPHA"],
27427            &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
27428            &[b"LRANGE", b"out", b"0", b"-1"],
27429            &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
27430            &[b"SORT", b"z", b"BY", b"nosort"],
27431            &[b"SORT", b"z", b"ALPHA", b"DESC"],
27432            // And the two ways it refuses: a key of the wrong type, and an
27433            // element that is not a number under a numeric sort.
27434            &[b"SET", b"str", b"v"],
27435            &[b"SORT", b"str"],
27436            &[b"RPUSH", b"words", b"one", b"two"],
27437            &[b"SORT", b"words"],
27438            &[b"SORT_RO", b"l", b"STORE", b"out"],
27439        ];
27440
27441        let mut one = Fixture::new();
27442        let mut many = Fixture::striped(8);
27443        for parts in script {
27444            let a = one.run(parts);
27445            let b = many.run(parts);
27446            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
27447        }
27448    }
27449
27450    /// One `SORT` whose four kinds of key are on stripes of their own.
27451    ///
27452    /// The script above spreads keys around by writing enough of them, and this
27453    /// one checks the spread rather than trusting it: the list, the weight key
27454    /// for one of its elements and the destination are asserted to be in three
27455    /// places before the command runs.
27456    #[test]
27457    fn a_sort_across_stripes_reads_every_pattern_key() {
27458        let mut f = Fixture::striped(8);
27459        let out = apart(&mut f, "l");
27460        let (list, dest) = (b"l".as_slice(), out.as_bytes());
27461
27462        f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
27463        f.run(&[
27464            b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
27465        ]);
27466        f.run(&[
27467            b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
27468        ]);
27469
27470        // The weights are four keys and they are not all in one place, which is
27471        // the thing that would go unnoticed if the command held a stripe.
27472        let db = f.server.striped(0);
27473        let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
27474            .iter()
27475            .map(|k| db.stripe_of(k.as_slice()))
27476            .collect();
27477        assert!(
27478            weights.iter().any(|s| *s != weights[0]),
27479            "the four weight keys all landed on one stripe, so this proves nothing"
27480        );
27481
27482        assert_eq!(
27483            f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
27484            "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
27485            "the order came off the weights and the answer off the data keys"
27486        );
27487        assert_eq!(
27488            f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
27489            ":4\r\n"
27490        );
27491        assert_eq!(
27492            f.run(&[b"LRANGE", dest, b"0", b"-1"]),
27493            "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
27494            "the destination is on a stripe of its own and got the whole answer"
27495        );
27496    }
27497
27498    /// A `CONFIG SET` reaches every stripe, so where a key landed does not
27499    /// decide what shape it is stored in.
27500    ///
27501    /// This is the setting that would go wrong quietly. A stripe that kept the
27502    /// old ladder would hold the same hash in a different encoding from the
27503    /// stripe next to it, and the only thing that would ever say so is
27504    /// `OBJECT ENCODING`, which is why the check is on that.
27505    #[test]
27506    fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
27507        let mut f = Fixture::striped(8);
27508        let other = apart(&mut f, "h");
27509        let (first, second) = (b"h".as_slice(), other.as_bytes());
27510
27511        assert_eq!(
27512            f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
27513            "+OK\r\n"
27514        );
27515        assert_eq!(
27516            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
27517            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
27518            "the read comes off one stripe and has to answer for all of them"
27519        );
27520        for key in [first, second] {
27521            f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
27522            assert_eq!(
27523                f.run(&[b"OBJECT", b"ENCODING", key]),
27524                "$8\r\nlistpack\r\n",
27525                "two fields is still under the ladder"
27526            );
27527            f.run(&[b"HSET", key, b"c", b"3"]);
27528            assert_eq!(
27529                f.run(&[b"OBJECT", b"ENCODING", key]),
27530                "$9\r\nhashtable\r\n",
27531                "three fields is over it, on whichever stripe the key is on"
27532            );
27533        }
27534
27535        // And the policy, which every stripe has to agree about for the same
27536        // reason: an eviction draws from one stripe at a time.
27537        assert_eq!(
27538            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
27539            "+OK\r\n"
27540        );
27541        let db = f.server.striped(0);
27542        assert!(
27543            (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
27544            "a stripe kept the old policy"
27545        );
27546    }
27547
27548    /// What an index holds, as the two numbers `FT.INFO` reports about it.
27549    ///
27550    /// Read off the registry rather than parsed back out of an `FT.INFO` reply,
27551    /// because the reply is thirty odd fields and these two are the ones the
27552    /// keyspace hook moves.
27553    fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
27554        let search = f.server.search.lock();
27555        let index = search.named(name).expect("the index is there");
27556        (index.held.docs.len(), index.held.docs.last())
27557    }
27558
27559    /// A hash written under an index's prefix reaches it, and one written
27560    /// outside the prefix does not.
27561    #[test]
27562    fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
27563        let mut f = Fixture::new();
27564        f.run(&[
27565            b"FT.CREATE",
27566            b"ix",
27567            b"PREFIX",
27568            b"1",
27569            b"p:",
27570            b"SCHEMA",
27571            b"t",
27572            b"TEXT",
27573        ]);
27574        f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
27575        assert_eq!(held(&f, b"ix"), (1, 1));
27576        f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
27577        assert_eq!(held(&f, b"ix"), (1, 1));
27578
27579        // Every field of the key and not the one the command named, since a
27580        // document is read from nothing every time.
27581        f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
27582        f.run(&[b"HDEL", b"p:1", b"u"]);
27583        assert_eq!(held(&f, b"ix"), (1, 3));
27584        let search = f.server.search.lock();
27585        let index = search.named(b"ix").expect("there");
27586        assert_eq!(index.held.docs.id(b"p:1"), Some(3));
27587    }
27588
27589    /// A fresh index reads the keys that were already there, and walks past a
27590    /// key of the wrong type without counting a failure.
27591    #[test]
27592    fn a_fresh_index_reads_the_keys_that_were_already_there() {
27593        let mut f = Fixture::new();
27594        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27595        f.run(&[b"SET", b"p:str", b"not a hash"]);
27596        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
27597        f.run(&[
27598            b"FT.CREATE",
27599            b"ix",
27600            b"PREFIX",
27601            b"1",
27602            b"p:",
27603            b"SCHEMA",
27604            b"t",
27605            b"TEXT",
27606        ]);
27607
27608        assert_eq!(held(&f, b"ix"), (1, 1));
27609        let search = f.server.search.lock();
27610        let index = search.named(b"ix").expect("there");
27611        assert_eq!(index.trouble.whole().failures(), 0);
27612    }
27613
27614    /// `SKIPINITIALSCAN` leaves what was there alone, and a later write to one
27615    /// of those keys still lands.
27616    #[test]
27617    fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
27618        let mut f = Fixture::new();
27619        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27620        f.run(&[
27621            b"FT.CREATE",
27622            b"ix",
27623            b"PREFIX",
27624            b"1",
27625            b"p:",
27626            b"SKIPINITIALSCAN",
27627            b"SCHEMA",
27628            b"t",
27629            b"TEXT",
27630        ]);
27631        assert_eq!(held(&f, b"ix"), (0, 0));
27632        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27633        assert_eq!(held(&f, b"ix"), (1, 1));
27634    }
27635
27636    /// A command that changed nothing leaves the document where it was, which
27637    /// is not the same as a command that was not a write.
27638    ///
27639    /// All five of these were measured against 8.10.1. Writing the same value
27640    /// again moves the number and a deadline set for later does not, which is
27641    /// the pair that makes the rule "the fields are not what they were" rather
27642    /// than "this was a write".
27643    #[test]
27644    fn only_a_real_change_gives_the_document_a_new_number() {
27645        let mut f = Fixture::new();
27646        f.run(&[
27647            b"FT.CREATE",
27648            b"ix",
27649            b"PREFIX",
27650            b"1",
27651            b"p:",
27652            b"SCHEMA",
27653            b"t",
27654            b"TEXT",
27655        ]);
27656        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27657        assert_eq!(held(&f, b"ix"), (1, 1));
27658
27659        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27660        assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
27661
27662        for quiet in [
27663            vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
27664            vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
27665            vec![b"HGET".as_slice(), b"p:1", b"t"],
27666            vec![b"HGETALL".as_slice(), b"p:1"],
27667            vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
27668            vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
27669            vec![
27670                b"HGETEX".as_slice(),
27671                b"p:1",
27672                b"EX",
27673                b"100",
27674                b"FIELDS",
27675                b"1",
27676                b"t",
27677            ],
27678            vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
27679        ] {
27680            f.run(&quiet);
27681            assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
27682        }
27683
27684        // And the ones that do change something.
27685        f.run(&[b"HSET", b"p:2", b"n", b"1"]);
27686        f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
27687        assert_eq!(held(&f, b"ix"), (2, 4));
27688        // A deadline that has already passed takes the field away, and taking
27689        // the last field away takes the key and the document with it. The
27690        // number still moves on the way past, because the field going and the
27691        // key going are two separate pieces of news and the first of them
27692        // writes the document one last time.
27693        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
27694        assert_eq!(held(&f, b"ix"), (1, 5));
27695    }
27696
27697    /// The two ways of emptying a hash, which do not leave the same thing
27698    /// behind. `HDEL` of the last field spends no number and is counted as a
27699    /// refusal, and a deadline that has already passed spends one on a document
27700    /// nobody sees and is counted as nothing. Measured against 8.10.1 and not
27701    /// something anyone would guess.
27702    #[test]
27703    fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
27704        /// The index's own failure count.
27705        fn refused(f: &Fixture, name: &[u8]) -> u64 {
27706            let search = f.server.search.lock();
27707            let index = search.named(name).expect("the index is there");
27708            index.trouble.whole().failures()
27709        }
27710
27711        let mut f = Fixture::new();
27712        f.run(&[
27713            b"FT.CREATE",
27714            b"ix",
27715            b"PREFIX",
27716            b"1",
27717            b"p:",
27718            b"SCHEMA",
27719            b"t",
27720            b"TEXT",
27721        ]);
27722        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27723        assert_eq!(held(&f, b"ix"), (1, 1));
27724        f.run(&[b"HDEL", b"p:1", b"t"]);
27725        assert_eq!(
27726            held(&f, b"ix"),
27727            (0, 1),
27728            "HDEL of the last field spends none"
27729        );
27730        assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
27731
27732        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
27733        assert_eq!(held(&f, b"ix"), (1, 2));
27734        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
27735        assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
27736        assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
27737
27738        f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
27739        assert_eq!(held(&f, b"ix"), (1, 4));
27740        f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
27741        assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
27742
27743        // Two fields and one command is one rewrite and not two, whichever way
27744        // the fields go.
27745        f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
27746        assert_eq!(held(&f, b"ix"), (1, 6));
27747        f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
27748        assert_eq!(held(&f, b"ix"), (0, 7));
27749        assert_eq!(refused(&f, b"ix"), 1);
27750    }
27751
27752    /// `HSETEX` with a deadline that has already passed is two pieces of news
27753    /// from one command, so the number moves twice and the value never reaches
27754    /// the index.
27755    #[test]
27756    fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
27757        let mut f = Fixture::new();
27758        f.run(&[
27759            b"FT.CREATE",
27760            b"ix",
27761            b"PREFIX",
27762            b"1",
27763            b"p:",
27764            b"SCHEMA",
27765            b"t",
27766            b"TEXT",
27767            b"u",
27768            b"TEXT",
27769        ]);
27770        f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
27771        assert_eq!(held(&f, b"ix"), (1, 1));
27772        f.run(&[
27773            b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
27774        ]);
27775        assert_eq!(
27776            held(&f, b"ix"),
27777            (1, 3),
27778            "the key lived and the field did not"
27779        );
27780
27781        // And the same when the key does not survive it.
27782        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
27783        assert_eq!(held(&f, b"ix"), (2, 4));
27784        f.run(&[
27785            b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
27786        ]);
27787        assert_eq!(held(&f, b"ix"), (1, 6));
27788    }
27789
27790    /// The number one key is indexed under, or `None` when it holds no
27791    /// document.
27792    fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
27793        let search = f.server.search.lock();
27794        let index = search.named(name).expect("the index is there");
27795        index.held.docs.id(key)
27796    }
27797
27798    /// An index over `p:` with one document under `p:1`, which is where four of
27799    /// the tests below start.
27800    fn indexed() -> Fixture {
27801        let mut f = Fixture::new();
27802        f.run(&[
27803            b"FT.CREATE",
27804            b"ix",
27805            b"PREFIX",
27806            b"1",
27807            b"p:",
27808            b"SCHEMA",
27809            b"t",
27810            b"TEXT",
27811        ]);
27812        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
27813        f
27814    }
27815
27816    /// Every way a keyspace command takes a key away leaves no document behind,
27817    /// and none of them spends a number or is counted as a refusal.
27818    #[test]
27819    fn a_key_a_keyspace_command_takes_away_loses_its_document() {
27820        for take in [
27821            vec![b"DEL".as_slice(), b"p:1"],
27822            vec![b"UNLINK".as_slice(), b"p:1"],
27823            vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
27824            vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
27825        ] {
27826            let mut f = indexed();
27827            assert_eq!(held(&f, b"ix"), (1, 1));
27828            f.run(&take);
27829            assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
27830            let search = f.server.search.lock();
27831            let index = search.named(b"ix").expect("the index is there");
27832            assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
27833        }
27834
27835        // A deadline that has not passed yet is not one of them.
27836        let mut f = indexed();
27837        f.run(&[b"EXPIRE", b"p:1", b"1000"]);
27838        assert_eq!(held(&f, b"ix"), (1, 1));
27839        f.run(&[b"PERSIST", b"p:1"]);
27840        assert_eq!(held(&f, b"ix"), (1, 1));
27841    }
27842
27843    /// A rename inside the prefix keeps the number the document had, which is
27844    /// the one write on a followed key that does not spend one. Out of the
27845    /// prefix is an erase and into it is a fresh reading, both measured.
27846    #[test]
27847    fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
27848        let mut f = indexed();
27849        f.run(&[b"RENAME", b"p:1", b"p:2"]);
27850        assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
27851        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
27852        assert_eq!(number(&f, b"ix", b"p:1"), None);
27853
27854        f.run(&[b"RENAME", b"p:2", b"q:1"]);
27855        assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
27856
27857        f.run(&[b"RENAME", b"q:1", b"p:3"]);
27858        assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
27859        assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
27860
27861        // `RENAMENX` goes the same way, and the one that answers zero changes
27862        // nothing.
27863        f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
27864        assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
27865        assert_eq!(held(&f, b"ix"), (2, 3));
27866        f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
27867        assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
27868    }
27869
27870    /// A rename over a key that already had a document leaves one document and
27871    /// not two. A real server leaves both, and D-64 is that difference.
27872    #[test]
27873    fn a_rename_over_a_document_leaves_one_of_them() {
27874        let mut f = indexed();
27875        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
27876        assert_eq!(held(&f, b"ix"), (2, 2));
27877        f.run(&[b"RENAME", b"p:1", b"p:2"]);
27878        assert_eq!(held(&f, b"ix"), (1, 2));
27879        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
27880    }
27881
27882    /// A key that arrives under the prefix by being copied or restored is read
27883    /// as a new document, and one that is written over by something that is not
27884    /// a hash is erased without a word.
27885    #[test]
27886    fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
27887        let mut f = indexed();
27888        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
27889        f.run(&[b"COPY", b"q:1", b"p:2"]);
27890        assert_eq!(held(&f, b"ix"), (2, 2));
27891        assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
27892
27893        // Out of the prefix, where the source keeps the document it had.
27894        f.run(&[b"COPY", b"p:1", b"q:2"]);
27895        assert_eq!(held(&f, b"ix"), (2, 2));
27896
27897        // Over a key that has one, which is a new reading and not a rename.
27898        f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
27899        assert_eq!(held(&f, b"ix"), (2, 3));
27900        assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
27901
27902        // And a string landing on top of a document takes it away, spending no
27903        // number and counting no failure.
27904        f.run(&[b"SET", b"s:1", b"plain"]);
27905        f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
27906        assert_eq!(held(&f, b"ix"), (1, 3));
27907        let dump = f.run(&[b"DUMP", b"q:1"]);
27908        assert!(dump.starts_with('$'), "{dump}");
27909    }
27910
27911    /// The keyspace group reads a key back on database zero whatever database
27912    /// the command ran on, which is measured and is not what the hash commands
27913    /// do. A `COPY` into another database indexes nothing and takes away
27914    /// whatever the destination had, and a `RESTORE` anywhere else is invisible.
27915    #[test]
27916    fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
27917        let mut f = indexed();
27918        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
27919        assert_eq!(held(&f, b"ix"), (2, 2));
27920        // Into database one, so the indexes look for `p:2` on database zero,
27921        // find the one that is still there and read it again.
27922        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
27923        assert_eq!(held(&f, b"ix"), (2, 3));
27924        // And with nothing under that name on database zero, the copy leaves
27925        // the index one document lighter than it found it.
27926        f.run(&[b"DEL", b"p:2"]);
27927        assert_eq!(held(&f, b"ix"), (1, 3));
27928        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
27929        assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
27930
27931        // A restore on another database is the same story.
27932        let dump = f.run(&[b"DUMP", b"p:1"]);
27933        assert!(dump.starts_with('$'), "{dump}");
27934        f.run(&[b"SELECT", b"1"]);
27935        f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
27936        f.run(&[b"RENAME", b"q:1", b"p:3"]);
27937        assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
27938    }
27939
27940    /// `MOVE` is not a change at all, because an index follows a key by name
27941    /// and a write on any database still reaches it.
27942    #[test]
27943    fn a_move_leaves_the_document_where_it_is() {
27944        let mut f = indexed();
27945        f.run(&[b"MOVE", b"p:1", b"1"]);
27946        assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
27947        assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
27948
27949        f.run(&[b"SELECT", b"1"]);
27950        f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
27951        assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
27952        f.run(&[b"DEL", b"p:1"]);
27953        assert_eq!(held(&f, b"ix"), (0, 2));
27954    }
27955
27956    /// A flush takes every index with it, whichever database it flushed.
27957    #[test]
27958    fn a_flush_drops_the_indexes() {
27959        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
27960            let mut f = indexed();
27961            f.run(&[flush]);
27962            assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
27963            assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
27964        }
27965
27966        // Even on a database no index ever read, which is what a real server
27967        // does and is not what anyone would guess.
27968        let mut f = indexed();
27969        f.run(&[b"SELECT", b"9"]);
27970        f.run(&[b"FLUSHDB"]);
27971        assert!(f.server.search.lock().is_empty());
27972    }
27973
27974    /// An index whose schema has one tag field of each kind, plus a number so
27975    /// there is something for `FT.TAGVALS` to refuse.
27976    fn tagged() -> Fixture {
27977        let mut f = Fixture::new();
27978        f.run(&[
27979            b"FT.CREATE",
27980            b"tv",
27981            b"PREFIX",
27982            b"1",
27983            b"tv:",
27984            b"SCHEMA",
27985            b"g",
27986            b"AS",
27987            b"gg",
27988            b"TAG",
27989            b"h",
27990            b"TAG",
27991            b"SEPARATOR",
27992            b"|",
27993            b"CASESENSITIVE",
27994            b"n",
27995            b"NUMERIC",
27996        ]);
27997        f.run(&[
27998            b"HSET",
27999            b"tv:1",
28000            b"g",
28001            b"Red, BLUE ",
28002            b"h",
28003            b"Aa|bB",
28004            b"n",
28005            b"1",
28006        ]);
28007        f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
28008        f
28009    }
28010
28011    /// The values come back as they are stored, so an ordinary tag field
28012    /// answers them folded and trimmed and a `CASESENSITIVE` one answers what
28013    /// it was given. Byte order either way, which puts the capital first.
28014    #[test]
28015    fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
28016        let mut f = tagged();
28017        assert_eq!(
28018            f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
28019            "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
28020        );
28021        assert_eq!(
28022            f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
28023            "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
28024        );
28025    }
28026
28027    /// The name asked about is the attribute, so the identifier of a field
28028    /// declared `AS` is not a name this knows.
28029    #[test]
28030    fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
28031        let mut f = tagged();
28032        for (name, want) in [
28033            (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
28034            (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
28035            (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
28036        ] {
28037            assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
28038        }
28039        assert_eq!(
28040            f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
28041            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
28042        );
28043    }
28044
28045    /// Looking up the index counts as a use of it on the roads that refuse the
28046    /// field as well as on the one that answers, which is measured.
28047    #[test]
28048    fn asking_for_tag_values_counts_a_use_of_the_index() {
28049        let mut f = tagged();
28050        let uses = |f: &mut Fixture| {
28051            let reply = f.run(&[b"FT.INFO", b"tv"]);
28052            let at = reply.find("number_of_uses").expect("the field is reported");
28053            let value = reply[at..].split("\r\n").nth(1).unwrap();
28054            value.trim_start_matches(':').parse::<i64>().unwrap()
28055        };
28056        let before = uses(&mut f);
28057        f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
28058        f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
28059        // Three more than before: two tag lookups and the second `FT.INFO`.
28060        assert_eq!(uses(&mut f), before + 3);
28061    }
28062
28063    /// A tag field nothing was ever written to has no list at all, which
28064    /// answers the same empty set a list that has been emptied does.
28065    #[test]
28066    fn a_tag_field_with_nothing_in_it_answers_empty() {
28067        let mut f = Fixture::new();
28068        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
28069        assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
28070    }
28071
28072    /// A dictionary is module state and not a key, so nothing in the keyspace
28073    /// can see one.
28074    #[test]
28075    fn a_dictionary_is_not_a_key() {
28076        let mut f = Fixture::new();
28077        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
28078        assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
28079        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
28080        assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
28081    }
28082
28083    /// The count is how many terms were new, an empty term is not a term, and
28084    /// the dump is sorted by bytes rather than folded.
28085    #[test]
28086    fn a_dictionary_counts_the_terms_it_had_not_seen() {
28087        let mut f = Fixture::new();
28088        assert_eq!(
28089            f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
28090            ":3\r\n"
28091        );
28092        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
28093        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
28094        assert_eq!(
28095            f.run(&[b"FT.DICTDUMP", b"d"]),
28096            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
28097        );
28098        assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
28099    }
28100
28101    /// A name nobody ever added to is not an error on either of the two
28102    /// commands that will take one, which is the only place in the group where
28103    /// a missing name is forgiven.
28104    #[test]
28105    fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
28106        let mut f = Fixture::new();
28107        assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
28108        assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
28109    }
28110
28111    /// The dictionaries go when the keyspace does, the same way the indexes do.
28112    #[test]
28113    fn a_flush_drops_the_dictionaries() {
28114        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
28115            let mut f = Fixture::new();
28116            f.run(&[b"FT.DICTADD", b"d", b"a"]);
28117            f.run(&[flush]);
28118            assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
28119        }
28120    }
28121
28122    // -------------------------------------------------------------- profile
28123
28124    /// A fixture holding one index over three documents, two of which hold the
28125    /// first word and two the second.
28126    fn profiling() -> Fixture {
28127        let mut f = Fixture::new();
28128        f.run(&[
28129            b"FT.CREATE",
28130            b"ix",
28131            b"PREFIX",
28132            b"1",
28133            b"p:",
28134            b"SCHEMA",
28135            b"t",
28136            b"TEXT",
28137            b"n",
28138            b"NUMERIC",
28139        ]);
28140        f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
28141        f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
28142        f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
28143        f
28144    }
28145
28146    /// The reply with every time taken out of it, since no two runs agree on
28147    /// those and everything else about a profile is exact.
28148    fn timeless(reply: &str) -> String {
28149        const KEYS: &[&str] = &[
28150            "+Total profile time",
28151            "+Parsing time",
28152            "+Workers queue time",
28153            "+Pipeline creation time",
28154            "+Time",
28155        ];
28156        let mut out = String::new();
28157        let mut parts = reply.split("\r\n").peekable();
28158        while let Some(part) = parts.next() {
28159            out.push_str(part);
28160            out.push_str("\r\n");
28161            if !KEYS.contains(&part) {
28162                continue;
28163            }
28164            // A double is one line on RESP3 and a bulk header and its digits on
28165            // RESP2, and both of them stand for the same one value.
28166            match parts.next() {
28167                Some(head) if head.starts_with('$') => {
28168                    parts.next();
28169                }
28170                _ => {}
28171            }
28172            out.push_str("<t>\r\n");
28173        }
28174        // The split leaves an empty piece past the last line ending.
28175        out.truncate(out.len() - 2);
28176        out
28177    }
28178
28179    /// The whole envelope on both protocols, which is a two element array on
28180    /// one and a two key map on the other.
28181    #[test]
28182    fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
28183        let mut f = profiling();
28184        assert_eq!(
28185            timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
28186            "*2\r\n\
28187             *5\r\n:2\r\n$3\r\np:1\r\n*4\r\n$1\r\nt\r\n$5\r\nalpha\r\n$1\r\nn\r\n$1\r\n1\r\n\
28188             $3\r\np:2\r\n*4\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n$1\r\nn\r\n$1\r\n2\r\n\
28189             *4\r\n+Shards\r\n*1\r\n*14\r\n\
28190             +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
28191             +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
28192             +Warning\r\n*1\r\n+None\r\n\
28193             +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
28194             +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
28195             +Estimated number of matches\r\n:2\r\n\
28196             +Result processors profile\r\n*4\r\n\
28197             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28198             *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28199             *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28200             *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
28201             +Coordinator\r\n*0\r\n"
28202        );
28203        let mut g = profiling();
28204        g.run(&[b"HELLO", b"3"]);
28205        let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
28206        assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
28207        assert!(
28208            three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
28209            "{three}"
28210        );
28211        assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
28212        assert!(
28213            three.contains(
28214                "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
28215                 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
28216                 +Estimated number of matches\r\n:2\r\n"
28217            ),
28218            "{three}"
28219        );
28220    }
28221
28222    /// Every kind of step names itself, and the three that hold other steps say
28223    /// so in the singular or the plural depending on how many they hold.
28224    #[test]
28225    fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
28226        let mut f = profiling();
28227        let tree = |f: &mut Fixture, query: &[u8]| {
28228            let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
28229            let at = reply.find("+Iterators profile").expect("a tree");
28230            let end = reply.find("+Result processors").expect("a list of steps");
28231            reply[at..end].to_string()
28232        };
28233        assert_eq!(
28234            tree(&mut f, b"alpha beta"),
28235            "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
28236             +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
28237             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
28238             +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
28239             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
28240             +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
28241        );
28242        assert!(tree(&mut f, b"alpha|beta").starts_with(
28243            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
28244             +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
28245        ));
28246        // One thing under it, named in the singular, which is a different key
28247        // and not a list holding one.
28248        assert!(tree(&mut f, b"-alpha").starts_with(
28249            "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
28250             +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
28251        ));
28252        assert!(tree(&mut f, b"~alpha").starts_with(
28253            "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
28254             +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
28255        ));
28256        // No guess at how many, which is the one leaf that leaves it off.
28257        assert_eq!(
28258            tree(&mut f, b"*"),
28259            "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
28260             +Number of reading operations\r\n:3\r\n"
28261        );
28262        assert!(tree(&mut f, b"@n:[1 2]").starts_with(
28263            "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
28264             $19\r\n1.000000 - 2.000000\r\n"
28265        ));
28266    }
28267
28268    /// A union an expansion made folds into a count of its branches and a union
28269    /// a client wrote with a bar does not.
28270    #[test]
28271    fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
28272        let mut f = profiling();
28273        f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
28274        let tree = |f: &mut Fixture, words: &[&[u8]]| {
28275            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
28276            argv.extend_from_slice(words);
28277            let reply = timeless(&f.run(&argv));
28278            let at = reply.find("+Iterators profile").expect("a tree");
28279            let end = reply.find("+Result processors").expect("a list of steps");
28280            reply[at..end].to_string()
28281        };
28282        assert_eq!(
28283            tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
28284            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
28285             +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
28286             +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
28287             +The number of iterators in the union is 2\r\n"
28288        );
28289        assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
28290        assert!(
28291            tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
28292                .contains("+Child iterators\r\n*2\r\n")
28293        );
28294        // A union that says nothing but its own name says it as a status, and
28295        // one that says what it stood for says that as a string. Measured, and
28296        // it is the one place in this reply where the two are told apart.
28297        assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
28298        assert!(
28299            tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
28300        );
28301    }
28302
28303    /// Which steps a search runs the rows through, which turns on the window,
28304    /// on whether anything asked for the fields and on what the order is.
28305    #[test]
28306    fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
28307        let mut f = profiling();
28308        let steps = |f: &mut Fixture, words: &[&[u8]]| {
28309            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
28310            argv.extend_from_slice(words);
28311            let reply = timeless(&f.run(&argv));
28312            let at = reply.find("+Result processors").expect("a list of steps");
28313            let end = reply.find("+Coordinator").expect("an end");
28314            let mut out = Vec::new();
28315            let mut parts = reply[at..end].split("\r\n").peekable();
28316            while let Some(part) = parts.next() {
28317                if part == "+Type" {
28318                    out.push(parts.next().unwrap_or_default().to_string());
28319                }
28320            }
28321            out
28322        };
28323        assert_eq!(
28324            steps(&mut f, &[]),
28325            ["+Index", "+Scorer", "+Sorter", "+Loader"]
28326        );
28327        assert_eq!(
28328            steps(&mut f, &[b"NOCONTENT"]),
28329            ["+Index", "+Scorer", "+Sorter"]
28330        );
28331        // A window of nothing is a client asking for the total and nothing
28332        // else, so nothing is scored and nothing is sorted.
28333        assert_eq!(
28334            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
28335            ["+Index", "+Counter"]
28336        );
28337        // A sort by a field does not need a score, and asking for the scores
28338        // puts the step back.
28339        assert_eq!(
28340            steps(&mut f, &[b"SORTBY", b"n"]),
28341            ["+Index", "+Sorter", "+Loader"]
28342        );
28343        assert_eq!(
28344            steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
28345            ["+Index", "+Scorer", "+Sorter", "+Loader"]
28346        );
28347        assert_eq!(
28348            steps(&mut f, &[b"HIGHLIGHT"]),
28349            ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
28350        );
28351        assert_eq!(
28352            steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
28353            ["+Index", "+Scorer", "+Sorter"]
28354        );
28355    }
28356
28357    /// A pipeline names each of its steps after the expression it runs, which
28358    /// is what a real server prints beside them.
28359    #[test]
28360    fn a_pipeline_names_every_step_after_what_it_runs() {
28361        let mut f = profiling();
28362        let steps = |f: &mut Fixture, words: &[&[u8]]| {
28363            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
28364            argv.extend_from_slice(words);
28365            let reply = timeless(&f.run(&argv));
28366            let at = reply.find("+Result processors").expect("a list of steps");
28367            let end = reply.find("+Coordinator").expect("an end");
28368            let mut out = Vec::new();
28369            let mut parts = reply[at..end].split("\r\n").peekable();
28370            while let Some(part) = parts.next() {
28371                if part == "+Type" {
28372                    out.push(parts.next().unwrap_or_default().to_string());
28373                }
28374            }
28375            out
28376        };
28377        assert_eq!(steps(&mut f, &[]), ["+Index"]);
28378        assert_eq!(
28379            steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
28380            ["+Index", "+Projector - Literal 1"]
28381        );
28382        assert_eq!(
28383            steps(
28384                &mut f,
28385                &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
28386            ),
28387            ["+Index", "+Loader", "+Projector - Operator *"]
28388        );
28389        assert_eq!(
28390            steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
28391            ["+Index", "+Loader", "+Filter - Predicate >"]
28392        );
28393        assert_eq!(
28394            steps(
28395                &mut f,
28396                &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
28397            ),
28398            ["+Index", "+Loader", "+Grouper"]
28399        );
28400        assert_eq!(
28401            steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
28402            ["+Index", "+Loader", "+Sorter"]
28403        );
28404        assert_eq!(
28405            steps(&mut f, &[b"LIMIT", b"0", b"2"]),
28406            ["+Index", "+Pager/Limiter"]
28407        );
28408        // Asking for the score by name is a step of its own, and it goes in
28409        // front of the read rather than after it.
28410        assert_eq!(
28411            steps(
28412                &mut f,
28413                &[
28414                    b"ADDSCORES",
28415                    b"LOAD",
28416                    b"1",
28417                    b"@n",
28418                    b"APPLY",
28419                    b"@__score",
28420                    b"AS",
28421                    b"s"
28422                ]
28423            ),
28424            [
28425                "+Index",
28426                "+Scorer",
28427                "+Loader",
28428                "+Projector - Property __score"
28429            ]
28430        );
28431    }
28432
28433    /// A field the schema marked sortable is held beside the document number,
28434    /// so a pipeline that only names those never opens a key and never reports
28435    /// a read.
28436    ///
28437    /// Measured: on a schema of `n NUMERIC SORTABLE g TAG`, `LOAD 1 @n` has no
28438    /// `Loader` step and `LOAD 1 @g` has one. So does `LOAD *`, because what a
28439    /// key turns out to hold is not knowable without opening it.
28440    #[test]
28441    fn a_sortable_field_is_read_without_the_key_being_opened() {
28442        let mut f = Fixture::new();
28443        f.run(&[
28444            b"FT.CREATE",
28445            b"sx",
28446            b"PREFIX",
28447            b"1",
28448            b"s:",
28449            b"SCHEMA",
28450            b"n",
28451            b"NUMERIC",
28452            b"SORTABLE",
28453            b"g",
28454            b"TAG",
28455        ]);
28456        f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
28457        f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
28458        let loads = |f: &mut Fixture, words: &[&[u8]]| {
28459            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
28460            argv.extend_from_slice(words);
28461            f.run(&argv).contains("+Loader")
28462        };
28463        assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
28464        assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
28465        assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
28466        assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
28467        assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
28468        assert!(loads(
28469            &mut f,
28470            &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
28471        ));
28472        assert!(loads(&mut f, &[b"LOAD", b"*"]));
28473    }
28474
28475    /// The four ways the words can be wrong, none of which reaches the search
28476    /// underneath.
28477    #[test]
28478    fn a_profile_checks_its_own_words_before_it_runs_anything() {
28479        let mut f = profiling();
28480        assert_eq!(
28481            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
28482            "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
28483        );
28484        assert_eq!(
28485            f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
28486            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
28487        );
28488        // The word goes between the two and nowhere else, so one written in
28489        // front of them is not the word at all.
28490        assert_eq!(
28491            f.run(&[
28492                b"FT.PROFILE",
28493                b"ix",
28494                b"LIMITED",
28495                b"SEARCH",
28496                b"QUERY",
28497                b"alpha"
28498            ]),
28499            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
28500        );
28501        assert_eq!(
28502            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
28503            "-The QUERY keyword is expected\r\n"
28504        );
28505        assert_eq!(
28506            f.run(&[
28507                b"FT.PROFILE",
28508                b"ix",
28509                b"AGGREGATE",
28510                b"QUERY",
28511                b"alpha",
28512                b"WITHCURSOR"
28513            ]),
28514            "-FT.PROFILE does not support cursor\r\n"
28515        );
28516        // And what the search itself complains about comes back on its own,
28517        // without an envelope around it saying the command worked.
28518        assert_eq!(
28519            f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
28520            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
28521        );
28522        assert_eq!(
28523            f.run(&[
28524                b"FT.PROFILE",
28525                b"ix",
28526                b"SEARCH",
28527                b"QUERY",
28528                b"alpha",
28529                b"extra"
28530            ]),
28531            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
28532        );
28533    }
28534
28535    /// Every word of the command's own is read without regard to case.
28536    #[test]
28537    fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
28538        let mut f = profiling();
28539        let one = f.run(&[
28540            b"FT.PROFILE",
28541            b"ix",
28542            b"search",
28543            b"limited",
28544            b"query",
28545            b"alpha",
28546        ]);
28547        let two = f.run(&[
28548            b"FT.PROFILE",
28549            b"ix",
28550            b"SEARCH",
28551            b"LIMITED",
28552            b"QUERY",
28553            b"alpha",
28554        ]);
28555        assert_eq!(timeless(&one), timeless(&two));
28556    }
28557
28558    // -------------------------------------------------------------- dropping
28559
28560    /// The two spellings take opposite defaults, which is measured and is the
28561    /// only difference between them that a client can see.
28562    #[test]
28563    fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
28564        let mut f = profiling();
28565        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
28566        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28567
28568        let mut f = profiling();
28569        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28570        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28571
28572        let mut f = profiling();
28573        assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
28574        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28575
28576        let mut f = profiling();
28577        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
28578        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28579    }
28580
28581    /// Each spelling takes its own word and refuses the other one's, which
28582    /// reads as an oversight and is what a real server answers.
28583    #[test]
28584    fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
28585        let mut f = profiling();
28586        let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
28587        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
28588        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
28589        // Refused rather than half done, so the index is still there.
28590        assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
28591    }
28592
28593    /// Only what the index read is deleted, which is not the same as
28594    /// everything under its prefix.
28595    #[test]
28596    fn dropping_the_documents_leaves_a_key_the_index_never_read() {
28597        let mut f = profiling();
28598        f.run(&[b"SET", b"p:4", b"alpha"]);
28599        f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
28600        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28601        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
28602        assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
28603    }
28604
28605    /// An index still standing over the same keys hears about them going,
28606    /// rather than answering later with keys that are not there.
28607    #[test]
28608    fn another_index_over_the_same_keys_loses_the_documents_too() {
28609        let mut f = profiling();
28610        f.run(&[
28611            b"FT.CREATE",
28612            b"other",
28613            b"PREFIX",
28614            b"1",
28615            b"p:",
28616            b"SCHEMA",
28617            b"t",
28618            b"TEXT",
28619        ]);
28620        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
28621        assert_eq!(
28622            f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
28623            "*1\r\n:0\r\n"
28624        );
28625    }
28626
28627    /// A drop that found nothing to drop deletes nothing either, which is the
28628    /// one case where the shortcut spelling answers `OK` without a sweep.
28629    #[test]
28630    fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
28631        let mut f = profiling();
28632        assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
28633        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28634        assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
28635        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
28636    }
28637
28638    // --------------------------------------------------------------- config
28639
28640    /// The two shapes a dump comes back in, which are the one mix of simple
28641    /// strings and bulk strings the group sends.
28642    #[test]
28643    fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
28644        let mut f = Fixture::new();
28645        assert_eq!(
28646            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28647            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28648        );
28649        assert_eq!(
28650            f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
28651            "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
28652        );
28653        let mut g = Fixture::new();
28654        g.run(&[b"HELLO", b"3"]);
28655        assert_eq!(
28656            g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28657            "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28658        );
28659        assert_eq!(
28660            g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
28661            "%1\r\n+EXTLOAD\r\n_\r\n"
28662        );
28663    }
28664
28665    /// The help text rides along in the middle of the same row, flat on RESP2
28666    /// and as a map of its own on RESP3.
28667    #[test]
28668    fn a_help_row_carries_the_description_and_the_value_together() {
28669        let mut f = Fixture::new();
28670        assert_eq!(
28671            f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
28672            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
28673             +Value\r\n$3\r\n500\r\n"
28674        );
28675        let mut g = Fixture::new();
28676        g.run(&[b"HELLO", b"3"]);
28677        assert_eq!(
28678            g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
28679            "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
28680             +Value\r\n$3\r\n500\r\n"
28681        );
28682    }
28683
28684    /// A name is matched whole, ignoring case, and the single word star is the
28685    /// only thing that means all of them.
28686    #[test]
28687    fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
28688        let mut f = Fixture::new();
28689        assert_eq!(
28690            f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
28691            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28692        );
28693        for name in [
28694            b"TIMEOUT*".as_slice(),
28695            b"?IMEOUT",
28696            b"*TIMEOUT*",
28697            b"TIME",
28698            b"NOSUCH",
28699            b"",
28700        ] {
28701            assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
28702        }
28703        assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
28704        assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
28705    }
28706
28707    /// Words after the name are stepped over rather than refused, on both of
28708    /// the two reads.
28709    #[test]
28710    fn a_read_ignores_whatever_follows_the_name() {
28711        let mut f = Fixture::new();
28712        assert_eq!(
28713            f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
28714            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
28715        );
28716        assert_eq!(
28717            f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
28718            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
28719             +Value\r\n$3\r\n500\r\n"
28720        );
28721    }
28722
28723    /// The container reports its own name and the subcommand it was given in
28724    /// the two lines the dispatcher writes.
28725    #[test]
28726    fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
28727        let mut f = Fixture::new();
28728        assert_eq!(
28729            f.run(&[b"FT.CONFIG"]),
28730            "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
28731        );
28732        for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
28733            let want = format!(
28734                "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
28735                String::from_utf8_lossy(sub)
28736            );
28737            assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
28738        }
28739        assert_eq!(
28740            f.run(&[b"ft.config", b"get"]),
28741            "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
28742        );
28743        assert_eq!(
28744            f.run(&[b"FT.CONFIG", b"bogus"]),
28745            "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
28746        );
28747    }
28748
28749    /// The name, then whether it can move, then the value, then the count of
28750    /// words, and each of the first three answers before the next is looked at.
28751    #[test]
28752    fn a_write_checks_the_name_then_the_setting_then_the_value() {
28753        let mut f = Fixture::new();
28754        for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
28755            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
28756            cmd.extend(tail);
28757            assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
28758        }
28759        for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
28760            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
28761            cmd.extend(tail);
28762            assert_eq!(
28763                f.run(&cmd),
28764                "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
28765            );
28766        }
28767        assert_eq!(
28768            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
28769            "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
28770        );
28771    }
28772
28773    /// Too many words is a status and not an error, and the value has already
28774    /// been written by the time it goes out.
28775    #[test]
28776    fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
28777        let mut f = Fixture::new();
28778        assert_eq!(
28779            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
28780            "+OK\r\n"
28781        );
28782        assert_eq!(
28783            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
28784            "+EXCESSARGS\r\n"
28785        );
28786        assert_eq!(
28787            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28788            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
28789        );
28790    }
28791
28792    /// Strictly first and loosely second, so a hexadecimal and a leading zero
28793    /// and an exponent all land and a fraction does not.
28794    #[test]
28795    fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
28796        let mut f = Fixture::new();
28797        for (given, want) in [
28798            (b"0x10".as_slice(), "16"),
28799            (b"0X1f", "31"),
28800            (b"+0x10", "16"),
28801            (b"+5", "5"),
28802            (b"010", "10"),
28803            (b"08", "8"),
28804            (b"0777", "777"),
28805            (b"1e3", "1000"),
28806            (b"0.0", "0"),
28807            (b"-0.0", "0"),
28808        ] {
28809            assert_eq!(
28810                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28811                "+OK\r\n",
28812                "{given:?}"
28813            );
28814            let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
28815            assert_eq!(
28816                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
28817                want,
28818                "{given:?}"
28819            );
28820        }
28821        for given in [
28822            b" 5".as_slice(),
28823            b"5 ",
28824            b"1.5",
28825            b"1e-3",
28826            b"x",
28827            b"",
28828            b"0b11",
28829            b"0xg",
28830            b"nan",
28831            b"inf",
28832            b"1e100",
28833            b"99999999999999999999",
28834        ] {
28835            assert_eq!(
28836                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28837                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
28838                "{given:?}"
28839            );
28840        }
28841    }
28842
28843    /// Which of the two readers found a negative decides what it is told, and
28844    /// on a setting with no range at all neither of them is refused.
28845    #[test]
28846    fn a_negative_is_answered_by_whichever_reader_found_it() {
28847        let mut f = Fixture::new();
28848        for given in [b"-1".as_slice(), b"-16"] {
28849            assert_eq!(
28850                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28851                "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
28852                "{given:?}"
28853            );
28854        }
28855        for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
28856            assert_eq!(
28857                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
28858                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
28859                "{given:?}"
28860            );
28861        }
28862        let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
28863        for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
28864            assert_eq!(
28865                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
28866                "+OK\r\n",
28867                "{given:?}"
28868            );
28869            assert_eq!(
28870                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
28871                unlimited,
28872                "{given:?}"
28873            );
28874        }
28875    }
28876
28877    /// The two settings with no range truncate into a signed thirty two bit
28878    /// slot and say so once the number has gone under.
28879    #[test]
28880    fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
28881        let mut f = Fixture::new();
28882        for (given, want) in [
28883            (b"2147483647".as_slice(), "2147483647"),
28884            (b"2147483648", "unlimited"),
28885            (b"4294967295", "unlimited"),
28886            (b"9223372036854775806", "unlimited"),
28887            (b"0", "0"),
28888        ] {
28889            assert_eq!(
28890                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
28891                "+OK\r\n",
28892                "{given:?}"
28893            );
28894            let want = format!(
28895                "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
28896                want.len()
28897            );
28898            assert_eq!(
28899                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
28900                want,
28901                "{given:?}"
28902            );
28903        }
28904    }
28905
28906    /// A number past what a setting will take says which way it went, and the
28907    /// ones with a softer roof of their own say what that roof is about.
28908    #[test]
28909    fn a_number_out_of_range_names_the_limit_it_crossed() {
28910        let mut f = Fixture::new();
28911        let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
28912        for (name, given) in [
28913            (b"MINPREFIX".as_slice(), b"0".as_slice()),
28914            (b"MAX_AGGREGATE_GROUPS", b"0"),
28915            (b"BM25STD_TANH_FACTOR", b"0"),
28916            (b"DEFAULT_DIALECT", b"0"),
28917            (b"MINSTEMLEN", b"4294967296"),
28918            (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
28919            (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
28920            (b"CONNECT_TIMEOUT", b"2147483648"),
28921        ] {
28922            assert_eq!(
28923                f.run(&[b"FT.CONFIG", b"SET", name, given]),
28924                bounds,
28925                "{name:?}"
28926            );
28927        }
28928        for (name, given, want) in [
28929            (
28930                b"MINSTEMLEN".as_slice(),
28931                b"1".as_slice(),
28932                "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
28933            ),
28934            (
28935                b"MAX_AGGREGATE_GROUPS",
28936                b"67108865",
28937                "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
28938            ),
28939            (
28940                b"WORKERS",
28941                b"17",
28942                "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
28943            ),
28944            (
28945                b"_NUMERIC_RANGES_PARENTS",
28946                b"3",
28947                "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
28948                 depth for balance\r\n",
28949            ),
28950            (
28951                b"DEFAULT_DIALECT",
28952                b"5",
28953                "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
28954            ),
28955            (
28956                b"_BG_INDEX_MEM_PCT_THR",
28957                b"101",
28958                "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
28959                 100%\r\n",
28960            ),
28961            (
28962                b"BM25STD_TANH_FACTOR",
28963                b"10001",
28964                "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
28965                 inclusive\r\n",
28966            ),
28967            (
28968                b"BG_INDEX_SLEEP_DURATION_US",
28969                b"1000000",
28970                "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
28971                 999999 (usleep POSIX limit)\r\n",
28972            ),
28973        ] {
28974            assert_eq!(
28975                f.run(&[b"FT.CONFIG", b"SET", name, given]),
28976                want,
28977                "{name:?}"
28978            );
28979        }
28980    }
28981
28982    /// The two trimming delays are measured against each other, and the answer
28983    /// names both settings and both numbers.
28984    #[test]
28985    fn the_trimming_delays_are_checked_against_one_another() {
28986        let mut f = Fixture::new();
28987        assert_eq!(
28988            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
28989            "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
28990             _MAX_TRIM_DELAY_MS (5000)\r\n"
28991        );
28992        assert_eq!(
28993            f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
28994            "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
28995             _MIN_TRIM_DELAY_MS (2000)\r\n"
28996        );
28997        assert_eq!(
28998            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
28999            "+OK\r\n"
29000        );
29001    }
29002
29003    /// Two of the word settings fold the spelling on the way in and the scorer
29004    /// does not, which is the one place in the table case counts.
29005    #[test]
29006    fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
29007        let mut f = Fixture::new();
29008        assert_eq!(
29009            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
29010            "+OK\r\n"
29011        );
29012        assert_eq!(
29013            f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
29014            "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
29015        );
29016        assert_eq!(
29017            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
29018            "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
29019        );
29020        assert_eq!(
29021            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
29022            "+OK\r\n"
29023        );
29024        assert_eq!(
29025            f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
29026            "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
29027        );
29028        assert_eq!(
29029            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
29030            "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
29031        );
29032        let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
29033        for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
29034            assert_eq!(
29035                f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
29036                bad,
29037                "{given:?}"
29038            );
29039        }
29040        assert_eq!(
29041            f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
29042            "+OK\r\n"
29043        );
29044    }
29045
29046    /// True and false, either case, and none of the other words a client might
29047    /// reach for.
29048    #[test]
29049    fn a_yes_or_no_setting_takes_those_two_words_only() {
29050        let mut f = Fixture::new();
29051        assert_eq!(
29052            f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
29053            "+OK\r\n"
29054        );
29055        assert_eq!(
29056            f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
29057            "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
29058        );
29059        for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
29060            assert_eq!(
29061                f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
29062                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
29063                "{given:?}"
29064            );
29065        }
29066    }
29067
29068    /// Two pairs of names sit over one number each, and one of that second pair
29069    /// takes no value at all.
29070    #[test]
29071    fn two_names_for_one_setting_move_together() {
29072        let mut f = Fixture::new();
29073        f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
29074        assert_eq!(
29075            f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
29076            "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
29077        );
29078        f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
29079        assert_eq!(
29080            f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
29081            "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
29082        );
29083        let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
29084        let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
29085        f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
29086        assert_eq!(
29087            f.run(&[b"FT.CONFIG", b"GET", short]),
29088            "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
29089        );
29090        assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
29091        assert_eq!(
29092            f.run(&[b"FT.CONFIG", b"GET", long]),
29093            "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
29094        );
29095    }
29096
29097    /// The one setting that takes a write and never gives it back.
29098    #[test]
29099    fn a_password_reads_back_as_stars_whatever_was_written() {
29100        let mut f = Fixture::new();
29101        assert_eq!(
29102            f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
29103            "+OK\r\n"
29104        );
29105        assert_eq!(
29106            f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
29107            "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
29108        );
29109    }
29110
29111    /// The settings are not in the keyspace, so unlike the dictionaries and the
29112    /// synonym groups beside them they live through an emptied one.
29113    #[test]
29114    fn a_flush_leaves_the_settings_alone() {
29115        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
29116            let mut f = Fixture::new();
29117            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
29118            f.run(&[flush]);
29119            assert_eq!(
29120                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
29121                "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
29122                "{flush:?}"
29123            );
29124        }
29125    }
29126
29127    // ---------------------------------------------------------------- debug
29128
29129    /// A small index with one of everything a dump can read, so the tests below
29130    /// all name the same three documents and the same four fields.
29131    fn debugging() -> Fixture {
29132        let mut f = Fixture::new();
29133        f.run(&[
29134            b"FT.CREATE",
29135            b"dx",
29136            b"PREFIX",
29137            b"1",
29138            b"d:",
29139            b"SCHEMA",
29140            b"t",
29141            b"TEXT",
29142            b"g",
29143            b"TAG",
29144            b"n",
29145            b"NUMERIC",
29146            b"s",
29147            b"TEXT",
29148            b"SORTABLE",
29149        ]);
29150        f.run(&[
29151            b"HSET",
29152            b"d:1",
29153            b"t",
29154            b"running dogs",
29155            b"g",
29156            b"red,blue",
29157            b"n",
29158            b"1",
29159            b"s",
29160            b"Alpha",
29161        ]);
29162        f.run(&[
29163            b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
29164        ]);
29165        f.run(&[
29166            b"HSET",
29167            b"d:3",
29168            b"t",
29169            b"dogs alpha",
29170            b"g",
29171            b"green",
29172            b"n",
29173            b"3",
29174        ]);
29175        f
29176    }
29177
29178    /// The whole dictionary in byte order, with the stems in it as entries of
29179    /// their own rather than hidden behind the words they came from.
29180    #[test]
29181    fn a_term_dump_lists_the_stems_beside_the_words() {
29182        let mut f = debugging();
29183        assert_eq!(
29184            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
29185            "*6\r\n$4\r\n+dog\r\n$4\r\n+run\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n\
29186             $4\r\ndogs\r\n$7\r\nrunning\r\n"
29187        );
29188    }
29189
29190    /// A posting list is looked up on the bytes given and nothing folds them, so
29191    /// the term that a query would have found is not the term a dump wants.
29192    #[test]
29193    fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
29194        let mut f = debugging();
29195        assert_eq!(
29196            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29197            "*2\r\n:1\r\n:2\r\n"
29198        );
29199        assert_eq!(
29200            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
29201            "*2\r\n:1\r\n:2\r\n"
29202        );
29203        for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
29204            assert_eq!(
29205                f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
29206                "-Can not find the inverted index\r\n",
29207                "{term:?}"
29208            );
29209        }
29210    }
29211
29212    /// Tag values come back folded and in byte order, each with the documents
29213    /// that hold it, and a document with two values is under both of them.
29214    #[test]
29215    fn a_tag_dump_pairs_every_value_with_its_documents() {
29216        let mut f = debugging();
29217        assert_eq!(
29218            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
29219            "*3\r\n*2\r\n$4\r\nblue\r\n*1\r\n:1\r\n*2\r\n$5\r\ngreen\r\n*1\r\n:3\r\n\
29220             *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
29221        );
29222    }
29223
29224    /// One list holding every document in the field, which is D-96: a range tree
29225    /// answers one list per range and this answers the one it keeps.
29226    #[test]
29227    fn a_number_dump_answers_a_single_range() {
29228        let mut f = debugging();
29229        assert_eq!(
29230            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
29231            "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
29232        );
29233    }
29234
29235    /// A point is a number underneath, so the field that holds points answers
29236    /// the subcommand that dumps numbers and not the one that dumps tags.
29237    #[test]
29238    fn a_geo_field_is_dumped_as_a_numeric_one() {
29239        let mut f = Fixture::new();
29240        f.run(&[
29241            b"FT.CREATE",
29242            b"gx",
29243            b"PREFIX",
29244            b"1",
29245            b"q:",
29246            b"SCHEMA",
29247            b"loc",
29248            b"GEO",
29249            b"gg",
29250            b"AS",
29251            b"tag",
29252            b"TAG",
29253        ]);
29254        f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
29255        f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
29256        assert_eq!(
29257            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
29258            "*1\r\n*2\r\n:1\r\n:2\r\n"
29259        );
29260        assert_eq!(
29261            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
29262            "-Could not find given field in index spec\r\n"
29263        );
29264    }
29265
29266    /// A field is named the way a query names it, so the attribute is the name
29267    /// and the identifier the value was read from is not one.
29268    #[test]
29269    fn a_dump_takes_the_attribute_and_not_the_identifier() {
29270        let mut f = Fixture::new();
29271        f.run(&[
29272            b"FT.CREATE",
29273            b"zx",
29274            b"PREFIX",
29275            b"1",
29276            b"z:",
29277            b"SCHEMA",
29278            b"gg",
29279            b"AS",
29280            b"tag",
29281            b"TAG",
29282        ]);
29283        f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
29284        assert_eq!(
29285            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
29286            "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
29287        );
29288        assert_eq!(
29289            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
29290            "-Could not find given field in index spec\r\n"
29291        );
29292    }
29293
29294    /// The seven keys, with the score as a bulk string here and a double there,
29295    /// and the whole row flat on one protocol and a map on the other.
29296    #[test]
29297    fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
29298        let mut f = debugging();
29299        assert_eq!(
29300            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
29301            "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
29302             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
29303             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
29304             +sortables\r\n*1\r\n*6\r\n+index\r\n:0\r\n$5\r\nfield\r\n$6\r\ns AS s\r\n\
29305             $5\r\nvalue\r\n$5\r\nalpha\r\n"
29306        );
29307        let mut g = debugging();
29308        g.run(&[b"HELLO", b"3"]);
29309        assert_eq!(
29310            g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
29311            "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
29312             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
29313             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
29314             +sortables\r\n*1\r\n*6\r\n+index\r\n:0\r\n$5\r\nfield\r\n$6\r\ns AS s\r\n\
29315             $5\r\nvalue\r\n$5\r\nalpha\r\n"
29316        );
29317    }
29318
29319    /// A document that wrote nothing into a sortable slot has no sortables key
29320    /// at all, so the row is a key shorter rather than carrying an empty list.
29321    #[test]
29322    fn a_document_with_no_sortable_value_drops_the_key() {
29323        let mut f = debugging();
29324        assert_eq!(
29325            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
29326            "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
29327             +score\r\n$1\r\n1\r\n+num_tokens\r\n:2\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n"
29328        );
29329    }
29330
29331    /// The flag word is the number and then the names it stands for, and an
29332    /// index built without offsets has none of the three set.
29333    #[test]
29334    fn the_flag_word_spells_out_the_bits_it_carries() {
29335        let mut f = Fixture::new();
29336        f.run(&[
29337            b"FT.CREATE",
29338            b"nx",
29339            b"NOOFFSETS",
29340            b"PREFIX",
29341            b"1",
29342            b"o:",
29343            b"SCHEMA",
29344            b"t",
29345            b"TEXT",
29346        ]);
29347        f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
29348        assert!(
29349            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
29350                .contains("$6\r\n(0x0):\r\n")
29351        );
29352    }
29353
29354    /// Obfuscation replaces the field name with where the field sits in the
29355    /// whole schema, which is not where its value sits among the sortables.
29356    #[test]
29357    fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
29358        let mut f = debugging();
29359        assert!(
29360            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
29361                .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
29362        );
29363    }
29364
29365    /// The keyword is read where it belongs and anything after it is stepped
29366    /// over, whatever the line that complains about it says.
29367    #[test]
29368    fn a_document_row_reads_its_keyword_at_a_fixed_place() {
29369        let mut f = debugging();
29370        let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
29371        assert_eq!(
29372            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
29373            want
29374        );
29375        assert_eq!(
29376            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
29377            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
29378        );
29379        assert_eq!(
29380            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
29381            "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
29382        );
29383    }
29384
29385    /// The key is looked up before the keyword is read, so a key nobody indexed
29386    /// beats a keyword nobody wrote.
29387    #[test]
29388    fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
29389        let mut f = debugging();
29390        assert_eq!(
29391            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
29392            "-Document not found in index\r\n"
29393        );
29394        assert_eq!(
29395            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
29396            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
29397        );
29398    }
29399
29400    /// The two directions of the document table, and the number nobody handed
29401    /// out reads as one that was given up rather than as one that never was.
29402    #[test]
29403    fn a_document_number_goes_both_ways() {
29404        let mut f = debugging();
29405        assert_eq!(
29406            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
29407            "$3\r\nd:2\r\n"
29408        );
29409        assert_eq!(
29410            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
29411            ":2\r\n"
29412        );
29413        assert_eq!(
29414            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
29415            ":0\r\n"
29416        );
29417        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
29418        for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
29419            assert_eq!(
29420                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
29421                "-document was removed\r\n",
29422                "{id:?}"
29423            );
29424        }
29425    }
29426
29427    /// A document number is read the strict way Redis reads an integer, so a
29428    /// leading zero, a leading plus and a leading space are all refused.
29429    #[test]
29430    fn a_document_number_is_read_the_strict_way() {
29431        let mut f = debugging();
29432        for id in [
29433            b"x".as_slice(),
29434            b"1.5",
29435            b" 1",
29436            b"+1",
29437            b"01",
29438            b"0x1",
29439            b"",
29440            b"9223372036854775808",
29441            b"18446744073709551615",
29442        ] {
29443            assert_eq!(
29444                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
29445                "-bad id given\r\n",
29446                "{id:?}"
29447            );
29448        }
29449    }
29450
29451    /// A number a document has given up is still in every list it was in, so a
29452    /// dump names documents that the table says are gone.
29453    #[test]
29454    fn a_dump_keeps_a_number_the_table_has_given_up() {
29455        let mut f = debugging();
29456        f.run(&[b"DEL", b"d:2"]);
29457        assert_eq!(
29458            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29459            "*2\r\n:1\r\n:2\r\n"
29460        );
29461        assert_eq!(
29462            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
29463            "-document was removed\r\n"
29464        );
29465        assert_eq!(
29466            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
29467            ":0\r\n"
29468        );
29469    }
29470
29471    /// A rewrite hands out a new number and leaves the old one behind, so the
29472    /// counter climbs past the number of documents there are.
29473    #[test]
29474    fn a_rewrite_takes_a_number_of_its_own() {
29475        let mut f = debugging();
29476        f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
29477        assert_eq!(
29478            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
29479            ":4\r\n"
29480        );
29481        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
29482        assert_eq!(
29483            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
29484            "-document was removed\r\n"
29485        );
29486        assert_eq!(
29487            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
29488            "*2\r\n:1\r\n:2\r\n"
29489        );
29490    }
29491
29492    /// An alias reads the index it stands for, the same as a query does.
29493    #[test]
29494    fn a_dump_follows_an_alias() {
29495        let mut f = debugging();
29496        f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
29497        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
29498        assert_eq!(
29499            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
29500            "$3\r\nd:1\r\n"
29501        );
29502    }
29503
29504    /// The index name is matched as written and the subcommand name is not, and
29505    /// an index nobody made is reported as a context that could not be built.
29506    #[test]
29507    fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
29508        let mut f = debugging();
29509        assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
29510        assert_eq!(
29511            f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
29512            "-Can not create a search ctx\r\n"
29513        );
29514        assert_eq!(
29515            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
29516            "-Can not create a search ctx\r\n"
29517        );
29518    }
29519
29520    /// A field with nothing written into it answers an empty dump rather than an
29521    /// error, since the field is in the schema and only the values are missing.
29522    #[test]
29523    fn an_empty_field_dumps_as_nothing_at_all() {
29524        let mut f = Fixture::new();
29525        f.run(&[
29526            b"FT.CREATE",
29527            b"ex",
29528            b"PREFIX",
29529            b"1",
29530            b"e:",
29531            b"SCHEMA",
29532            b"t",
29533            b"TEXT",
29534            b"g",
29535            b"TAG",
29536            b"n",
29537            b"NUMERIC",
29538        ]);
29539        assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
29540        assert_eq!(
29541            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
29542            "*0\r\n"
29543        );
29544        assert_eq!(
29545            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
29546            "*0\r\n"
29547        );
29548        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
29549    }
29550
29551    /// The two lines the dispatcher owns are the two that carry a code word, and
29552    /// every subcommand but `DOCINFO` counts its arguments exactly.
29553    #[test]
29554    fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
29555        let mut f = debugging();
29556        for (sub, extra) in [
29557            (b"DUMP_TERMS".as_slice(), 1),
29558            (b"GET_MAX_DOC_ID", 1),
29559            (b"DUMP_INVIDX", 2),
29560            (b"DUMP_TAGIDX", 2),
29561            (b"DUMP_NUMIDX", 2),
29562            (b"IDTODOCID", 2),
29563            (b"DOCIDTOID", 2),
29564        ] {
29565            let want = format!(
29566                "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
29567                str::from_utf8(sub).unwrap()
29568            );
29569            for given in [extra - 1, extra + 1] {
29570                let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
29571                cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
29572                assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
29573            }
29574            let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
29575            right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
29576            assert_ne!(f.run(&right), want, "{sub:?}");
29577        }
29578        assert_eq!(
29579            f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
29580            "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
29581        );
29582    }
29583
29584    /// The eight names that answer rather than the sixty two a real server
29585    /// registers, which is D-97, and anything after the name is stepped over.
29586    #[test]
29587    fn the_help_names_the_subcommands_that_answer() {
29588        let mut f = Fixture::new();
29589        let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
29590             $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
29591             $14\r\nGET_MAX_DOC_ID\r\n";
29592        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
29593        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
29594    }
29595
29596    // ------------------------------------------------------------- synonyms
29597
29598    /// The terms are folded on the way in and the group ids are not, and one
29599    /// term can be in more than one group.
29600    #[test]
29601    fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
29602        let mut f = Fixture::new();
29603        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29604        assert_eq!(
29605            f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
29606            "+OK\r\n"
29607        );
29608        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
29609        assert_eq!(
29610            f.run(&[b"FT.SYNDUMP", b"e"]),
29611            "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
29612             $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
29613        );
29614    }
29615
29616    /// A group is not a comparison made at query time. It is a term of its
29617    /// own, so a word in a group reads as a union of the word, the groups it
29618    /// is in and its stem.
29619    #[test]
29620    fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
29621        let mut f = Fixture::new();
29622        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29623        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
29624        assert_eq!(
29625            f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
29626            "$69\r\nUNION {\n  jogging\n  ~gr(expanded)\n  +jog(expanded)\n  jog(expanded)\n}\n\r\n"
29627        );
29628    }
29629
29630    /// The lookup on the document side is on the word and never on the stem,
29631    /// and a group written after the documents were still finds them because
29632    /// the index is read again.
29633    ///
29634    /// The group holds `running` and `d2` says `runs`, so a query for another
29635    /// word of the group finds `d1` and leaves `d2` where it is. A query for
29636    /// `running` itself does find `d2`, through the stem branch of the union
29637    /// rather than through the group, which is why the two asserts differ.
29638    #[test]
29639    fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
29640        let mut f = Fixture::new();
29641        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29642        f.run(&[b"HSET", b"d1", b"t", b"boy"]);
29643        f.run(&[b"HSET", b"d2", b"t", b"runs"]);
29644        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
29645        assert_eq!(
29646            f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
29647            "*2\r\n:1\r\n$2\r\nd1\r\n"
29648        );
29649        assert_eq!(
29650            f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
29651            "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
29652        );
29653    }
29654
29655    /// Neither command makes an index and neither forgives a name that is not
29656    /// there, in the same words the rest of the group uses.
29657    #[test]
29658    fn a_synonym_command_on_a_name_that_is_not_there_fails() {
29659        let mut f = Fixture::new();
29660        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
29661        assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
29662        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
29663    }
29664
29665    /// The words after `PARAMS n` are counted before their shape is looked at,
29666    /// so a count that reaches past the end of the command and a count that is
29667    /// merely odd are two different errors.
29668    #[test]
29669    fn params_counts_the_words_before_it_pairs_them_up() {
29670        let mut f = Fixture::new();
29671        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
29672        let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
29673                    Expected an argument, but none provided\r\n";
29674        let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
29675        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
29676        assert_eq!(
29677            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
29678            none
29679        );
29680        assert_eq!(
29681            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
29682            odd
29683        );
29684        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
29685        assert_eq!(
29686            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
29687            "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
29688        );
29689    }
29690
29691    // --------------------------------------------------------------- vectors
29692
29693    /// Five documents a unit apart along one axis, written in the opposite
29694    /// order to the one they sit in, so a reply in document order and a reply
29695    /// in distance order are two different replies.
29696    ///
29697    /// `d1` is furthest from the origin and `d5` is on it. The text field
29698    /// splits them so a query can narrow before it measures: `d1`, `d2` and
29699    /// `d4` say `alpha` and the other two say `beta`.
29700    fn vectored(f: &mut Fixture) {
29701        f.run(&[
29702            b"FT.CREATE",
29703            b"h",
29704            b"SCHEMA",
29705            b"t",
29706            b"TEXT",
29707            b"v",
29708            b"VECTOR",
29709            b"FLAT",
29710            b"6",
29711            b"TYPE",
29712            b"FLOAT32",
29713            b"DIM",
29714            b"2",
29715            b"DISTANCE_METRIC",
29716            b"L2",
29717        ]);
29718        let at: [&[u8]; 5] = [
29719            b"\x00\x00\x80\x40\x00\x00\x00\x00",
29720            b"\x00\x00\x40\x40\x00\x00\x00\x00",
29721            b"\x00\x00\x00\x40\x00\x00\x00\x00",
29722            b"\x00\x00\x80\x3f\x00\x00\x00\x00",
29723            b"\x00\x00\x00\x00\x00\x00\x00\x00",
29724        ];
29725        for (n, point) in at.iter().enumerate() {
29726            let key = format!("d{}", n + 1);
29727            let word: &[u8] = match n {
29728                0 | 1 | 3 => b"alpha",
29729                _ => b"beta",
29730            };
29731            f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
29732        }
29733    }
29734
29735    /// The origin, which every query below asks about.
29736    const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
29737
29738    /// A `KNN` picks the k nearest and then answers them in document order,
29739    /// which is measured: asking for three of five that were written furthest
29740    /// first answers the last three written and not the first three.
29741    #[test]
29742    fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
29743        let mut f = Fixture::new();
29744        vectored(&mut f);
29745        assert_eq!(
29746            f.run(&[
29747                b"FT.SEARCH",
29748                b"h",
29749                b"*=>[KNN 5 @v $vec]",
29750                b"PARAMS",
29751                b"2",
29752                b"vec",
29753                ORIGIN,
29754                b"DIALECT",
29755                b"2",
29756                b"NOCONTENT",
29757            ]),
29758            "*6\r\n:5\r\n$2\r\nd1\r\n$2\r\nd2\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
29759        );
29760        assert_eq!(
29761            f.run(&[
29762                b"FT.SEARCH",
29763                b"h",
29764                b"*=>[KNN 3 @v $vec]",
29765                b"PARAMS",
29766                b"2",
29767                b"vec",
29768                ORIGIN,
29769                b"DIALECT",
29770                b"2",
29771                b"NOCONTENT",
29772            ]),
29773            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
29774        );
29775    }
29776
29777    /// A range takes what is really inside it, where the distances are squared
29778    /// so the five documents sit at 16, 9, 4, 1 and 0.
29779    #[test]
29780    fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
29781        let mut f = Fixture::new();
29782        vectored(&mut f);
29783        for (radius, want) in [
29784            ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
29785            ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
29786            (
29787                "9",
29788                "*5\r\n:4\r\n$2\r\nd2\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n",
29789            ),
29790        ] {
29791            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
29792            assert_eq!(
29793                f.run(&[
29794                    b"FT.SEARCH",
29795                    b"h",
29796                    query.as_bytes(),
29797                    b"PARAMS",
29798                    b"2",
29799                    b"vec",
29800                    ORIGIN,
29801                    b"DIALECT",
29802                    b"2",
29803                    b"NOCONTENT",
29804                ]),
29805                want,
29806                "radius {radius}"
29807            );
29808        }
29809    }
29810
29811    /// A `KNN` behind a query is the nearest of what the query matched, so
29812    /// asking for two of the three documents that say `alpha` answers the two
29813    /// of those three that are nearest and not the two nearest overall.
29814    #[test]
29815    fn a_knn_measures_what_the_query_in_front_of_it_matched() {
29816        let mut f = Fixture::new();
29817        vectored(&mut f);
29818        assert_eq!(
29819            f.run(&[
29820                b"FT.SEARCH",
29821                b"h",
29822                b"alpha=>[KNN 2 @v $vec]",
29823                b"PARAMS",
29824                b"2",
29825                b"vec",
29826                ORIGIN,
29827                b"DIALECT",
29828                b"2",
29829                b"NOCONTENT",
29830            ]),
29831            "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
29832        );
29833    }
29834
29835    /// A `KNN` counts in whole numbers and a range measures from zero, and the
29836    /// two are refused in their own words.
29837    ///
29838    /// The count is a token of its own and is checked where it stands, ahead of
29839    /// the field and ahead of the vector. A count that arrives through `PARAMS`
29840    /// is read by looser rules than one written into the query, which is
29841    /// measured: a leading plus is fine in a parameter and a syntax error in
29842    /// the query text.
29843    #[test]
29844    fn a_count_and_a_radius_are_refused_in_their_own_words() {
29845        let mut f = Fixture::new();
29846        vectored(&mut f);
29847        let ask = |f: &mut Fixture, query: &str| {
29848            f.run(&[
29849                b"FT.SEARCH",
29850                b"h",
29851                query.as_bytes(),
29852                b"PARAMS",
29853                b"2",
29854                b"vec",
29855                ORIGIN,
29856                b"DIALECT",
29857                b"2",
29858                b"NOCONTENT",
29859            ])
29860        };
29861        for (query, at, near) in [
29862            ("*=>[KNN -1 @v $vec]", 8, "-1"),
29863            ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
29864            ("*=>[KNN +3 @v $vec]", 8, "+3"),
29865            ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
29866            ("*=>[KNN abc @v $vec]", 8, "abc"),
29867            ("*=>[KNN 3 $vec]", 10, "vec"),
29868            ("*=>[KNN 3 @v vec]", 13, "vec"),
29869            ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
29870        ] {
29871            assert_eq!(
29872                ask(&mut f, query),
29873                format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
29874                "{query}"
29875            );
29876        }
29877
29878        // Read as a double the way a real server reads it, so the bound plus
29879        // thirty two rounds back onto the bound and gets in.
29880        let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29881                     query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
29882        assert_eq!(
29883            ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
29884            "*6\r\n:5\r\n$2\r\nd1\r\n$2\r\nd2\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
29885        );
29886        assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
29887        assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
29888
29889        for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
29890            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
29891            assert_eq!(
29892                ask(&mut f, &query),
29893                format!(
29894                    "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29895                     negative radius ({printed}) given in a range query\r\n"
29896                ),
29897                "{query}"
29898            );
29899        }
29900        // A radius of minus zero is not below zero and is a radius of zero.
29901        assert_eq!(
29902            ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
29903            "*2\r\n:1\r\n$2\r\nd5\r\n"
29904        );
29905    }
29906
29907    /// A count passed with `PARAMS` is read the way a real server reads one,
29908    /// which is not the way the same digits are read in the query text.
29909    #[test]
29910    fn a_count_that_came_from_params_is_read_by_its_own_rules() {
29911        let mut f = Fixture::new();
29912        vectored(&mut f);
29913        let ask = |f: &mut Fixture, count: &[u8]| {
29914            f.run(&[
29915                b"FT.SEARCH",
29916                b"h",
29917                b"*=>[KNN $k @v $vec]",
29918                b"PARAMS",
29919                b"4",
29920                b"vec",
29921                ORIGIN,
29922                b"k",
29923                count,
29924                b"DIALECT",
29925                b"2",
29926                b"NOCONTENT",
29927            ])
29928        };
29929        let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
29930        assert_eq!(ask(&mut f, b"3"), three);
29931        assert_eq!(ask(&mut f, b"  3"), three);
29932        assert_eq!(ask(&mut f, b"+3"), three);
29933        for bad in [
29934            &b"3.0"[..],
29935            b"0x3",
29936            b"-1",
29937            b"abc",
29938            b"",
29939            b"99999999999999999999",
29940        ] {
29941            let value = String::from_utf8_lossy(bad).into_owned();
29942            assert_eq!(
29943                ask(&mut f, bad),
29944                format!(
29945                    "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
29946                     for parameter `k`\r\n"
29947                ),
29948                "{value}"
29949            );
29950        }
29951        assert_eq!(
29952            ask(&mut f, b"288230376151711777"),
29953            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29954             query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
29955        );
29956    }
29957
29958    /// A vector the wrong size is refused against the field it was passed to,
29959    /// naming both sizes in bytes.
29960    #[test]
29961    fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
29962        let mut f = Fixture::new();
29963        vectored(&mut f);
29964        assert_eq!(
29965            f.run(&[
29966                b"FT.SEARCH",
29967                b"h",
29968                b"*=>[KNN 5 @v $vec]",
29969                b"PARAMS",
29970                b"2",
29971                b"vec",
29972                b"abc",
29973                b"DIALECT",
29974                b"2",
29975                b"NOCONTENT",
29976            ]),
29977            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
29978             query vector blob size (3) does not match index's expected size (8).\r\n"
29979        );
29980    }
29981
29982    /// A nearest neighbour clause puts its distance on every row it answers,
29983    /// under `__v_score` unless the query renamed it. A range clause puts
29984    /// nothing there at all unless the query named it, which is what
29985    /// `YIELD_DISTANCE_AS` is for.
29986    #[test]
29987    fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
29988        let mut f = Fixture::new();
29989        vectored(&mut f);
29990        let ask = |f: &mut Fixture, query: &str| {
29991            f.run(&[
29992                b"FT.SEARCH",
29993                b"h",
29994                query.as_bytes(),
29995                b"PARAMS",
29996                b"2",
29997                b"vec",
29998                ORIGIN,
29999                b"DIALECT",
30000                b"2",
30001                b"LIMIT",
30002                b"0",
30003                b"1",
30004            ])
30005        };
30006        assert_eq!(
30007            ask(&mut f, "*=>[KNN 3 @v $vec]"),
30008            "*3\r\n:3\r\n$2\r\nd3\r\n*6\r\n$9\r\n__v_score\r\n$1\r\n4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
30009        );
30010        assert_eq!(
30011            ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
30012            "*3\r\n:3\r\n$2\r\nd3\r\n*6\r\n$1\r\nd\r\n$1\r\n4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
30013        );
30014        assert_eq!(
30015            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
30016            "*3\r\n:3\r\n$2\r\nd3\r\n*4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
30017        );
30018        assert_eq!(
30019            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
30020            "*3\r\n:3\r\n$2\r\nd3\r\n*6\r\n$1\r\nd\r\n$1\r\n4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
30021        );
30022    }
30023
30024    /// What decides whether a `RETURN` answers the distance is the name the row
30025    /// would carry it under and not the field it would have been read from,
30026    /// because it is on the row before any key is read.
30027    ///
30028    /// So naming it answers it, renaming it answers nothing at all, and giving
30029    /// its name to another field answers the distance under that name.
30030    #[test]
30031    fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
30032        let mut f = Fixture::new();
30033        vectored(&mut f);
30034        let ask = |f: &mut Fixture, ret: &[&[u8]]| {
30035            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
30036            args.extend_from_slice(ret);
30037            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
30038            f.run(&args)
30039        };
30040        assert_eq!(
30041            ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
30042            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n"
30043        );
30044        assert_eq!(
30045            ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
30046            "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
30047        );
30048        assert_eq!(
30049            ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
30050            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n"
30051        );
30052        assert_eq!(
30053            ask(&mut f, &[b"RETURN", b"1", b"t"]),
30054            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
30055        );
30056        // The distance goes in front of the rest whatever order they were
30057        // named in, and `NOCONTENT` takes it away with everything else.
30058        assert_eq!(
30059            ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
30060            "*3\r\n:1\r\n$2\r\nd5\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
30061        );
30062        assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
30063    }
30064
30065    /// A `SORTBY` can name a distance the query yielded, which sorts by the
30066    /// number rather than by anything the key holds. A name the query did not
30067    /// yield is refused the way any other unknown property is.
30068    #[test]
30069    fn a_sortby_can_name_a_distance_the_query_yielded() {
30070        let mut f = Fixture::new();
30071        vectored(&mut f);
30072        let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
30073            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
30074            if desc {
30075                args.push(b"DESC");
30076            }
30077            args.extend_from_slice(&[
30078                b"PARAMS",
30079                b"2",
30080                b"vec",
30081                ORIGIN,
30082                b"DIALECT",
30083                b"2",
30084                b"NOCONTENT",
30085            ]);
30086            f.run(&args)
30087        };
30088        assert_eq!(
30089            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
30090            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
30091        );
30092        assert_eq!(
30093            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
30094            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
30095        );
30096        assert_eq!(
30097            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
30098            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
30099        );
30100        // Renaming it takes the old name away, and a query with no vector
30101        // clause in it never had the property at all.
30102        let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
30103                       not loaded nor in schema\r\n";
30104        assert_eq!(
30105            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
30106            missing
30107        );
30108        assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
30109        // The query is read before the property is looked up, which is
30110        // measured: a query that will not parse is answered first.
30111        assert_eq!(
30112            ask(&mut f, "foo(", b"zz", false),
30113            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
30114        );
30115    }
30116
30117    /// Two vector clauses in one query answer two distances, outermost first.
30118    #[test]
30119    fn two_vector_clauses_answer_two_distances() {
30120        let mut f = Fixture::new();
30121        vectored(&mut f);
30122        assert_eq!(
30123            f.run(&[
30124                b"FT.SEARCH",
30125                b"h",
30126                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
30127                b"RETURN",
30128                b"2",
30129                b"rr",
30130                b"__v_score",
30131                b"PARAMS",
30132                b"2",
30133                b"vec",
30134                ORIGIN,
30135                b"DIALECT",
30136                b"2",
30137            ]),
30138            "*5\r\n:2\r\n$2\r\nd4\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$2\r\nrr\r\n$1\r\n1\r\n$2\r\nd5\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$2\r\nrr\r\n$1\r\n0\r\n"
30139        );
30140    }
30141
30142    /// An aggregation carries the distance on every row whether or not the
30143    /// pipeline ever mentions it, and carries it in front of everything a
30144    /// `LOAD` asked for.
30145    #[test]
30146    fn an_aggregation_answers_a_distance_nothing_asked_for() {
30147        let mut f = Fixture::new();
30148        vectored(&mut f);
30149        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
30150            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
30151            args.extend_from_slice(rest);
30152            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
30153            f.run(&args)
30154        };
30155        assert_eq!(
30156            ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
30157            "*3\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
30158        );
30159        assert_eq!(
30160            ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
30161            "*3\r\n:1\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n"
30162        );
30163        assert_eq!(
30164            ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
30165            "*3\r\n:1\r\n*2\r\n$1\r\nd\r\n$1\r\n0\r\n*2\r\n$1\r\nd\r\n$1\r\n1\r\n"
30166        );
30167        // A range shows nothing until the query names it.
30168        assert_eq!(
30169            ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
30170            "*3\r\n:1\r\n*0\r\n*0\r\n"
30171        );
30172        assert_eq!(
30173            ask(
30174                &mut f,
30175                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
30176                &[]
30177            ),
30178            "*3\r\n:1\r\n*2\r\n$2\r\nrr\r\n$1\r\n1\r\n*2\r\n$2\r\nrr\r\n$1\r\n0\r\n"
30179        );
30180    }
30181
30182    /// A nearest neighbour clause hands its documents back nearest first and an
30183    /// aggregation keeps them that way, where a search sorts them into document
30184    /// order. A tie goes to the document written first.
30185    #[test]
30186    fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
30187        let mut f = Fixture::new();
30188        vectored(&mut f);
30189        // Sitting on `d3`, so `d2` and `d4` are the same distance away.
30190        const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
30191        let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
30192            f.run(&[
30193                b"FT.AGGREGATE",
30194                b"h",
30195                query.as_bytes(),
30196                b"LOAD",
30197                b"1",
30198                b"@t",
30199                b"PARAMS",
30200                b"2",
30201                b"vec",
30202                vec,
30203                b"DIALECT",
30204                b"2",
30205            ])
30206        };
30207        assert_eq!(
30208            ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
30209            "*4\r\n:1\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n"
30210        );
30211        // A range does no ordering, so those rows stay in document order.
30212        assert_eq!(
30213            ask(
30214                &mut f,
30215                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
30216                MIDDLE
30217            ),
30218            "*4\r\n:1\r\n*4\r\n$2\r\nrr\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n*4\r\n$2\r\nrr\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n*4\r\n$2\r\nrr\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n"
30219        );
30220    }
30221
30222    /// Every step of the pipeline can name a distance the query yielded, and a
30223    /// query with no vector clause in it is refused for the name three
30224    /// different ways depending on which step asked.
30225    #[test]
30226    fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
30227        let mut f = Fixture::new();
30228        vectored(&mut f);
30229        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
30230            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
30231            args.extend_from_slice(rest);
30232            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
30233            f.run(&args)
30234        };
30235        let knn = "*=>[KNN 2 @v $vec]";
30236        assert_eq!(
30237            ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
30238            "*3\r\n:1\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nx\r\n$1\r\n0\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nx\r\n$1\r\n2\r\n"
30239        );
30240        assert_eq!(
30241            ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
30242            "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
30243        );
30244        assert_eq!(
30245            ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
30246            "*3\r\n:2\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n"
30247        );
30248        assert_eq!(
30249            ask(
30250                &mut f,
30251                knn,
30252                &[
30253                    b"GROUPBY",
30254                    b"1",
30255                    b"@t",
30256                    b"REDUCE",
30257                    b"MAX",
30258                    b"1",
30259                    b"@__v_score",
30260                    b"AS",
30261                    b"m"
30262                ]
30263            ),
30264            "*3\r\n:2\r\n*4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nm\r\n$1\r\n0\r\n*4\r\n$1\r\nt\r\n$5\r\nalpha\r\n$1\r\nm\r\n$1\r\n1\r\n"
30265        );
30266        assert_eq!(
30267            ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
30268            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
30269             `__v_score`\r\n"
30270        );
30271        assert_eq!(
30272            ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
30273            "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
30274        );
30275        assert_eq!(
30276            ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
30277            "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
30278             schema\r\n"
30279        );
30280    }
30281
30282    /// An aggregation reads every word before it reads the query, and reads the
30283    /// query before it ties anything on the pipeline to a place on the row.
30284    ///
30285    /// So a command with a fault in all three answers the one about the words,
30286    /// a command with a fault in the last two answers the one about the query,
30287    /// and the pipeline speaks last. That is measured, and it is the whole
30288    /// reason the arguments are read twice.
30289    #[test]
30290    fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
30291        let mut f = Fixture::new();
30292        vectored(&mut f);
30293        let ask = |f: &mut Fixture, rest: &[&[u8]]| {
30294            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
30295            args.extend_from_slice(rest);
30296            f.run(&args)
30297        };
30298        assert_eq!(
30299            ask(
30300                &mut f,
30301                &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
30302            ),
30303            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
30304        );
30305        assert_eq!(
30306            ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
30307            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
30308        );
30309        assert_eq!(
30310            ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
30311            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
30312        );
30313        // An expression that will not read is the pipeline's fault too, so it
30314        // speaks after the query and after a property named before it.
30315        assert_eq!(
30316            ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
30317            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
30318        );
30319        assert_eq!(
30320            ask(
30321                &mut f,
30322                &[
30323                    b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
30324                ]
30325            ),
30326            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
30327        );
30328        assert_eq!(
30329            ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
30330            "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
30331        );
30332    }
30333
30334    /// A vector clause says which of the ways of answering one it took, and a
30335    /// range says nothing at all when there is no distance to hand back.
30336    #[test]
30337    fn a_vector_step_says_which_way_it_was_answered() {
30338        let mut f = Fixture::new();
30339        vectored(&mut f);
30340        let tree = |f: &mut Fixture, query: &[u8]| {
30341            let reply = timeless(&f.run(&[
30342                b"FT.PROFILE",
30343                b"h",
30344                b"AGGREGATE",
30345                b"QUERY",
30346                query,
30347                b"PARAMS",
30348                b"2",
30349                b"vec",
30350                ORIGIN,
30351                b"DIALECT",
30352                b"2",
30353            ]));
30354            let at = reply.find("+Iterators profile").expect("a tree");
30355            let end = reply.find("+Result processors").expect("a list of steps");
30356            reply[at..end].to_string()
30357        };
30358        assert_eq!(
30359            tree(&mut f, b"*=>[KNN 3 @v $vec]"),
30360            "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
30361             +Number of reading operations\r\n:3\r\n\
30362             +Vector search mode\r\n+STANDARD_KNN\r\n"
30363        );
30364        // Renaming the distance changes nothing about how it was answered.
30365        assert_eq!(
30366            tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
30367            tree(&mut f, b"*=>[KNN 3 @v $vec]")
30368        );
30369        // A range with nothing to yield is not a vector step at all, and one
30370        // that yields names the distance in its own type.
30371        assert_eq!(
30372            tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
30373            "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
30374             +Number of reading operations\r\n:4\r\n"
30375        );
30376        assert_eq!(
30377            tree(
30378                &mut f,
30379                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
30380            ),
30381            "+Iterators profile\r\n*8\r\n\
30382             +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
30383             +Number of reading operations\r\n:4\r\n\
30384             +Vector search mode\r\n+RANGE_QUERY\r\n"
30385        );
30386    }
30387
30388    /// What a vector clause narrowed itself down with hangs under it as a
30389    /// single child, and the step that works the distances out is behind the
30390    /// index whenever the query yields one.
30391    #[test]
30392    fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
30393        let mut f = Fixture::new();
30394        vectored(&mut f);
30395        let ask = |f: &mut Fixture, query: &[u8]| {
30396            timeless(&f.run(&[
30397                b"FT.PROFILE",
30398                b"h",
30399                b"AGGREGATE",
30400                b"QUERY",
30401                query,
30402                b"PARAMS",
30403                b"2",
30404                b"vec",
30405                ORIGIN,
30406                b"DIALECT",
30407                b"2",
30408            ]))
30409        };
30410        let cut = |reply: &str| {
30411            let at = reply.find("+Iterators profile").expect("a tree");
30412            reply[at..].to_string()
30413        };
30414        assert_eq!(
30415            cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
30416            "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
30417             +Number of reading operations\r\n:3\r\n\
30418             +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
30419             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
30420             +Number of reading operations\r\n:3\r\n\
30421             +Estimated number of matches\r\n:3\r\n\
30422             +Result processors profile\r\n*2\r\n\
30423             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
30424             *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
30425             +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
30426        );
30427        // A range nobody named yields nothing, so nothing works a distance out
30428        // and the step is not there.
30429        assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
30430            "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
30431             +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
30432        ));
30433        // A nearest neighbour clause with nothing in front of it yields all
30434        // the same, so the step is there without a child above it.
30435        assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
30436    }
30437
30438    /// A `LIMIT 0 0` on an aggregation is a client asking for the total and
30439    /// nothing else, so the step that would have paged the rows counts them
30440    /// instead, whether or not a `SORTBY` put an order in front of it.
30441    #[test]
30442    fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
30443        let mut f = profiling();
30444        let steps = |f: &mut Fixture, words: &[&[u8]]| {
30445            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
30446            argv.extend_from_slice(words);
30447            let reply = timeless(&f.run(&argv));
30448            let at = reply.find("+Result processors").expect("a list of steps");
30449            reply[at..].to_string()
30450        };
30451        assert_eq!(
30452            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
30453            "+Result processors profile\r\n*2\r\n\
30454             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
30455             *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
30456             +Coordinator\r\n*0\r\n"
30457        );
30458        assert!(
30459            steps(
30460                &mut f,
30461                &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
30462            )
30463            .contains("+Type\r\n+Counter\r\n")
30464        );
30465        // A window that keeps something is still a window.
30466        assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
30467            "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
30468             +Results processed\r\n:2\r\n"
30469        ));
30470    }
30471
30472    // ----------------------------------------------------------- spellcheck
30473
30474    /// The score is how many documents hold the suggestion over how many
30475    /// documents there are, and how close the suggestion is to the word does
30476    /// not come into it at all, so the nearer of the two words here is second.
30477    #[test]
30478    fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
30479        let mut f = Fixture::new();
30480        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30481        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30482        f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
30483        assert_eq!(
30484            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
30485            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
30486             *2\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n*2\r\n$3\r\n0.5\r\n$5\r\nhallo\r\n"
30487        );
30488    }
30489
30490    /// On RESP3 the whole thing is wrapped in a map under one name, a word
30491    /// carries a list of one pair maps, and the score is a double rather than
30492    /// a string.
30493    #[test]
30494    fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
30495        let mut f = Fixture::new();
30496        f.run(&[b"HELLO", b"3"]);
30497        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30498        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30499        assert_eq!(
30500            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
30501            "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
30502             *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
30503        );
30504    }
30505
30506    /// A word the index already holds is not a mistake and is left out of the
30507    /// answer, and that check never looks at the field the query named, while
30508    /// the search for candidates does.
30509    #[test]
30510    fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
30511        let mut f = Fixture::new();
30512        f.run(&[
30513            b"FT.CREATE",
30514            b"e",
30515            b"SCHEMA",
30516            b"a",
30517            b"TEXT",
30518            b"NOSTEM",
30519            b"b",
30520            b"TEXT",
30521            b"NOSTEM",
30522        ]);
30523        f.run(&[b"HSET", b"d1", b"b", b"world"]);
30524        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
30525        assert_eq!(
30526            f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
30527            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
30528        );
30529    }
30530
30531    /// A dictionary named by `INCLUDE` adds words the index never read, scored
30532    /// zero and reported in the spelling the dictionary was given, and one
30533    /// named by `EXCLUDE` says a word is spelled right after all.
30534    #[test]
30535    fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
30536        let mut f = Fixture::new();
30537        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30538        f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
30539        assert_eq!(
30540            f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
30541            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
30542             *2\r\n*2\r\n$1\r\n0\r\n$5\r\nHellp\r\n*2\r\n$1\r\n0\r\n$5\r\nhellq\r\n"
30543        );
30544        assert_eq!(
30545            f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
30546            "*0\r\n"
30547        );
30548        assert_eq!(
30549            f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
30550            "-Dict does not exist: nope\r\n"
30551        );
30552    }
30553
30554    /// The first `DISTANCE` counts and the rest are dropped, an argument
30555    /// nobody recognises is stepped over rather than refused, and a distance
30556    /// outside one to four is the one thing here that does fail.
30557    #[test]
30558    fn a_spellcheck_reads_its_arguments_leniently() {
30559        let mut f = Fixture::new();
30560        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
30561        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
30562        let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
30563                   *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
30564        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
30565        let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
30566        let args: &[&[u8]] = &[
30567            b"FT.SPELLCHECK",
30568            b"e",
30569            b"helqp",
30570            b"DISTANCE",
30571            b"1",
30572            b"DISTANCE",
30573            b"4",
30574        ];
30575        assert_eq!(f.run(args), none);
30576        assert_eq!(
30577            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
30578            "-bad distance given, distance must be a natural number between 1 to 4\r\n"
30579        );
30580        assert_eq!(
30581            f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
30582            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
30583        );
30584    }
30585
30586    // -------------------------------------------------------------- suggest
30587
30588    /// The reply is the size of the dictionary afterwards, which is neither
30589    /// what was added nor whether anything changed.
30590    #[test]
30591    fn an_add_answers_how_many_suggestions_are_in_there_now() {
30592        let mut f = Fixture::new();
30593        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
30594        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
30595        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
30596        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
30597        assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
30598    }
30599
30600    /// A suggestion dictionary is the one thing the search module puts in the
30601    /// keyspace, so every keyspace command reaches it.
30602    #[test]
30603    fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
30604        let mut f = Fixture::new();
30605        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30606        assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
30607        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
30608        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
30609        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
30610        assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
30611        assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
30612        assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
30613        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
30614    }
30615
30616    /// The last suggestion out takes the key with it, which most module types
30617    /// do not do.
30618    #[test]
30619    fn deleting_the_last_suggestion_deletes_the_key() {
30620        let mut f = Fixture::new();
30621        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30622        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
30623        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
30624        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
30625        assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
30626    }
30627
30628    /// A key holding anything else is refused rather than overwritten, on all
30629    /// four of them.
30630    #[test]
30631    fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
30632        let mut f = Fixture::new();
30633        f.run(&[b"SET", b"s", b"x"]);
30634        for cmd in [
30635            vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
30636            vec![&b"FT.SUGGET"[..], b"s", b"t"],
30637            vec![&b"FT.SUGDEL"[..], b"s", b"t"],
30638            vec![&b"FT.SUGLEN"[..], b"s"],
30639        ] {
30640            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
30641        }
30642        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
30643    }
30644
30645    /// The scores in here were read off a real server, single precision and
30646    /// all. An exact match answers a sentinel so it sorts in front.
30647    #[test]
30648    fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
30649        let mut f = Fixture::new();
30650        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30651        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
30652        f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
30653        assert_eq!(
30654            f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
30655            "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
30656             $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
30657             $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
30658        );
30659        assert_eq!(
30660            f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
30661            "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
30662        );
30663        assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
30664    }
30665
30666    /// `FUZZY` is one edit, and the edit is a rune rather than a byte.
30667    #[test]
30668    fn fuzzy_allows_one_edit_and_nothing_allows_two() {
30669        let mut f = Fixture::new();
30670        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
30671        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
30672        assert_eq!(
30673            f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
30674            "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
30675        );
30676        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
30677    }
30678
30679    /// Five without a `MAX`, and the terms come back in score order.
30680    #[test]
30681    fn a_lookup_answers_five_unless_it_is_told_otherwise() {
30682        let mut f = Fixture::new();
30683        for (term, score) in [
30684            (&b"a1"[..], &b"1"[..]),
30685            (b"a2", b"2"),
30686            (b"a3", b"3"),
30687            (b"a4", b"4"),
30688            (b"a5", b"5"),
30689            (b"a6", b"6"),
30690        ] {
30691            f.run(&[b"FT.SUGADD", b"s", term, score]);
30692        }
30693        assert_eq!(
30694            f.run(&[b"FT.SUGGET", b"s", b"a"]),
30695            "*5\r\n$2\r\na6\r\n$2\r\na5\r\n$2\r\na4\r\n$2\r\na3\r\n$2\r\na2\r\n"
30696        );
30697        assert_eq!(
30698            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
30699            "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
30700        );
30701        // A `MAX` larger than the dictionary answers what there is.
30702        assert!(
30703            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
30704                .starts_with("*6\r\n")
30705        );
30706    }
30707
30708    /// A payload is replaced only when one is given, and an empty one is no
30709    /// payload at all.
30710    #[test]
30711    fn a_payload_comes_back_beside_the_term_or_a_null_does() {
30712        let mut f = Fixture::new();
30713        f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
30714        assert_eq!(
30715            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30716            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30717        );
30718        f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
30719        assert_eq!(
30720            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30721            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30722        );
30723        // An empty payload is the same as not having given one at all, so it
30724        // leaves the payload where it is rather than clearing it.
30725        f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
30726        assert_eq!(
30727            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
30728            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
30729        );
30730        // A term that never had one answers a null.
30731        f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
30732        assert_eq!(
30733            f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
30734            "*2\r\n$5\r\nother\r\n$-1\r\n"
30735        );
30736    }
30737
30738    /// `INCR` adds to the score that is there rather than replacing it, and
30739    /// three tenths a tenth at a time is the reading that shows the score is
30740    /// held in single precision.
30741    #[test]
30742    fn incr_adds_to_the_score_that_is_already_there() {
30743        let mut f = Fixture::new();
30744        for _ in 0..3 {
30745            f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
30746        }
30747        assert_eq!(
30748            f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
30749            "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
30750        );
30751    }
30752
30753    /// The five error sentences, none of which are written the same way.
30754    #[test]
30755    fn the_suggestion_errors_are_the_lines_the_module_sends() {
30756        let mut f = Fixture::new();
30757        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30758        assert_eq!(
30759            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
30760            "-ERR invalid score\r\n"
30761        );
30762        // The unknown word is complained about before the score is converted.
30763        assert_eq!(
30764            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
30765            "-Unknown argument `NOPE`\r\n"
30766        );
30767        assert_eq!(
30768            f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
30769            "-Invalid payload: Expected an argument, but none provided\r\n"
30770        );
30771        // Too many words is an arity error and not an unknown argument.
30772        assert!(
30773            f.run(&[
30774                b"FT.SUGADD",
30775                b"s",
30776                b"t",
30777                b"1",
30778                b"PAYLOAD",
30779                b"a",
30780                b"PAYLOAD",
30781                b"b"
30782            ])
30783            .contains("wrong number of arguments")
30784        );
30785        assert_eq!(
30786            f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
30787            "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
30788        );
30789        // A count read as a whole number and then found to be out of range,
30790        // against one that had to be read as a double first, where anything
30791        // under one is a conversion that failed rather than a range that did.
30792        for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
30793            assert_eq!(
30794                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30795                "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
30796                "{}",
30797                String::from_utf8_lossy(max)
30798            );
30799        }
30800        for max in [
30801            &b"abc"[..],
30802            b"0.0",
30803            b"00",
30804            b"-0",
30805            b"+0",
30806            b"0.5",
30807            b"-1.5",
30808            b"1e400",
30809        ] {
30810            assert_eq!(
30811                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30812                "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
30813                "{}",
30814                String::from_utf8_lossy(max)
30815            );
30816        }
30817        for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
30818            assert_eq!(
30819                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
30820                "*1\r\n$3\r\none\r\n",
30821                "{}",
30822                String::from_utf8_lossy(max)
30823            );
30824        }
30825        assert_eq!(
30826            f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
30827            "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
30828        );
30829        // A score too large for a double is refused where one spelled out is
30830        // taken, which is the module reading errno after the conversion.
30831        assert_eq!(
30832            f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
30833            "-ERR invalid score\r\n"
30834        );
30835        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
30836    }
30837
30838    /// An empty term is taken and not stored, so the reply is the length that
30839    /// was already there and nothing new comes back. The key is still made,
30840    /// and a delete that finds nothing is what clears it away again.
30841    #[test]
30842    fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
30843        let mut f = Fixture::new();
30844        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
30845        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
30846        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
30847        assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
30848        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
30849        assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
30850        assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
30851        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
30852    }
30853
30854    /// A key that will not read is counted against the index and against the
30855    /// field, and `FT.INFO` says so.
30856    #[test]
30857    fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
30858        let mut f = Fixture::new();
30859        f.run(&[
30860            b"FT.CREATE",
30861            b"ix",
30862            b"PREFIX",
30863            b"1",
30864            b"p:",
30865            b"SCHEMA",
30866            b"n",
30867            b"NUMERIC",
30868        ]);
30869        f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
30870        assert_eq!(held(&f, b"ix"), (0, 0));
30871
30872        let reply = f.run(&[b"FT.INFO", b"ix"]);
30873        assert!(
30874            reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
30875            "{reply}"
30876        );
30877        assert!(reply.contains("hash_indexing_failures"), "{reply}");
30878    }
30879
30880    /// An index can only be made on database zero, and the check comes after
30881    /// the `IFNX` shortcut and before everything else.
30882    #[test]
30883    fn an_index_can_only_be_made_on_database_zero() {
30884        let mut f = Fixture::new();
30885        f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
30886        f.run(&[b"SELECT", b"1"]);
30887        let refused = "-Cannot create index on db != 0\r\n";
30888        assert_eq!(
30889            f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
30890            refused
30891        );
30892        // The name is taken, and it still answers about the database.
30893        assert_eq!(
30894            f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
30895            refused
30896        );
30897        // And so does one whose arguments are nonsense.
30898        assert_eq!(
30899            f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
30900            refused
30901        );
30902        // `IFNX` over a name that is taken is the one that gets through.
30903        assert_eq!(
30904            f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
30905            "+OK\r\n"
30906        );
30907        assert_eq!(f.server.search.lock().len(), 1);
30908    }
30909
30910    /// The scan reads the database the create was run on, and after that the
30911    /// index follows its keys in every database.
30912    ///
30913    /// The asymmetry is a real server's, measured, and it is the sort of thing
30914    /// nobody would arrive at by choosing.
30915    #[test]
30916    fn the_scan_is_one_database_and_the_following_is_all_of_them() {
30917        let mut f = Fixture::new();
30918        f.run(&[b"SELECT", b"1"]);
30919        f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
30920        f.run(&[b"SELECT", b"0"]);
30921        f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
30922        f.run(&[
30923            b"FT.CREATE",
30924            b"ix",
30925            b"PREFIX",
30926            b"1",
30927            b"p:",
30928            b"SCHEMA",
30929            b"t",
30930            b"TEXT",
30931        ]);
30932        assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
30933
30934        f.run(&[b"SELECT", b"1"]);
30935        f.run(&[b"HSET", b"p:8", b"t", b"later"]);
30936        assert_eq!(
30937            held(&f, b"ix"),
30938            (2, 2),
30939            "and then it follows every database"
30940        );
30941    }
30942
30943    /// Four documents over the two kinds of field a query can ask about, which
30944    /// is the corpus the searches below read.
30945    fn corpus(f: &mut Fixture) {
30946        f.run(&[
30947            b"FT.CREATE",
30948            b"sx",
30949            b"PREFIX",
30950            b"1",
30951            b"d:",
30952            b"SCHEMA",
30953            b"t",
30954            b"TEXT",
30955            b"g",
30956            b"TAG",
30957            b"n",
30958            b"NUMERIC",
30959        ]);
30960        for (key, text, tag, number) in [
30961            (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
30962            (b"d:2", "alpha gamma", "bb", "2"),
30963            (b"d:3", "delta", "cc", "3"),
30964            (b"d:4", "alpha beta gamma", "aa,cc", "4"),
30965        ] {
30966            f.run(&[
30967                b"HSET",
30968                key,
30969                b"t",
30970                text.as_bytes(),
30971                b"g",
30972                tag.as_bytes(),
30973                b"n",
30974                number.as_bytes(),
30975            ]);
30976        }
30977    }
30978
30979    /// A corpus with something to sort by: a text field the index keeps a copy
30980    /// of, a number, the same text field under another name, and a text field
30981    /// the index keeps nothing of.
30982    fn sortable(f: &mut Fixture) {
30983        f.run(&[
30984            b"FT.CREATE",
30985            b"sy",
30986            b"PREFIX",
30987            b"1",
30988            b"s:",
30989            b"SCHEMA",
30990            b"t",
30991            b"TEXT",
30992            b"SORTABLE",
30993            b"n",
30994            b"NUMERIC",
30995            b"SORTABLE",
30996            b"body",
30997            b"AS",
30998            b"b",
30999            b"TEXT",
31000            b"SORTABLE",
31001            b"p",
31002            b"TEXT",
31003        ]);
31004        for (key, text, number) in [
31005            (b"s:1".as_slice(), "Banana Split", "2"),
31006            (b"s:2", "apple", "10"),
31007        ] {
31008            f.run(&[
31009                b"HSET",
31010                key,
31011                b"t",
31012                text.as_bytes(),
31013                b"n",
31014                number.as_bytes(),
31015                b"body",
31016                text.as_bytes(),
31017                b"p",
31018                b"alpha",
31019            ]);
31020        }
31021        // A key with nothing under either sortable field, which is what sorts
31022        // last whichever way round the sort runs.
31023        f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
31024    }
31025
31026    /// A sort runs off the copy of the value the index keeps, and a row with no
31027    /// value at all is last both ways round.
31028    #[test]
31029    fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
31030        let mut f = Fixture::new();
31031        sortable(&mut f);
31032        assert_eq!(
31033            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
31034            "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
31035        );
31036        assert_eq!(
31037            f.run(&[
31038                b"FT.SEARCH",
31039                b"sy",
31040                b"alpha",
31041                b"SORTBY",
31042                b"n",
31043                b"DESC",
31044                b"NOCONTENT"
31045            ]),
31046            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
31047        );
31048        // The copy of a text field is folded, so `apple` sorts before
31049        // `Banana Split` where a comparison of the bytes would not.
31050        assert_eq!(
31051            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
31052            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
31053        );
31054    }
31055
31056    /// A field the index keeps no copy of is sorted by the value read off the
31057    /// key, which happens after the walk rather than during it.
31058    #[test]
31059    fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
31060        let mut f = Fixture::new();
31061        sortable(&mut f);
31062        f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
31063        assert_eq!(
31064            f.run(&[
31065                b"FT.SEARCH",
31066                b"sy",
31067                b"alpha",
31068                b"SORTBY",
31069                b"p",
31070                b"NOCONTENT",
31071                b"LIMIT",
31072                b"0",
31073                b"2"
31074            ]),
31075            "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
31076        );
31077        // Nothing is folded on this side, because the schema never asked for a
31078        // copy to fold, so the value goes into the sort as it was written.
31079        assert_eq!(
31080            f.run(&[
31081                b"FT.SEARCH",
31082                b"sy",
31083                b"alpha",
31084                b"SORTBY",
31085                b"p",
31086                b"WITHSORTKEYS",
31087                b"NOCONTENT",
31088                b"LIMIT",
31089                b"2",
31090                b"1"
31091            ]),
31092            "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
31093        );
31094    }
31095
31096    /// The value the sort compared goes beside every row, as a number after a
31097    /// hash, as text after a dollar, and as a null on a row that had none.
31098    #[test]
31099    fn a_search_can_send_the_value_it_sorted_by_back() {
31100        let mut f = Fixture::new();
31101        sortable(&mut f);
31102        assert_eq!(
31103            f.run(&[
31104                b"FT.SEARCH",
31105                b"sy",
31106                b"alpha",
31107                b"SORTBY",
31108                b"n",
31109                b"WITHSORTKEYS",
31110                b"NOCONTENT"
31111            ]),
31112            concat!(
31113                "*7\r\n:3\r\n",
31114                "$3\r\ns:1\r\n$2\r\n#2\r\n",
31115                "$3\r\ns:2\r\n$3\r\n#10\r\n",
31116                "$3\r\ns:3\r\n$-1\r\n"
31117            )
31118        );
31119        assert_eq!(
31120            f.run(&[
31121                b"FT.SEARCH",
31122                b"sy",
31123                b"alpha",
31124                b"SORTBY",
31125                b"t",
31126                b"WITHSORTKEYS",
31127                b"NOCONTENT"
31128            ]),
31129            concat!(
31130                "*7\r\n:3\r\n",
31131                "$3\r\ns:2\r\n$6\r\n$apple\r\n",
31132                "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
31133                "$3\r\ns:3\r\n$-1\r\n"
31134            )
31135        );
31136        // Asking for a sort key without sorting is taken and answers a null on
31137        // every row, which is what a real server does.
31138        assert_eq!(
31139            f.run(&[
31140                b"FT.SEARCH",
31141                b"sy",
31142                b"banana",
31143                b"WITHSORTKEYS",
31144                b"NOCONTENT"
31145            ]),
31146            "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
31147        );
31148    }
31149
31150    /// The field a search sorted by is written in front of the fields of the
31151    /// key, and the key's own value for it wins when the two share a name.
31152    #[test]
31153    fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
31154        let mut f = Fixture::new();
31155        sortable(&mut f);
31156        // `b` is what the schema calls the field the key calls `body`, so the
31157        // folded copy comes back under one name and the value as it was written
31158        // comes back under the other.
31159        assert_eq!(
31160            f.run(&[
31161                b"FT.SEARCH",
31162                b"sy",
31163                b"alpha",
31164                b"SORTBY",
31165                b"b",
31166                b"LIMIT",
31167                b"0",
31168                b"1"
31169            ]),
31170            concat!(
31171                "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
31172                "$1\r\nb\r\n$5\r\napple\r\n",
31173                "$1\r\nt\r\n$5\r\napple\r\n",
31174                "$1\r\nn\r\n$2\r\n10\r\n",
31175                "$4\r\nbody\r\n$5\r\napple\r\n",
31176                "$1\r\np\r\n$5\r\nalpha\r\n"
31177            )
31178        );
31179        // With a `RETURN` list there is nothing to put in, so the field is moved
31180        // to the front of the names that were asked for instead.
31181        assert_eq!(
31182            f.run(&[
31183                b"FT.SEARCH",
31184                b"sy",
31185                b"alpha",
31186                b"SORTBY",
31187                b"b",
31188                b"RETURN",
31189                b"2",
31190                b"p",
31191                b"b",
31192                b"LIMIT",
31193                b"0",
31194                b"1"
31195            ]),
31196            concat!(
31197                "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
31198                "$1\r\nb\r\n$5\r\napple\r\n",
31199                "$1\r\np\r\n$5\r\nalpha\r\n"
31200            )
31201        );
31202    }
31203
31204    /// The four ways a `SORTBY` on a search is refused.
31205    #[test]
31206    fn a_search_refuses_the_sorts_it_cannot_run() {
31207        let mut f = Fixture::new();
31208        sortable(&mut f);
31209        assert_eq!(
31210            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
31211            "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
31212        );
31213        assert_eq!(
31214            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
31215            "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
31216        );
31217        assert_eq!(
31218            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
31219            "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
31220        );
31221        assert_eq!(
31222            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
31223            "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
31224        );
31225        // The property is looked up once the whole list has read cleanly, so a
31226        // word after it that nobody knows is the error that comes back.
31227        assert_eq!(
31228            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
31229            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
31230        );
31231    }
31232
31233    /// An index over two text fields, a number and a tag, holding one key whose
31234    /// `a` runs long enough to be worth cutting down and whose `b` and `g` hold
31235    /// nothing the query matches.
31236    fn marking(f: &mut Fixture) {
31237        f.run(&[
31238            b"FT.CREATE",
31239            b"mk",
31240            b"ON",
31241            b"HASH",
31242            b"PREFIX",
31243            b"1",
31244            b"m:",
31245            b"SCHEMA",
31246            b"a",
31247            b"TEXT",
31248            b"b",
31249            b"TEXT",
31250            b"n",
31251            b"NUMERIC",
31252            b"g",
31253            b"TAG",
31254        ]);
31255        f.run(&[
31256            b"HSET",
31257            b"m:1",
31258            b"a",
31259            b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
31260            b"b",
31261            b"t1 t2 t3 t4 t5 t6 t7 t8",
31262            b"n",
31263            b"1",
31264            b"g",
31265            b"red",
31266        ]);
31267    }
31268
31269    /// A field the query matched comes back as fragments and a field it did not
31270    /// comes back as its own front.
31271    #[test]
31272    fn a_summarize_cuts_a_field_down_to_what_matched() {
31273        let mut f = Fixture::new();
31274        marking(&mut f);
31275        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
31276        assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
31277        // `b` holds no match, so it keeps its front and loses its last word.
31278        assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
31279        // And so does the tag, which is a value like any other to this clause.
31280        assert!(got.contains("$1\r\nr\r\n"), "{got}");
31281    }
31282
31283    /// `FRAGS` is applied before the context either side of a fragment is worked
31284    /// out, so the fragment that is left runs over the match of the one that was
31285    /// dropped rather than stopping on it.
31286    #[test]
31287    fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
31288        let mut f = Fixture::new();
31289        marking(&mut f);
31290        let got = f.run(&[
31291            b"FT.SEARCH",
31292            b"mk",
31293            b"fox",
31294            b"SUMMARIZE",
31295            b"FRAGS",
31296            b"1",
31297            b"LEN",
31298            b"20",
31299        ]);
31300        assert!(
31301            got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
31302            "{got}"
31303        );
31304        // Keep both and the first stops on the second rather than running over
31305        // it, on the same query and the same budget.
31306        let two = f.run(&[
31307            b"FT.SEARCH",
31308            b"mk",
31309            b"fox",
31310            b"SUMMARIZE",
31311            b"FRAGS",
31312            b"2",
31313            b"LEN",
31314            b"20",
31315        ]);
31316        assert!(
31317            two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
31318            "{two}"
31319        );
31320    }
31321
31322    /// A `HIGHLIGHT` wraps every match, and on a field with no match in it the
31323    /// clause also calls off the cutting down a `SUMMARIZE` would have done.
31324    #[test]
31325    fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
31326        let mut f = Fixture::new();
31327        marking(&mut f);
31328        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
31329        assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
31330        let both = f.run(&[
31331            b"FT.SEARCH",
31332            b"mk",
31333            b"fox",
31334            b"SUMMARIZE",
31335            b"LEN",
31336            b"2",
31337            b"HIGHLIGHT",
31338        ]);
31339        assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
31340        // `b` still holds no match, and this time it comes back whole.
31341        assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
31342        assert!(both.contains("$3\r\nred\r\n"), "{both}");
31343        // Naming a field one clause does not cover leaves it cut down again.
31344        let split = f.run(&[
31345            b"FT.SEARCH",
31346            b"mk",
31347            b"fox",
31348            b"SUMMARIZE",
31349            b"FIELDS",
31350            b"1",
31351            b"b",
31352            b"LEN",
31353            b"2",
31354            b"HIGHLIGHT",
31355            b"FIELDS",
31356            b"1",
31357            b"a",
31358        ]);
31359        assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
31360    }
31361
31362    /// A tag is never marked, in its own field or in a text field beside it.
31363    #[test]
31364    fn a_highlight_does_not_mark_a_tag() {
31365        let mut f = Fixture::new();
31366        marking(&mut f);
31367        f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
31368        let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
31369        assert!(!got.contains("<b>"), "{got}");
31370        assert!(got.contains("red and blue"), "{got}");
31371    }
31372
31373    /// A search answers a total and then a row for every key in the window,
31374    /// with the fields of that key after it.
31375    #[test]
31376    fn a_search_answers_a_total_and_then_the_rows() {
31377        let mut f = Fixture::new();
31378        corpus(&mut f);
31379        assert_eq!(
31380            f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
31381            "*3\r\n:1\r\n$3\r\nd:3\r\n*6\r\n$1\r\nt\r\n$5\r\ndelta\r\n$1\r\ng\r\n$2\r\ncc\r\n$1\r\nn\r\n$1\r\n3\r\n"
31382        );
31383        // The fields are what the key holds and not what the schema names, so
31384        // a field nobody indexed comes back too.
31385        f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
31386        assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
31387        // `NOCONTENT` leaves the keys on their own, and `LIMIT 0 0` leaves
31388        // the total on its own.
31389        assert_eq!(
31390            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
31391            "*2\r\n:1\r\n$3\r\nd:3\r\n"
31392        );
31393        assert_eq!(
31394            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
31395            "*1\r\n:3\r\n"
31396        );
31397    }
31398
31399    /// The window is ten rows when nobody said, and the cap is on how wide it
31400    /// is rather than on where it starts.
31401    #[test]
31402    fn the_window_is_ten_rows_and_a_million_wide_at_most() {
31403        let mut f = Fixture::new();
31404        corpus(&mut f);
31405        assert_eq!(
31406            f.run(&[
31407                b"FT.SEARCH",
31408                b"sx",
31409                b"alpha",
31410                b"NOCONTENT",
31411                b"LIMIT",
31412                b"1",
31413                b"1"
31414            ]),
31415            "*2\r\n:3\r\n$3\r\nd:2\r\n"
31416        );
31417        assert_eq!(
31418            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
31419            "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
31420        );
31421        assert_eq!(
31422            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
31423            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
31424        );
31425        assert_eq!(
31426            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
31427            "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
31428        );
31429        assert_eq!(
31430            f.run(&[
31431                b"FT.SEARCH",
31432                b"sx",
31433                b"alpha",
31434                b"NOCONTENT",
31435                b"LIMIT",
31436                b"999999",
31437                b"1000000"
31438            ]),
31439            "*1\r\n:3\r\n"
31440        );
31441    }
31442
31443    /// `RETURN 0` reads on the wire like `NOCONTENT` and is not the same
31444    /// thing, because a later `RETURN` puts the fields back and a later
31445    /// `RETURN` after a `NOCONTENT` does not.
31446    #[test]
31447    fn a_return_of_nothing_is_not_the_same_as_nocontent() {
31448        let mut f = Fixture::new();
31449        corpus(&mut f);
31450        let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
31451        assert_eq!(
31452            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
31453            bare
31454        );
31455        assert_eq!(
31456            f.run(&[
31457                b"FT.SEARCH",
31458                b"sx",
31459                b"delta",
31460                b"NOCONTENT",
31461                b"RETURN",
31462                b"1",
31463                b"t"
31464            ]),
31465            bare
31466        );
31467        assert_eq!(
31468            f.run(&[
31469                b"FT.SEARCH",
31470                b"sx",
31471                b"delta",
31472                b"RETURN",
31473                b"0",
31474                b"RETURN",
31475                b"1",
31476                b"t"
31477            ]),
31478            "*3\r\n:1\r\n$3\r\nd:3\r\n*2\r\n$1\r\nt\r\n$5\r\ndelta\r\n"
31479        );
31480    }
31481
31482    /// The count after `RETURN` counts words and not fields, so the `AS` and
31483    /// the name after it are two of them.
31484    #[test]
31485    fn the_count_after_return_counts_words() {
31486        let mut f = Fixture::new();
31487        corpus(&mut f);
31488        // Two words is one renamed field, and the name is the one it comes
31489        // back under.
31490        assert_eq!(
31491            f.run(&[
31492                b"FT.SEARCH",
31493                b"sx",
31494                b"delta",
31495                b"RETURN",
31496                b"3",
31497                b"t",
31498                b"AS",
31499                b"x"
31500            ]),
31501            "*3\r\n:1\r\n$3\r\nd:3\r\n*2\r\n$1\r\nx\r\n$5\r\ndelta\r\n"
31502        );
31503        // A count that stops on the `AS` has nothing to rename to, and one
31504        // that reaches past the last word is short an argument.
31505        assert_eq!(
31506            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
31507            "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
31508        );
31509        assert_eq!(
31510            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
31511            "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
31512        );
31513        // A count that stops before the `AS` asks for a field called `AS`,
31514        // which no key holds, and a field the key does not hold is left out
31515        // rather than sent empty.
31516        assert_eq!(
31517            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
31518            "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
31519        );
31520    }
31521
31522    /// A `FILTER` is a numeric range written outside the query, and it is only
31523    /// the wrong way round on a field the schema holds as a number.
31524    #[test]
31525    fn a_filter_is_a_range_written_outside_the_query() {
31526        let mut f = Fixture::new();
31527        corpus(&mut f);
31528        assert_eq!(
31529            f.run(&[
31530                b"FT.SEARCH",
31531                b"sx",
31532                b"alpha",
31533                b"NOCONTENT",
31534                b"FILTER",
31535                b"n",
31536                b"2",
31537                b"4"
31538            ]),
31539            "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
31540        );
31541        assert_eq!(
31542            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
31543            "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
31544        );
31545        assert_eq!(
31546            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
31547            "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
31548        );
31549        assert_eq!(
31550            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
31551            "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
31552        );
31553        // The same range on a field that is not a number at all, and on a
31554        // field that is not there, answers nothing rather than refusing.
31555        for field in [b"g".as_slice(), b"nope"] {
31556            assert_eq!(
31557                f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
31558                "*1\r\n:0\r\n"
31559            );
31560        }
31561    }
31562
31563    /// The index is resolved before the arguments after it are read, so a name
31564    /// that is not there answers about the name whatever else is wrong.
31565    #[test]
31566    fn the_index_is_found_before_the_arguments_are_read() {
31567        let mut f = Fixture::new();
31568        corpus(&mut f);
31569        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
31570        assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
31571        assert_eq!(
31572            f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
31573            missing
31574        );
31575        // And the arguments are read before the query is, so a query that
31576        // will not parse still answers about the argument.
31577        assert_eq!(
31578            f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
31579            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
31580        );
31581    }
31582
31583    /// `INKEYS` filters the answer before the total is taken, which is not
31584    /// where a client would guess it happens.
31585    #[test]
31586    fn inkeys_comes_off_the_total() {
31587        let mut f = Fixture::new();
31588        corpus(&mut f);
31589        assert_eq!(
31590            f.run(&[
31591                b"FT.SEARCH",
31592                b"sx",
31593                b"alpha",
31594                b"NOCONTENT",
31595                b"INKEYS",
31596                b"1",
31597                b"d:1"
31598            ]),
31599            "*2\r\n:1\r\n$3\r\nd:1\r\n"
31600        );
31601        assert_eq!(
31602            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
31603            "*1\r\n:0\r\n"
31604        );
31605    }
31606
31607    /// The fields come from the database the session is on, and a row whose
31608    /// key will not load there is dropped from the reply and taken off the
31609    /// total.
31610    ///
31611    /// Measured against a real server, which follows a key on every database
31612    /// and then loads it from one.
31613    #[test]
31614    fn the_fields_are_read_from_the_session_database() {
31615        let mut f = Fixture::new();
31616        corpus(&mut f);
31617        f.run(&[b"SELECT", b"1"]);
31618        f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
31619        // Both documents are in the index, and only one of them is in this
31620        // database.
31621        assert_eq!(
31622            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
31623            "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
31624        );
31625        assert_eq!(
31626            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
31627            "*3\r\n:1\r\n$3\r\nd:9\r\n*2\r\n$1\r\nn\r\n$1\r\n9\r\n"
31628        );
31629    }
31630
31631    /// The deeper protocol answers a map of five rather than an array, with
31632    /// every row a map of its own.
31633    #[test]
31634    fn the_third_protocol_answers_a_map_of_five() {
31635        let mut f = Fixture::new();
31636        corpus(&mut f);
31637        f.out = Out::new(Proto::Resp3);
31638        assert_eq!(
31639            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
31640            concat!(
31641                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
31642                "%3\r\n+id\r\n$3\r\nd:3\r\n+extra_attributes\r\n%1\r\n$1\r\nn\r\n$1\r\n3\r\n",
31643                "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
31644            )
31645        );
31646    }
31647
31648    /// A window of nothing is a client asking for the count on its own, and a
31649    /// window of nothing that starts somewhere else is a contradiction all
31650    /// three commands refuse in the same words.
31651    #[test]
31652    fn a_window_of_nothing_has_to_start_at_the_top() {
31653        let mut f = Fixture::new();
31654        corpus(&mut f);
31655        let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
31656        assert_eq!(
31657            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31658            refused
31659        );
31660        assert_eq!(
31661            f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31662            refused
31663        );
31664        assert_eq!(
31665            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
31666            refused
31667        );
31668        assert_eq!(
31669            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
31670            "*1\r\n:3\r\n"
31671        );
31672    }
31673
31674    /// An aggregation answers a count and then a list of properties for every
31675    /// row, which is empty until something asks for a field.
31676    #[test]
31677    fn an_aggregation_answers_a_count_and_then_the_properties() {
31678        let mut f = Fixture::new();
31679        corpus(&mut f);
31680        assert_eq!(
31681            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
31682            "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
31683        );
31684        // Every row, and not the ten a search would have cut it down to. The
31685        // count in front of them is one because that is how far the reply had
31686        // got when it was written, which is measured against a real server.
31687        assert_eq!(
31688            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
31689            concat!(
31690                "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
31691                "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
31692                "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
31693            )
31694        );
31695        // Ascending document number, because nothing sorts the answer. The
31696        // second and fourth documents are the ones the window lands on and the
31697        // best scoring one is not among them.
31698        assert_eq!(
31699            f.run(&[
31700                b"FT.AGGREGATE",
31701                b"sx",
31702                b"alpha",
31703                b"LOAD",
31704                b"1",
31705                b"@n",
31706                b"LIMIT",
31707                b"1",
31708                b"2"
31709            ]),
31710            "*3\r\n:2\r\n*2\r\n$1\r\nn\r\n$1\r\n2\r\n*2\r\n$1\r\nn\r\n$1\r\n4\r\n"
31711        );
31712        // A query nothing answers is a count of nothing and no rows at all.
31713        assert_eq!(
31714            f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
31715            "*1\r\n:0\r\n"
31716        );
31717    }
31718
31719    /// `LOAD` counts words rather than fields, names the property after the
31720    /// path unless an `AS` renames it, and reads everything the key holds when
31721    /// it is given a star.
31722    #[test]
31723    fn a_load_counts_words_and_can_rename_what_it_reads() {
31724        let mut f = Fixture::new();
31725        corpus(&mut f);
31726        // Three words, which are the path, the `AS` and the name.
31727        assert_eq!(
31728            f.run(&[
31729                b"FT.AGGREGATE",
31730                b"sx",
31731                b"alpha",
31732                b"LOAD",
31733                b"3",
31734                b"@t",
31735                b"AS",
31736                b"text"
31737            ]),
31738            concat!(
31739                "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
31740                "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
31741                "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
31742            )
31743        );
31744        assert_eq!(
31745            f.run(&[
31746                b"FT.AGGREGATE",
31747                b"sx",
31748                b"alpha",
31749                b"LOAD",
31750                b"*",
31751                b"LIMIT",
31752                b"0",
31753                b"1"
31754            ]),
31755            concat!(
31756                "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
31757                "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
31758            )
31759        );
31760        // A field the key does not hold is left out rather than sent empty.
31761        assert_eq!(
31762            f.run(&[
31763                b"FT.AGGREGATE",
31764                b"sx",
31765                b"alpha",
31766                b"LOAD",
31767                b"2",
31768                b"@n",
31769                b"@nope",
31770                b"LIMIT",
31771                b"0",
31772                b"2"
31773            ]),
31774            "*3\r\n:1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n*2\r\n$1\r\nn\r\n$1\r\n2\r\n"
31775        );
31776    }
31777
31778    /// The `LOAD` grammar, which has four ways to go wrong and one of them is
31779    /// only reported once the rest of the argument list has read cleanly.
31780    #[test]
31781    fn a_load_refuses_a_count_it_cannot_use() {
31782        let mut f = Fixture::new();
31783        corpus(&mut f);
31784        let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
31785        assert_eq!(
31786            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
31787            format!("{head}Expected number of fields or `*`\r\n")
31788        );
31789        assert_eq!(
31790            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
31791            format!("{head}Value is outside acceptable bounds\r\n")
31792        );
31793        assert_eq!(
31794            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
31795            format!("{head}Expected an argument, but none provided\r\n")
31796        );
31797        assert_eq!(
31798            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
31799            format!("{head}Expected an argument, but none provided\r\n")
31800        );
31801        // A count that runs out on the `AS` is held back, because the word
31802        // after it is read as an argument of its own and may be worth an error
31803        // of its own. Nothing follows here, so the held back line is the one.
31804        assert_eq!(
31805            f.run(&[
31806                b"FT.AGGREGATE",
31807                b"sx",
31808                b"alpha",
31809                b"LOAD",
31810                b"2",
31811                b"@t",
31812                b"AS"
31813            ]),
31814            "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
31815        );
31816        // And here the word after it is one an aggregation stops taking once a
31817        // step has been read, so that is what the client hears about.
31818        assert_eq!(
31819            f.run(&[
31820                b"FT.AGGREGATE",
31821                b"sx",
31822                b"alpha",
31823                b"LOAD",
31824                b"2",
31825                b"@t",
31826                b"AS",
31827                b"VERBATIM"
31828            ]),
31829            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
31830        );
31831        // A `LOAD 0` is a step that names nothing. It shuts the same door
31832        // without becoming a loader, so the count stays the one a query with no
31833        // `LOAD` gets.
31834        assert_eq!(
31835            f.run(&[
31836                b"FT.AGGREGATE",
31837                b"sx",
31838                b"alpha",
31839                b"LOAD",
31840                b"0",
31841                b"LIMIT",
31842                b"0",
31843                b"1"
31844            ]),
31845            "*2\r\n:1\r\n*0\r\n"
31846        );
31847    }
31848
31849    /// Reading a step of the pipeline stops the words about the search itself
31850    /// being taken, and `LIMIT` and `TIMEOUT` are not steps.
31851    #[test]
31852    fn a_pipeline_step_closes_the_door_on_the_search_words() {
31853        let mut f = Fixture::new();
31854        corpus(&mut f);
31855        assert_eq!(
31856            f.run(&[
31857                b"FT.AGGREGATE",
31858                b"sx",
31859                b"alpha",
31860                b"LOAD",
31861                b"1",
31862                b"@t",
31863                b"VERBATIM"
31864            ]),
31865            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
31866        );
31867        assert_eq!(
31868            f.run(&[
31869                b"FT.AGGREGATE",
31870                b"sx",
31871                b"alpha",
31872                b"LIMIT",
31873                b"0",
31874                b"1",
31875                b"VERBATIM"
31876            ]),
31877            "*2\r\n:1\r\n*0\r\n"
31878        );
31879        // Three words a search takes that this command names in its refusal
31880        // rather than calling them unknown.
31881        for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
31882            let name = core::str::from_utf8(word).expect("the three words are text");
31883            assert_eq!(
31884                f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
31885                format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
31886            );
31887        }
31888    }
31889
31890    /// `ADDSCORES` writes the score as a property to twelve significant digits
31891    /// where `WITHSCORES` writes it beside the row in full.
31892    #[test]
31893    fn addscores_writes_a_shorter_score_than_withscores() {
31894        let mut f = Fixture::new();
31895        corpus(&mut f);
31896        assert_eq!(
31897            f.run(&[
31898                b"FT.AGGREGATE",
31899                b"sx",
31900                b"alpha",
31901                b"ADDSCORES",
31902                b"LOAD",
31903                b"1",
31904                b"@n",
31905                b"LIMIT",
31906                b"0",
31907                b"2"
31908            ]),
31909            concat!(
31910                "*3\r\n:1\r\n",
31911                "*4\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n$1\r\nn\r\n$1\r\n1\r\n",
31912                "*4\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n$1\r\nn\r\n$1\r\n2\r\n"
31913            )
31914        );
31915        // `NOCONTENT` takes the properties away and leaves whatever was asked
31916        // for beside them, and a sort key is always null because nothing sorts
31917        // by one yet.
31918        assert_eq!(
31919            f.run(&[
31920                b"FT.AGGREGATE",
31921                b"sx",
31922                b"alpha",
31923                b"NOCONTENT",
31924                b"WITHSCORES",
31925                b"LIMIT",
31926                b"0",
31927                b"2"
31928            ]),
31929            "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
31930        );
31931        assert_eq!(
31932            f.run(&[
31933                b"FT.AGGREGATE",
31934                b"sx",
31935                b"alpha",
31936                b"WITHSORTKEYS",
31937                b"LOAD",
31938                b"1",
31939                b"@n",
31940                b"LIMIT",
31941                b"0",
31942                b"1"
31943            ]),
31944            "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
31945        );
31946    }
31947
31948    /// The one scorer that has to see the whole answer first turns the count
31949    /// into the real total and hands the rows back backwards.
31950    #[test]
31951    fn a_normalising_scorer_answers_the_rows_backwards() {
31952        let mut f = Fixture::new();
31953        corpus(&mut f);
31954        assert_eq!(
31955            f.run(&[
31956                b"FT.AGGREGATE",
31957                b"sx",
31958                b"alpha",
31959                b"SCORER",
31960                b"BM25STD.NORM",
31961                b"ADDSCORES",
31962                b"LOAD",
31963                b"1",
31964                b"@n",
31965                b"LIMIT",
31966                b"1",
31967                b"2"
31968            ]),
31969            concat!(
31970                "*3\r\n:3\r\n",
31971                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
31972                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
31973            )
31974        );
31975        // Without `ADDSCORES` nothing on the row needs the score, so the rows
31976        // come back the way every other query answers them.
31977        assert_eq!(
31978            f.run(&[
31979                b"FT.AGGREGATE",
31980                b"sx",
31981                b"alpha",
31982                b"SCORER",
31983                b"BM25STD.NORM",
31984                b"LOAD",
31985                b"1",
31986                b"@n",
31987                b"LIMIT",
31988                b"1",
31989                b"2"
31990            ]),
31991            "*3\r\n:2\r\n*2\r\n$1\r\nn\r\n$1\r\n2\r\n*2\r\n$1\r\nn\r\n$1\r\n4\r\n"
31992        );
31993    }
31994
31995    /// The deeper protocol answers the same map of five a search answers, with
31996    /// the `id` gone because an aggregation is about the properties.
31997    #[test]
31998    fn an_aggregation_answers_a_map_of_five_as_well() {
31999        let mut f = Fixture::new();
32000        corpus(&mut f);
32001        f.out = Out::new(Proto::Resp3);
32002        assert_eq!(
32003            f.run(&[
32004                b"FT.AGGREGATE",
32005                b"sx",
32006                b"alpha",
32007                b"ADDSCORES",
32008                b"WITHSCORES",
32009                b"WITHSORTKEYS",
32010                b"LOAD",
32011                b"1",
32012                b"@n",
32013                b"LIMIT",
32014                b"0",
32015                b"1"
32016            ]),
32017            concat!(
32018                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
32019                "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
32020                "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
32021                "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
32022                "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
32023            )
32024        );
32025        // The count is worked out from the rows the reply reached under this
32026        // protocol, where under RESP2 it is worked out from the first of them.
32027        assert_eq!(
32028            f.run(&[
32029                b"FT.AGGREGATE",
32030                b"sx",
32031                b"alpha",
32032                b"NOCONTENT",
32033                b"LIMIT",
32034                b"0",
32035                b"1"
32036            ]),
32037            concat!(
32038                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
32039                "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
32040            )
32041        );
32042    }
32043    // ------------------------------------------------------------- CLIENT
32044
32045    /// The field names `CLIENT INFO` reports, in the order 8.10.1 reports them.
32046    ///
32047    /// Written out rather than derived, because the whole point of the command
32048    /// is that a parser somewhere else knows this list, so a change to it is a
32049    /// change a test should have to be edited for.
32050    const INFO_FIELDS: &[&str] = &[
32051        "id",
32052        "addr",
32053        "laddr",
32054        "fd",
32055        "name",
32056        "age",
32057        "idle",
32058        "flags",
32059        "db",
32060        "sub",
32061        "psub",
32062        "ssub",
32063        "multi",
32064        "watch",
32065        "qbuf",
32066        "qbuf-free",
32067        "argv-mem",
32068        "multi-mem",
32069        "rbs",
32070        "rbp",
32071        "obl",
32072        "oll",
32073        "omem",
32074        "omem-shared",
32075        "omem-unshared",
32076        "tot-mem",
32077        "events",
32078        "cmd",
32079        "user",
32080        "redir",
32081        "resp",
32082        "lib-name",
32083        "lib-ver",
32084        "io-thread",
32085        "tot-net-in",
32086        "tot-net-out",
32087        "tot-cmds",
32088        "read-events",
32089        "avg-pipeline-len-sum",
32090        "avg-pipeline-len-cnt",
32091    ];
32092
32093    /// The report as a list of name and value pairs, taken out of the bulk
32094    /// string the reply is on RESP2.
32095    fn client_info(f: &mut Fixture) -> Vec<(String, String)> {
32096        let reply = f.run(&[b"CLIENT", b"INFO"]);
32097        let body = reply.split_once("\r\n").expect("a bulk header").1;
32098        // A verbatim string on RESP3 carries its format in front of the text,
32099        // and the same reply is a plain bulk string on RESP2.
32100        let line = body.trim_end_matches("\r\n").trim_start_matches("txt:");
32101        assert!(
32102            line.ends_with('\n'),
32103            "the report ends in a newline: {line:?}"
32104        );
32105        line.trim_end()
32106            .split(' ')
32107            .map(|pair| {
32108                let (name, value) = pair.split_once('=').expect("name=value");
32109                (name.to_string(), value.to_string())
32110            })
32111            .collect()
32112    }
32113
32114    /// One field of the report.
32115    fn client_field(f: &mut Fixture, name: &str) -> String {
32116        client_info(f)
32117            .into_iter()
32118            .find(|(n, _)| n == name)
32119            .map(|(_, v)| v)
32120            .unwrap_or_else(|| panic!("no {name} field"))
32121    }
32122
32123    #[test]
32124    fn client_info_names_every_field_a_real_server_names() {
32125        let mut f = Fixture::new();
32126        let got: Vec<String> = client_info(&mut f).into_iter().map(|(n, _)| n).collect();
32127        assert_eq!(got, INFO_FIELDS);
32128    }
32129
32130    /// A session nobody told about a socket is what an embedded caller gets, and
32131    /// it has to answer rather than pretend to have an address.
32132    #[test]
32133    fn a_connection_with_no_socket_reports_no_address_and_no_descriptor() {
32134        let mut f = Fixture::new();
32135        assert_eq!(client_field(&mut f, "addr"), "");
32136        assert_eq!(client_field(&mut f, "laddr"), "");
32137        assert_eq!(client_field(&mut f, "fd"), "-1");
32138        assert_eq!(client_field(&mut f, "id"), "7");
32139    }
32140
32141    #[test]
32142    fn client_setname_takes_a_name_back_and_refuses_one_with_a_space_in_it() {
32143        let mut f = Fixture::new();
32144        assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$-1\r\n");
32145        assert_eq!(f.run(&[b"CLIENT", b"SETNAME", b"worker"]), "+OK\r\n");
32146        assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
32147        assert_eq!(client_field(&mut f, "name"), "worker");
32148        assert_eq!(
32149            f.run(&[b"CLIENT", b"SETNAME", b"two words"]),
32150            "-ERR Client names cannot contain spaces, newlines or special characters.\r\n"
32151        );
32152        // And the name it had is still the name it has.
32153        assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
32154    }
32155
32156    /// `RESET` is `clearClientConnectionState`, and the surprising half of it is
32157    /// what it keeps: the library behind the socket is the same library it was.
32158    #[test]
32159    fn reset_clears_the_name_and_the_switches_and_keeps_the_library() {
32160        let mut f = Fixture::new();
32161        f.run(&[b"CLIENT", b"SETNAME", b"worker"]);
32162        f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME", b"yo-py"]);
32163        f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"1.2.3"]);
32164        f.run(&[b"CLIENT", b"NO-EVICT", b"on"]);
32165        f.run(&[b"CLIENT", b"NO-TOUCH", b"on"]);
32166        assert_eq!(client_field(&mut f, "flags"), "eT");
32167
32168        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
32169        assert_eq!(client_field(&mut f, "name"), "");
32170        assert_eq!(client_field(&mut f, "flags"), "N");
32171        assert_eq!(client_field(&mut f, "lib-name"), "yo-py");
32172        assert_eq!(client_field(&mut f, "lib-ver"), "1.2.3");
32173    }
32174
32175    #[test]
32176    fn client_setinfo_complains_the_way_a_real_server_does() {
32177        let mut f = Fixture::new();
32178        assert_eq!(
32179            f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME"]),
32180            "-ERR wrong number of arguments for 'client|setinfo' command\r\n"
32181        );
32182        assert_eq!(
32183            f.run(&[b"CLIENT", b"SETINFO", b"NOPE", b"x"]),
32184            "-ERR Unrecognized option 'NOPE'\r\n"
32185        );
32186        assert_eq!(
32187            f.run(&[b"CLIENT", b"SETINFO", b"lib-name", b"ok x"]),
32188            "-ERR lib-name cannot contain spaces, newlines or special characters.\r\n"
32189        );
32190        assert_eq!(
32191            f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"has space"]),
32192            "-ERR lib-ver cannot contain spaces, newlines or special characters.\r\n"
32193        );
32194    }
32195
32196    #[test]
32197    fn client_refuses_a_subcommand_it_does_not_have_and_arguments_it_did_not_ask_for() {
32198        let mut f = Fixture::new();
32199        assert_eq!(
32200            f.run(&[b"CLIENT", b"NOPE"]),
32201            "-ERR unknown subcommand 'NOPE'. Try CLIENT HELP.\r\n"
32202        );
32203        assert_eq!(
32204            f.run(&[b"CLIENT", b"GETNAME", b"extra"]),
32205            "-ERR wrong number of arguments for 'client|getname' command\r\n"
32206        );
32207        assert_eq!(
32208            f.run(&[b"CLIENT", b"NO-EVICT", b"maybe"]),
32209            "-ERR syntax error\r\n"
32210        );
32211        assert_eq!(
32212            f.run(&[b"CLIENT", b"REPLY", b"BAD"]),
32213            "-ERR syntax error\r\n"
32214        );
32215    }
32216
32217    /// The three subscribe namespaces are counted apart, which is not the same
32218    /// count a subscribe reply carries: that one puts channels and patterns
32219    /// together.
32220    #[test]
32221    fn client_info_counts_the_three_subscribe_namespaces_apart() {
32222        let mut f = Fixture::new();
32223        // On RESP3, because a subscribed RESP2 connection may only send nine
32224        // commands and `CLIENT` is not one of them.
32225        f.run(&[b"HELLO", b"3"]);
32226        f.run(&[b"SUBSCRIBE", b"a", b"b"]);
32227        f.run(&[b"PSUBSCRIBE", b"p*"]);
32228        f.run(&[b"SSUBSCRIBE", b"s"]);
32229        let info = client_info(&mut f);
32230        let at = |name: &str| {
32231            info.iter()
32232                .find(|(n, _)| n == name)
32233                .map(|(_, v)| v.clone())
32234                .unwrap()
32235        };
32236        assert_eq!(at("sub"), "2");
32237        assert_eq!(at("psub"), "1");
32238        assert_eq!(at("ssub"), "1");
32239        assert_eq!(at("flags"), "P");
32240        forget_session(&f.server, &mut f.session);
32241    }
32242
32243    /// The `cmd` field names the subcommand, which for this command is always
32244    /// `client|info` and is the one field that reports the command asking.
32245    #[test]
32246    fn client_info_reports_itself_as_the_command_running() {
32247        let mut f = Fixture::new();
32248        assert_eq!(client_field(&mut f, "cmd"), "client|info");
32249        f.run(&[b"GET", b"nothing"]);
32250        // Still `client|info`, because the field is about the command asking
32251        // and the command asking is this one.
32252        assert_eq!(client_field(&mut f, "cmd"), "client|info");
32253    }
32254
32255    /// A container called in mixed case is still the same command underneath.
32256    #[test]
32257    fn the_command_field_is_lower_case_however_the_client_spelled_it() {
32258        let mut f = Fixture::new();
32259        let reply = f.run(&[b"CLIENT", b"Info"]);
32260        assert!(reply.contains("cmd=client|info"), "{reply}");
32261    }
32262
32263    #[test]
32264    fn client_help_lists_the_subcommands_that_are_here() {
32265        let mut f = Fixture::new();
32266        let reply = f.run(&[b"CLIENT", b"HELP"]);
32267        for sub in ["ID", "GETNAME", "SETNAME", "SETINFO", "INFO", "REPLY"] {
32268            assert!(reply.contains(sub), "no {sub} in {reply}");
32269        }
32270        // And not the ones that are not, since a client reads this to find out
32271        // what it can send.
32272        assert!(!reply.contains("TRACKING"), "{reply}");
32273    }
32274
32275    // ------------------------------------------------- what crosses to a replica
32276
32277    /// The stream, split back into the commands it is made of.
32278    ///
32279    /// A replica reads this with the same parser it reads a client with, so a
32280    /// test can read it the same way, and a list of words is what the rewrite
32281    /// table in the spec is written in.
32282    fn commands(stream: &str) -> Vec<Vec<String>> {
32283        let mut out = Vec::new();
32284        let mut rest = stream;
32285        while let Some(tail) = rest.strip_prefix('*') {
32286            let (n, tail) = tail.split_once("\r\n").expect("a header ends");
32287            let mut one = Vec::new();
32288            let mut tail = tail;
32289            for _ in 0..n.parse::<usize>().expect("a count") {
32290                let body = tail.strip_prefix('$').expect("a bulk string");
32291                let (len, body) = body.split_once("\r\n").expect("a length ends");
32292                let len: usize = len.parse().expect("a length");
32293                one.push(body[..len].to_string());
32294                tail = &body[len + 2..];
32295            }
32296            out.push(one);
32297            rest = tail;
32298        }
32299        assert!(rest.is_empty(), "left over: {rest:?}");
32300        out
32301    }
32302
32303    /// The words of the one command a test expects to have crossed.
32304    fn only(stream: &str) -> Vec<String> {
32305        let mut each = commands(stream);
32306        assert_eq!(each.len(), 1, "expected one command: {stream:?}");
32307        each.pop().expect("one command")
32308    }
32309
32310    #[test]
32311    fn the_stream_opens_with_a_select_and_says_it_once() {
32312        let mut f = Fixture::replicated();
32313        assert_eq!(
32314            commands(&f.crossed(&[b"SET", b"k", b"v"])),
32315            vec![
32316                vec!["SELECT".to_string(), "0".to_string()],
32317                vec!["SET".to_string(), "k".to_string(), "v".to_string()],
32318            ]
32319        );
32320        // The second write is on the same database, so it goes on its own.
32321        assert_eq!(only(&f.crossed(&[b"SET", b"k2", b"v"])), ["SET", "k2", "v"]);
32322        // A different one says so first, and the SELECT is not the client's,
32323        // which crossed nothing on its own.
32324        f.run(&[b"SELECT", b"3"]);
32325        assert_eq!(
32326            commands(&f.crossed(&[b"SET", b"k3", b"v"])),
32327            vec![
32328                vec!["SELECT".to_string(), "3".to_string()],
32329                vec!["SET".to_string(), "k3".to_string(), "v".to_string()],
32330            ]
32331        );
32332    }
32333
32334    /// The deadline is read back off the key rather than worked out twice.
32335    ///
32336    /// So what crosses is the instant this server picked, and a replica that
32337    /// applies it an hour later still expires the key at the same moment.
32338    #[test]
32339    fn a_relative_deadline_crosses_as_the_instant_it_resolved_to() {
32340        let mut f = Fixture::replicated();
32341        f.crossed(&[b"SET", b"seed", b"1"]);
32342        for parts in [
32343            &[b"SET".as_slice(), b"k", b"v", b"EX", b"100"][..],
32344            &[b"SETEX".as_slice(), b"k", b"100", b"v"][..],
32345        ] {
32346            let words = only(&f.crossed(parts));
32347            assert_eq!(&words[..3], ["SET", "k", "v"], "{words:?}");
32348            assert_eq!(words[3], "PXAT", "{words:?}");
32349            let at: i64 = words[4].parse().expect("an instant");
32350            assert!(at > f.server.clock.now_ms() as i64, "{words:?}");
32351        }
32352        for parts in [
32353            &[b"EXPIRE".as_slice(), b"k", b"50"][..],
32354            &[b"PEXPIRE".as_slice(), b"k", b"50000"][..],
32355            &[b"EXPIREAT".as_slice(), b"k", b"99999999999"][..],
32356            &[b"GETEX".as_slice(), b"k", b"EX", b"100"][..],
32357        ] {
32358            let words = only(&f.crossed(parts));
32359            assert_eq!(&words[..2], ["PEXPIREAT", "k"], "{words:?}");
32360        }
32361        assert_eq!(
32362            only(&f.crossed(&[b"GETEX", b"k", b"PERSIST"])),
32363            ["PERSIST", "k"]
32364        );
32365    }
32366
32367    /// A read sends nothing, and neither does a write that was refused.
32368    #[test]
32369    fn nothing_crosses_for_a_read_or_for_a_failure() {
32370        let mut f = Fixture::replicated();
32371        f.crossed(&[b"SET", b"k", b"v"]);
32372        for parts in [
32373            &[b"GET".as_slice(), b"k"][..],
32374            &[b"TYPE".as_slice(), b"k"][..],
32375            &[b"STRLEN".as_slice(), b"k"][..],
32376            &[b"EXISTS".as_slice(), b"k"][..],
32377            &[b"PING".as_slice()][..],
32378            // Refused, and a refusal leaves the stream alone whatever the body
32379            // pushed before it found out.
32380            &[b"LPUSH".as_slice(), b"k", b"a"][..],
32381            &[b"INCR".as_slice(), b"k"][..],
32382        ] {
32383            assert_eq!(f.crossed(parts), "", "{parts:?}");
32384        }
32385    }
32386
32387    /// A write that changed nothing still crosses, which is D-140.
32388    ///
32389    /// Redis decides with a counter of real changes and sends nothing when it
32390    /// did not move. There is no such counter here yet, so what is sent is what
32391    /// can be said without one: an accepted write goes down the link. The ones
32392    /// whose verbatim form would be wrong rather than merely wasteful already
32393    /// say so for themselves, which is the second half of this.
32394    #[test]
32395    fn a_write_that_changed_nothing_still_crosses() {
32396        let mut f = Fixture::replicated();
32397        f.crossed(&[b"SET", b"k", b"v"]);
32398        assert_eq!(
32399            only(&f.crossed(&[b"DEL", b"nosuchkey"])),
32400            ["DEL", "nosuchkey"]
32401        );
32402        assert_eq!(only(&f.crossed(&[b"SET", b"k", b"v"])), ["SET", "k", "v"]);
32403        // And the ones that would be wrong say nothing, whatever the rule above.
32404        for parts in [
32405            &[b"SPOP".as_slice(), b"nosuchset"][..],
32406            &[b"EXPIRE".as_slice(), b"nosuchkey", b"100"][..],
32407            &[
32408                b"XADD".as_slice(),
32409                b"nosuchstream",
32410                b"NOMKSTREAM",
32411                b"*",
32412                b"f",
32413                b"v",
32414            ][..],
32415        ] {
32416            assert_eq!(f.crossed(parts), "", "{parts:?}");
32417        }
32418    }
32419
32420    /// A conditional write crosses as the plain one, since the condition was
32421    /// decided here and a replica has no business deciding it again.
32422    #[test]
32423    fn a_condition_that_held_crosses_without_it() {
32424        let mut f = Fixture::replicated();
32425        f.crossed(&[b"SET", b"seed", b"1"]);
32426        assert_eq!(only(&f.crossed(&[b"SETNX", b"k", b"v"])), ["SET", "k", "v"]);
32427        assert_eq!(
32428            only(&f.crossed(&[b"SET", b"k", b"w", b"XX"])),
32429            ["SET", "k", "w"]
32430        );
32431    }
32432
32433    /// A write whose result depends on where it ran crosses as the result.
32434    #[test]
32435    fn a_random_or_derived_write_crosses_as_what_it_did() {
32436        let mut f = Fixture::replicated();
32437        f.crossed(&[b"SET", b"seed", b"1"]);
32438        f.crossed(&[b"SADD", b"s", b"one", b"two"]);
32439        let words = only(&f.crossed(&[b"SPOP", b"s"]));
32440        assert_eq!(&words[..2], ["SREM", "s"], "{words:?}");
32441        assert!(words[2] == "one" || words[2] == "two", "{words:?}");
32442        // The one that took the last member still crosses as the removal and
32443        // not as the key going, which is a real server's rule and not an
32444        // oversight: the far side takes the member out and finds it is holding
32445        // an empty set, which it drops on its own.
32446        let words = only(&f.crossed(&[b"SPOP", b"s"]));
32447        assert_eq!(&words[..2], ["SREM", "s"], "{words:?}");
32448        // The form with a count is where taking the lot is sent as the delete,
32449        // because there it can be one line instead of a whole set of them.
32450        f.crossed(&[b"SADD", b"s", b"one", b"two"]);
32451        assert_eq!(only(&f.crossed(&[b"SPOP", b"s", b"2"])), ["DEL", "s"]);
32452        f.crossed(&[b"SET", b"n", b"1"]);
32453        assert_eq!(
32454            only(&f.crossed(&[b"INCRBYFLOAT", b"n", b"1.5"])),
32455            ["SET", "n", "2.5", "KEEPTTL"]
32456        );
32457        assert_eq!(only(&f.crossed(&[b"GETDEL", b"n"])), ["DEL", "n"]);
32458        let words = only(&f.crossed(&[b"XADD", b"st", b"*", b"f", b"v"]));
32459        assert_eq!(&words[..2], ["XADD", "st"], "{words:?}");
32460        assert_ne!(words[2], "*", "an auto id has to be resolved: {words:?}");
32461        assert_eq!(&words[3..], ["f", "v"], "{words:?}");
32462    }
32463
32464    /// A key that went on its own crosses as the deletion, ahead of whatever the
32465    /// command that noticed was doing.
32466    ///
32467    /// A replica never expires anything itself, so this is the only way it hears
32468    /// about it, and the order matters: the write that follows would be refused
32469    /// by a replica still holding the old key at the old type.
32470    #[test]
32471    fn an_expiry_a_read_noticed_crosses_as_a_deletion() {
32472        let mut f = Fixture::replicated();
32473        f.crossed(&[b"SET", b"k", b"v", b"PX", b"50"]);
32474        f.advance(100);
32475        assert_eq!(only(&f.crossed(&[b"GET", b"k"])), ["DEL", "k"]);
32476        // And the deletion goes first when the command had something of its own.
32477        f.run(&[b"SET", b"k2", b"v", b"PX", b"50"]);
32478        f.crossed(&[b"PING"]);
32479        f.advance(100);
32480        assert_eq!(
32481            commands(&f.crossed(&[b"LPUSH", b"k2", b"a"])),
32482            vec![
32483                vec!["DEL".to_string(), "k2".to_string()],
32484                vec!["LPUSH".to_string(), "k2".to_string(), "a".to_string()],
32485            ]
32486        );
32487    }
32488
32489    /// A command that parked has done nothing, so nothing crosses.
32490    ///
32491    /// What must never cross is the command as it arrived, since a replica told
32492    /// to `BLPOP` would stop and wait on the one connection that cannot stop.
32493    #[test]
32494    fn a_blocking_command_that_parked_crosses_nothing() {
32495        let mut f = Fixture::replicated();
32496        f.crossed(&[b"SET", b"seed", b"1"]);
32497        assert_eq!(f.flow(&[b"BLPOP", b"gone", b"0"]).0, Flow::Block);
32498        assert_eq!(f.server.stream_since(f.mark).0, "");
32499        f.run(&[b"XADD", b"st", b"1-1", b"f", b"v"]);
32500        f.run(&[b"XGROUP", b"CREATE", b"st", b"g", b"$"]);
32501        f.crossed(&[b"PING"]);
32502        // A group read that read nothing is in the same position, and this one
32503        // does not even park.
32504        f.run(&[
32505            b"XREADGROUP",
32506            b"GROUP",
32507            b"g",
32508            b"c",
32509            b"COUNT",
32510            b"1",
32511            b"STREAMS",
32512            b"st",
32513            b">",
32514        ]);
32515        assert_eq!(
32516            only(&f.crossed(&[b"PING"])),
32517            ["XGROUP", "CREATECONSUMER", "st", "g", "c"]
32518        );
32519    }
32520
32521    /// `XGROUP` carries its write flag on its subcommands, which are not in the
32522    /// table yet, so each arm says for itself what it did.
32523    #[test]
32524    fn every_xgroup_subcommand_that_changed_something_crosses() {
32525        let mut f = Fixture::replicated();
32526        f.crossed(&[b"XADD", b"st", b"1-1", b"f", b"v"]);
32527        // The dollar is resolved here, because by the time a replica reads it
32528        // the stream it means is a different length.
32529        assert_eq!(
32530            only(&f.crossed(&[b"XGROUP", b"CREATE", b"st", b"g", b"$"])),
32531            ["XGROUP", "CREATE", "st", "g", "1-1"]
32532        );
32533        assert_eq!(
32534            only(&f.crossed(&[b"XGROUP", b"CREATECONSUMER", b"st", b"g", b"c"])),
32535            ["XGROUP", "CREATECONSUMER", "st", "g", "c"]
32536        );
32537        assert_eq!(
32538            only(&f.crossed(&[b"XGROUP", b"SETID", b"st", b"g", b"0"])),
32539            ["XGROUP", "SETID", "st", "g", "0-0"]
32540        );
32541        assert_eq!(
32542            only(&f.crossed(&[b"XGROUP", b"DELCONSUMER", b"st", b"g", b"c"])),
32543            ["XGROUP", "DELCONSUMER", "st", "g", "c"]
32544        );
32545        assert_eq!(
32546            only(&f.crossed(&[b"XGROUP", b"DESTROY", b"st", b"g"])),
32547            ["XGROUP", "DESTROY", "st", "g"]
32548        );
32549        // And one that changed nothing crosses nothing.
32550        assert_eq!(f.crossed(&[b"XGROUP", b"DESTROY", b"st", b"g"]), "");
32551    }
32552
32553    /// A publish crosses even though it is not a write and touches no key.
32554    ///
32555    /// A client subscribed to a replica is subscribed to the whole server, so
32556    /// it has to hear what was published on the master.
32557    #[test]
32558    fn a_publish_crosses_with_nobody_listening() {
32559        let mut f = Fixture::replicated();
32560        f.crossed(&[b"SET", b"seed", b"1"]);
32561        assert_eq!(
32562            only(&f.crossed(&[b"PUBLISH", b"news", b"hello"])),
32563            ["PUBLISH", "news", "hello"]
32564        );
32565        assert_eq!(
32566            only(&f.crossed(&[b"SPUBLISH", b"news", b"hello"])),
32567            ["SPUBLISH", "news", "hello"]
32568        );
32569    }
32570
32571    /// A replica that lost the link for a moment is given the bytes it missed.
32572    ///
32573    /// The number it sends is the position of the first byte it wants counted
32574    /// from one, so a replica that has everything asks for one past the end.
32575    /// Reading that as a count of bytes written instead is an off by one that
32576    /// turns every reconnect into a full resync, which is exactly what a real
32577    /// replica did until this was fixed.
32578    #[test]
32579    fn a_replica_asking_to_carry_on_is_caught_up_from_the_backlog() {
32580        let mut f = Fixture::replicated();
32581        f.crossed(&[b"SET", b"k", b"v"]);
32582        let id = f.server.repl_id();
32583        let had = f.mark;
32584        f.crossed(&[b"SET", b"k2", b"later"]);
32585        let asked = (had + 1).to_string();
32586        let reply = f.run(&[b"PSYNC", &id, asked.as_bytes()]);
32587        assert!(reply.starts_with("+CONTINUE "), "{reply}");
32588        assert!(reply.contains("later"), "{reply}");
32589        // And what it already had is not sent twice.
32590        assert_eq!(reply.matches("k2").count(), 1, "{reply}");
32591    }
32592
32593    /// A replica with nothing to carry on from is sent the whole dataset.
32594    #[test]
32595    fn a_replica_with_no_history_is_sent_a_snapshot() {
32596        let mut f = Fixture::replicated();
32597        f.crossed(&[b"SET", b"k", b"v"]);
32598        let reply = f.run(&[b"PSYNC", b"?", b"-1"]);
32599        assert!(reply.starts_with("+FULLRESYNC "), "{reply}");
32600        // The header, then the image as a bulk string with no newline after it.
32601        let body = reply.split_once("\r\n").expect("a header ends").1;
32602        assert!(body.starts_with('$'), "{body:?}");
32603        assert!(!body.ends_with("\r\n"), "{body:?}");
32604    }
32605
32606    // ------------------------------------------------------ being a replica
32607
32608    /// The whole point of the read only refusal, and the read that goes through.
32609    #[test]
32610    fn a_read_only_replica_refuses_a_write_and_answers_a_read() {
32611        let mut f = Fixture::new();
32612        f.run(&[b"SET", b"k", b"v"]);
32613        f.server.pretend_following("127.0.0.1", 6379, true);
32614        assert_eq!(
32615            f.run(&[b"SET", b"k", b"other"]),
32616            "-READONLY You can't write against a read only replica.\r\n"
32617        );
32618        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
32619        // And a command that is not a write at all is not touched by any of it.
32620        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
32621    }
32622
32623    /// The refusal is off on a server that is nobody's replica, whatever the
32624    /// setting says, because the setting is about being a replica.
32625    #[test]
32626    fn a_master_takes_writes_however_the_read_only_setting_is_left() {
32627        let mut f = Fixture::new();
32628        f.server.set_replica_read_only(true);
32629        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32630        f.server.pretend_following("127.0.0.1", 6379, true);
32631        assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32632        // And a replica that was told it is writable takes the write.
32633        f.server.set_replica_read_only(false);
32634        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32635        f.server.set_replica_read_only(true);
32636        // Stopping being a replica is enough on its own, with the setting left
32637        // exactly where it was.
32638        f.server.pretend_master();
32639        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32640    }
32641
32642    /// The master's own connection is what the refusal is not for.
32643    #[test]
32644    fn the_link_to_the_master_writes_through_the_read_only_refusal() {
32645        let mut f = Fixture::new();
32646        f.server.pretend_following("127.0.0.1", 6379, true);
32647        assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32648        f.session.serve_master(true);
32649        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
32650        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
32651    }
32652
32653    /// A refused `EXEC` fails the whole transaction rather than one command in
32654    /// it, which is the same rule every other gate in `resolved` follows.
32655    #[test]
32656    fn a_transaction_on_a_read_only_replica_is_refused_whole() {
32657        let mut f = Fixture::new();
32658        f.server.pretend_following("127.0.0.1", 6379, true);
32659        f.run(&[b"MULTI"]);
32660        assert!(f.run(&[b"SET", b"k", b"v"]).starts_with("-READONLY"));
32661        assert!(f.run(&[b"EXEC"]).starts_with("-EXECABORT"));
32662    }
32663
32664    /// What an operator reads to find out who this server is following.
32665    #[test]
32666    fn a_replica_says_who_it_follows_in_info_and_in_role() {
32667        let mut f = Fixture::new();
32668        assert!(f.run(&[b"INFO", b"replication"]).contains("role:master"));
32669        f.server.pretend_following("10.0.0.4", 7000, true);
32670        let info = f.run(&[b"INFO", b"replication"]);
32671        assert!(info.contains("role:slave"), "{info}");
32672        assert!(info.contains("master_host:10.0.0.4"), "{info}");
32673        assert!(info.contains("master_port:7000"), "{info}");
32674        assert!(info.contains("master_link_status:up"), "{info}");
32675        assert!(info.contains("slave_read_only:1"), "{info}");
32676        // The five element replica form, and the state word is the one that
32677        // tells an operator whether anything is arriving.
32678        let role = f.run(&[b"ROLE"]);
32679        assert!(role.starts_with("*5\r\n$5\r\nslave\r\n"), "{role}");
32680        assert!(role.contains("10.0.0.4"), "{role}");
32681        assert!(role.contains("connected"), "{role}");
32682        // A link that is down says so in both places rather than in one.
32683        f.server.pretend_following("10.0.0.4", 7000, false);
32684        assert!(
32685            f.run(&[b"INFO", b"replication"])
32686                .contains("master_link_status:down"),
32687            "a link that is not up is down"
32688        );
32689        assert!(f.run(&[b"ROLE"]).contains("connect"));
32690    }
32691
32692    /// A server nobody wrapped in a handle cannot start a link, and says so
32693    /// rather than answering `OK` and doing nothing.
32694    #[test]
32695    fn replicaof_on_an_embedded_server_says_it_is_not_available() {
32696        let mut f = Fixture::new();
32697        let said = f.run(&[b"REPLICAOF", b"127.0.0.1", b"6379"]);
32698        assert!(
32699            said.contains("not available on an embedded server"),
32700            "{said}"
32701        );
32702        // The arity and the port are checked first, so a caller that got the
32703        // command wrong hears about that and not about the handle.
32704        assert!(
32705            f.run(&[b"REPLICAOF", b"127.0.0.1"])
32706                .starts_with("-ERR wrong number")
32707        );
32708        assert_eq!(
32709            f.run(&[b"SLAVEOF", b"127.0.0.1", b"abc"]),
32710            "-ERR Invalid master port\r\n"
32711        );
32712        assert_eq!(
32713            f.run(&[b"REPLICAOF", b"127.0.0.1", b"99999"]),
32714            "-ERR Invalid master port\r\n"
32715        );
32716    }
32717
32718    /// Promotion keeps the history it was part of, which is what lets the
32719    /// replicas that shared it carry on rather than start again.
32720    #[test]
32721    fn a_promotion_keeps_the_old_history_as_the_second_id() {
32722        let f = Fixture::new();
32723        let was = f.server.repl_id();
32724        f.server.promote();
32725        assert_ne!(f.server.repl_id(), was);
32726        let info = {
32727            let mut f = f;
32728            f.run(&[b"INFO", b"replication"])
32729        };
32730        let was = String::from_utf8_lossy(&was).into_owned();
32731        assert!(info.contains(&format!("master_replid2:{was}")), "{info}");
32732    }
32733
32734    /// `DEBUG CHANGE-REPL-ID` is the opposite: a new history and no claim on the
32735    /// old one, so the next `PSYNC` between two servers that shared it is full.
32736    #[test]
32737    fn change_repl_id_takes_a_new_id_and_forgets_the_old_one() {
32738        let mut f = Fixture::new();
32739        let was = f.server.repl_id();
32740        f.server.promote();
32741        assert_eq!(f.run(&[b"DEBUG", b"CHANGE-REPL-ID"]), "+OK\r\n");
32742        assert_ne!(f.server.repl_id(), was);
32743        let info = f.run(&[b"INFO", b"replication"]);
32744        assert!(
32745            info.contains(&format!("master_replid2:{}", "0".repeat(40))),
32746            "{info}"
32747        );
32748    }
32749
32750    /// Every way of getting `FAILOVER` wrong, in the order a real server checks
32751    /// them, because the order is what a script sees when it gets two things
32752    /// wrong at once.
32753    #[test]
32754    fn failover_refuses_in_the_order_the_reference_refuses() {
32755        let mut f = Fixture::new();
32756        // Nothing going on, so ABORT has nothing to abort.
32757        assert_eq!(
32758            f.run(&[b"FAILOVER", b"ABORT"]),
32759            "-ERR No failover in progress.\r\n"
32760        );
32761        // The parsing comes before any of the state checks, and a timeout of
32762        // nought or less has a sentence of its own rather than being a syntax
32763        // error.
32764        assert_eq!(
32765            f.run(&[b"FAILOVER", b"TIMEOUT", b"0"]),
32766            "-ERR FAILOVER timeout must be greater than 0\r\n"
32767        );
32768        assert_eq!(
32769            f.run(&[b"FAILOVER", b"TIMEOUT", b"-1"]),
32770            "-ERR FAILOVER timeout must be greater than 0\r\n"
32771        );
32772        assert!(
32773            f.run(&[b"FAILOVER", b"TIMEOUT", b"abc"])
32774                .starts_with("-ERR value is not an integer")
32775        );
32776        // Each word is taken at most once, so a second one is a syntax error and
32777        // not an overwrite, and anything unrecognised is one too.
32778        assert_eq!(f.run(&[b"FAILOVER", b"bogus"]), "-ERR syntax error\r\n");
32779        assert_eq!(
32780            f.run(&[b"FAILOVER", b"TIMEOUT", b"1", b"TIMEOUT", b"2"]),
32781            "-ERR syntax error\r\n"
32782        );
32783        assert_eq!(
32784            f.run(&[b"FAILOVER", b"FORCE", b"FORCE"]),
32785            "-ERR syntax error\r\n"
32786        );
32787        // TO wants both of its words, so one word short of it is a syntax error
32788        // rather than a target with a missing port.
32789        assert_eq!(f.run(&[b"FAILOVER", b"TO", b"h"]), "-ERR syntax error\r\n");
32790        // ABORT is only ABORT when it is the whole command.
32791        assert_eq!(
32792            f.run(&[b"FAILOVER", b"ABORT", b"TIMEOUT", b"1"]),
32793            "-ERR syntax error\r\n"
32794        );
32795        // Then the state checks. Nobody is following this server, so there is
32796        // nobody to hand the job to, and that is asked before FORCE is.
32797        assert_eq!(
32798            f.run(&[b"FAILOVER"]),
32799            "-ERR FAILOVER requires connected replicas.\r\n"
32800        );
32801        assert_eq!(
32802            f.run(&[b"FAILOVER", b"FORCE"]),
32803            "-ERR FAILOVER requires connected replicas.\r\n"
32804        );
32805        // A replica has nothing of its own to give away.
32806        f.server.pretend_following("10.0.0.4", 7000, true);
32807        assert_eq!(
32808            f.run(&[b"FAILOVER"]),
32809            "-ERR FAILOVER is not valid when server is a replica.\r\n"
32810        );
32811    }
32812
32813    /// The state word `INFO` reports, which is what an operator watching a
32814    /// handover reads, and which is `no-failover` on a server that is not in one.
32815    #[test]
32816    fn a_server_that_is_not_failing_over_says_no_failover() {
32817        let mut f = Fixture::new();
32818        assert!(
32819            f.run(&[b"INFO", b"replication"])
32820                .contains("master_failover_state:no-failover"),
32821            "the field is there and says nothing is going on"
32822        );
32823    }
32824
32825    /// A transaction crosses as the commands it ran, which is D-141: a real
32826    /// server wraps them in `MULTI` and `EXEC`.
32827    #[test]
32828    fn a_transaction_crosses_as_its_commands() {
32829        let mut f = Fixture::replicated();
32830        f.crossed(&[b"SET", b"seed", b"1"]);
32831        f.run(&[b"MULTI"]);
32832        f.run(&[b"SET", b"a", b"1"]);
32833        f.run(&[b"INCR", b"a"]);
32834        assert_eq!(
32835            commands(&f.crossed(&[b"EXEC"])),
32836            vec![
32837                vec!["SET".to_string(), "a".to_string(), "1".to_string()],
32838                vec!["INCR".to_string(), "a".to_string()],
32839            ]
32840        );
32841    }
32842
32843    /// A cluster node owning every slot, with a second node in the table that
32844    /// nobody has met, which is the only way a redirection can fire before the
32845    /// bus is in.
32846    ///
32847    /// The slot `foo` lands in, read off a real server.
32848    const FOO: u16 = 12182;
32849
32850    /// The slot `bar` lands in, which is a different one and is the whole point.
32851    const BAR: u16 = 5061;
32852
32853    fn clustered() -> Fixture {
32854        let mut server = Server::new();
32855        server.enable_cluster("", 7000);
32856        server.cluster_own_everything();
32857        let other = server.cluster_pretend_node(
32858            "5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f",
32859            "10.0.0.9",
32860            7002,
32861        );
32862        assert_eq!(other, 1, "the made up node is the second one in the table");
32863        Fixture::on(server)
32864    }
32865
32866    /// The runs come out in slot order and not in node order, which is the
32867    /// order a real server walks and the order a client that caches the reply
32868    /// by position is counting on.
32869    #[test]
32870    fn cluster_slots_comes_out_in_slot_order() {
32871        let mut f = clustered();
32872        for slot in 0..100u16 {
32873            f.server.cluster_hand_over(slot, 1);
32874        }
32875        let reply = f.run(&[b"CLUSTER", b"SLOTS"]);
32876        assert!(
32877            reply.starts_with("*2\r\n*3\r\n:0\r\n:99\r\n*4\r\n$8\r\n10.0.0.9\r\n:7002\r\n"),
32878            "the other node's run is first because it starts at slot 0: {reply}"
32879        );
32880        assert!(
32881            reply.contains("*3\r\n:100\r\n:16383\r\n"),
32882            "and this node's run is the rest of them: {reply}"
32883        );
32884    }
32885
32886    /// A key in a slot somebody else owns is a redirection and not an answer.
32887    #[test]
32888    fn a_key_on_another_node_is_moved_there() {
32889        let mut f = clustered();
32890        f.server.cluster_hand_over(BAR, 1);
32891        assert_eq!(
32892            f.run(&[b"GET", b"bar"]),
32893            format!("-MOVED {BAR} 10.0.0.9:7002\r\n")
32894        );
32895        // Every other slot is still this node's, so nothing about them moves.
32896        assert_eq!(f.run(&[b"GET", b"foo"]), "$-1\r\n");
32897    }
32898
32899    /// A command that names no key never redirects, whatever the table says,
32900    /// which is what lets a client talk to any node at all.
32901    #[test]
32902    fn a_command_with_no_keys_never_redirects() {
32903        let mut f = clustered();
32904        for slot in 0..16384u16 {
32905            f.server.cluster_hand_over(slot, 1);
32906        }
32907        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
32908        assert_eq!(f.run(&[b"ECHO", b"hi"]), "$2\r\nhi\r\n");
32909    }
32910
32911    /// Two keys in two slots cannot be served by anybody, so the client is told
32912    /// that rather than being sent somewhere that would only fail again.
32913    #[test]
32914    fn two_slots_in_one_command_is_a_cross_slot() {
32915        let mut f = clustered();
32916        assert_eq!(
32917            f.run(&[b"MGET", b"foo", b"bar"]),
32918            "-CROSSSLOT Keys in request don't hash to the same slot\r\n"
32919        );
32920        // The same two keys with a tag that puts them together are fine.
32921        assert_eq!(
32922            f.run(&[b"MGET", b"{t}foo", b"{t}bar"]),
32923            "*2\r\n$-1\r\n$-1\r\n"
32924        );
32925    }
32926
32927    /// A hole in the table beats everything, including the two slots, because a
32928    /// real server works out the first key's node before it looks at the rest.
32929    #[test]
32930    fn a_hole_is_reported_before_the_cross_slot() {
32931        let mut server = Server::new();
32932        server.enable_cluster("", 7000);
32933        let mut f = Fixture::on(server);
32934        assert_eq!(
32935            f.run(&[b"MGET", b"foo", b"bar"]),
32936            "-CLUSTERDOWN Hash slot not served\r\n"
32937        );
32938        // And with the slots back it is the two slots again.
32939        f.server.cluster_own_everything();
32940        assert_eq!(
32941            f.run(&[b"MGET", b"foo", b"bar"]),
32942            "-CROSSSLOT Keys in request don't hash to the same slot\r\n"
32943        );
32944    }
32945
32946    /// A slot on its way out sends a client on for the keys that have gone and
32947    /// answers for the ones that are still here, which is what makes a slot move
32948    /// without a window where a key is on neither node.
32949    #[test]
32950    fn a_migrating_slot_asks_for_the_keys_that_have_gone() {
32951        let mut f = clustered();
32952        f.run(&[b"SET", b"foo", b"1"]);
32953        f.server.cluster_moving(FOO, Some(1), None);
32954        // Still here, so this node answers.
32955        assert_eq!(f.run(&[b"GET", b"foo"]), "$1\r\n1\r\n");
32956        // Gone, so the client is sent on for this one command only.
32957        assert_eq!(
32958            f.run(&[b"GET", b"{foo}gone"]),
32959            format!("-ASK {FOO} 10.0.0.9:7002\r\n")
32960        );
32961    }
32962
32963    /// Some here and some gone is nobody's command to run, and the client is
32964    /// told to come back rather than being given half an answer.
32965    #[test]
32966    fn a_half_moved_slot_is_a_try_again() {
32967        let mut f = clustered();
32968        f.run(&[b"SET", b"{t}here", b"1"]);
32969        let slot = cluster::key_slot(b"{t}here");
32970        f.server.cluster_moving(slot, Some(1), None);
32971        assert_eq!(
32972            f.run(&[b"MGET", b"{t}here", b"{t}gone"]),
32973            "-TRYAGAIN Multiple keys request during rehashing of slot\r\n"
32974        );
32975    }
32976
32977    /// A slot coming in is refused until the connection says `ASKING`, and the
32978    /// permission lasts exactly one command.
32979    #[test]
32980    fn asking_lets_one_command_into_an_importing_slot() {
32981        let mut f = clustered();
32982        f.server.cluster_hand_over(FOO, 1);
32983        f.server.cluster_moving(FOO, None, Some(1));
32984        let moved = format!("-MOVED {FOO} 10.0.0.9:7002\r\n");
32985        assert_eq!(f.run(&[b"GET", b"foo"]), moved);
32986        assert_eq!(f.run(&[b"ASKING"]), "+OK\r\n");
32987        assert_eq!(f.run(&[b"SET", b"foo", b"1"]), "+OK\r\n");
32988        // And it is spent, so the next one is a redirection again.
32989        assert_eq!(f.run(&[b"GET", b"foo"]), moved);
32990    }
32991
32992    /// `RESTORE-ASKING` carries its own `ASKING`, which is the whole reason it
32993    /// exists: the node being sent a slot's keys does not own the slot yet, so a
32994    /// plain `RESTORE` would come back as a redirection to the node sending
32995    /// them and the migration would never get off the ground.
32996    #[test]
32997    fn restore_asking_gets_into_an_importing_slot_on_its_own() {
32998        let mut f = clustered();
32999        f.server.cluster_hand_over(FOO, 1);
33000        f.server.cluster_moving(FOO, None, Some(1));
33001        // The payload is whatever `DUMP` makes of a one byte string, taken from
33002        // this server so the footer is this server's.
33003        f.run(&[b"SET", b"scratch", b"1"]);
33004        let dumped = f.raw(&[b"DUMP", b"scratch"]);
33005        let payload =
33006            &dumped[dumped.iter().position(|b| *b == b'\n').unwrap() + 1..dumped.len() - 2];
33007        let payload = payload.to_vec();
33008        // The ordinary spelling is turned away.
33009        assert_eq!(
33010            f.run(&[b"RESTORE", b"foo", b"0", &payload]),
33011            format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
33012        );
33013        // And the one migration uses is not.
33014        assert_eq!(
33015            f.run(&[b"RESTORE-ASKING", b"foo", b"0", &payload]),
33016            "+OK\r\n"
33017        );
33018        // It is not a connection wide permission either, so the next ordinary
33019        // command is redirected the same as before.
33020        assert_eq!(
33021            f.run(&[b"GET", b"foo"]),
33022            format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
33023        );
33024    }
33025
33026    /// The end of a slot import takes an epoch above everybody else's, which is
33027    /// the only thing that makes the rest of the cluster stop pointing clients
33028    /// at the node the slot came from.
33029    #[test]
33030    fn closing_an_import_takes_a_higher_epoch() {
33031        let mut f = clustered();
33032        f.server.cluster_hand_over(FOO, 1);
33033        f.server.cluster_moving(FOO, None, Some(1));
33034        let me = f.run(&[b"CLUSTER", b"MYID"]);
33035        let me = me[me.find("\r\n").unwrap() + 2..me.len() - 2].to_owned();
33036        assert_eq!(
33037            f.run(&[
33038                b"CLUSTER",
33039                b"SETSLOT",
33040                FOO.to_string().as_bytes(),
33041                b"NODE",
33042                me.as_bytes()
33043            ]),
33044            "+OK\r\n"
33045        );
33046        // The epoch moved on its own, so a bump asked for now has nothing left
33047        // to outrank and says so.
33048        assert_eq!(f.run(&[b"CLUSTER", b"BUMPEPOCH"]), "+STILL 1\r\n");
33049        // And the slot is this node's with nothing left marked.
33050        assert_eq!(f.run(&[b"GET", b"foo"]), "$-1\r\n");
33051    }
33052
33053    /// And a slot handed over without an import behind it does not, because
33054    /// nothing has been taken off anybody and there is nothing to outrank.
33055    #[test]
33056    fn a_plain_hand_over_does_not_touch_the_epoch() {
33057        let mut f = clustered();
33058        let me = f.run(&[b"CLUSTER", b"MYID"]);
33059        let me = me[me.find("\r\n").unwrap() + 2..me.len() - 2].to_owned();
33060        assert_eq!(
33061            f.run(&[
33062                b"CLUSTER",
33063                b"SETSLOT",
33064                FOO.to_string().as_bytes(),
33065                b"NODE",
33066                me.as_bytes()
33067            ]),
33068            "+OK\r\n"
33069        );
33070        assert_eq!(
33071            f.run(&[b"CLUSTER", b"BUMPEPOCH"]),
33072            "+BUMPED 1\r\n",
33073            "the epoch was still zero, so this is the first thing to move it"
33074        );
33075    }
33076
33077    /// A slot is not handed to somebody else while this node still holds keys
33078    /// for it, because that would leave two nodes answering for the same data.
33079    #[test]
33080    fn a_slot_with_keys_in_it_is_not_handed_over() {
33081        let mut f = clustered();
33082        f.run(&[b"SET", b"foo", b"1"]);
33083        let them = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
33084        assert_eq!(
33085            f.run(&[
33086                b"CLUSTER",
33087                b"SETSLOT",
33088                FOO.to_string().as_bytes(),
33089                b"NODE",
33090                them
33091            ]),
33092            format!(
33093                "-ERR Can't assign hashslot {FOO} to a different node while I still hold keys for this hash slot.\r\n"
33094            )
33095        );
33096        // With the key gone it goes through.
33097        f.run(&[b"DEL", b"foo"]);
33098        assert_eq!(
33099            f.run(&[
33100                b"CLUSTER",
33101                b"SETSLOT",
33102                FOO.to_string().as_bytes(),
33103                b"NODE",
33104                them
33105            ]),
33106            "+OK\r\n"
33107        );
33108        assert_eq!(
33109            f.run(&[b"GET", b"foo"]),
33110            format!("-MOVED {FOO} 10.0.0.9:7002\r\n")
33111        );
33112    }
33113
33114    /// The slot migration protocol is shut to anybody who is not a node, and the
33115    /// connection goes with the refusal.
33116    ///
33117    /// The hang up is the reference's and it is the part worth having. Nothing
33118    /// behind this command checks that it is being driven in order, because the
33119    /// only thing that ever drives it is another node following the same state
33120    /// machine, so the whole defence is getting in at all and making a guess cost
33121    /// a fresh connection is most of that defence.
33122    #[test]
33123    fn the_slot_migration_protocol_is_shut_to_a_client() {
33124        let mut f = clustered();
33125        // The arity is read first, so a client that sends the container on its own
33126        // is told that much and keeps its connection.
33127        let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS"]);
33128        assert_eq!(
33129            reply,
33130            "-ERR wrong number of arguments for 'cluster|syncslots' command\r\n"
33131        );
33132        assert_eq!(flow, Flow::Continue);
33133        let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
33134        assert_eq!(
33135            reply,
33136            "-ERR CLUSTER SYNCSLOTS subcommands are only allowed for internal clients\r\n"
33137        );
33138        assert_eq!(flow, Flow::Close, "and the socket goes with it");
33139    }
33140
33141    /// The one way in is the secret the whole cluster has agreed on, and there is
33142    /// no secret at all on a server that is not in a cluster.
33143    #[test]
33144    fn the_internal_login_wants_the_cluster_secret() {
33145        let mut f = Fixture::new();
33146        assert_eq!(
33147            f.run(&[b"AUTH", b"internal connection", b"x"]),
33148            "-ERR Cannot authenticate as an internal connection on non-cluster instances\r\n"
33149        );
33150        assert_eq!(
33151            f.run(&[b"DEBUG", b"INTERNAL_SECRET"]),
33152            "-ERR Internal secret is missing\r\n"
33153        );
33154        let mut f = clustered();
33155        assert_eq!(
33156            f.run(&[b"AUTH", b"internal connection", b"x"]),
33157            "-WRONGPASS invalid internal password\r\n"
33158        );
33159        // The name is matched exactly and not the way a keyword is, so this is a
33160        // failed login as a user of that name rather than a failed internal one.
33161        assert_eq!(
33162            f.run(&[b"AUTH", b"INTERNAL CONNECTION", b"x"]),
33163            "-WRONGPASS invalid username-password pair or user is disabled.\r\n"
33164        );
33165        let secret = f.server.cluster_secret();
33166        assert_eq!(secret.len(), 40, "forty characters, like a node id");
33167        assert_eq!(
33168            f.run(&[b"DEBUG", b"INTERNAL_SECRET"]),
33169            format!(":{}\r\n", yo_common::crc::crc16(secret.as_bytes())),
33170            "what comes back is a checksum, so a test can see two nodes agree \
33171             and nobody can log in with what they read"
33172        );
33173        assert_eq!(
33174            f.run(&[b"AUTH", b"internal connection", secret.as_bytes()]),
33175            "+OK\r\n"
33176        );
33177        assert_eq!(
33178            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]),
33179            "+OK\r\n"
33180        );
33181    }
33182
33183    /// `CONF` carries on past an option it did not understand and still says
33184    /// `OK`, so one command can answer with two replies.
33185    #[test]
33186    fn conf_says_ok_after_an_option_it_did_not_know() {
33187        let mut f = clustered();
33188        assert_eq!(f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]), "+OK\r\n");
33189        assert_eq!(
33190            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"zzz", b"1"]),
33191            "-ERR Unknown option zzz\r\n+OK\r\n"
33192        );
33193        // A capability nobody here has heard of is not an unknown option, which
33194        // is what lets a newer node say something to an older one.
33195        assert_eq!(
33196            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"quantum"]),
33197            "+OK\r\n"
33198        );
33199        // The node saying who it is has to be a node this one knows.
33200        assert_eq!(
33201            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"node-id", b"abc"]),
33202            "-ERR Invalid node id length 3\r\n"
33203        );
33204        let unknown = b"1111111111111111111111111111111111111111";
33205        assert_eq!(
33206            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"node-id", unknown]),
33207            "-ERR Node 1111111111111111111111111111111111111111 not found in cluster\r\n"
33208        );
33209        assert_eq!(
33210            f.run(&[
33211                b"CLUSTER",
33212                b"SYNCSLOTS",
33213                b"CONF",
33214                b"node-id",
33215                b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f"
33216            ]),
33217            "+OK\r\n"
33218        );
33219        // The size hint is three numbers and the first of them is a slot.
33220        assert_eq!(
33221            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"slot-info", b"5:10:2"]),
33222            "+OK\r\n"
33223        );
33224        for bad in [b"zz".as_slice(), b"16384:0:0", b"5:10:2:3", b"5:-1:0"] {
33225            assert_eq!(
33226                f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"slot-info", bad]),
33227                format!(
33228                    "-ERR Invalid slot info: {}\r\n",
33229                    String::from_utf8_lossy(bad)
33230                )
33231            );
33232        }
33233        // And a master has no business being told what its own migration looks
33234        // like, since it is the one running it.
33235        assert_eq!(
33236            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"asm-task", b"x"]),
33237            "-ERR CLUSTER SYNCSLOTS CONF ASM-TASK only allowed on replica\r\n"
33238        );
33239        assert_eq!(
33240            f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT", b"UNMARK"]),
33241            "+OK\r\n"
33242        );
33243        let (flow, _) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
33244        assert_eq!(flow, Flow::Close, "and the door shuts again");
33245    }
33246
33247    /// The slot ranges are checked in full before anything is asked to move, and
33248    /// the answers name what is wrong with them.
33249    #[test]
33250    fn the_slot_ranges_of_a_sync_are_checked_in_full() {
33251        let mut f = clustered();
33252        f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33253        let id = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
33254        fn sync<'a>(id: &'a [u8], slots: &[&'a [u8]]) -> Vec<&'a [u8]> {
33255            let mut parts: Vec<&[u8]> = vec![b"CLUSTER", b"SYNCSLOTS", b"SYNC", id];
33256            parts.extend_from_slice(slots);
33257            parts
33258        }
33259        let bar = BAR.to_string();
33260        let bar = bar.as_bytes();
33261        assert_eq!(
33262            f.run(&sync(id, &[b"5", b"4"])),
33263            "-ERR start slot number 5 is greater than end slot number 4\r\n"
33264        );
33265        assert_eq!(
33266            f.run(&sync(id, &[b"99999", b"2"])),
33267            "-ERR Invalid or out of range slot\r\n"
33268        );
33269        // Ranges that touch are joined up and ranges that overlap are not, so
33270        // this is one slot asked for twice and the one below is a run of four.
33271        assert_eq!(
33272            f.run(&sync(id, &[b"1", b"2", b"2", b"3"])),
33273            "-ERR Slot 2 specified multiple times\r\n"
33274        );
33275        // Ranges it can serve get the task and the invitation to open the second
33276        // connection, which is the whole of what the far side is waiting on.
33277        assert_eq!(
33278            f.run(&sync(id, &[b"1", b"2", b"3", b"4"])),
33279            "+RDBCHANNELSYNCSLOTS\r\n"
33280        );
33281        assert_eq!(
33282            f.run(&[b"CLUSTER", b"MIGRATION", b"CANCEL", b"ALL"]),
33283            ":1\r\n"
33284        );
33285        // A slot somebody else owns is not this node's to send.
33286        f.server.cluster_hand_over(BAR, 1);
33287        assert_eq!(
33288            f.run(&sync(id, &[bar])),
33289            "-ERR syntax error\r\n",
33290            "one slot number is not a range, and a shape it does not know is a \
33291             syntax error rather than a count it can complain about"
33292        );
33293        assert_eq!(
33294            f.run(&sync(id, &[bar, bar])),
33295            "-ERR This node is not the owner of the slots\r\n"
33296        );
33297        // And neither way of moving a slot runs while the other one is half done.
33298        f.server.cluster_moving(FOO, Some(1), None);
33299        assert_eq!(
33300            f.run(&sync(id, &[b"1", b"2"])),
33301            "-ERR all slot states must be STABLE to start a slot migration task.\r\n"
33302        );
33303    }
33304
33305    /// The whole of the giving up side, over the wire, in the order the node
33306    /// taking the slots does it.
33307    ///
33308    /// The two connections are one here, which the real protocol never does and
33309    /// nothing in the dispatch layer cares about: what is being read is that the
33310    /// task is created, that the second request is what releases the snapshot,
33311    /// and that the snapshot holds the slots asked for and nothing else.
33312    #[test]
33313    fn a_sync_and_an_rdbchannel_hand_over_the_slots_asked_for() {
33314        let mut f = clustered();
33315        f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33316        f.run(&[b"SET", b"foo", b"in the range"]);
33317        f.run(&[b"SET", b"bar", b"outside it"]);
33318        let id = b"5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f";
33319        let foo = FOO.to_string();
33320        let foo = foo.as_bytes();
33321
33322        // Nothing running, so nothing to report and nothing to cancel.
33323        assert_eq!(
33324            f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ALL"]),
33325            "*0\r\n"
33326        );
33327        assert_eq!(
33328            f.run(&[b"CLUSTER", b"MIGRATION", b"CANCEL", b"ALL"]),
33329            ":0\r\n"
33330        );
33331
33332        // The snapshot connection cannot come first, because there is no task
33333        // for it to belong to.
33334        assert_eq!(
33335            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", id]),
33336            "-ERR No slot migration task in progress\r\n"
33337        );
33338        assert_eq!(
33339            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SYNC", id, foo, foo]),
33340            "+RDBCHANNELSYNCSLOTS\r\n"
33341        );
33342        // Which is a task, waiting for exactly that connection.
33343        let status = f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ALL"]);
33344        assert!(status.starts_with("*1\r\n"), "{status:?}");
33345        assert!(status.contains("wait-rdbchannel"), "{status:?}");
33346        assert!(status.contains("migrate"), "{status:?}");
33347        // And one at a time, whoever asks.
33348        assert_eq!(
33349            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SYNC", id, foo, foo]),
33350            "-ERR Another ASM task is already in progress\r\n"
33351        );
33352
33353        let snapshot = f.raw(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", id]);
33354        let text = String::from_utf8_lossy(&snapshot);
33355        assert!(text.starts_with("+SLOTSSNAPSHOT\r\n"), "{text:?}");
33356        assert!(text.contains("$8\r\nFUNCTION\r\n"), "{text:?}");
33357        assert!(
33358            text.contains("$3\r\nSET\r\n$3\r\nfoo\r\n$12\r\nin the range\r\n"),
33359            "{text:?}"
33360        );
33361        assert!(!text.contains("$3\r\nbar\r\n"), "{text:?}");
33362        assert!(
33363            text.ends_with("$7\r\nCLUSTER\r\n$9\r\nSYNCSLOTS\r\n$12\r\nSNAPSHOT-EOF\r\n"),
33364            "{text:?}"
33365        );
33366        // The snapshot has gone, so what is left is the stream behind it.
33367        let status = f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ID", id]);
33368        assert!(status.contains("send-stream"), "{status:?}");
33369        assert_eq!(
33370            f.run(&[b"CLUSTER", b"MIGRATION", b"CANCEL", b"ID", id]),
33371            ":1\r\n"
33372        );
33373        // Cancelled and kept, so an operator can still ask what happened.
33374        let status = f.run(&[b"CLUSTER", b"MIGRATION", b"STATUS", b"ID", id]);
33375        assert!(status.contains("canceled"), "{status:?}");
33376        assert!(
33377            status.contains("Cancelled due to user request"),
33378            "{status:?}"
33379        );
33380    }
33381
33382    /// A replica takes one thing off its master and nothing at all off anybody
33383    /// else, because there is nothing it could be being asked to hand over.
33384    #[test]
33385    fn a_replica_only_hears_the_settings_and_only_from_its_master() {
33386        let mut server = Server::new();
33387        server.enable_cluster("", 7000);
33388        let of = server.cluster_pretend_node(
33389            "5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f",
33390            "10.0.0.9",
33391            7002,
33392        );
33393        server.cluster_pretend_follower(of);
33394        let mut f = Fixture::on(server);
33395        f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33396        let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]);
33397        assert_eq!(
33398            reply,
33399            "-ERR CLUSTER SYNCSLOTS subcommands are only allowed for master\r\n"
33400        );
33401        assert_eq!(flow, Flow::Close);
33402        // Off the master's own stream the settings go through, and anything else
33403        // is dropped without a word rather than refused, because an error written
33404        // into the replication stream is an error nobody reads.
33405        f.session.serve_master(true);
33406        assert_eq!(
33407            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"capa", b"x"]),
33408            "+OK\r\n"
33409        );
33410        assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"SNAPSHOT-EOF"]), "");
33411        assert_eq!(
33412            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"CONF", b"asm-task", b"x"]),
33413            "-ERR Failed to handle master task: x\r\n+OK\r\n",
33414            "there is no migration for a replica to follow along with yet, and \
33415             this option is one the reference keeps going past as well"
33416        );
33417    }
33418
33419    /// The arms that answer nothing at all, which is how the far side of a
33420    /// migration says something it does not expect a reply to.
33421    #[test]
33422    fn the_one_way_arms_of_the_protocol_say_nothing_back() {
33423        let mut f = clustered();
33424        f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33425        assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"ACK", b"x", b"1"]), "");
33426        assert_eq!(f.run(&[b"CLUSTER", b"SYNCSLOTS", b"FAIL", b"boom"]), "");
33427        // The two that say a transfer has ended do the same and drop the
33428        // connection, since there is no transfer here for them to be about.
33429        let (flow, reply) = f.flow(&[b"CLUSTER", b"SYNCSLOTS", b"STREAM-EOF"]);
33430        assert_eq!(reply, "");
33431        assert_eq!(flow, Flow::Close);
33432        // And the one arm that has a real answer on a node with nothing running.
33433        let mut f = clustered();
33434        f.run(&[b"DEBUG", b"MARK-INTERNAL-CLIENT"]);
33435        assert_eq!(
33436            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"RDBCHANNEL", b"abc"]),
33437            "-ERR Invalid task id\r\n"
33438        );
33439        assert_eq!(
33440            f.run(&[
33441                b"CLUSTER",
33442                b"SYNCSLOTS",
33443                b"RDBCHANNEL",
33444                b"0000000000000000000000000000000000000000"
33445            ]),
33446            "-ERR No slot migration task in progress\r\n"
33447        );
33448        assert_eq!(
33449            f.run(&[b"CLUSTER", b"SYNCSLOTS", b"NONSENSE"]),
33450            "-ERR syntax error\r\n"
33451        );
33452    }
33453
33454    /// A transaction is refused at queue time rather than at `EXEC`, so a client
33455    /// finds out about the redirection while it can still do something about it.
33456    #[test]
33457    fn a_transaction_is_refused_when_it_is_queued() {
33458        let mut f = clustered();
33459        f.server.cluster_hand_over(BAR, 1);
33460        assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
33461        assert_eq!(
33462            f.run(&[b"GET", b"bar"]),
33463            format!("-MOVED {BAR} 10.0.0.9:7002\r\n")
33464        );
33465        assert_eq!(
33466            f.run(&[b"EXEC"]),
33467            "-EXECABORT Transaction discarded because of previous errors.\r\n"
33468        );
33469    }
33470
33471    /// The two commands a cluster refuses outright, because there is only one
33472    /// database in a cluster and nothing to swap it with.
33473    #[test]
33474    fn select_and_swapdb_are_not_cluster_commands() {
33475        let mut f = clustered();
33476        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
33477        assert_eq!(
33478            f.run(&[b"SELECT", b"1"]),
33479            "-ERR SELECT is not allowed in cluster mode\r\n"
33480        );
33481        assert_eq!(
33482            f.run(&[b"SWAPDB", b"0", b"1"]),
33483            "-ERR SWAPDB is not allowed in cluster mode\r\n"
33484        );
33485    }
33486
33487    /// Everything in the container is refused on a server that was not started
33488    /// as a cluster node, and so are the three connection commands.
33489    #[test]
33490    fn a_plain_server_has_no_cluster_in_it() {
33491        let mut f = Fixture::new();
33492        for argv in [
33493            &[b"CLUSTER".as_slice(), b"INFO".as_slice()][..],
33494            &[b"CLUSTER", b"MYID"],
33495            &[b"CLUSTER", b"SLOTS"],
33496            &[b"CLUSTER", b"HELP"],
33497            &[b"ASKING"],
33498            &[b"READONLY"],
33499            &[b"READWRITE"],
33500        ] {
33501            assert_eq!(
33502                f.run(argv),
33503                "-ERR This instance has cluster support disabled\r\n",
33504                "{argv:?}"
33505            );
33506        }
33507        // The arity is still checked in front of the refusal.
33508        assert_eq!(
33509            f.run(&[b"CLUSTER", b"KEYSLOT"]),
33510            "-ERR wrong number of arguments for 'cluster|keyslot' command\r\n"
33511        );
33512    }
33513}