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 args;
55mod arrays;
56mod backup;
57mod bits;
58mod blocking;
59mod bloom;
60mod client;
61mod clients;
62mod cms;
63mod cpu;
64mod cuckoo;
65mod geo;
66mod graph;
67mod hashes;
68mod himport;
69mod hll;
70mod indexing;
71mod json;
72mod keyspace;
73mod lists;
74mod lua;
75mod migrate;
76mod misses;
77mod multi;
78mod notify;
79mod pubsub;
80mod scan;
81mod scripting;
82mod search;
83mod server;
84mod sets;
85mod streams;
86mod strings;
87mod suggest;
88pub mod table;
89mod tdigest;
90mod topk;
91mod ts;
92mod vectors;
93mod vfilter;
94mod zsets;
95
96pub use args::Args;
97pub use blocking::{Parked, Waiters};
98pub use clients::Client;
99pub(crate) use pubsub::Envelope;
100pub use server::parse_memory;
101pub use table::{COMMANDS, Spec, arity_ok, lookup};
102
103use crate::reply::Out;
104use std::cell::Cell;
105use std::path::{Path, PathBuf};
106use std::sync::Arc;
107use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
108use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize};
109use yo_common::lock::{Held, Lock};
110use yo_common::{Code, Error};
111use yo_kv::cold::Store;
112use yo_kv::lookups;
113use yo_kv::{Clock, Db, Keyspace};
114use yo_search::Registry;
115
116use multi::Watches;
117use search::cursor::Cursors;
118
119/// How many databases a server has.
120///
121/// Redis's default is sixteen and its `databases` setting can change it. Ours
122/// is sixteen and cannot, which is why `CONFIG GET databases` can answer with a
123/// constant. Nothing in the design needs the number to be fixed; nothing yet
124/// needs it not to be.
125pub const DATABASES: usize = 16;
126
127/// Every database's bit in [`Server::dirty`], which is what a fresh server
128/// starts on so that the first maintenance turn asks all of them.
129///
130/// A `u64` holds sixteen bits with room to spare, and the assertion below is
131/// what turns raising [`DATABASES`] past sixty four into a build failure rather
132/// than a shift that silently drops the databases past the end.
133const ALL_DATABASES: u64 = if DATABASES == 64 {
134    u64::MAX
135} else {
136    (1u64 << DATABASES) - 1
137};
138const _: () = assert!(DATABASES <= 64);
139
140/// How many keys one command throws away before it leaves the rest to the next.
141///
142/// A bound and not a loop to the end, because this runs in front of a client
143/// that is waiting for its reply, and a server a long way over its limit would
144/// otherwise hold that client for as long as it took to walk all the way back
145/// under. Sixty four is a batch's worth of commands, so a server that went over
146/// by what one batch allocated comes back under in one command, and a server
147/// whose limit was just cut in half works through it over the next few thousand
148/// rather than in one long stall. Redis bounds the same loop by a time slice
149/// instead of a count and hands the rest to a timer; there is no timer here, so
150/// the rest goes to the next command that runs.
151const EVICT_BUDGET: usize = 64;
152
153/// The `maxstore` a server with no storage limit carries.
154///
155/// Sixteen exabytes, which is every disk there is and then some, so a server
156/// that set a limit this high and a server that set none behave the same way and
157/// the only difference is what `CONFIG GET maxstore` says. Zero cannot be the
158/// sentinel because zero is a limit with a meaning: nothing may live on the
159/// file.
160const NO_MAXSTORE: u64 = u64::MAX;
161
162/// What a server says to a command that would allocate when it has no room.
163///
164/// Redis's `shared.oomerr`, word for word including the full stop, because
165/// clients match on the `OOM` prefix and people match on the sentence.
166const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
167
168/// What the connection should do after a command.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum Flow {
171    /// Read the next command.
172    Continue,
173    /// Write what is buffered and then close, which is what `QUIT` asks for.
174    Close,
175    /// Nothing was written and nothing is owed yet.
176    ///
177    /// The client is on the waiter list and its reply comes when a key it named
178    /// has something in it or when its deadline passes, whichever happens first.
179    /// Until then the connection stops reading commands, because a client that
180    /// is waiting for an answer is not a client that has sent another question.
181    Block,
182    /// Nothing was written and the command has not run at all.
183    ///
184    /// The server is paused, so the connection keeps the command it was about to
185    /// run and runs it again once the pause is over. Everything the client
186    /// pipelined behind it is kept in the order it arrived, the same way a
187    /// blocking command keeps it.
188    Hold,
189}
190
191/// A number one thread adds to and any thread may read.
192///
193/// The add is a load, an add and a store rather than a fetch and add, which on
194/// x86 is three ordinary instructions instead of one locked one. That is sound
195/// because every counter here has exactly one writer, which is what the slots
196/// below are for: two threads never hold the same counter, so nothing can be
197/// lost between the load and the store. A reader can be a command or two behind,
198/// and `INFO` on a running server is behind by the time the reply reaches the
199/// client anyway.
200#[derive(Debug, Default)]
201pub struct Counter(AtomicU64);
202
203impl Counter {
204    /// One more.
205    fn bump(&self) {
206        self.0.store(self.get().wrapping_add(1), Relaxed);
207    }
208
209    /// One fewer, stopping at zero.
210    ///
211    /// The floor is for the gauge, which is the number of open connections: a
212    /// close that arrives without its open, which nothing can do now and a
213    /// misplaced call could, is a number that stays at zero rather than one
214    /// that wraps to eighteen quintillion clients.
215    fn drop_one(&self) {
216        self.0.store(self.get().saturating_sub(1), Relaxed);
217    }
218
219    /// What it says.
220    fn get(&self) -> u64 {
221        self.0.load(Relaxed)
222    }
223
224    /// Back to zero, which is `CONFIG RESETSTAT`.
225    fn zero(&self) {
226        self.0.store(0, Relaxed);
227    }
228}
229
230/// The numbers `INFO` reports that this layer cannot see for itself.
231///
232/// The reactor owns the sockets, so the reactor is what knows how many clients
233/// there are. It counts them here and nothing else does anything with them
234/// except report them.
235#[derive(Debug, Default)]
236pub struct Stats {
237    /// Connections open right now.
238    clients: Counter,
239    /// Connections accepted since the server started.
240    connections: Counter,
241    /// Commands run since the server started, which this layer counts itself.
242    commands: Counter,
243}
244
245impl Stats {
246    /// A connection arrived.
247    pub fn opened(&self) {
248        self.clients.bump();
249        self.connections.bump();
250    }
251
252    /// A connection went away.
253    pub fn closed(&self) {
254        self.clients.drop_one();
255    }
256}
257
258/// Every thread's [`Stats`] added together, which is what `INFO` answers.
259#[derive(Debug, Clone, Copy, Default)]
260pub struct Totals {
261    /// Connections open right now.
262    pub clients: u64,
263    /// Connections accepted since the server started.
264    pub connections: u64,
265    /// Commands run since the server started.
266    pub commands: u64,
267}
268
269thread_local! {
270    /// Which set of counters the running thread writes into.
271    ///
272    /// Claimed the first time a thread counts anything and kept for as long as
273    /// the thread runs. It is a number rather than a pointer, so a thread that
274    /// has counted on one server and then counts on another lands in the same
275    /// place in both, and a process with two servers in it shares the numbering
276    /// between them. That is the tests and it is not `yodb`, which has one.
277    static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
278}
279
280/// What one thread keeps to itself.
281///
282/// One of these per thread and not one per server, because a number every
283/// thread writes to is a cache line every thread has to own to write to it, and
284/// at a few million commands a second that one line is the server. So each
285/// thread writes into its own and whoever needs the whole picture, which is
286/// `INFO` and the maintenance turn, puts the pieces together when it asks.
287///
288/// A cache line apart for the same reason, so that two threads writing at once
289/// are not two threads passing one line back and forth.
290#[derive(Debug)]
291#[repr(align(64))]
292struct Local {
293    /// What the reactor counts.
294    stats: Stats,
295    /// A counter per command, for `INFO commandstats`.
296    cmdstats: CommandStats,
297    /// Which databases this thread has run a command against since the
298    /// maintenance turn last took the mask.
299    ///
300    /// One bit per database. The thread ors into it and the turn takes the whole
301    /// of it with a swap, which is what keeps a mark that lands during the swap
302    /// from being lost: the worst that can happen is a bit the turn has already
303    /// taken being set again, and that costs one more look at a database with
304    /// nothing to collect.
305    dirty: AtomicU64,
306    /// The mask this thread's maintenance turn is working from.
307    ///
308    /// Its own and not a shared one, because a turn reads it in place and then
309    /// clears bits of it, and a shared mask cleared that way would lose whatever
310    /// another thread marked in between. Every thread turns a loop and every
311    /// loop maintains, so what stops the same work being done twice is not the
312    /// mask but the stripe lock underneath it: two threads that both look at
313    /// database nine take turns, and the second one finds nothing left to move.
314    ///
315    /// Starts with every database set, so a server that has just been built
316    /// looks at all of them once rather than waiting to be told about the ones
317    /// something was loaded into before any command ran.
318    turn: AtomicU64,
319    /// How many of this thread's clients are on the waiter list.
320    ///
321    /// The waiter list is one list behind one lock, and a thread can only answer
322    /// the waiters it parked itself, so a thread with none of its own has no
323    /// reason to take that lock at all. Without this the check is the server
324    /// wide count, and one client blocked anywhere puts every thread through the
325    /// shared lock after every command it runs and again on every disconnect.
326    ///
327    /// Only the thread this belongs to writes it, because parking, answering and
328    /// forgetting a waiter all happen on the thread that read the command, so
329    /// the load and the store either side of a change cannot lose one.
330    parked: AtomicUsize,
331}
332
333impl Default for Local {
334    fn default() -> Local {
335        Local {
336            stats: Stats::default(),
337            cmdstats: CommandStats::default(),
338            dirty: AtomicU64::new(0),
339            turn: AtomicU64::new(ALL_DATABASES),
340            parked: AtomicUsize::new(0),
341        }
342    }
343}
344
345impl Local {
346    /// Note that a command has run against these databases.
347    fn mark(&self, dbs: u64) {
348        self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
349    }
350
351    /// Add `dbs` to what this thread's turn is going to look at.
352    fn note(&self, dbs: u64) {
353        self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
354    }
355
356    /// Take `at` off the list of databases this thread's turn will look at.
357    fn done(&self, at: usize) {
358        self.turn
359            .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
360    }
361
362    /// Whether this thread's turn still has database `at` to look at.
363    fn wanted(&self, at: usize) -> bool {
364        self.turn.load(Relaxed) & (1u64 << at) != 0
365    }
366
367    /// Note that `n` more of this thread's clients are parked.
368    fn blocked(&self, n: usize) {
369        self.parked
370            .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
371    }
372
373    /// Note that `n` of them are not parked any more.
374    fn woke(&self, n: usize) {
375        self.parked
376            .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
377    }
378}
379
380/// Room for one thread, which is what a server starts with.
381fn one_thread() -> Box<[Local]> {
382    slots(1)
383}
384
385/// Room for `threads` of them.
386fn slots(threads: usize) -> Box<[Local]> {
387    (0..threads.max(1)).map(|_| Local::default()).collect()
388}
389
390/// Where the process was started, which is what `dir` defaults to.
391///
392/// A dot if the working directory cannot be read, which happens when it has
393/// been deleted out from under a running process. That is not a reason to
394/// refuse to start a server, and it leaves `BACKUP` to fail with the real error
395/// from the filesystem if anybody asks for one.
396fn working_dir() -> PathBuf {
397    std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
398}
399
400/// One command's counters, for `INFO commandstats`.
401///
402/// Three of Redis's five. `usec` and `usec_per_call` are not here because
403/// nothing times a command, and timing one means two clock reads around a call
404/// that takes tens of nanoseconds to begin with. Redis pays that because Redis
405/// has room for it; this does not, and a zero under a name that says microseconds
406/// is worse than an absent field, which is the same rule the rest of `INFO`
407/// follows.
408#[derive(Debug, Clone, Copy, Default)]
409pub struct CommandStat {
410    /// Times the command ran, whatever it answered.
411    pub calls: u64,
412    /// Times it was turned away before it ran, which is the wrong number of
413    /// arguments or no room under `maxmemory`.
414    pub rejected: u64,
415    /// Times it ran and answered with an error.
416    pub failed: u64,
417}
418
419impl CommandStat {
420    /// Whether this command has ever been seen.
421    ///
422    /// A row that has not is left out of the reply, which is what Redis does and
423    /// is why the section is a handful of lines on a working server rather than
424    /// one line per command in the table.
425    const fn seen(&self) -> bool {
426        self.calls != 0 || self.rejected != 0 || self.failed != 0
427    }
428}
429
430/// One command's counters as one thread keeps them.
431///
432/// The same three numbers as [`CommandStat`], which is what they add up to when
433/// `INFO` asks. This is the written form and that is the read one.
434#[derive(Debug, Default)]
435struct Row {
436    /// Times the command ran.
437    calls: Counter,
438    /// Times it was turned away before it ran.
439    rejected: Counter,
440    /// Times it ran and answered with an error.
441    failed: Counter,
442}
443
444/// A counter per command, indexed the way [`table::index_of`] says.
445///
446/// A flat array and not a map, because the dispatcher is already holding the
447/// spec and the spec's position in the table is two addresses subtracted. That
448/// makes the counting a load, an add and a store on a row the previous command
449/// of the same name has already pulled into cache.
450#[derive(Debug)]
451struct CommandStats(Box<[Row]>);
452
453impl Default for CommandStats {
454    fn default() -> CommandStats {
455        CommandStats((0..table::count()).map(|_| Row::default()).collect())
456    }
457}
458
459impl CommandStats {
460    /// The row for one command.
461    fn at(&self, spec: &'static Spec) -> &Row {
462        &self.0[table::index_of(spec)]
463    }
464}
465
466/// Where a database gets its store from, asked by database number.
467///
468/// `None` means that database cannot have one. The caller owns whatever the
469/// stores are cut out of, which for `yodb` is one `.yo` file with a log per
470/// database, and this crate never learns what any of that is.
471pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
472
473/// Every thread that runs commands here shares this server, so it has to be
474/// `Send` and `Sync`, and the check is here so that a type added to it that is
475/// neither is a compile error where it was added rather than an error in the
476/// code that starts the threads.
477const _: () = {
478    const fn shareable<T: Send + Sync>() {}
479    shareable::<Server>();
480};
481
482/// Everything a server holds.
483///
484/// One per process, however many threads are serving out of it. What is inside
485/// is either shared outright, which is the counters and the settings, or behind
486/// a lock, which is the stripes and the few pieces of state a command can
487/// change. What makes this a server rather than a shard is that it is the whole
488/// of what a connection can address.
489pub struct Server {
490    dbs: Vec<Db>,
491    /// How many stripes each database is cut into, the same for all of them.
492    ///
493    /// Kept here as well as in each database so that the flat slot arithmetic
494    /// below is a multiply and a divide against a field on the server rather
495    /// than a walk asking each database how wide it is.
496    width: usize,
497    clock: Clock,
498    started_ms: u64,
499    /// Where the next maintenance turn starts looking, so that a database
500    /// under constant write load cannot hold the other fifteen's space.
501    ///
502    /// Shared, because compaction is asked for from two places: the maintenance
503    /// turn, which is one thread, and a command that went over the memory limit
504    /// and is trying to get back under it, which is any thread. Two threads that
505    /// read the same cursor start on the same database, and what that costs is
506    /// one of them finding the other has already moved what was there.
507    next_db: AtomicUsize,
508    /// One bit per database, set when a command ran against it.
509    ///
510    /// The maintenance turn after every batch used to ask all sixteen
511    /// databases whether they had anything to collect, and asking costs a load
512    /// and a store in each one. Fifteen of those are cold lines on a server
513    /// where every client is on database zero, which is every server, and the
514    /// answer is no every time. This is the cheap half of the question: a
515    /// database nobody has touched since it last said no cannot have started
516    /// saying yes.
517    ///
518    /// What the connections are holding, kept by the engine.
519    ///
520    /// Shared, because every thread has connections and the memory total is one
521    /// total. Each thread adds and subtracts its own change rather than storing
522    /// a figure it worked out, so two threads whose buffers grew in the same
523    /// moment both count.
524    conn_bytes: AtomicUsize,
525    /// The `maxmemory` limit in bytes, zero when there is not one.
526    ///
527    /// Zero is the default and it is the whole reason the check in front of
528    /// every write is one comparison against a field that is already warm. It
529    /// is read by every command on every thread and written by a client that
530    /// sends `CONFIG SET`, so it is a number the threads can share rather than
531    /// a field one of them owns.
532    maxmemory: AtomicU64,
533    /// Where a database gets a store from the first time it needs one.
534    ///
535    /// A closure and not a store, because there are sixteen databases and a
536    /// server that fills memory on database zero should not have opened
537    /// anything for the other fifteen. Nothing is asked of this until a memory
538    /// limit is actually reached, so a server that never fills memory never
539    /// opens a file, and a server that has no file never has one of these.
540    ///
541    /// `None` from the closure means that database cannot have one, which is
542    /// how the caller says the file it opened has no more room for logs.
543    ///
544    /// Behind a lock because it is a closure the caller gave us and there is no
545    /// saying it can be run by two threads at once. It is asked once per
546    /// database, the first time that database has to move something, so a
547    /// server that has reached its memory limit takes this lock sixteen times
548    /// in its life.
549    store: Lock<Option<Box<StoreSource>>>,
550    /// The `maxstore` limit in bytes, `None` when there is not one.
551    ///
552    /// The storage limit, and the other half of the inversion `14` section 4.1
553    /// describes. `maxmemory` is a limit on memory and the right answer to a
554    /// memory limit on a system with a file under it is to move data to the
555    /// file, not to delete it. Deleting is the right answer to a limit on the
556    /// file, and this is that limit.
557    ///
558    /// Zero is not "no limit" here, which is the one place this reads
559    /// differently from `maxmemory` and is the difference that makes a drop in
560    /// cache possible. A storage budget of zero bytes means nothing may live on
561    /// the file, so migration cannot make room and eviction is the only thing
562    /// left, which is Redis exactly. `None` is no limit and is the default,
563    /// which with `noeviction` means the database grows until the disk is full
564    /// and then writes fail, which is what a database does.
565    ///
566    /// Shared between the threads the same way `maxmemory` is, and no limit is
567    /// [`NO_MAXSTORE`] rather than a second field saying whether the first one
568    /// counts. Two fields cannot be read as one, and a limit that was on when
569    /// the bytes were read and off by the time the number was is a limit that
570    /// answers from a server that never existed.
571    maxstore: AtomicU64,
572    /// What [`Server::memory_bytes`] said at the last maintenance turn.
573    ///
574    /// The reading is a walk over every collection in every database and cannot
575    /// go on a command path, so the command path reads this instead and is at
576    /// most one batch behind. What that costs is overshoot: a server can end a
577    /// batch holding one batch's worth of allocation more than its limit before
578    /// anything notices. A batch is 64 commands, so that is bounded by what 64
579    /// commands can allocate and not by how long the server runs.
580    ///
581    /// Only kept up to date when there is a limit to judge it against. A server
582    /// with no `maxmemory` never reads it and never pays for it.
583    ///
584    /// Shared, because it is read in front of every write on every thread and
585    /// written by whichever thread last took a reading. A reader that catches it
586    /// mid write gets one of the two readings and both of them were true a
587    /// moment ago, which is all this number ever claims to be.
588    used: AtomicUsize,
589    /// Which database the next eviction draws from.
590    ///
591    /// Its own cursor and not [`Server::next_db`], because eviction and
592    /// compaction move at different rates and sharing one would make the
593    /// database that gets compacted depend on how many keys were evicted.
594    ///
595    /// Shared for the same reason [`Server::next_db`] is, and with the same
596    /// answer: two threads evicting at once may pick the same database, and one
597    /// of them finds the other got there first and moves on.
598    evict_db: AtomicUsize,
599    /// Which database the next active expiry sweep starts at.
600    ///
601    /// A third cursor for the same reason there is a second one. A sweep runs on
602    /// every turn of the loop and compaction runs when there is dead space, so
603    /// sharing a cursor would make which database gets swept depend on which one
604    /// was last collected.
605    expire_db: AtomicUsize,
606    /// The millisecond the last active expiry sweep ran on, so the next one on
607    /// the same millisecond does not bother.
608    ///
609    /// One for the server and not one per thread, so the sweeping a server does
610    /// is a function of how long it has been running and not of how many threads
611    /// it was started with. Two threads that read the same millisecond can both
612    /// decide to sweep, which costs one extra sweep of a budget that is already
613    /// small and cannot happen twice for the same millisecond more than once per
614    /// thread.
615    expire_ms: AtomicU64,
616    /// Clients parked on a blocking command.
617    ///
618    /// Behind a lock because a client parks on the thread that ran its command
619    /// and is woken by whichever thread later puts something under a key it
620    /// named, and those are not the same thread. The lock is only ever taken to
621    /// park somebody, to serve somebody or to forget a connection that has gone,
622    /// so a command that does not block never touches it.
623    waiters: Lock<Waiters>,
624    /// How many clients are parked.
625    ///
626    /// Beside the list rather than read out of it, because every command asks
627    /// whether anybody is waiting and nearly every answer is no. Taking a lock
628    /// to be told no would be a cache line every thread has to own to ask, which
629    /// is the cost the list was put behind a lock to avoid.
630    ///
631    /// Written under the lock, by whoever changed the list, so the number and
632    /// the list agree except while a change is in progress. A reader that asks
633    /// during one is told about the moment before it, and the worst that costs
634    /// is a walk of the list that serves nobody or one that has not started yet
635    /// and happens on the next command instead.
636    parked: AtomicUsize,
637    /// Sockets `MIGRATE` is holding open to the servers it has talked to.
638    ///
639    /// Empty on a server nobody has migrated a key out of, which is nearly all
640    /// of them, and it costs a vector's three words to be empty.
641    ///
642    /// Behind a lock because a socket cannot be written by two threads at once
643    /// and a cache of them cannot be searched by one while another is taking an
644    /// entry out. It is held for the whole of a migration, which is a round trip
645    /// to another server, so two threads migrating at the same time take turns.
646    /// That is the right way round: the alternative is a socket per thread per
647    /// peer, and a `MIGRATE` is not what a server spends its time on.
648    peers: Lock<migrate::Peers>,
649    /// What each thread that runs commands here keeps to itself.
650    ///
651    /// A fixed list, because a thread reading its own entry must not have the
652    /// list move under it, and how many threads there will be is known before
653    /// any of them starts. A server nobody told otherwise has one.
654    locals: Box<[Local]>,
655    /// How many entries have been handed out.
656    claimed: AtomicUsize,
657    /// The next client id, which is what `CLIENT ID` answers.
658    ///
659    /// On the server and not on a front, because CLIENT LIST and CLIENT KILL
660    /// name a client by this number across the whole server, and two threads
661    /// counting on their own would hand the same number to two clients. Starts
662    /// at one so that zero is never a client, which is what makes it usable as
663    /// the id of a command that came from nowhere.
664    next_client: AtomicU64,
665    /// Where `BACKUP` puts its files, and where `CONFIG GET dir` points.
666    ///
667    /// Absolute, and resolved once when the server is built rather than every
668    /// time somebody asks. `BACKUP LIST` answers absolute paths and a client is
669    /// entitled to hand one of them to a copy tool, so a relative path that
670    /// meant something different after a `chdir` would be a path that stops
671    /// working for reasons nobody could see.
672    dir: PathBuf,
673    /// What backup is running, if one is.
674    ///
675    /// On the server and not on a session, because a backup outlives the
676    /// connection that asked for it and any other connection can seal it.
677    ///
678    /// Behind a lock because there is one backup at a time and any thread can be
679    /// the one that starts, seals or abandons it. It is held while the base file
680    /// is written, which is what keeps two `BACKUP START` commands from writing
681    /// over each other's files.
682    backup: Lock<backup::State>,
683    /// Whether a sealed backup is sitting on disk.
684    ///
685    /// Beside the state rather than read out of it, because every batch of
686    /// commands asks whether there is a backup old enough to sweep away and on
687    /// nearly every server the answer is that there is no backup at all. A load
688    /// answers that. Written under the lock by whoever moved the phase, so a
689    /// reader that asks mid-change sees the moment before and sweeps one batch
690    /// later, which is a file staying on disk for a few microseconds longer than
691    /// it had to.
692    sealed: AtomicBool,
693    /// The search indexes and the names pointing at them.
694    ///
695    /// On the server and not on a database, which is the one collection in this
696    /// build that is. A real server keeps its indexes in the search module, the
697    /// module has one table, and `SELECT 1` followed by `FT._LIST` lists the
698    /// indexes made on database zero. `search.rs` has the rest of why.
699    ///
700    /// A server nobody has made an index on holds two empty vectors here, which
701    /// is six words and no allocation.
702    ///
703    /// Behind a lock because an index is made and dropped by whichever thread
704    /// ran the command, and the table it goes in is one table. Only the `FT`
705    /// commands take it, so nothing a working server spends its time on comes
706    /// through here.
707    search: Lock<Registry>,
708    /// The replies that came back in pieces and have pieces left.
709    ///
710    /// Beside the indexes rather than inside one, because a cursor is read
711    /// under its own number and a real server resolves the index name on a read
712    /// and then pays no attention to it, so a cursor made on one index reads
713    /// through the name of another. Behind a lock for the reason the registry is
714    /// behind one, and a server nobody has opened a cursor on holds an empty map
715    /// here.
716    cursors: Lock<Cursors>,
717    /// The script bodies `EVALSHA` runs, by their digests.
718    ///
719    /// On the server rather than on a connection, because that is the whole
720    /// point of the cache. A client loads its scripts once when it starts up,
721    /// on whichever connection it happened to open first, and then sends nothing
722    /// but digests forever after, from every connection in its pool.
723    ///
724    /// Behind a lock because loading is a write and every thread can be the one
725    /// doing it. Held only long enough to add a body or copy one out, never
726    /// across a run: a running script calls commands, and those take locks of
727    /// their own.
728    scripts: Lock<lua::Scripts>,
729    /// Every library `FUNCTION LOAD` has taken, and what each one registered.
730    ///
731    /// Data only. A callback is a Lua value and there is an interpreter per
732    /// thread, so what is here is the name, the code, the digest of the code and
733    /// one row per function, and every thread compiles the code for itself the
734    /// first time one of its clients calls into the library.
735    libraries: Lock<lua::library::Libraries>,
736    /// Set by `SHUTDOWN`, and read by whatever is turning the loop.
737    ///
738    /// A flag rather than an exit, because the command layer is not what owns
739    /// the process. It runs inside a batch that has other commands behind it
740    /// and inside a driver that has a socket file to take away and a file to
741    /// close, and a server that calls `exit` from a command handler skips all
742    /// of that. So the command says stop and the driver stops, on the same turn
743    /// and through the same door a signal uses.
744    stopping: AtomicBool,
745    /// Every key any connection is watching, with a stamp on each.
746    ///
747    /// Here and not on the connection, and that is the whole design of `WATCH`
748    /// rather than an implementation detail. A connection cannot see a write
749    /// another thread made, so what records the write has to sit beside the key.
750    /// See the `multi` module for the rest of it.
751    watches: Lock<Watches>,
752    /// How many watched keys there are, so the write path can ask without
753    /// taking the lock.
754    ///
755    /// Zero on every server nobody has sent `WATCH` to, which is very nearly all
756    /// of them, and that is what keeps the cost of watches on a server that has
757    /// none down to one relaxed load per write.
758    watched: AtomicUsize,
759    /// Who is listening on what, for pub/sub.
760    ///
761    /// Here and not on the connection for the reason the watches are: a publish
762    /// arrives on a connection that knows nothing about the subscribers, so what
763    /// finds them has to sit beside the name rather than beside the client. See
764    /// the `pubsub` module for the rest of it.
765    pubsub: Lock<pubsub::Registry>,
766    /// How many subscriptions there are, so a publish can ask without taking
767    /// the lock.
768    ///
769    /// Zero on every server nobody has subscribed on, which is what keeps
770    /// `PUBLISH` on a server with no listeners down to one relaxed load.
771    subs: AtomicUsize,
772    /// One inbox per thread, for messages published on another one.
773    ///
774    /// Its own array and not a field on [`Local`], which is a cache line per
775    /// thread precisely so that no other thread writes to it. A mailbox is a
776    /// line another thread is meant to write to, so it gets one of its own.
777    mail: Box<[pubsub::Mailbox]>,
778    /// Which classes of keyspace notification are turned on.
779    ///
780    /// Zero is off and is the default, so the read every write does costs one
781    /// relaxed load and a test. It is `notify-keyspace-events` and the bits are
782    /// Redis's own, kept in the `notify` module beside the two parsers that
783    /// turn them into the setting text and back.
784    notify: AtomicU32,
785    /// One row per open connection, which is what `CLIENT LIST` reads and what
786    /// `CLIENT KILL` writes to.
787    ///
788    /// Here and not on the front for the reason the watches and the
789    /// subscriptions are here: both commands are about connections the thread
790    /// running them does not own and cannot borrow. See the `clients` module.
791    clients: Lock<clients::Clients>,
792    /// How many connections have been asked to close and not closed yet.
793    ///
794    /// Zero on every server nobody has run `CLIENT KILL` on, which is what keeps
795    /// the check on the flush path down to one load.
796    kills: AtomicUsize,
797    /// When the pause `CLIENT PAUSE` armed runs out, and what it covers.
798    ///
799    /// One word rather than a deadline and a mode beside it, because every
800    /// command on every thread reads this and a server that has never been
801    /// paused should pay one load and one test for it. The low bit says whether
802    /// everything is held or only the writes, and the rest is the deadline in
803    /// milliseconds. Zero is no pause at all, which is why the deadline is
804    /// shifted up rather than packed into the top bits: the whole word is zero
805    /// exactly when nothing is armed.
806    pause: AtomicU64,
807}
808
809impl Server {
810    /// A server with [`DATABASES`] empty databases on the system clock.
811    #[must_use]
812    pub fn new() -> Server {
813        let clock = Clock::system();
814        Server {
815            dbs: (0..DATABASES)
816                .map(|_| Db::with_clock(clock.clone(), 1))
817                .collect(),
818            width: 1,
819            started_ms: clock.now_ms(),
820            clock,
821            next_db: AtomicUsize::new(0),
822            conn_bytes: AtomicUsize::new(0),
823            maxmemory: AtomicU64::new(0),
824            store: Lock::new(None),
825            maxstore: AtomicU64::new(NO_MAXSTORE),
826            used: AtomicUsize::new(0),
827            evict_db: AtomicUsize::new(0),
828            expire_db: AtomicUsize::new(0),
829            expire_ms: AtomicU64::new(0),
830            waiters: Lock::default(),
831            parked: AtomicUsize::new(0),
832            peers: Lock::default(),
833            locals: one_thread(),
834            claimed: AtomicUsize::new(0),
835            next_client: AtomicU64::new(1),
836            dir: working_dir(),
837            backup: Lock::default(),
838            sealed: AtomicBool::new(false),
839            search: Lock::new(Registry::new()),
840            cursors: Lock::default(),
841            scripts: Lock::default(),
842            libraries: Lock::default(),
843            stopping: AtomicBool::new(false),
844            watches: Lock::default(),
845            watched: AtomicUsize::new(0),
846            pubsub: Lock::default(),
847            subs: AtomicUsize::new(0),
848            notify: AtomicU32::new(0),
849            clients: Lock::default(),
850            kills: AtomicUsize::new(0),
851            pause: AtomicU64::new(0),
852            mail: pubsub::boxes(1),
853        }
854    }
855
856    /// A server whose databases are cut into `width` stripes each.
857    ///
858    /// Not reachable from the command line yet. Every command group answers on
859    /// a server of any width now and so does everything that walks a whole
860    /// database, and the tests run each group at a width of one and a width of
861    /// eight and check the two agree.
862    ///
863    /// What is left before this is what `--threads` sets is the engine. A
864    /// database being several objects is what makes more than one thread
865    /// possible, and it is not what makes more than one thread happen.
866    #[must_use]
867    pub fn with_width(width: usize) -> Server {
868        let mut server = Server::new();
869        // The server's own clock and not a fresh one, because a database
870        // reading a different clock from the server it is on is a database
871        // whose keys expire against a time nobody set.
872        let clock = server.clock.clone();
873        server.dbs = (0..DATABASES)
874            .map(|_| Db::with_clock(clock.clone(), width))
875            .collect();
876        server.width = server.dbs[0].width();
877        server
878    }
879
880    /// A server on a clock the caller moves by hand, for tests.
881    #[must_use]
882    pub fn with_clock(clock: Clock) -> Server {
883        Server {
884            dbs: (0..DATABASES)
885                .map(|_| Db::with_clock(clock.clone(), 1))
886                .collect(),
887            width: 1,
888            started_ms: clock.now_ms(),
889            clock,
890            next_db: AtomicUsize::new(0),
891            conn_bytes: AtomicUsize::new(0),
892            maxmemory: AtomicU64::new(0),
893            store: Lock::new(None),
894            maxstore: AtomicU64::new(NO_MAXSTORE),
895            used: AtomicUsize::new(0),
896            evict_db: AtomicUsize::new(0),
897            expire_db: AtomicUsize::new(0),
898            expire_ms: AtomicU64::new(0),
899            waiters: Lock::default(),
900            parked: AtomicUsize::new(0),
901            peers: Lock::default(),
902            locals: one_thread(),
903            claimed: AtomicUsize::new(0),
904            next_client: AtomicU64::new(1),
905            dir: working_dir(),
906            backup: Lock::default(),
907            sealed: AtomicBool::new(false),
908            search: Lock::new(Registry::new()),
909            cursors: Lock::default(),
910            scripts: Lock::default(),
911            libraries: Lock::default(),
912            stopping: AtomicBool::new(false),
913            watches: Lock::default(),
914            watched: AtomicUsize::new(0),
915            pubsub: Lock::default(),
916            subs: AtomicUsize::new(0),
917            notify: AtomicU32::new(0),
918            clients: Lock::default(),
919            kills: AtomicUsize::new(0),
920            pause: AtomicU64::new(0),
921            mail: pubsub::boxes(1),
922        }
923    }
924
925    /// One database, by index.
926    ///
927    /// A caller that knows which key it wants names the one stripe the key is
928    /// on rather than working over the whole thing, which is what `at` and its
929    /// neighbours on [`Db`] are for. A caller that is about a database rather
930    /// than about a key, which is the snapshot walk and a setting, works over
931    /// all of them.
932    ///
933    /// The database is marked as having had something run against it, which is
934    /// what this does that [`Server::striped_ref`] does not. Anything that only
935    /// reads asks for that one and leaves the mark alone.
936    ///
937    /// The borrow is shared, and what makes that enough is that a database is
938    /// several stripes behind a lock each. A caller that wants to change
939    /// something holds the stripe it is changing, so two threads working on two
940    /// keys work at once and two working on one key take turns, which is the
941    /// whole point of cutting a database up.
942    ///
943    /// # Panics
944    ///
945    /// If `i` is not a database. `SELECT` is the only way a client changes the
946    /// index and it checks, so an index that is out of range here is a bug in
947    /// the caller and not something a client can ask for.
948    pub fn striped(&self, i: usize) -> &Db {
949        self.mine().mark(1u64 << i);
950        &self.dbs[i]
951    }
952
953    /// Every keyspace on the server, which is every stripe of every database.
954    ///
955    /// What the aggregates walk. A total over the whole server is a total over
956    /// all of these and the stripe boundaries do not appear in it, which is
957    /// what makes the numbers `INFO` reports the same numbers whatever the
958    /// server was cut into.
959    fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
960        self.dbs
961            .iter()
962            .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
963    }
964
965    /// How many keyspaces there are, counting every stripe of every database.
966    ///
967    /// The maintenance turns walk these rather than the databases, because a
968    /// stripe is the thing that holds an arena and a deadline heap and so it is
969    /// the thing that has anything to collect.
970    const fn slots(&self) -> usize {
971        DATABASES * self.width
972    }
973
974    /// Which database slot `i` belongs to.
975    const fn slot_db(&self, i: usize) -> usize {
976        i / self.width
977    }
978
979    /// Keyspace `i` of [`Server::slots`].
980    fn slot(&self, i: usize) -> Held<'_, Keyspace> {
981        let (db, stripe) = (i / self.width, i % self.width);
982        self.dbs[db].hold_stripe(stripe)
983    }
984
985    /// Where `BACKUP` writes and what `CONFIG GET dir` answers.
986    #[must_use]
987    pub fn dir(&self) -> &Path {
988        &self.dir
989    }
990
991    /// Point the server at a different directory, which `yodb serve --dir` does.
992    ///
993    /// Only before it is serving. There is no `CONFIG SET dir` here and there
994    /// is none on a real server either without turning protected configs on,
995    /// for the good reason that moving it out from under a running backup would
996    /// leave files nothing can find again.
997    pub fn set_dir(&mut self, dir: PathBuf) {
998        self.dir = dir;
999    }
1000
1001    /// Drop a sealed backup that has outlived `backup-sealed-ttl`.
1002    ///
1003    /// Once per batch, from the same maintenance turn that collects the arena.
1004    /// It reads two fields and returns on a server that has never taken a
1005    /// backup, which is nearly all of them.
1006    pub fn backup_expire(&self) {
1007        backup::expire(self);
1008    }
1009
1010    /// Ask for the server to stop, which is what `SHUTDOWN` does.
1011    ///
1012    /// It sets a flag and returns. Nothing here closes a socket, flushes a file
1013    /// or ends the process, because none of those belong to this layer, and a
1014    /// batch that is halfway through still has to finish and be written out.
1015    pub fn stop(&self) {
1016        self.stopping.store(true, Release);
1017    }
1018
1019    /// Whether somebody has asked the server to stop.
1020    ///
1021    /// Read once per turn by the loop, next to the flag a signal sets. The two
1022    /// mean the same thing and are separate only because one arrives from the
1023    /// operating system and the other from a client.
1024    #[must_use]
1025    pub fn stopping(&self) -> bool {
1026        self.stopping.load(Acquire)
1027    }
1028
1029    /// One database, by index, without taking it mutably.
1030    ///
1031    /// What the prefetch stage needs. It runs for all 64 commands in a batch
1032    /// before any of them executes, so it cannot hold the mutable borrow `run`
1033    /// is about to want, and it does not need one: warming a cache line reads
1034    /// nothing and changes nothing.
1035    #[must_use]
1036    pub fn striped_ref(&self, i: usize) -> &Db {
1037        &self.dbs[i]
1038    }
1039
1040    /// The stripe that answers for a database when a setting is read back.
1041    ///
1042    /// A ladder setting and an eviction policy are one number on a real server,
1043    /// and the fact that every stripe of every database carries a copy of it is
1044    /// ours rather than the client's problem. A write puts the same value on
1045    /// every one of them, so any stripe answers for all of them and this is the
1046    /// first one.
1047    fn settings(&self) -> Held<'_, Keyspace> {
1048        self.dbs[0].hold_stripe(0)
1049    }
1050
1051    /// Take a new clock reading, which every database is looking at.
1052    ///
1053    /// Once per turn of the event loop, which is the only place time moves. A
1054    /// command asking what the time is gets the answer the whole batch got, so
1055    /// two keys written by the same batch expire together (`04` section 3).
1056    ///
1057    /// Every thread does this on every turn of its own loop and they do not
1058    /// have to agree about when. The reading is only stored when the
1059    /// millisecond has changed, so what the threads are sharing is a line that
1060    /// is written about a thousand times a second and read millions.
1061    pub fn refresh_clock(&self) {
1062        self.clock.refresh();
1063    }
1064
1065    /// Move every clock here on by `ms`, for tests about expiry.
1066    ///
1067    /// The same thing [`Server::set_clock_ms`] does and by the same argument,
1068    /// except that it moves from wherever the clock is rather than to a stated
1069    /// moment, which is what a test that wants a key to have expired asks for.
1070    pub fn advance_clock_ms(&self, ms: u64) {
1071        let now = self.clock.now_ms() + ms;
1072        self.set_clock_ms(now);
1073    }
1074
1075    /// Move every clock here to `ms` by hand, for tests about expiry.
1076    ///
1077    /// A test cannot wait a hundred seconds and a test that waits a hundred
1078    /// milliseconds is a test that fails on a loaded machine, so time moves on
1079    /// request. The system clock underneath will overwrite this on the next
1080    /// [`Server::refresh_clock`], which is why this is only useful in a test
1081    /// that drives commands directly rather than through the event loop.
1082    pub fn set_clock_ms(&self, ms: u64) {
1083        self.clock.set(ms);
1084    }
1085
1086    /// Seconds since this server was built.
1087    #[must_use]
1088    pub fn uptime_secs(&self) -> u64 {
1089        self.clock.now_ms().saturating_sub(self.started_ms) / 1000
1090    }
1091
1092    /// Bytes held by every database's index and arena, plus the read and reply
1093    /// buffers of every connection.
1094    ///
1095    /// The buffers are in here because they are real and because Redis counts
1096    /// its own, so leaving them out would make the one number people compare
1097    /// flattering rather than true. They are not a database, so nothing in the
1098    /// keyspace can change them and the engine has to say when they move.
1099    #[must_use]
1100    pub fn memory_bytes(&self) -> usize {
1101        self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1102    }
1103
1104    /// What the keyspace itself is holding, live records only.
1105    ///
1106    /// `used_memory` minus this is what the store costs to run: the index, the
1107    /// space dead records are sitting in until compaction gets to them, and the
1108    /// connections' buffers.
1109    #[must_use]
1110    pub fn dataset_bytes(&self) -> usize {
1111        self.keyspaces()
1112            .map(|db| db.map().arena().live_bytes() as usize)
1113            .sum()
1114    }
1115
1116    /// Bytes the arenas are holding, live and dead together.
1117    #[must_use]
1118    pub fn arena_bytes(&self) -> usize {
1119        self.keyspaces()
1120            .map(|db| db.map().arena().reserved_bytes() as usize)
1121            .sum()
1122    }
1123
1124    /// Bytes the indexes are holding.
1125    #[must_use]
1126    pub fn index_bytes(&self) -> usize {
1127        self.keyspaces()
1128            .map(|db| db.map().index().memory_bytes())
1129            .sum()
1130    }
1131
1132    /// What arena compaction has cost, across every database.
1133    ///
1134    /// The write amplification of value separation, which is invisible from the
1135    /// outside otherwise: a client that writes a megabyte can leave the store
1136    /// copying several more, and the only sign of it without these is that the
1137    /// writes got slower.
1138    #[must_use]
1139    pub fn compaction(&self) -> yo_kv::Compaction {
1140        self.keyspaces().map(|db| db.map().compaction()).fold(
1141            yo_kv::Compaction::default(),
1142            |a, b| yo_kv::Compaction {
1143                walked: a.walked + b.walked,
1144                moved: a.moved + b.moved,
1145                bytes: a.bytes + b.bytes,
1146            },
1147        )
1148    }
1149
1150    /// Arena segments whose pages are real, across every database.
1151    #[must_use]
1152    pub fn segment_count(&self) -> usize {
1153        self.keyspaces()
1154            .map(|db| db.map().arena().resident_segments())
1155            .sum()
1156    }
1157
1158    /// What the connections' read and reply buffers are holding.
1159    #[must_use]
1160    pub fn conn_bytes(&self) -> usize {
1161        self.conn_bytes.load(Relaxed)
1162    }
1163
1164    /// Note that the connections are holding `delta` bytes more than they were,
1165    /// or fewer when it is negative.
1166    ///
1167    /// A delta and not a total because the alternative is a walk over every
1168    /// connection, and the walk would have to happen on a turn of the loop
1169    /// rather than when `INFO` asks, which puts the cost of a report on the
1170    /// command path of a server nobody is asking.
1171    pub fn note_conn_bytes(&self, delta: isize) {
1172        // A read and a write and not a fetch and add, because the number is a
1173        // sum of signed changes and the saturating part has to happen in the
1174        // middle. Two threads that change their buffers in the same instant can
1175        // lose one of the two changes, which is a report that is a few kilobytes
1176        // out until the next connection on either thread moves it again.
1177        self.conn_bytes
1178            .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1179    }
1180
1181    /// Keys reclaimed by running into them after their deadline.
1182    #[must_use]
1183    pub fn expired_keys(&self) -> u64 {
1184        self.keyspaces().map(|db| db.expired_keys()).sum()
1185    }
1186
1187    /// Hash fields reclaimed after their own deadline passed.
1188    #[must_use]
1189    pub fn expired_fields(&self) -> u64 {
1190        self.keyspaces().map(|db| db.expired_fields()).sum()
1191    }
1192
1193    /// The share of those the cycle found rather than a command tripping over.
1194    #[must_use]
1195    pub fn expired_fields_active(&self) -> u64 {
1196        self.keyspaces().map(|db| db.expired_fields_active()).sum()
1197    }
1198
1199    /// Keys thrown away to make room, which is the other number entirely.
1200    #[must_use]
1201    pub fn evicted_keys(&self) -> u64 {
1202        self.keyspaces().map(|db| db.evicted_keys()).sum()
1203    }
1204
1205    /// Lookups a client's read made that found the key.
1206    #[must_use]
1207    pub fn keyspace_hits(&self) -> u64 {
1208        self.keyspaces().map(|db| db.hits()).sum()
1209    }
1210
1211    /// Lookups a client's read made that did not.
1212    #[must_use]
1213    pub fn keyspace_misses(&self) -> u64 {
1214        self.keyspaces().map(|db| db.misses()).sum()
1215    }
1216
1217    /// Every command that has been seen, with its counters.
1218    ///
1219    /// Only the ones that have. A server reports a handful of lines rather than
1220    /// one per command in the table, which is what Redis does and is the
1221    /// difference between a section a person can read and one they cannot.
1222    pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1223        (0..table::count())
1224            .map(|at| (table::name_at(at), self.command_stat(at)))
1225            .filter(|(_, row)| row.seen())
1226    }
1227
1228    /// One command's counters, added up over every thread.
1229    fn command_stat(&self, at: usize) -> CommandStat {
1230        let mut sum = CommandStat::default();
1231        for thread in &self.locals {
1232            let row = &thread.cmdstats.0[at];
1233            sum.calls += row.calls.get();
1234            sum.rejected += row.rejected.get();
1235            sum.failed += row.failed.get();
1236        }
1237        sum
1238    }
1239
1240    /// The counters the calling thread writes into.
1241    ///
1242    /// The first call on a thread claims a set and every call after it is a
1243    /// thread local read and an index. A server asked to count from more threads
1244    /// than it was built for wraps round and shares a set, which loses the odd
1245    /// count between two threads and cannot happen to a server `yodb serve`
1246    /// built, because that one is told how many threads it will have before it
1247    /// starts any of them.
1248    pub fn counted(&self) -> &Stats {
1249        &self.mine().stats
1250    }
1251
1252    /// The next client id, taken.
1253    ///
1254    /// Every accept anywhere on this server comes through here, so no two
1255    /// clients share a number however many threads are accepting.
1256    pub fn next_client(&self) -> u64 {
1257        self.next_client.fetch_add(1, Relaxed)
1258    }
1259
1260    /// Which set of per thread state the calling thread is on.
1261    ///
1262    /// The number a blocked client is filed under, so that the thread holding
1263    /// that client's connection is the one that answers it. Claims a set on the
1264    /// first call the same way [`Server::counted`] does, and gives back the same
1265    /// number every time after.
1266    pub fn my_slot(&self) -> usize {
1267        self.mine_at()
1268    }
1269
1270    /// Everything the calling thread keeps to itself.
1271    fn mine(&self) -> &Local {
1272        &self.locals[self.mine_at()]
1273    }
1274
1275    /// The calling thread's place in `locals`, claiming one if it has none.
1276    ///
1277    /// Wraps round when more threads count here than the server was built for,
1278    /// which shares a set between two threads and loses the odd count. That
1279    /// cannot happen to the server `yodb serve` builds, because it is told how
1280    /// many threads it will have before it starts any of them.
1281    fn mine_at(&self) -> usize {
1282        let mut slot = SLOT.get();
1283        if slot == usize::MAX {
1284            slot = self.claimed.fetch_add(1, Relaxed);
1285            SLOT.set(slot);
1286        }
1287        slot % self.locals.len()
1288    }
1289
1290    /// Every thread's numbers added together, which is what `INFO` reports.
1291    #[must_use]
1292    pub fn totals(&self) -> Totals {
1293        let mut sum = Totals::default();
1294        for thread in &self.locals {
1295            sum.clients += thread.stats.clients.get();
1296            sum.connections += thread.stats.connections.get();
1297            sum.commands += thread.stats.commands.get();
1298        }
1299        sum
1300    }
1301
1302    /// Put the totals back to zero, which is `CONFIG RESETSTAT`.
1303    ///
1304    /// Every thread's set and not only the one asking, since the number the
1305    /// client is resetting is the sum it was just shown. The open connections
1306    /// are left alone because that is a gauge and not a total: the connections
1307    /// are still open.
1308    pub fn reset_stats(&self) {
1309        for thread in &self.locals {
1310            thread.stats.connections.zero();
1311            thread.stats.commands.zero();
1312        }
1313        // These live on the stripes rather than on the threads, so resetting
1314        // them means holding each stripe for as long as it takes to write a
1315        // handful of zeroes. `CONFIG RESETSTAT` is a command a person types, and
1316        // the alternative is a set of numbers a dashboard cannot put back.
1317        for mut db in self.keyspaces() {
1318            db.zero_stats();
1319        }
1320    }
1321
1322    /// Say how many threads will run commands here, before any of them does.
1323    ///
1324    /// What it changes is how many sets of counters there are, and how many
1325    /// pub/sub mailboxes. Called once at startup by whoever is about to start
1326    /// the threads, and calling it on a running server throws away what has been
1327    /// counted so far, which is why it wants the server to itself.
1328    pub fn set_threads(&mut self, threads: usize) {
1329        self.locals = slots(threads);
1330        self.mail = pubsub::boxes(threads);
1331        self.claimed = AtomicUsize::new(0);
1332    }
1333
1334    /// The `maxmemory` limit in bytes, zero when there is not one.
1335    #[must_use]
1336    pub fn maxmemory(&self) -> u64 {
1337        self.maxmemory.load(Relaxed)
1338    }
1339
1340    /// Set the limit, and take a reading straight away.
1341    ///
1342    /// The reading is here rather than left to the next maintenance turn because
1343    /// a client that sets the limit and sends a write in the same batch expects
1344    /// the write to be judged against the limit it just set, and because the
1345    /// cached number is meaningless until the first time there is a limit to
1346    /// compare it with.
1347    ///
1348    /// Turning the limit on also turns on the running total every slab keeps of
1349    /// what its collections hold, and turning it off turns that back off, so a
1350    /// server with no limit is not paying to count something nobody reads. The
1351    /// first reading after switching it on is the walk that the total starts
1352    /// from, and it is the only walk.
1353    pub fn set_maxmemory(&self, bytes: u64) {
1354        self.maxmemory.store(bytes, Relaxed);
1355        for db in &self.dbs {
1356            db.track_memory(bytes != 0);
1357        }
1358        self.used.store(self.settled_memory(), Relaxed);
1359    }
1360
1361    /// Say where a database should get its store from when it needs one.
1362    ///
1363    /// This is what turns the eviction inversion on. Until it is called every
1364    /// database answers a memory limit by evicting, which is Redis, and after it
1365    /// is called a database under memory pressure moves values to whatever the
1366    /// closure hands back instead of throwing keys away.
1367    ///
1368    /// Called at most once per database and only under pressure, so a server
1369    /// that is given a file and never fills memory never touches it.
1370    pub fn set_store_source(
1371        &mut self,
1372        source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1373    ) {
1374        *self.store.lock() = Some(Box::new(source));
1375    }
1376
1377    /// Whether this server has been given somewhere to put cold values.
1378    #[must_use]
1379    pub fn has_store_source(&self) -> bool {
1380        self.store.lock().is_some()
1381    }
1382
1383    /// Open database `at`'s store, if it has not got one and there is one to be
1384    /// had.
1385    ///
1386    /// A store that will not open leaves the database where it was, which is
1387    /// evicting, because a memory limit that cannot be answered by moving data
1388    /// still has to be answered.
1389    fn attach_store(&self, at: usize) {
1390        if self.slot(at).store_bytes().is_some() {
1391            return;
1392        }
1393        // The closure is run with its lock held and the keyspace is taken after
1394        // it has answered, so the file is opened once however many threads asked
1395        // for it and the stripe is not held while a file is being opened.
1396        let mut source = self.store.lock();
1397        let Some(source) = source.as_mut() else {
1398            return;
1399        };
1400        if let Some(blocks) = source(at) {
1401            self.slot(at).attach(blocks);
1402        }
1403    }
1404
1405    /// The `maxstore` limit in bytes, `None` when there is not one.
1406    #[must_use]
1407    pub fn maxstore(&self) -> Option<u64> {
1408        match self.maxstore.load(Relaxed) {
1409            NO_MAXSTORE => None,
1410            bytes => Some(bytes),
1411        }
1412    }
1413
1414    /// Set the storage limit, or clear it with `None`.
1415    ///
1416    /// Nothing is read here the way [`Server::set_maxmemory`] reads the memory
1417    /// total, because this limit is compared against a number the store keeps
1418    /// and answers on demand, not against a walk.
1419    pub fn set_maxstore(&self, bytes: Option<u64>) {
1420        self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1421    }
1422
1423    /// What every attached store is holding, for `INFO memory`.
1424    ///
1425    /// Zero on a server with nothing attached, which is not the same as a server
1426    /// whose file is empty, and [`Server::regime`] is the field that tells those
1427    /// two apart.
1428    #[must_use]
1429    pub fn store_bytes(&self) -> u64 {
1430        self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1431    }
1432
1433    /// What the file has been asked to do, added up over every database.
1434    ///
1435    /// Counters and not levels, so they only ever go up and a run is the
1436    /// difference between two readings. G9 is a ratio over these: the faults a
1437    /// run took, divided by the point reads it issued, has to come out at 1.05
1438    /// or less with a working set ten times memory. There is no way to work that
1439    /// out from outside the server, so it is reported rather than inferred.
1440    ///
1441    /// A fault is a read that went to the store. Whether it also went to the
1442    /// device depends on the store: a log serves a read out of a resident page
1443    /// without touching anything. At ten times memory almost every fault is a
1444    /// real read, which is why the gate is written against this number, but the
1445    /// two are not the same thing and a run tight against the bar should be
1446    /// checked against what the operating system says.
1447    #[must_use]
1448    pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1449        let mut total = yo_kv::tier::Stats::default();
1450        for db in self.keyspaces() {
1451            let Some(tier) = db.tier() else { continue };
1452            let s = tier.stats();
1453            total.demoted += s.demoted;
1454            total.promoted += s.promoted;
1455            total.faults += s.faults;
1456            total.served += s.served;
1457            total.bytes_out += s.bytes_out;
1458            total.bytes_in += s.bytes_in;
1459        }
1460        total
1461    }
1462
1463    /// Which way this server answers a memory limit, in one word for `INFO`.
1464    ///
1465    /// `evict` is Redis: a memory limit throws keys away. `migrate` is the
1466    /// inversion: a memory limit moves values to the file and nothing stored is
1467    /// lost. A server reports one word rather than leaving an operator to work
1468    /// it out from a limit, a setting and whether a file happens to be open.
1469    #[must_use]
1470    pub fn regime(&self) -> &'static str {
1471        if (0..self.slots()).any(|at| self.migrates(at)) {
1472            "migrate"
1473        } else {
1474            "evict"
1475        }
1476    }
1477
1478    /// Whether database `at` answers a memory limit by moving values to the
1479    /// file rather than by throwing keys away.
1480    ///
1481    /// Three things have to hold. There has to be somewhere to move them, which
1482    /// is a store attached to that database or a source that can open one, and
1483    /// on a server that was never given a file this is false everywhere and
1484    /// every database behaves exactly as it did.
1485    /// The storage budget has to be more than nothing, which is what
1486    /// `maxstore 0` says it is not. And the file has to be under that budget,
1487    /// because a full file is a storage limit reached and eviction is the right
1488    /// answer to a storage limit.
1489    fn migrates(&self, at: usize) -> bool {
1490        let cap = self.maxstore();
1491        if cap == Some(0) {
1492            return false;
1493        }
1494        // Out of the stripe first. A match keeps whatever it is looking at
1495        // alive for the whole of itself, and that would be this stripe held
1496        // across the arms for no reason.
1497        let bytes = self.slot(at).store_bytes();
1498        match bytes {
1499            Some(held) => cap.is_none_or(|cap| held < cap),
1500            // Nothing attached, but somewhere to get one from the moment this
1501            // database needs it, which is what makes the answer yes rather than
1502            // no. Opening it here would mean `INFO` opened files.
1503            None => self.store.lock().is_some(),
1504        }
1505    }
1506
1507    /// Take a fresh memory reading, which the maintenance turn does once a batch.
1508    ///
1509    /// Nothing at all when there is no limit, which is the default and is every
1510    /// server that has not asked for one.
1511    pub fn refresh_memory(&self) {
1512        if self.maxmemory() != 0 {
1513            self.used.store(self.settled_memory(), Relaxed);
1514        }
1515    }
1516
1517    /// [`Server::memory_bytes`], asked the cheap way.
1518    ///
1519    /// The same number. The difference is that this asks each database only
1520    /// about the collections that could have moved since the last time, which is
1521    /// what a batch touched rather than what the server holds, so it can be
1522    /// asked once a batch and again on every command that is over the limit.
1523    fn settled_memory(&self) -> usize {
1524        self.keyspaces()
1525            .map(|mut db| db.settled_memory_bytes())
1526            .sum::<usize>()
1527            + self.conn_bytes()
1528    }
1529
1530    /// Make room under the `maxmemory` limit, throwing keys away if that is what
1531    /// it takes. Answers whether there is anything left it could throw away.
1532    ///
1533    /// Redis runs the same thing from `processCommand` before every command and
1534    /// so does this: a client that writes has to be judged at the moment it
1535    /// writes, not a batch later, or the limit is a suggestion.
1536    ///
1537    /// Three things happen in the loop and all three are needed. Eviction picks
1538    /// a key and drops it. Compaction gives the pages back, because dropping a
1539    /// key marks its record dead and returns nothing on its own, so a loop that
1540    /// only evicted would throw the whole keyspace away and watch the number
1541    /// stay where it was. The reading is taken again each time round, because
1542    /// the two of them together are the only thing that moves it.
1543    ///
1544    /// # Why running out of budget is not a no
1545    ///
1546    /// `false` means there was nothing left to evict, which is `noeviction`, or
1547    /// a `volatile` policy on a database where nothing has a deadline, or a
1548    /// keyspace that is already empty. It does not mean the server is still over
1549    /// its limit, and that difference is Redis's: `performEvictions` answers
1550    /// `EVICT_FAIL` only when it has run out of things to delete, and
1551    /// `processCommand` refuses the client on that and on nothing else. Running
1552    /// out of time part way through a job it is doing well comes back as
1553    /// `EVICT_RUNNING` and the command goes through, because a server that is
1554    /// evicting steadily and refusing every write while it does it is worse for
1555    /// the client than a little overshoot.
1556    ///
1557    /// # What the limit is worth
1558    ///
1559    /// Space comes back a segment at a time and a segment is two megabytes, so
1560    /// this holds a server to its limit give or take a segment. A `maxmemory` of
1561    /// a few hundred megabytes gets what it asked for. A `maxmemory` of four
1562    /// megabytes is asking for a precision this store does not have.
1563    pub fn make_room(&self) -> bool {
1564        let limit = self.maxmemory();
1565        if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1566            return true;
1567        }
1568        // The cached reading is a batch old and the batch may have compacted
1569        // since, so take a fresh one before throwing anything away. It is the
1570        // settled reading and not the walk, so what this costs is the handful of
1571        // collections the last batch touched and not the whole database.
1572        let mut used = self.settled_memory();
1573        self.used.store(used, Relaxed);
1574        let mut budget = EVICT_BUDGET;
1575        while used as u64 > limit {
1576            let over = used - limit as usize;
1577            if !self.relieve_step(over) {
1578                return false;
1579            }
1580            self.compact_hard_step();
1581            used = self.settled_memory();
1582            self.used.store(used, Relaxed);
1583            budget -= 1;
1584            if budget == 0 {
1585                break;
1586            }
1587        }
1588        true
1589    }
1590
1591    /// Give back `over` bytes from whichever database can, by moving values to
1592    /// the file where there is one and by throwing keys away where there is not.
1593    ///
1594    /// The two answers are the eviction inversion and which one a database gets
1595    /// is [`Server::migrates`]. Answers whether anything was given back at all,
1596    /// and `false` is what refuses the client's write.
1597    ///
1598    /// A store that will not take the bytes counts as nothing given back, so the
1599    /// write is refused rather than turned into a deletion. A disk that is
1600    /// misbehaving is a reason to stop accepting writes and it is not a reason
1601    /// to start losing data that was accepted already.
1602    ///
1603    /// Round robin from a cursor rather than always starting at database zero,
1604    /// so a server using more than one of them does not empty the first before
1605    /// touching the second. Almost every server is on database zero only, where
1606    /// this is one call that answers and fifteen that say the map is empty.
1607    fn relieve_step(&self, over: usize) -> bool {
1608        let from = self.evict_db.load(Relaxed);
1609        for turn in 0..self.slots() {
1610            let i = (from + turn) % self.slots();
1611            // An empty keyspace has nothing to move and opening a log for one
1612            // would cost a resident page window to find that out.
1613            let used = !self.slot(i).is_empty();
1614            let gave = if used && self.migrates(i) {
1615                self.attach_store(i);
1616                // Whether it made room and not whether it moved a key. A round
1617                // that demoted nothing and handed back a segment is a round
1618                // that made room, and reading only the count refuses the write
1619                // that provoked it.
1620                self.slot(i)
1621                    .relieve(over)
1622                    .is_ok_and(yo_kv::tier::Relief::made_room)
1623            } else {
1624                // Against this database rather than whichever one the write
1625                // that provoked the eviction was aimed at, since the key that
1626                // goes is this one's. The funnel is already armed above and
1627                // this is a second one inside it, which is what the answer
1628                // going back into the drain is for.
1629                let armed = notify::arm(self, self.slot_db(i));
1630                let gone = self.slot(i).evict_one();
1631                notify::drain(self, armed);
1632                gone
1633            };
1634            if gave {
1635                self.evict_db.store((i + 1) % self.slots(), Relaxed);
1636                self.mine().mark(1u64 << self.slot_db(i));
1637                return true;
1638            }
1639        }
1640        false
1641    }
1642
1643    /// The sweep the shard loop calls, at most once a millisecond.
1644    ///
1645    /// The gate is the whole difference between this and [`Server::expire_step`].
1646    /// A maintenance slice runs on every turn of the loop and a turn is a
1647    /// hundred nanoseconds, so an ungated sweep would draw a fresh sample ten
1648    /// thousand times per millisecond and spend a real share of the shard on
1649    /// looking for keys that cannot have died since the last look. Nothing in a
1650    /// database changes fast enough to be worth asking about more often than the
1651    /// clock can tell the difference, and the clock here is milliseconds.
1652    ///
1653    /// A millisecond is also far finer than Redis, whose slow cycle runs at ten
1654    /// hertz, so this is not the thing that decides how promptly memory comes
1655    /// back. What it decides is that an idle server sweeps a thousand times a
1656    /// second rather than a million.
1657    pub fn expire_slice(&self, budget: usize) -> usize {
1658        let now = self.clock.now_ms();
1659        if now == self.expire_ms.load(Relaxed) {
1660            return 0;
1661        }
1662        self.expire_ms.store(now, Relaxed);
1663        self.expire_step(budget)
1664    }
1665
1666    /// Sweep dead keys out of the databases, spending at most `budget` looks.
1667    ///
1668    /// Answers what it spent, so the caller can charge its maintenance slice for
1669    /// it. See [`yo_kv::expiry`] for why the budget is in keys looked at.
1670    ///
1671    /// Round robin from its own cursor, and every database gets offered whatever
1672    /// is left of the budget rather than a sixteenth of it each, so a server on
1673    /// database zero only, which is nearly every server, spends the whole slice
1674    /// where the keys are. The fifteen empty ones cost a comparison apiece
1675    /// because a database with no key carrying a deadline says so without
1676    /// drawing anything.
1677    ///
1678    /// The cursor moves to the database after whichever one did the work, so two
1679    /// busy databases take turns instead of the lower numbered one starving the
1680    /// other.
1681    pub fn expire_step(&self, budget: usize) -> usize {
1682        let mut spent = 0;
1683        let from = self.expire_db.load(Relaxed);
1684        for turn in 0..self.slots() {
1685            if spent >= budget {
1686                break;
1687            }
1688            let i = (from + turn) % self.slots();
1689            // Nothing armed this thread, because nothing asked for any of this:
1690            // the shard loop is between commands. So the sweep arms and drains
1691            // around itself, and a key it takes is news to a subscriber in the
1692            // same way a key a lookup took on the way past is.
1693            let armed = notify::arm(self, self.slot_db(i));
1694            let c = self.slot(i).expire_cycle(budget - spent);
1695            // And the fields, which are the other thing with a deadline nobody
1696            // is waiting on. It draws from its own list and charges the same
1697            // budget, so a database with no hash field deadlines anywhere pays a
1698            // comparison for it and a database full of them cannot starve the
1699            // key sweep.
1700            let left = (budget - spent).saturating_sub(c.examined);
1701            let fields = self.slot(i).field_expire_cycle(left);
1702            notify::drain(self, armed);
1703            spent += c.examined + fields;
1704            if c.expired > 0 {
1705                self.expire_db.store((i + 1) % self.slots(), Relaxed);
1706                self.mine().note(1u64 << self.slot_db(i));
1707            }
1708        }
1709        spent
1710    }
1711
1712    /// One slice of compaction for a server that is over its limit.
1713    ///
1714    /// Takes the databases in the same order [`Server::compact_step`] does and
1715    /// stops at the first one that had something to move, and it asks with the
1716    /// ratios off. See [`Keyspace::compact_hard`] for what that changes.
1717    fn compact_hard_step(&self) -> Option<usize> {
1718        let from = self.next_db.load(Relaxed);
1719        for turn in 0..self.slots() {
1720            let i = (from + turn) % self.slots();
1721            if let Some(moved) = self.slot(i).compact_hard() {
1722                self.next_db.store((i + 1) % self.slots(), Relaxed);
1723                return Some(moved);
1724            }
1725        }
1726        None
1727    }
1728
1729    /// Take what every thread has marked and add it to the turn's own mask.
1730    ///
1731    /// The mask the turn works from is its own and not a shared one, because a
1732    /// mask it read in place and then cleared a bit of would be a mask that lost
1733    /// whatever another thread marked in between. A swap cannot lose a mark: a
1734    /// thread that ors while the swap happens either gets its bit in before the
1735    /// swap or leaves it there afterwards, and the second one costs one look at
1736    /// a database the turn has already been through.
1737    fn collect_marks(&self) {
1738        let mut marked = 0;
1739        for thread in &self.locals {
1740            marked |= thread.dirty.swap(0, Relaxed);
1741        }
1742        self.mine().note(marked);
1743    }
1744
1745    /// Give one database's dead space back, if any database has enough of it to
1746    /// be worth the move. `None` when no database had a candidate.
1747    ///
1748    /// Once per batch, next to the clock. Overwriting a key writes a new record
1749    /// and counts the old one dead, so without this a server holds everything
1750    /// it has ever written: 400000 sets over 100000 keys measured at 742 bytes
1751    /// a key against Redis at 144 for the same load, and the whole difference
1752    /// was dead records nothing ever came back for.
1753    ///
1754    /// At most one segment moves per call and the search starts one database
1755    /// further along each time, so the cost of asking is a comparison per
1756    /// database and the cost of acting is bounded by a segment.
1757    pub fn compact_step(&self) -> Option<usize> {
1758        self.collect_marks();
1759        let mine = self.mine();
1760        let from = self.next_db.load(Relaxed);
1761        for turn in 0..self.slots() {
1762            let i = (from + turn) % self.slots();
1763            // Nothing has run against this database since it last said it had
1764            // nothing to collect, so it still has nothing to collect and the
1765            // line it lives on stays where it is.
1766            let at = self.slot_db(i);
1767            if !mine.wanted(at) {
1768                continue;
1769            }
1770            if let Some(moved) = self.slot(i).compact_step() {
1771                self.next_db.store((i + 1) % self.slots(), Relaxed);
1772                return Some(moved);
1773            }
1774            // Only once every stripe of the database has said it has nothing,
1775            // since the bit is per database and one stripe answering for all of
1776            // them would stop the others being asked at all.
1777            if i % self.width == self.width - 1 {
1778                mine.done(at);
1779            }
1780        }
1781        None
1782    }
1783}
1784
1785impl Server {
1786    /// Whether anybody is watching anything.
1787    ///
1788    /// The one thing every write asks about watches, and it is a relaxed load of
1789    /// a word that is zero and shared on a server where no client has ever sent
1790    /// `WATCH`. Relaxed is enough because the answer only has to be right by the
1791    /// time it matters: a `WATCH` that has not been published yet has not
1792    /// returned to its client either, so no client can have started a
1793    /// transaction that depends on it.
1794    fn watching(&self) -> bool {
1795        self.watched.load(Relaxed) != 0
1796    }
1797
1798    /// Which classes of keyspace notification are turned on.
1799    ///
1800    /// Zero is off, which is the default and is what nearly every server runs
1801    /// with. Relaxed for the same reason the watch count is: a `CONFIG SET` that
1802    /// has not been published to another thread yet has not answered its client
1803    /// either.
1804    pub(crate) fn notify_flags(&self) -> u32 {
1805        self.notify.load(Relaxed)
1806    }
1807
1808    /// Turn a set of notification classes on, or turn them all off with zero.
1809    pub(crate) fn set_notify_flags(&self, flags: u32) {
1810        self.notify.store(flags, Relaxed);
1811    }
1812
1813    /// Note how many watched keys there are, after the table changed.
1814    ///
1815    /// Taken from the table under the same lock the change was made under, so
1816    /// the count can never say nobody is watching while somebody is.
1817    fn recount(&self, watches: &Watches) {
1818        self.watched.store(watches.len(), Relaxed);
1819    }
1820}
1821
1822impl Default for Server {
1823    fn default() -> Server {
1824        Server::new()
1825    }
1826}
1827
1828/// What one connection has chosen.
1829pub struct Session {
1830    db: usize,
1831    id: u64,
1832    /// Which connection slot on the front this session belongs to.
1833    ///
1834    /// Carried here so that a command can say where a reply for this connection
1835    /// goes without the front having to be asked. Pub/sub is what needs it: a
1836    /// subscription is a row on the server naming a slot, and the subscribe
1837    /// command is the only moment the connection and the server are both in
1838    /// hand. [`u32::MAX`] for a session that is not on a front, which is a test.
1839    conn: u32,
1840    name: Vec<u8>,
1841    /// The `HIMPORT` fieldsets this connection has prepared.
1842    ///
1843    /// Connection state and not keyspace state, which is the reference's design
1844    /// and not a shortcut: a fieldset is invisible to every other connection and
1845    /// the keys built from one outlive it.
1846    sets: himport::Fieldsets,
1847    /// Whether the command running right now was called by a script.
1848    ///
1849    /// The one thing it changes is what a blocking command does when it finds
1850    /// nothing to take. A client that sent `BLPOP` waits; a script that called
1851    /// `BLPOP` cannot, because the whole server is waiting on the script, and a
1852    /// script that parked would park everything behind it. So inside a script a
1853    /// blocking command times out at once and answers the null a client that
1854    /// waited its full timeout would have got. That is a real server's rule and
1855    /// it is why `BLPOP` is not on the list a script may not call.
1856    scripted: bool,
1857    /// The commands held since `MULTI`, `None` when no transaction is open.
1858    ///
1859    /// Connection state and nothing else. A transaction is invisible to every
1860    /// other connection until `EXEC` runs it, and a connection that goes away
1861    /// with one open has simply not run it.
1862    multi: Option<multi::Queue>,
1863    /// What this connection asked `WATCH` about, and what those keys looked
1864    /// like at the time.
1865    ///
1866    /// The other half is on the server, beside the keys, because a write by
1867    /// another thread has to reach it. See `multi` for why keeping the value
1868    /// here and comparing it at `EXEC` is not the same thing.
1869    watching: Vec<multi::Watched>,
1870    /// Whether the command running right now was handed over by `EXEC`.
1871    ///
1872    /// The one thing it changes is the RESP2 subscribe mode refusal, which a
1873    /// real server makes in `processCommand` and so does not make for a command
1874    /// that was queued: `MULTI`, `SUBSCRIBE z`, `GET x`, `EXEC` runs the `GET`
1875    /// on 8.10.1 even though sending it on its own would have been refused.
1876    running: bool,
1877    /// The buffer `EXEC` decodes the queued commands through.
1878    ///
1879    /// It lives here rather than in `exec` so that its capacity survives the
1880    /// transaction. A fresh one has no room for spans, so the first command of
1881    /// every transaction would allocate, and a client that runs transactions in
1882    /// a loop would be allocating on a command path forever. Everywhere else
1883    /// the buffer belongs to the connection already and the same reserve is
1884    /// free after the first command.
1885    replay: crate::request::Argv,
1886    /// What this connection has subscribed to, `None` until it subscribes to
1887    /// anything.
1888    ///
1889    /// Boxed so that a connection that never subscribes carries a null pointer
1890    /// rather than three empty vectors. The other half is on the server, keyed
1891    /// by name, because a publish arrives on a connection that cannot see this
1892    /// one. See the `pubsub` module.
1893    subs: Option<Box<pubsub::Subs>>,
1894    /// The library name and version a client library announces with
1895    /// `CLIENT SETINFO`, empty when it has not.
1896    ///
1897    /// Nothing on the server reads them. They are here because an operator
1898    /// looking at `CLIENT LIST` on a server with a hundred connections wants to
1899    /// know which of them is the Python worker and which is the dashboard, and
1900    /// every mainstream client library sends them on connect.
1901    lib_name: Vec<u8>,
1902    lib_ver: Vec<u8>,
1903    /// `CLIENT NO-EVICT`, which asks that this connection's buffers are not the
1904    /// ones given up when the server is short of memory.
1905    ///
1906    /// Nothing gives up a connection's buffers here yet, so this is remembered
1907    /// and reported and does nothing else, which is the honest half of the
1908    /// command: a client that sets it and reads it back sees what it set.
1909    no_evict: bool,
1910    /// `CLIENT NO-TOUCH`, which asks that reads by this connection do not move
1911    /// a key's place in the eviction order.
1912    no_touch: bool,
1913    /// What this connection has asked to be told about, which is `CLIENT REPLY`.
1914    reply: Reply,
1915    /// The row every other thread sees this connection through.
1916    ///
1917    /// Shared rather than owned, because `CLIENT LIST` and `CLIENT KILL` run on
1918    /// whichever thread the client asking is on and that is very often not this
1919    /// one. Everything the report says about the socket lives in there and
1920    /// nowhere else, and the handful of things the session needs for itself are
1921    /// kept here as well and written to both. See the `clients` module for why
1922    /// the row is words and a small lock rather than one lock.
1923    sock: Arc<Client>,
1924}
1925
1926/// What a connection has asked to hear back, which is `CLIENT REPLY`.
1927///
1928/// The two skipping states are one command apart on purpose. `CLIENT REPLY
1929/// SKIP` says nothing itself and skips the reply of the command after it, so
1930/// the state has to survive one command and no more, and the way Redis does
1931/// that is with a pair of flags that step forward once a command.
1932#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
1933pub enum Reply {
1934    /// Everything, which is where every connection starts.
1935    #[default]
1936    On,
1937    /// Nothing at all until the client says `ON` again.
1938    Off,
1939    /// Nothing for the command after this one.
1940    SkipNext,
1941    /// This is that command.
1942    SkipNow,
1943}
1944
1945impl Session {
1946    /// A new connection, on database zero with no name.
1947    #[must_use]
1948    pub fn new(id: u64) -> Session {
1949        Session {
1950            db: 0,
1951            id,
1952            conn: u32::MAX,
1953            name: Vec::new(),
1954            sets: himport::Fieldsets::default(),
1955            scripted: false,
1956            multi: None,
1957            watching: Vec::new(),
1958            running: false,
1959            replay: crate::request::Argv::new(),
1960            subs: None,
1961            lib_name: Vec::new(),
1962            lib_ver: Vec::new(),
1963            no_evict: false,
1964            no_touch: false,
1965            reply: Reply::On,
1966            sock: Arc::new(Client::new(id)),
1967        }
1968    }
1969
1970    /// The row every other thread sees this connection through.
1971    ///
1972    /// Handed to the server once, when the connection is accepted, so that
1973    /// `CLIENT LIST` can find it. A session nobody hands over is one no other
1974    /// thread can see, which is every embedded caller and every test.
1975    #[must_use]
1976    pub fn row(&self) -> &Arc<Client> {
1977        &self.sock
1978    }
1979
1980    /// Say when this connection was opened, which is what `age` counts from.
1981    ///
1982    /// Called by whoever opened it, which is the only place that knows. A
1983    /// session nobody tells has no age and reports zero, which is every
1984    /// embedded caller and every test.
1985    pub fn opened(&mut self, now_ms: u64) {
1986        self.sock.since_ms.store(now_ms, Relaxed);
1987        self.sock.last_ms.store(now_ms, Relaxed);
1988    }
1989
1990    /// Say what the socket under this connection is.
1991    ///
1992    /// Called once, by whoever accepted it, which is the only place that knows.
1993    /// The two addresses are already in the spelling `CLIENT INFO` reports them
1994    /// in, because turning a socket address into that spelling is the job of the
1995    /// layer that has the socket.
1996    pub fn set_socket(&mut self, peer: &str, local: &str, fd: i32, unix: bool) {
1997        yo_alloc::allow(|| {
1998            let mut text = self.sock.text.lock();
1999            text.peer.clear();
2000            text.peer.extend_from_slice(peer.as_bytes());
2001            text.local.clear();
2002            text.local.extend_from_slice(local.as_bytes());
2003        });
2004        self.sock.fd.store(fd, Relaxed);
2005        self.sock.set_flag(clients::UNIX, unix);
2006    }
2007
2008    /// Note bytes that arrived, and that a read carried them.
2009    pub fn read_bytes(&mut self, n: usize) {
2010        let row = &self.sock;
2011        row.net_in
2012            .store(row.net_in.load(Relaxed) + n as u64, Relaxed);
2013        row.reads.store(row.reads.load(Relaxed) + 1, Relaxed);
2014    }
2015
2016    /// Note bytes that went out.
2017    pub fn wrote_bytes(&mut self, n: usize) {
2018        let row = &self.sock;
2019        row.net_out
2020            .store(row.net_out.load(Relaxed) + n as u64, Relaxed);
2021    }
2022
2023    /// Note what the two buffers are holding, and which protocol they are in.
2024    ///
2025    /// `waiting` is the framed bytes that have not been read yet, `room` is what
2026    /// is left in the read buffer after them, `held` is what the reply buffer
2027    /// still owes and `reply` is its capacity. The high water mark is kept here
2028    /// rather than by the caller so that the caller only has to say what is true
2029    /// now.
2030    pub fn note_buffers(&mut self, waiting: usize, room: usize, held: usize, reply: usize) {
2031        let row = &self.sock;
2032        row.qbuf.store(waiting as u64, Relaxed);
2033        row.qbuf_free.store(room as u64, Relaxed);
2034        row.obl.store(held as u64, Relaxed);
2035        row.rbs.store(reply as u64, Relaxed);
2036        row.rbp
2037            .store(row.rbp.load(Relaxed).max(reply as u64), Relaxed);
2038    }
2039
2040    /// Note which protocol this connection is being answered in.
2041    ///
2042    /// Written after each command rather than with the buffers, because `HELLO`
2043    /// changes it in the reply buffer and a connection that switched to RESP3
2044    /// halfway through a pipeline should be listed as being on it.
2045    pub fn note_proto(&mut self, version: i64) {
2046        self.sock.resp.store(version as u32, Relaxed);
2047    }
2048
2049    /// Note which command is running, before it runs.
2050    ///
2051    /// The clock is passed in because the session has no way to reach one, and
2052    /// the caller is holding the server anyway. `at` is where the command is in
2053    /// the table, since an index is a word another thread can read and a name is
2054    /// not.
2055    pub(crate) fn ran(&mut self, at: usize, sub: Option<&[u8]>, argv: u64, now_ms: u64) {
2056        self.sock.last_ms.store(now_ms, Relaxed);
2057        self.sock.argv_mem.store(argv, Relaxed);
2058        self.sock.note_command(at, sub);
2059    }
2060
2061    /// Note that the command running is over, and put what it changed about this
2062    /// connection where another thread can see it.
2063    ///
2064    /// The count goes up here and not where the command name is noted, so that
2065    /// a connection asking `CLIENT INFO` is told how many commands it had sent
2066    /// before this one. That is what a real server answers: it counts in
2067    /// `commandProcessed` and that runs after the body.
2068    ///
2069    /// The rest is the publishing. Which database a connection is in, what it is
2070    /// subscribed to, whether it is in a transaction and how many keys it is
2071    /// watching are all things a command can have just changed, and they are all
2072    /// things `CLIENT LIST` on another thread reports. Rather than hunting down
2073    /// every command that can move one of them, all six are written out here,
2074    /// which is six ordinary stores to a line this thread already owns.
2075    pub fn finished(&mut self) {
2076        let (sub, psub, ssub) = self.sub_counts();
2077        let (multi, multi_mem) = self.queued();
2078        let subscribed = self.subscribed();
2079        let in_multi = self.in_multi();
2080        let watching = self.watching.len();
2081        let db = self.db;
2082        let row = &self.sock;
2083        row.cmds.store(row.cmds.load(Relaxed) + 1, Relaxed);
2084        row.db.store(db as u32, Relaxed);
2085        row.sub.store(sub as u32, Relaxed);
2086        row.psub.store(psub as u32, Relaxed);
2087        row.ssub.store(ssub as u32, Relaxed);
2088        row.watch.store(watching as u32, Relaxed);
2089        row.multi.store(multi, Relaxed);
2090        row.multi_mem.store(multi_mem, Relaxed);
2091        row.set_flag(clients::SUBSCRIBED, subscribed);
2092        row.set_flag(clients::IN_MULTI, in_multi);
2093    }
2094
2095    /// What this connection has asked to hear back.
2096    #[must_use]
2097    pub const fn reply_mode(&self) -> Reply {
2098        self.reply
2099    }
2100
2101    /// Step the skipping state on by one command.
2102    ///
2103    /// Called after every command by whoever is deciding whether to keep the
2104    /// reply, so that `SKIP` covers exactly the one command after it.
2105    pub const fn step_reply(&mut self) {
2106        self.reply = match self.reply {
2107            Reply::SkipNext => Reply::SkipNow,
2108            Reply::SkipNow => Reply::On,
2109            other => other,
2110        };
2111    }
2112
2113    /// Whether a script is what is asking, which only a blocking command reads.
2114    pub(crate) const fn scripted(&self) -> bool {
2115        self.scripted
2116    }
2117
2118    /// Whether `EXEC` is what is asking.
2119    pub(crate) const fn running(&self) -> bool {
2120        self.running
2121    }
2122
2123    /// Say which connection slot this session is in.
2124    ///
2125    /// Called by the front when it opens the connection, which is the only place
2126    /// that knows. A session nobody tells is not on a front, and the one thing
2127    /// that reads this checks the client id before it acts on it.
2128    pub(crate) fn set_conn(&mut self, conn: u32) {
2129        self.conn = conn;
2130        self.sock.conn.store(conn, Relaxed);
2131    }
2132
2133    /// The connection id, which `HELLO` reports and `CLIENT` will.
2134    #[must_use]
2135    pub const fn id(&self) -> u64 {
2136        self.id
2137    }
2138
2139    /// Which database this connection is working in.
2140    #[must_use]
2141    pub const fn db(&self) -> usize {
2142        self.db
2143    }
2144
2145    /// The name the client gave itself, empty if it gave none.
2146    #[must_use]
2147    pub fn name(&self) -> &[u8] {
2148        &self.name
2149    }
2150
2151    /// Put everything back the way it was when the connection was opened.
2152    ///
2153    /// The protocol is not here because it is not here: it lives in the reply
2154    /// buffer, and `RESET` sets it back there.
2155    pub fn reset(&mut self) {
2156        self.db = 0;
2157        self.name.clear();
2158        self.sock.set_text(|text| &mut text.name, b"");
2159        // `SELECT` leaves these alone and `RESET` does not, both checked
2160        // against 8.10.1, which is the one pair of answers you could not guess
2161        // from what the command is for.
2162        self.sets.clear();
2163        // The three `CLIENT` settings that are a choice about this connection go
2164        // back to their defaults, and the library name and version stay, since
2165        // the library behind the socket is the same library it was. Both halves
2166        // are `clearClientConnectionState`'s.
2167        self.reply = Reply::On;
2168        self.set_no_evict(false);
2169        self.set_no_touch(false);
2170    }
2171
2172    /// Record the name from `HELLO ... SETNAME` or `CLIENT SETNAME`.
2173    fn set_name(&mut self, name: &[u8]) {
2174        yo_alloc::allow(|| {
2175            self.name.clear();
2176            self.name.extend_from_slice(name);
2177        });
2178        self.sock.set_text(|text| &mut text.name, name);
2179    }
2180
2181    /// Record what `CLIENT SETINFO LIB-NAME` was told.
2182    fn set_lib_name(&mut self, value: &[u8]) {
2183        yo_alloc::allow(|| {
2184            self.lib_name.clear();
2185            self.lib_name.extend_from_slice(value);
2186        });
2187        self.sock.set_text(|text| &mut text.lib_name, value);
2188    }
2189
2190    /// Record what `CLIENT SETINFO LIB-VER` was told.
2191    fn set_lib_ver(&mut self, value: &[u8]) {
2192        yo_alloc::allow(|| {
2193            self.lib_ver.clear();
2194            self.lib_ver.extend_from_slice(value);
2195        });
2196        self.sock.set_text(|text| &mut text.lib_ver, value);
2197    }
2198
2199    /// Record `CLIENT NO-EVICT`.
2200    fn set_no_evict(&mut self, on: bool) {
2201        self.no_evict = on;
2202        self.sock.set_flag(clients::NO_EVICT, on);
2203    }
2204
2205    /// Record `CLIENT NO-TOUCH`.
2206    fn set_no_touch(&mut self, on: bool) {
2207        self.no_touch = on;
2208        self.sock.set_flag(clients::NO_TOUCH, on);
2209    }
2210}
2211
2212/// Give back everything a connection was holding on the server.
2213///
2214/// The transaction, the watches and the subscriptions, and it is here rather
2215/// than in [`Session::reset`] because letting go of any of the three is a change
2216/// to the server. A `Session` on its own cannot reach one, and a connection that
2217/// dropped its lists without saying so would leave rows nobody is watching and
2218/// subscriptions nobody is listening to, which would keep every write and every
2219/// publish on the server paying for clients that are not there.
2220pub fn forget_session(server: &Server, session: &mut Session) {
2221    multi::release(server, session);
2222    pubsub::release(server, session);
2223}
2224
2225/// Run one command and write its reply.
2226///
2227/// The name is looked up and the arity is checked here, once, so that no body
2228/// has to. Everything after that is the command's own.
2229pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
2230    // The decoder never produces a command with no name. If one ever arrives,
2231    // it is not something to answer.
2232    if args.is_empty() {
2233        return Flow::Continue;
2234    }
2235    let flow = resolved(server, session, lookup(args.name()), args, out);
2236    // The engine does this itself, after the reply has been decided, because it
2237    // is also what settles `CLIENT REPLY`. An embedded caller has no engine, so
2238    // it happens here instead, and the two paths never both run: the engine
2239    // reaches the funnel through `resolved` and not through this.
2240    //
2241    // Not for a command the pause held, because that command has not run and is
2242    // going to be run again. An embedded caller has nowhere to park it, so it
2243    // gets the answer back and decides for itself; a caller that has not paused
2244    // its own server, which is nearly all of them, never sees this.
2245    if flow != Flow::Hold {
2246        session.finished();
2247    }
2248    flow
2249}
2250
2251/// The commands that are a container for a set of subcommands.
2252///
2253/// A hand written list because the table has one row per container and none per
2254/// subcommand, so there is nothing to ask. It goes away with D-114, which gives
2255/// every subcommand a row of its own and makes this a flag on the container.
2256const CONTAINERS: [&str; 10] = [
2257    "backup", "client", "command", "config", "function", "object", "pubsub", "script", "xgroup",
2258    "xinfo",
2259];
2260
2261/// The subcommand a container command was given, for the `cmd` field of
2262/// `CLIENT INFO`, which reads `client|info` and not `client`.
2263///
2264/// `None` for everything else, and for a container called with nothing after
2265/// it, which is a wrong arity and has no subcommand to name.
2266fn container_sub<'a>(spec: &Spec, args: &Args<'a>) -> Option<&'a [u8]> {
2267    (args.len() > 1 && CONTAINERS.contains(&spec.name)).then(|| args.get(1))
2268}
2269
2270/// The six commands Redis marks `may-replicate` and does not mark `write`.
2271///
2272/// A short list rather than a flag on every row, because six is what it is and
2273/// the only thing that asks is the pause gate below. It goes away with the flag
2274/// if anything else ever needs the same question answered.
2275const MAY_REPLICATE: [&str; 6] = ["eval", "evalsha", "fcall", "pfcount", "publish", "spublish"];
2276
2277/// Whether `CLIENT PAUSE WRITE` holds this command.
2278///
2279/// The writes, the six above, and `EXEC` when the transaction it is about to run
2280/// holds one of either. That last part is why this is asked of the session as
2281/// well as of the command: a transaction of nothing but reads runs through a
2282/// write pause, and one write anywhere in it makes the whole transaction wait.
2283fn may_replicate(spec: &Spec, session: &Session) -> bool {
2284    spec.flags.contains(&"write")
2285        || MAY_REPLICATE.contains(&spec.name)
2286        || (spec.name == "exec" && session.queued_writes())
2287}
2288
2289/// The same, for a caller that has already found the command.
2290///
2291/// The engine frames a command before it runs it, and between those two it also
2292/// asks which key the command touches so the record can be prefetched. That is
2293/// two more chances to look the name up, and looking it up three times to run it
2294/// once is three times the cost of the cheapest thing in the path. So the engine
2295/// resolves the name where it frames the command, carries the answer on the
2296/// framed command, and both the other two take it from there.
2297///
2298/// `spec` is `None` for a name that is not a command, which is the same thing
2299/// [`lookup`] says and lands in the same reply.
2300pub fn resolved(
2301    server: &Server,
2302    session: &mut Session,
2303    spec: Option<&'static Spec>,
2304    args: Args<'_>,
2305    out: &mut Out,
2306) -> Flow {
2307    if args.is_empty() {
2308        return Flow::Continue;
2309    }
2310    server.mine().stats.commands.bump();
2311
2312    // The four refusals below are the ones a real server makes in
2313    // `processCommand`, before the command's own body is reached, and they are
2314    // the ones that kill an open transaction. That is the whole of the rule: an
2315    // error raised here means `EXEC` will refuse to run anything, and an error
2316    // raised by a command body does not, which is why `MULTI` inside `MULTI`
2317    // complains and leaves the transaction alive.
2318    let Some(spec) = spec else {
2319        multi::refuse(server, session, None, &args::unknown_command(args), out);
2320        return Flow::Continue;
2321    };
2322    if !arity_ok(spec, args.len()) {
2323        server.mine().cmdstats.at(spec).rejected.bump();
2324        multi::refuse(
2325            server,
2326            session,
2327            Some(spec),
2328            &args::wrong_arity(spec.name),
2329            out,
2330        );
2331        return Flow::Continue;
2332    }
2333    // What this connection is doing, which only `CLIENT` reads back. Here and
2334    // not further down because a command that is about to be refused or queued
2335    // is still the last command the connection sent, which is what a real
2336    // server reports: it notes the name in `processCommand` before any of the
2337    // decisions below.
2338    let argv = (0..args.len()).map(|i| args.get(i).len() as u64).sum();
2339    session.ran(
2340        table::index_of(spec),
2341        container_sub(spec, &args),
2342        argv,
2343        server.now_ms(),
2344    );
2345
2346    if session.in_multi()
2347        && let Some(e) = multi::refused_in_multi(spec)
2348    {
2349        server.mine().cmdstats.at(spec).rejected.bump();
2350        multi::refuse(server, session, Some(spec), &e, out);
2351        return Flow::Continue;
2352    }
2353
2354    // The limit first, so a server with no `maxmemory`, which is the default and
2355    // is nearly all of them, pays one comparison against a field that is already
2356    // warm. Every command and not only the writes, because that is where Redis
2357    // puts it: making room is the server's job whatever the client asked for,
2358    // and the flag only decides who gets told no when there is no room to make.
2359    //
2360    // The flag is Redis's own `denyoom` and the list of commands carrying it is
2361    // Redis's list, so a command that only frees is let through with nothing
2362    // left, which is what lets a client dig itself out with `DEL`.
2363    if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
2364        server.mine().cmdstats.at(spec).rejected.bump();
2365        session.dirty_multi();
2366        out.error_line(b"OOM ", OOM);
2367        return Flow::Continue;
2368    }
2369
2370    // A RESP2 connection that has subscribed to something may only send a
2371    // handful of commands, because RESP2 sends a published message as an
2372    // ordinary array and a client with a reply outstanding could not tell the
2373    // two apart. Here, after the refusals above and before the queue below,
2374    // which is where a real server puts it: `EXEC` sent while subscribed comes
2375    // back as an `EXECABORT` rather than as this error, and a command `EXEC`
2376    // hands over is not asked at all.
2377    if let Some(e) = pubsub::refused(session, spec, out) {
2378        server.mine().cmdstats.at(spec).rejected.bump();
2379        multi::refuse(server, session, Some(spec), &e, out);
2380        return Flow::Continue;
2381    }
2382
2383    // `CLIENT PAUSE`, and this is the whole of it on the command path: one
2384    // relaxed load on a server nobody has paused. Here, after every refusal
2385    // above and before the queue below, which is where a real server puts it. So
2386    // a command that would have been refused is still refused while the server
2387    // is paused, and `MULTI` on a paused server waits rather than opening a
2388    // transaction that would queue commands nobody is allowed to send yet.
2389    //
2390    // Nothing is exempt, not even `CLIENT UNPAUSE`, which is Redis's behaviour
2391    // and is worth being clear about: a `CLIENT PAUSE 10000 ALL` cannot be
2392    // called off, by anybody, until it runs out.
2393    // A command `EXEC` is replaying is not a command the client just sent, and a
2394    // real server runs those through `call` rather than through
2395    // `processCommand`, so the gate is not in front of them. Holding one would
2396    // mean a transaction that has written half of itself and stopped.
2397    if !session.running
2398        && let Some(all) = server.paused(server.now_ms())
2399        && (all || may_replicate(spec, session))
2400    {
2401        return Flow::Hold;
2402    }
2403
2404    // Held rather than run, and the reply is `QUEUED`. After the refusals above
2405    // and before everything below, which is where a real server puts it: a
2406    // command has to be a real command with the right number of arguments to be
2407    // queued at all, and nothing it would have done gets done now.
2408    if session.queues(spec.name) {
2409        return multi::queue(session, spec, args, out);
2410    }
2411
2412    // Which databases the maintenance turn after this batch has to ask. Marked
2413    // for every command and not only for the writes, because a read can make
2414    // garbage too: a `GET` on a key whose expiry has passed reaps it, and the
2415    // record it dropped is exactly the kind of thing the collector is for.
2416    // `COPY`, `SWAPDB` and `FLUSHALL` reach a database nobody selected, so the
2417    // two groups that hold them mark all of them rather than the session's.
2418    server.mine().mark(match spec.group {
2419        "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
2420        | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
2421            1u64 << session.db
2422        }
2423        _ => ALL_DATABASES,
2424    });
2425
2426    let mark = out.len();
2427    // Before the group, because the five that block are list commands and would
2428    // otherwise land in `lists`, which is handed one database and nothing that
2429    // could park a client. The flag is the right thing to branch on rather than
2430    // a list of names: it is what `COMMAND INFO` reports about exactly these
2431    // commands, and the sorted set and stream ones that arrive later carry it
2432    // too.
2433    // What the command is about to do to the keyspace, for anybody subscribed to
2434    // hear about it. Armed here and drained after the group, because the bodies
2435    // below are handed a database and their arguments and have no way to reach
2436    // the pub/sub registry from there. Off costs one thread local store.
2437    let armed = notify::arm(server, session.db);
2438    // Which of the keys this command reads are not there. A real server says
2439    // this from inside each lookup and this says all of them in front, which is
2440    // the same order for every command whose first act is to read what it was
2441    // given, and that is nearly all of them.
2442    misses::report(&server.dbs[session.db], session.db, spec, args);
2443    // And whether the lookups it is about to make are reads, for the two
2444    // counters in `INFO stats`. Armed after the walk above so that the walk's
2445    // own probes are not counted, and dropped after the body so that nothing the
2446    // dispatcher does afterwards is either.
2447    let reading = lookups::reading(misses::reading(spec, args));
2448    let done = if spec.flags.contains(&"blocking") {
2449        blocking::execute(server, session, spec, args, out)
2450    } else {
2451        match spec.group {
2452            "string" => {
2453                let db = session.db;
2454                strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2455            }
2456            // Its own group and its own file, and the same values underneath:
2457            // a bitmap is a string, so `STRLEN` on one answers and `SETBIT` on
2458            // something a `SET` left behind works.
2459            "bitmap" => {
2460                let db = session.db;
2461                bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2462            }
2463            // The same again: a sketch is a string with a documented layout, so
2464            // `GET` hands one to a client and `SET` takes it back.
2465            "hyperloglog" => {
2466                let db = session.db;
2467                hll::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2468            }
2469            "set" => {
2470                let db = session.db;
2471                sets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2472            }
2473            // The one hash command whose state is not in the keyspace. A
2474            // fieldset belongs to the connection, so this is handed the session
2475            // as well as the database, the same exception `MIGRATE` gets in the
2476            // keyspace group for the socket it keeps.
2477            "hash" if spec.name == "himport" => {
2478                let db = session.db;
2479                himport::execute(&server.dbs[db], &mut session.sets, args, out)
2480                    .map(|()| Flow::Continue)
2481            }
2482            // The one group that reaches back into the server after it has
2483            // written its reply, because a hash is what a search index is
2484            // made of. What comes back is what the indexes have to be told,
2485            // which is not the same as whether the command was a write.
2486            "hash" => {
2487                let db = session.db;
2488                let changed = hashes::execute(&server.dbs[db], db, spec, args, out);
2489                changed.map(|changed| {
2490                    indexing::changed(server, db, args.get(1), changed);
2491                    Flow::Continue
2492                })
2493            }
2494            "list" => {
2495                let db = session.db;
2496                lists::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2497            }
2498            "zset" => {
2499                let db = session.db;
2500                zsets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2501            }
2502            // A geo key is a sorted set and these are sorted set commands with
2503            // arithmetic on the way in and on the way out, so a client can ZREM
2504            // a place out of one and ZCARD it to count them.
2505            "geo" => {
2506                let db = session.db;
2507                geo::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2508            }
2509            "array" => {
2510                let db = session.db;
2511                arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2512            }
2513            "graph" => {
2514                let db = session.db;
2515                graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2516            }
2517            // A document under a key, reached by a path. The group is Redis's
2518            // module surface and the storage is ours, the same trade the vector
2519            // set group makes.
2520            "json" => {
2521                let db = session.db;
2522                json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2523            }
2524            "vector" => {
2525                let db = session.db;
2526                vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2527            }
2528            "bloom" => {
2529                let db = session.db;
2530                bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2531            }
2532            "cuckoo" => {
2533                let db = session.db;
2534                cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2535            }
2536            "cms" => {
2537                let db = session.db;
2538                cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2539            }
2540            "topk" => {
2541                let db = session.db;
2542                topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2543            }
2544            "tdigest" => {
2545                let db = session.db;
2546                tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2547            }
2548            "ts" => {
2549                let db = session.db;
2550                ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2551            }
2552            // The clock is read before the database is borrowed, because every
2553            // stream command needs the time and it lives on the server. An
2554            // `XADD` with no ID, an `XCLAIM` working out what is idle and an
2555            // `XINFO` reporting it all have to agree about what moment this is.
2556            "stream" => {
2557                let db = session.db;
2558                let now = server.now_ms();
2559                streams::execute(&server.dbs[db], db, spec, args, now, out).map(|()| Flow::Continue)
2560            }
2561            // The one keyspace command that needs more than the databases,
2562            // because the socket it talks down is held on the server between
2563            // commands and not opened again for each one.
2564            "keyspace" if spec.name == "migrate" => {
2565                migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
2566            }
2567            // Every database and not the one the session is on, because `COPY` takes
2568            // a `DB n` and writes into a database nobody selected. The other group
2569            // that reaches back into the server afterwards, and it hands back a list
2570            // rather than one answer, because `DEL a b c` is three keys and a rename
2571            // is two.
2572            "keyspace" => {
2573                let mut touched = indexing::Touched::new(server);
2574                let done =
2575                    keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
2576                done.map(|()| {
2577                    indexing::touched(server, &touched);
2578                    Flow::Continue
2579                })
2580            }
2581            // No database at all, because an index is not a key. The registry
2582            // is the whole of what these sixteen commands touch, and then
2583            // `FT.CREATE` hands back the name it made so the keys that
2584            // already match its prefix can be read into it. The lock goes
2585            // before the scan runs, since the scan takes it again for every
2586            // key it reads.
2587            "search" if spec.name == "FT.SEARCH" => {
2588                // The two search commands that read documents, and so the two
2589                // that need the keyspace as well as the registry. They take and
2590                // let go of the registry themselves, because they cannot hold
2591                // that and a stripe at the same time.
2592                search::find(server, session.db, args, out).map(|()| Flow::Continue)
2593            }
2594            "search" if spec.name == "FT.AGGREGATE" => {
2595                search::roll(server, session.db, args, out).map(|()| Flow::Continue)
2596            }
2597            "search" if spec.name == "FT.HYBRID" => {
2598                search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
2599            }
2600            "search" if spec.name == "FT.PROFILE" => {
2601                // Which is one of those two with the working shown, so it needs
2602                // everything they need and takes the same route to it.
2603                search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
2604            }
2605            // The four search commands that name a key rather than an index.
2606            // A suggestion dictionary is a real key with a type of its own, so
2607            // these are handed a database and never touch the registry.
2608            "search" if spec.name.starts_with("FT.SUG") => {
2609                let db = session.db;
2610                suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2611            }
2612            // The five deprecated document commands, which are the other search
2613            // commands that need the keyspace as well as the registry: what they
2614            // write and read is an ordinary hash.
2615            "search"
2616                if matches!(
2617                    spec.name,
2618                    "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
2619                ) =>
2620            {
2621                let db = session.db;
2622                search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
2623            }
2624            "search" if spec.name == "FT.CURSOR" => {
2625                // Its own arm because the cursors are not in the registry, and
2626                // it takes and lets go of the registry itself to look up the
2627                // index name it is given.
2628                search::cursor::execute(server, args, out).map(|()| Flow::Continue)
2629            }
2630            "search" => {
2631                let db = session.db;
2632                let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
2633                made.map(|made| {
2634                    match made {
2635                        Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
2636                        Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
2637                        None => {}
2638                    }
2639                    Flow::Continue
2640                })
2641            }
2642            "scripting" => {
2643                scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
2644            }
2645            "transactions" => multi::execute(server, session, spec, args, out),
2646            // No database either, and the one group whose replies do not all go
2647            // to the connection that asked. The session is in it because a
2648            // subscription is connection state as well as server state.
2649            "pubsub" => pubsub::execute(server, session, spec, args, out),
2650            _ => server::execute(server, session, spec, args, out),
2651        }
2652    };
2653    drop(reading);
2654    // Before the error is written and not after, because a command that failed
2655    // half way through still changed whatever it changed before it failed and a
2656    // real server has already published those. Draining here also keeps the
2657    // notifications of a command run by `EXEC` in front of the next one's.
2658    // And back out the misses reported in front of a command that turned out to
2659    // have failed on its own arguments, since a server that fires from inside
2660    // the lookup never reached one.
2661    if let Err(e) = &done {
2662        misses::undo(spec, e);
2663    }
2664    notify::drain(server, armed);
2665
2666    let flow = match done {
2667        Ok(flow) => flow,
2668        Err(e) => {
2669            out.truncate(mark);
2670            write_error(out, &e);
2671            Flow::Continue
2672        }
2673    };
2674
2675    // After the command rather than before, so that whether each key it named is
2676    // there is read at the moment a real server would have signalled the change.
2677    // The load is what this costs a server nobody has sent `WATCH` to, and the
2678    // flag is Redis's own, so a command that only reads is never asked.
2679    if server.watching() && spec.flags.contains(&"write") {
2680        multi::touched(server, session, spec, args);
2681    }
2682
2683    // Counted here and not before the call, which is where Redis counts it, so
2684    // that `INFO commandstats` leaves out the `INFO` that asked for it in the
2685    // same way theirs does.
2686    //
2687    // Failure is read off the reply rather than off the `Result`, because the
2688    // two are not the same set. A command that ran out of arguments comes back
2689    // as an `Err` and a command that was sent the wrong password writes its own
2690    // error line and comes back `Ok`, and both of those are a call that failed.
2691    // The first byte at the mark is what a client would branch on, and it is `-`
2692    // for an error on either protocol and `!` for RESP3's long form.
2693    let row = server.mine().cmdstats.at(spec);
2694    row.calls.bump();
2695    if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2696        row.failed.bump();
2697    }
2698    flow
2699}
2700
2701/// The error line for an error value.
2702///
2703/// The prefix is what a client branches on, and there are three of them:
2704/// `WRONGTYPE` for a command sent at the wrong kind of value, `INVALIDOBJ` for a
2705/// HyperLogLog whose opcodes do not add up, and `ERR` for everything else. The three errors that need a different one,
2706/// `NOPROTO`, `WRONGPASS` and `OOM`, are written where they are decided rather
2707/// than routed through here. `OOM` is not a [`Code`] of its own because
2708/// [`Code::Full`] already covers the string that is too long for
2709/// `proto-max-bulk-len`, and that one goes out as `ERR` on a real server.
2710fn write_error(out: &mut Out, e: &Error) {
2711    let prefix: &[u8] = match e.code() {
2712        Code::WrongType => b"WRONGTYPE ",
2713        // Only the HyperLogLog commands answer this one, and the prefix is the
2714        // sentence a client branches on to tell a sketch it cannot read from a
2715        // sketch it sent wrong.
2716        Code::Corrupt => b"INVALIDOBJ ",
2717        _ => b"ERR ",
2718    };
2719    out.error_line(prefix, e.message().as_bytes());
2720}
2721
2722#[cfg(test)]
2723mod tests {
2724    use super::*;
2725    use crate::proto::{Limits, Proto};
2726    use crate::request::Argv;
2727
2728    /// Build the wire bytes for a command.
2729    ///
2730    /// Tests go through the codec rather than around it, so an argument in a
2731    /// test is the same borrowed slice a connection produces.
2732    pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2733        let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2734        for p in parts {
2735            wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2736            wire.extend_from_slice(p);
2737            wire.extend_from_slice(b"\r\n");
2738        }
2739        wire
2740    }
2741
2742    /// A server, a connection and a buffer, driven the way the reactor will.
2743    struct Fixture {
2744        server: Server,
2745        session: Session,
2746        argv: Argv,
2747        out: Out,
2748    }
2749
2750    impl Fixture {
2751        fn new() -> Fixture {
2752            Fixture::on(Server::new())
2753        }
2754
2755        /// The same, on a server whose databases are cut into `width` stripes.
2756        fn striped(width: usize) -> Fixture {
2757            Fixture::on(Server::with_width(width))
2758        }
2759
2760        fn on(server: Server) -> Fixture {
2761            Fixture {
2762                server,
2763                session: Session::new(7),
2764                argv: Argv::new(),
2765                out: Out::new(Proto::Resp2),
2766            }
2767        }
2768
2769        /// Run one command and answer with the bytes it wrote.
2770        fn run(&mut self, parts: &[&[u8]]) -> String {
2771            self.flow(parts).1
2772        }
2773
2774        /// Run one command and answer with the bytes exactly as written.
2775        ///
2776        /// [`Fixture::run`] goes through `from_utf8_lossy`, which is fine for
2777        /// every reply that is text and destroys a `DUMP` payload, since a
2778        /// payload is arbitrary bytes and a checksum on the end of them.
2779        fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2780            let wire = encode(parts);
2781            self.argv.decode(&wire, &Limits::default()).unwrap();
2782            self.out.clear();
2783            execute(
2784                &self.server,
2785                &mut self.session,
2786                Args::new(&self.argv, &wire),
2787                &mut self.out,
2788            );
2789            self.out.as_slice().to_vec()
2790        }
2791
2792        /// Move every clock in the server on by `ms`.
2793        fn advance(&mut self, ms: u64) {
2794            self.server.advance_clock_ms(ms);
2795        }
2796
2797        /// Run one command as a second connection to the same server.
2798        ///
2799        /// What `WATCH` is for is a write another connection made, and a test
2800        /// that only has one connection cannot tell the two apart.
2801        fn other(&mut self, parts: &[&[u8]]) -> String {
2802            self.other_in(self.session.db(), parts)
2803        }
2804
2805        /// The same, on a database of its own.
2806        fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
2807            let mut session = Session::new(8);
2808            session.db = db;
2809            let reply = self.by(&mut session, parts);
2810            forget_session(&self.server, &mut session);
2811            reply
2812        }
2813
2814        /// Run one command on a session the caller holds.
2815        fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
2816            let wire = encode(parts);
2817            let mut argv = Argv::new();
2818            argv.decode(&wire, &Limits::default()).unwrap();
2819            let mut out = Out::new(Proto::Resp2);
2820            execute(&self.server, session, Args::new(&argv, &wire), &mut out);
2821            String::from_utf8_lossy(out.as_slice()).into_owned()
2822        }
2823
2824        /// The same, with what the connection should do next.
2825        fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2826            let wire = encode(parts);
2827            self.argv.decode(&wire, &Limits::default()).unwrap();
2828            self.out.clear();
2829            let flow = execute(
2830                &self.server,
2831                &mut self.session,
2832                Args::new(&self.argv, &wire),
2833                &mut self.out,
2834            );
2835            (
2836                flow,
2837                String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2838            )
2839        }
2840    }
2841
2842    #[test]
2843    fn multi_holds_commands_and_exec_runs_them() {
2844        let mut f = Fixture::new();
2845        assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
2846        assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
2847        assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
2848        // Nothing ran while it was being queued.
2849        assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
2850        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
2851        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
2852    }
2853
2854    /// The test the `high_water` claim in `multi::exec` asks for.
2855    ///
2856    /// A `Vec` reaches the allocator exactly when its capacity changes, so a
2857    /// replay buffer whose room is the same before and after is one that did
2858    /// not allocate. The first transaction is what sets the room, which is the
2859    /// high water mark, and the second is the one that has to be free. Before
2860    /// the buffer moved onto the session this failed on every transaction,
2861    /// because `exec` made a new one each time and the room went back to zero.
2862    #[test]
2863    fn the_second_exec_of_a_shape_does_not_grow_the_buffer() {
2864        let mut f = Fixture::new();
2865        for _ in 0..2 {
2866            f.run(&[b"MULTI"]);
2867            f.run(&[b"SET", b"k", b"1"]);
2868            f.run(&[b"INCR", b"k"]);
2869            f.run(&[b"EXEC"]);
2870        }
2871        let room = f.session.replay.room();
2872        assert!(room > 0, "the first transaction should have set the room");
2873        f.run(&[b"MULTI"]);
2874        f.run(&[b"SET", b"k", b"1"]);
2875        f.run(&[b"INCR", b"k"]);
2876        f.run(&[b"EXEC"]);
2877        assert_eq!(f.session.replay.room(), room);
2878    }
2879
2880    #[test]
2881    fn an_empty_transaction_answers_an_empty_array() {
2882        let mut f = Fixture::new();
2883        f.run(&[b"MULTI"]);
2884        assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
2885    }
2886
2887    #[test]
2888    fn exec_and_discard_want_a_transaction_to_be_open() {
2889        let mut f = Fixture::new();
2890        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2891        assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
2892        // And `UNWATCH` does not, which is the one of the three that is happy
2893        // being sent for no reason.
2894        assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
2895    }
2896
2897    #[test]
2898    fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
2899        let mut f = Fixture::new();
2900        f.run(&[b"MULTI"]);
2901        assert_eq!(
2902            f.run(&[b"MULTI"]),
2903            "-ERR MULTI calls can not be nested\r\n",
2904            "nested MULTI is raised by the command and not by the funnel"
2905        );
2906        assert_eq!(
2907            f.run(&[b"WATCH", b"k"]),
2908            "-ERR WATCH inside MULTI is not allowed\r\n"
2909        );
2910        f.run(&[b"SET", b"k", b"1"]);
2911        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
2912    }
2913
2914    #[test]
2915    fn an_error_the_funnel_raises_kills_the_transaction() {
2916        for bad in [
2917            &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
2918            &[b"GET".as_slice()],
2919        ] {
2920            let mut f = Fixture::new();
2921            f.run(&[b"MULTI"]);
2922            assert!(f.run(bad).starts_with("-ERR "));
2923            assert_eq!(
2924                f.run(&[b"SET", b"k", b"1"]),
2925                "+QUEUED\r\n",
2926                "a dead transaction still answers QUEUED, which is Redis"
2927            );
2928            assert_eq!(
2929                f.run(&[b"EXEC"]),
2930                "-EXECABORT Transaction discarded because of previous errors.\r\n"
2931            );
2932            assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2933        }
2934    }
2935
2936    #[test]
2937    fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
2938        let mut f = Fixture::new();
2939        f.run(&[b"MULTI"]);
2940        f.run(&[b"SET", b"k", b"1"]);
2941        assert_eq!(
2942            f.run(&[b"EXEC", b"x"]),
2943            "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
2944        );
2945        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2946        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2947    }
2948
2949    #[test]
2950    fn a_command_a_transaction_may_not_hold_kills_it() {
2951        let mut f = Fixture::new();
2952        f.run(&[b"MULTI"]);
2953        assert_eq!(
2954            f.run(&[b"SHUTDOWN", b"NOSAVE"]),
2955            "-ERR Command not allowed inside a transaction\r\n"
2956        );
2957        assert_eq!(
2958            f.run(&[b"EXEC"]),
2959            "-EXECABORT Transaction discarded because of previous errors.\r\n"
2960        );
2961    }
2962
2963    #[test]
2964    fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
2965        let mut f = Fixture::new();
2966        f.run(&[b"RPUSH", b"l", b"v"]);
2967        f.run(&[b"MULTI"]);
2968        f.run(&[b"INCR", b"l"]);
2969        f.run(&[b"SET", b"y", b"2"]);
2970        assert_eq!(
2971            f.run(&[b"EXEC"]),
2972            "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
2973        );
2974        assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
2975    }
2976
2977    #[test]
2978    fn discard_and_reset_both_throw_the_queue_away() {
2979        let mut f = Fixture::new();
2980        f.run(&[b"MULTI"]);
2981        f.run(&[b"SET", b"k", b"1"]);
2982        assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
2983        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2984        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2985
2986        f.run(&[b"MULTI"]);
2987        f.run(&[b"SET", b"k", b"1"]);
2988        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2989        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2990        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2991    }
2992
2993    #[test]
2994    fn select_is_queued_and_applied_when_exec_runs_it() {
2995        let mut f = Fixture::new();
2996        f.run(&[b"MULTI"]);
2997        assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
2998        f.run(&[b"SET", b"k", b"1"]);
2999        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
3000        assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
3001        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
3002    }
3003
3004    #[test]
3005    fn a_write_by_another_connection_fails_the_transaction() {
3006        let mut f = Fixture::new();
3007        f.run(&[b"SET", b"k", b"1"]);
3008        assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
3009        f.other(&[b"SET", b"k", b"2"]);
3010        f.run(&[b"MULTI"]);
3011        f.run(&[b"GET", b"k"]);
3012        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3013    }
3014
3015    #[test]
3016    fn a_write_that_puts_the_same_value_back_still_fails_it() {
3017        let mut f = Fixture::new();
3018        f.run(&[b"SET", b"k", b"1"]);
3019        f.run(&[b"WATCH", b"k"]);
3020        f.other(&[b"SET", b"k", b"1"]);
3021        f.run(&[b"MULTI"]);
3022        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3023    }
3024
3025    #[test]
3026    fn a_read_by_another_connection_does_not() {
3027        let mut f = Fixture::new();
3028        f.run(&[b"SET", b"k", b"1"]);
3029        f.run(&[b"WATCH", b"k"]);
3030        f.other(&[b"GET", b"k"]);
3031        f.other(&[b"STRLEN", b"k"]);
3032        f.run(&[b"MULTI"]);
3033        f.run(&[b"GET", b"k"]);
3034        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
3035    }
3036
3037    #[test]
3038    fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
3039        let mut f = Fixture::new();
3040        f.run(&[b"WATCH", b"k"]);
3041        f.other(&[b"DEL", b"k"]);
3042        f.run(&[b"MULTI"]);
3043        f.run(&[b"PING"]);
3044        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
3045        // And creating it does, which is the other half of the same rule.
3046        f.run(&[b"WATCH", b"k"]);
3047        f.other(&[b"SET", b"k", b"1"]);
3048        f.run(&[b"MULTI"]);
3049        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3050    }
3051
3052    #[test]
3053    fn a_watched_key_that_expires_fails_the_transaction() {
3054        let mut f = Fixture::new();
3055        f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
3056        f.run(&[b"WATCH", b"k"]);
3057        f.run(&[b"MULTI"]);
3058        f.advance(100);
3059        assert_eq!(
3060            f.run(&[b"EXEC"]),
3061            "*-1\r\n",
3062            "nothing wrote to the key, so only the liveness check can catch this"
3063        );
3064    }
3065
3066    #[test]
3067    fn every_way_a_transaction_ends_lets_go_of_the_watches() {
3068        for end in [
3069            &[b"EXEC".as_slice()] as &[&[u8]],
3070            &[b"DISCARD".as_slice()],
3071            &[b"UNWATCH".as_slice()],
3072            &[b"RESET".as_slice()],
3073        ] {
3074            let mut f = Fixture::new();
3075            f.run(&[b"SET", b"k", b"1"]);
3076            f.run(&[b"WATCH", b"k"]);
3077            if end[0] != b"UNWATCH" && end[0] != b"RESET" {
3078                f.run(&[b"MULTI"]);
3079            }
3080            f.run(end);
3081            assert!(!f.server.watching(), "{end:?} left a row behind");
3082            // And the connection can start again with nothing carried over.
3083            f.other(&[b"SET", b"k", b"2"]);
3084            f.run(&[b"MULTI"]);
3085            f.run(&[b"GET", b"k"]);
3086            assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
3087        }
3088    }
3089
3090    #[test]
3091    fn a_connection_going_away_lets_go_of_its_watches() {
3092        let mut f = Fixture::new();
3093        f.run(&[b"SET", b"k", b"1"]);
3094        f.run(&[b"WATCH", b"k"]);
3095        assert!(f.server.watching());
3096        forget_session(&f.server, &mut f.session);
3097        assert!(!f.server.watching());
3098    }
3099
3100    #[test]
3101    fn watching_the_same_key_twice_is_one_watch() {
3102        let mut f = Fixture::new();
3103        f.run(&[b"SET", b"k", b"1"]);
3104        f.run(&[b"WATCH", b"k", b"k"]);
3105        f.run(&[b"UNWATCH"]);
3106        assert!(
3107            !f.server.watching(),
3108            "the row counts watchers, so a doubled watch would leave one behind"
3109        );
3110    }
3111
3112    #[test]
3113    fn two_connections_can_watch_the_same_key() {
3114        let mut f = Fixture::new();
3115        f.run(&[b"SET", b"k", b"1"]);
3116        f.run(&[b"WATCH", b"k"]);
3117        let mut second = Session::new(9);
3118        second.db = f.session.db();
3119        assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
3120        // One lets go and the other's watch still works.
3121        forget_session(&f.server, &mut second);
3122        assert!(f.server.watching());
3123        f.other(&[b"SET", b"k", b"2"]);
3124        f.run(&[b"MULTI"]);
3125        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3126    }
3127
3128    #[test]
3129    fn flushdb_fails_a_watch_on_a_key_that_was_there() {
3130        let mut f = Fixture::new();
3131        f.run(&[b"SET", b"k", b"1"]);
3132        f.run(&[b"WATCH", b"k"]);
3133        f.other(&[b"FLUSHDB"]);
3134        f.run(&[b"MULTI"]);
3135        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
3136    }
3137
3138    #[test]
3139    fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
3140        let mut f = Fixture::new();
3141        f.run(&[b"WATCH", b"k"]);
3142        f.other(&[b"FLUSHDB"]);
3143        f.run(&[b"MULTI"]);
3144        f.run(&[b"PING"]);
3145        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
3146    }
3147
3148    #[test]
3149    fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
3150        let mut f = Fixture::new();
3151        f.run(&[b"SET", b"k", b"1"]);
3152        f.run(&[b"WATCH", b"k"]);
3153        // The same name in another database is another key.
3154        let elsewhere = f.session.db() + 1;
3155        f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
3156        f.run(&[b"MULTI"]);
3157        f.run(&[b"GET", b"k"]);
3158        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
3159    }
3160
3161    #[test]
3162    fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
3163        let mut f = Fixture::new();
3164        f.run(&[b"RPUSH", b"src", b"1"]);
3165        f.run(&[b"WATCH", b"dst"]);
3166        f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
3167        f.run(&[b"MULTI"]);
3168        assert_eq!(
3169            f.run(&[b"EXEC"]),
3170            "*-1\r\n",
3171            "SORT is movablekeys, so every watched key in the database is asked"
3172        );
3173    }
3174
3175    #[test]
3176    fn a_server_nobody_is_watching_says_so() {
3177        let mut f = Fixture::new();
3178        assert!(!f.server.watching());
3179        f.run(&[b"SET", b"k", b"1"]);
3180        assert!(!f.server.watching());
3181    }
3182
3183    /// The count on the end of a subscribe reply is channels and patterns
3184    /// together, which is a thing a client uses to know when it is out of
3185    /// subscribe mode and so has to be the number the mode is decided on.
3186    /// Shard channels are counted on their own because they are their own
3187    /// namespace.
3188    #[test]
3189    fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
3190        let mut f = Fixture::new();
3191        assert_eq!(
3192            f.run(&[b"SUBSCRIBE", b"a", b"b"]),
3193            "*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"
3194        );
3195        assert_eq!(
3196            f.run(&[b"PSUBSCRIBE", b"c*"]),
3197            "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
3198        );
3199        assert_eq!(
3200            f.run(&[b"SSUBSCRIBE", b"s"]),
3201            "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
3202        );
3203        // Subscribing again to something already held answers again with the
3204        // count unchanged, rather than counting it twice or saying nothing.
3205        assert_eq!(
3206            f.run(&[b"SUBSCRIBE", b"a"]),
3207            "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
3208        );
3209    }
3210
3211    /// Unsubscribe has three shapes and a client has to be able to tell them
3212    /// apart, because the last one is what tells it the mode is over.
3213    #[test]
3214    fn unsubscribe_answers_for_names_it_was_not_holding_too() {
3215        let mut f = Fixture::new();
3216        f.run(&[b"SUBSCRIBE", b"a"]);
3217
3218        // A name that was never subscribed still gets a reply, with the count
3219        // as it stands.
3220        assert_eq!(
3221            f.run(&[b"UNSUBSCRIBE", b"zz"]),
3222            "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
3223        );
3224        // With no names, one reply per channel held, counting down.
3225        f.run(&[b"SUBSCRIBE", b"b"]);
3226        f.run(&[b"PSUBSCRIBE", b"p*"]);
3227        assert_eq!(
3228            f.run(&[b"UNSUBSCRIBE"]),
3229            "*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"
3230        );
3231        // With no names and none of that family held, one reply with a nil
3232        // where the name goes and the count that is left.
3233        assert_eq!(
3234            f.run(&[b"UNSUBSCRIBE"]),
3235            "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
3236            "the pattern is still held, so the count is one"
3237        );
3238        assert_eq!(
3239            f.run(&[b"SUNSUBSCRIBE"]),
3240            "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
3241            "shard channels are counted on their own"
3242        );
3243    }
3244
3245    /// The gate is on the funnel and the funnel is what `EXEC` goes through
3246    /// for the commands it queued, so it has to know it is running one.
3247    /// Redis lets a queued command through, and a transaction that subscribes
3248    /// and then reads is the case that says which way round it is.
3249    #[test]
3250    fn the_subscribe_gate_does_not_reach_inside_exec() {
3251        let mut f = Fixture::new();
3252        f.run(&[b"SET", b"k", b"1"]);
3253        f.run(&[b"MULTI"]);
3254        assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
3255        assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
3256        assert_eq!(
3257            f.run(&[b"EXEC"]),
3258            "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
3259        );
3260        // And once EXEC is done the connection really is subscribed, so the
3261        // gate is back on.
3262        assert_eq!(
3263            f.run(&[b"GET", b"k"]),
3264            "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
3265        );
3266    }
3267
3268    /// `EXEC` sent by a subscribed RESP2 client is refused by the gate like
3269    /// anything else, and a refusal on the funnel kills the transaction.
3270    #[test]
3271    fn exec_sent_by_a_subscriber_aborts_the_transaction() {
3272        let mut f = Fixture::new();
3273        f.run(&[b"MULTI"]);
3274        f.run(&[b"SET", b"k", b"1"]);
3275        f.run(&[b"SUBSCRIBE", b"z"]);
3276        f.run(&[b"EXEC"]);
3277        f.run(&[b"MULTI"]);
3278        assert_eq!(
3279            f.run(&[b"EXEC"]),
3280            "-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"
3281        );
3282    }
3283
3284    /// `RESET` is one of the few things a subscriber may send, and what it
3285    /// resets includes every subscription it is holding.
3286    #[test]
3287    fn reset_lets_go_of_every_subscription() {
3288        let mut f = Fixture::new();
3289        f.run(&[b"SUBSCRIBE", b"a"]);
3290        f.run(&[b"PSUBSCRIBE", b"p*"]);
3291        f.run(&[b"SSUBSCRIBE", b"s"]);
3292        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
3293        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
3294        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
3295        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
3296        // And the connection takes ordinary commands again.
3297        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3298    }
3299
3300    /// What `PUBSUB` can be asked, on a server with one subscriber holding one
3301    /// of each.
3302    #[test]
3303    fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
3304        let mut f = Fixture::new();
3305        let mut sub = Session::new(9);
3306        f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
3307        f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
3308        f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
3309
3310        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
3311        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
3312        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
3313        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
3314        assert_eq!(
3315            f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
3316            "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
3317        );
3318        assert_eq!(
3319            f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
3320            "*2\r\n$1\r\na\r\n:1\r\n",
3321            "the shard channel and the channel share a name and not a count"
3322        );
3323        assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
3324
3325        forget_session(&f.server, &mut sub);
3326        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
3327        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
3328    }
3329
3330    /// The one setting whose value is neither a number nor a word, and whose
3331    /// spelling on the way out is not the spelling on the way in.
3332    #[test]
3333    fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
3334        let mut f = Fixture::new();
3335        assert_eq!(
3336            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
3337            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
3338        );
3339        assert_eq!(
3340            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
3341            "+OK\r\n"
3342        );
3343        // `A` is a class of its own on the way in and stays one on the way out,
3344        // and the two channel letters move to the end.
3345        assert_eq!(
3346            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
3347            "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
3348        );
3349        assert_eq!(
3350            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
3351            "+OK\r\n"
3352        );
3353        assert_eq!(
3354            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
3355            "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
3356        );
3357    }
3358
3359    #[test]
3360    fn a_letter_the_notification_setting_does_not_know_is_refused() {
3361        let mut f = Fixture::new();
3362        assert_eq!(
3363            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
3364            "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
3365             - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
3366        );
3367        // And nothing was applied, since the whole setting is parsed before any
3368        // of it is stored.
3369        assert_eq!(
3370            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
3371            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
3372        );
3373    }
3374
3375    /// One mistake in a `PUBSUB` subcommand has two error shapes depending on
3376    /// which subcommand it is, because the ones with a fixed argument count are
3377    /// checked by the subcommand table and the ones without fall through to
3378    /// the generic syntax error. Both are copied here rather than tidied,
3379    /// since a client that matches on the text sees the difference.
3380    #[test]
3381    fn pubsub_says_no_two_different_ways() {
3382        let mut f = Fixture::new();
3383        assert_eq!(
3384            f.run(&[b"PUBSUB"]),
3385            "-ERR wrong number of arguments for 'pubsub' command\r\n"
3386        );
3387        assert_eq!(
3388            f.run(&[b"PUBSUB", b"NOPE"]),
3389            "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
3390        );
3391        assert_eq!(
3392            f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
3393            "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
3394        );
3395        assert_eq!(
3396            f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
3397            "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
3398        );
3399        assert_eq!(
3400            f.run(&[b"PUBSUB", b"HELP", b"x"]),
3401            "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
3402        );
3403    }
3404
3405    /// Publishing to nobody costs a lookup and answers zero, which is the
3406    /// common case on a server that has pub/sub compiled in and not in use.
3407    #[test]
3408    fn publishing_to_nobody_answers_zero() {
3409        let mut f = Fixture::new();
3410        assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
3411        assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
3412        // An empty channel name is a name like any other.
3413        assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
3414    }
3415
3416    /// A publish counts everybody it reached, which is not the same as the
3417    /// number of subscribers: one connection holding two patterns that both
3418    /// match is two.
3419    #[test]
3420    fn a_publish_counts_the_deliveries_and_not_the_clients() {
3421        let mut f = Fixture::new();
3422        let mut sub = Session::new(9);
3423        f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
3424        f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
3425        f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
3426        assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
3427        forget_session(&f.server, &mut sub);
3428    }
3429
3430    /// What a client does all day: write the same keys again and again. Every
3431    /// one of those writes leaves the previous record behind, so a server that
3432    /// never compacts holds every version of every key it has ever been sent.
3433    ///
3434    /// Not under Miri, and not because of anything it would find. The bound
3435    /// only means something once several megabytes have gone through the
3436    /// arena, which reclaims a segment at a time and has segments of two
3437    /// megabytes, so a server that reclaimed nothing would still be under the
3438    /// bound in any smaller version of this. Thirty two megabytes is thirty
3439    /// two thousand commands and was over forty minutes interpreted. The paths
3440    /// it walks are walked by the hundreds of tests around it that write a key
3441    /// and read it back, which do run there.
3442    #[cfg_attr(miri, ignore = "megabytes through the arena")]
3443    #[test]
3444    fn rewriting_the_same_keys_does_not_grow_the_server() {
3445        let mut f = Fixture::new();
3446        let val = vec![b'v'; 1024];
3447        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3448
3449        for k in &keys {
3450            f.run(&[b"SET", k, &val]);
3451        }
3452        f.server.compact_step();
3453        let after_first = f.server.memory_bytes();
3454
3455        // 64 KiB a pass, five hundred passes, and the same 64 keys at the end
3456        // of it. Thirty two megabytes written to hold sixty four kilobytes,
3457        // which is the shape of a real workload and is enough churn to fill
3458        // sixteen segments if nothing ever comes back.
3459        for _ in 0..500 {
3460            for k in &keys {
3461                f.run(&[b"SET", k, &val]);
3462            }
3463            f.server.compact_step();
3464        }
3465
3466        assert!(
3467            f.server.memory_bytes() <= after_first * 2,
3468            "held {} after five hundred passes against {after_first} after one",
3469            f.server.memory_bytes()
3470        );
3471        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3472        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3473    }
3474
3475    /// The same churn on a database nobody starts on, either side of a quiet
3476    /// spell long enough for the maintenance turn to stop asking about it.
3477    ///
3478    /// The turn after each batch skips a database that has already said it has
3479    /// nothing to collect and has not been touched since, which is what keeps a
3480    /// server whose clients are all on database zero from loading and storing
3481    /// in the other fifteen every batch to be told no. Two things could go
3482    /// wrong with that. A database might never be marked at all, so this uses
3483    /// database nine, which nothing marks by accident. And a database whose
3484    /// mark was cleared might never get it back, so this drains the collector
3485    /// until it says there is nothing left, checks the mark really is gone, and
3486    /// then writes another thirty two megabytes through the same sixty four
3487    /// keys. If either went wrong the server would hold all of it.
3488    ///
3489    /// Not under Miri, for the reason on the test above: the volume is the
3490    /// claim, and the volume is what the interpreter charges for.
3491    #[cfg_attr(miri, ignore = "megabytes through the arena")]
3492    #[test]
3493    fn a_database_nobody_started_on_is_still_collected() {
3494        let mut f = Fixture::new();
3495        assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
3496        let val = vec![b'v'; 1024];
3497        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3498
3499        for k in &keys {
3500            f.run(&[b"SET", k, &val]);
3501        }
3502        while f.server.compact_step().is_some() {}
3503        assert!(
3504            !f.server.mine().wanted(9),
3505            "database nine was drained and should not be asked again until it is written to"
3506        );
3507        let after_first = f.server.memory_bytes();
3508
3509        for _ in 0..500 {
3510            for k in &keys {
3511                f.run(&[b"SET", k, &val]);
3512            }
3513            f.server.compact_step();
3514        }
3515
3516        assert!(
3517            f.server.memory_bytes() <= after_first * 2,
3518            "held {} after five hundred passes against {after_first} after one",
3519            f.server.memory_bytes()
3520        );
3521        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3522        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3523        // And nothing landed anywhere else on the way.
3524        f.run(&[b"SELECT", b"0"]);
3525        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3526    }
3527
3528    #[test]
3529    fn a_command_goes_from_bytes_to_bytes() {
3530        let mut f = Fixture::new();
3531        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3532        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3533        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3534        assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
3535        // The name is matched whatever case it came in, and so are the options.
3536        assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
3537        assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
3538    }
3539
3540    #[test]
3541    fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
3542        let mut f = Fixture::new();
3543        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
3544        // A key named twice exists twice and can only be deleted once, and both
3545        // of those are Redis's answers rather than tidier ones.
3546        assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
3547        assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
3548        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3549        // UNLINK is the same body and reports the same way.
3550        assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
3551        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3552    }
3553
3554    #[test]
3555    fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
3556        let mut f = Fixture::new();
3557        f.run(&[b"SET", b"k", b"v"]);
3558        // A simple string on both protocols, which is unusual: most replies
3559        // that carry a word are bulk strings.
3560        assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
3561        assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
3562    }
3563
3564    #[test]
3565    fn touch_counts_the_way_exists_counts() {
3566        let mut f = Fixture::new();
3567        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3568        assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
3569        assert_eq!(
3570            f.run(&[b"TOUCH", b"a", b"a"]),
3571            ":2\r\n",
3572            "twice counts twice"
3573        );
3574        assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
3575        assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
3576    }
3577
3578    #[test]
3579    fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
3580        let mut f = Fixture::new();
3581        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3582        f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
3583
3584        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
3585        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3586        assert_eq!(
3587            f.run(&[b"TTL", b"b"]),
3588            ":100\r\n",
3589            "the source's and not b's"
3590        );
3591        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3592    }
3593
3594    #[test]
3595    fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
3596        let mut f = Fixture::new();
3597        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
3598        // The source is checked before the destination, so this is the error
3599        // and not the zero RENAMENX would otherwise answer for a taken name.
3600        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
3601    }
3602
3603    #[test]
3604    fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
3605        let mut f = Fixture::new();
3606        f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
3607
3608        assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
3609        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3610        // Renaming onto itself is 0 here and OK for plain RENAME, which is the
3611        // one call the two disagree about and neither does any work for.
3612        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
3613        assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
3614        assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
3615        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
3616    }
3617
3618    #[test]
3619    fn renaming_a_set_does_not_touch_a_member() {
3620        let mut f = Fixture::new();
3621        for i in 0..300 {
3622            f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
3623        }
3624        let before = f.server.memory_bytes();
3625
3626        assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
3627        assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
3628        assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
3629        assert!(
3630            f.server.memory_bytes().abs_diff(before) < 256,
3631            "the members were copied: {} against {before}",
3632            f.server.memory_bytes()
3633        );
3634    }
3635
3636    #[test]
3637    fn a_copy_is_a_second_value_and_not_a_second_name() {
3638        let mut f = Fixture::new();
3639        f.run(&[b"SADD", b"s", b"m1", b"m2"]);
3640
3641        assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
3642        f.run(&[b"SADD", b"t", b"m3"]);
3643        assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
3644        assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
3645    }
3646
3647    /// Every type a key can hold, copied, because two of them used to panic.
3648    ///
3649    /// `COPY` reads the value out of the source through one match on the type
3650    /// tag, and that match had a catch all at the bottom from back when a set
3651    /// and a hash were the only bodies. The list and the sorted set landed after
3652    /// it and nobody came back, so `COPY mylist other` took the shard down. It
3653    /// is an ordinary command against a type the server supports everywhere
3654    /// else, so this walks all five rather than the two that were broken: the
3655    /// point is that the next type cannot land the same way.
3656    #[test]
3657    fn every_type_can_be_copied() {
3658        let mut f = Fixture::new();
3659        f.run(&[b"SET", b"str", b"v1"]);
3660        f.run(&[b"SADD", b"set", b"m1"]);
3661        f.run(&[b"HSET", b"hash", b"f", b"v"]);
3662        f.run(&[b"RPUSH", b"list", b"a", b"b"]);
3663        f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
3664
3665        for name in [
3666            &b"str"[..],
3667            &b"set"[..],
3668            &b"hash"[..],
3669            &b"list"[..],
3670            &b"zset"[..],
3671        ] {
3672            let dst = [name, b":copy"].concat();
3673            assert_eq!(
3674                f.run(&[b"COPY", name, &dst]),
3675                ":1\r\n",
3676                "copying {}",
3677                String::from_utf8_lossy(name)
3678            );
3679            assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
3680        }
3681
3682        assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
3683            let mut want = String::from("*2\r\n");
3684            want.push_str("$1\r\na\r\n$1\r\nb\r\n");
3685            want
3686        });
3687        assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
3688
3689        // And the copy is its own value, not a second name for the source.
3690        f.run(&[b"RPUSH", b"list:copy", b"c"]);
3691        assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
3692        assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
3693    }
3694
3695    #[test]
3696    fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
3697        let mut f = Fixture::new();
3698        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3699        f.run(&[b"SET", b"b", b"v2"]);
3700
3701        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
3702        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3703        assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
3704        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3705        assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
3706        assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
3707    }
3708
3709    #[test]
3710    fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
3711        let mut f = Fixture::new();
3712        f.run(&[b"SET", b"a", b"v1"]);
3713
3714        // Same key, different database, so this is not the same object and is
3715        // an ordinary copy. Same key in the same database is the error below.
3716        assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
3717        f.run(&[b"SELECT", b"1"]);
3718        assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
3719        assert_eq!(
3720            f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
3721            ":0\r\n",
3722            "taken"
3723        );
3724        assert_eq!(
3725            f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
3726            ":1\r\n"
3727        );
3728    }
3729
3730    #[test]
3731    fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
3732        let mut f = Fixture::new();
3733        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3734        assert_eq!(
3735            f.run(&[b"SORT", b"l"]),
3736            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3737        );
3738        // DESC then ASC is ASC, because the only thing ASC does is undo a DESC.
3739        assert_eq!(
3740            f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
3741            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3742        );
3743        assert_eq!(
3744            f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
3745            "*1\r\n$1\r\n2\r\n"
3746        );
3747    }
3748
3749    #[test]
3750    fn sort_reads_a_key_per_element_for_by_and_for_get() {
3751        let mut f = Fixture::new();
3752        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3753        f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
3754        // `b` weighs less so it comes first, and its `GET` hits where `a`'s
3755        // misses, which is a nil in the middle of the array and not a short one.
3756        assert_eq!(
3757            f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
3758            "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
3759        );
3760    }
3761
3762    #[test]
3763    fn sort_store_writes_a_list_and_answers_its_length() {
3764        let mut f = Fixture::new();
3765        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3766        assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
3767        assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
3768        assert_eq!(
3769            f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
3770            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3771        );
3772        // An empty result takes the destination with it rather than leaving a
3773        // list that holds nothing.
3774        assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
3775        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
3776    }
3777
3778    #[test]
3779    fn sort_ro_does_not_know_the_word_store() {
3780        let mut f = Fixture::new();
3781        f.run(&[b"RPUSH", b"l", b"2", b"1"]);
3782        assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
3783        assert_eq!(
3784            f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
3785            "-ERR syntax error\r\n"
3786        );
3787        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3788    }
3789
3790    #[test]
3791    fn sort_refuses_what_it_cannot_sort() {
3792        let mut f = Fixture::new();
3793        assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
3794        f.run(&[b"SET", b"s", b"x"]);
3795        assert_eq!(
3796            f.run(&[b"SORT", b"s"]),
3797            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
3798        );
3799        f.run(&[b"RPUSH", b"words", b"one", b"two"]);
3800        assert_eq!(
3801            f.run(&[b"SORT", b"words"]),
3802            "-ERR One or more scores can't be converted into double\r\n"
3803        );
3804        assert_eq!(
3805            f.run(&[b"SORT", b"words", b"ALPHA"]),
3806            "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
3807        );
3808        assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
3809    }
3810
3811    #[test]
3812    fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
3813        let mut f = Fixture::new();
3814        assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
3815        assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
3816        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3817        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3818        assert_eq!(
3819            f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
3820            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3821        );
3822        // And back, which proves the body survived the trip rather than being
3823        // rebuilt from a copy that happened to look the same.
3824        assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
3825        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3826    }
3827
3828    #[test]
3829    fn move_answers_zero_when_either_end_says_no() {
3830        let mut f = Fixture::new();
3831        assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
3832        assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
3833        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3834        assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
3835        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3836        // The destination is taken, so nothing moves and the source is still
3837        // there with what it had.
3838        assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
3839        assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
3840        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3841        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
3842    }
3843
3844    #[test]
3845    fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
3846        let mut f = Fixture::new();
3847        assert_eq!(
3848            f.run(&[b"MOVE", b"a", b"0"]),
3849            "-ERR source and destination objects are the same\r\n"
3850        );
3851        assert_eq!(
3852            f.run(&[b"MOVE", b"a", b"99"]),
3853            "-ERR DB index is out of range\r\n"
3854        );
3855        assert_eq!(
3856            f.run(&[b"MOVE", b"a", b"-1"]),
3857            "-ERR DB index is out of range\r\n"
3858        );
3859        assert_eq!(
3860            f.run(&[b"MOVE", b"a", b"x"]),
3861            "-ERR value is not an integer or out of range\r\n"
3862        );
3863    }
3864
3865    #[test]
3866    fn swapdb_swaps_what_two_connections_would_see() {
3867        let mut f = Fixture::new();
3868        assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
3869        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3870        assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
3871        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3872
3873        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3874        // Still on database zero, and database zero is a different database.
3875        assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
3876        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3877        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3878        // A database swapped with itself is fine and changes nothing.
3879        assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
3880        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3881    }
3882
3883    /// Every database on a server reads the server's clock and not one of its
3884    /// own. They used to be told the time one at a time and now they share the
3885    /// reading, so a server that built its databases from a second clock would
3886    /// answer a deadline worked out against a time nobody had set.
3887    #[test]
3888    fn a_wide_server_puts_its_databases_on_its_own_clock() {
3889        let mut f = Fixture::striped(8);
3890        f.server.set_clock_ms(1_700_000_000_000);
3891        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
3892        assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
3893        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3894        f.server.set_clock_ms(1_700_000_050_000);
3895        assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
3896    }
3897
3898    /// The swap is stripe by stripe, so a database cut into more than one
3899    /// stripe is the case that would catch it exchanging some of the keys and
3900    /// leaving the rest. Sixteen keys over four stripes is enough that every
3901    /// stripe has something in it whatever the hashes come out as.
3902    #[test]
3903    fn swapdb_swaps_every_stripe_of_a_wide_database() {
3904        let mut f = Fixture::striped(4);
3905        for i in 0..16u32 {
3906            let key = format!("k{i}");
3907            assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
3908        }
3909        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3910        assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
3911        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3912
3913        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3914        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3915        assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
3916        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3917        assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
3918        for i in 0..16u32 {
3919            let key = format!("k{i}");
3920            assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
3921        }
3922    }
3923
3924    #[test]
3925    fn swapdb_says_which_index_it_could_not_read() {
3926        let mut f = Fixture::new();
3927        assert_eq!(
3928            f.run(&[b"SWAPDB", b"x", b"1"]),
3929            "-ERR invalid first DB index\r\n"
3930        );
3931        assert_eq!(
3932            f.run(&[b"SWAPDB", b"0", b"y"]),
3933            "-ERR invalid second DB index\r\n"
3934        );
3935        // A number too big to be an index on a server that keeps one in an int
3936        // is the same complaint, and a plausible one that is not ours is the
3937        // range complaint instead. The split is Redis's.
3938        assert_eq!(
3939            f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
3940            "-ERR invalid first DB index\r\n"
3941        );
3942        assert_eq!(
3943            f.run(&[b"SWAPDB", b"0", b"99"]),
3944            "-ERR DB index is out of range\r\n"
3945        );
3946        assert_eq!(
3947            f.run(&[b"SWAPDB", b"-1", b"0"]),
3948            "-ERR DB index is out of range\r\n"
3949        );
3950    }
3951
3952    #[test]
3953    fn wait_answers_zero_replicas_without_waiting() {
3954        let mut f = Fixture::new();
3955        assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
3956        assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
3957        // A replica that is never going to arrive, and a timeout that would be
3958        // a real wait on a server that had one.
3959        assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
3960        // Negative replicas is not an error, because zero is already more than
3961        // it asked for.
3962        assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
3963        assert_eq!(
3964            f.run(&[b"WAIT", b"x", b"0"]),
3965            "-ERR value is not an integer or out of range\r\n"
3966        );
3967        assert_eq!(
3968            f.run(&[b"WAIT", b"0", b"-1"]),
3969            "-ERR timeout is negative\r\n"
3970        );
3971        assert_eq!(
3972            f.run(&[b"WAIT", b"0", b"1.5"]),
3973            "-ERR timeout is not an integer or out of range\r\n"
3974        );
3975    }
3976
3977    #[test]
3978    fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
3979        let mut f = Fixture::new();
3980        assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
3981        assert_eq!(
3982            f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
3983            "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
3984        );
3985        assert_eq!(
3986            f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
3987            "-ERR value is out of range, value must between 0 and 1\r\n"
3988        );
3989        assert_eq!(
3990            f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
3991            "-ERR value is out of range, must be positive\r\n"
3992        );
3993        // The arguments are all read before the server looks at itself, so a
3994        // bad timeout beats the append only complaint even with numlocal set.
3995        assert_eq!(
3996            f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
3997            "-ERR timeout is negative\r\n"
3998        );
3999    }
4000
4001    /// The bytes inside a bulk reply, with the header and the trailing break
4002    /// taken off. Every `DUMP` test needs this and none of them care how the
4003    /// length was written.
4004    fn payload(reply: &[u8]) -> Vec<u8> {
4005        let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
4006        reply[head + 2..reply.len() - 2].to_vec()
4007    }
4008
4009    #[test]
4010    fn a_value_survives_a_dump_and_a_restore() {
4011        let mut f = Fixture::new();
4012        f.run(&[b"SET", b"s", b"hello"]);
4013        f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
4014        f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
4015        f.run(&[b"SADD", b"u", b"x", b"y"]);
4016        f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
4017        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
4018
4019        for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
4020            let mut copy = key.to_vec();
4021            copy.push(b'2');
4022            let bytes = payload(&f.raw(&[b"DUMP", key]));
4023            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
4024            assert_eq!(f.run(&[b"TYPE", &copy]), f.run(&[b"TYPE", key]));
4025        }
4026
4027        assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
4028        assert_eq!(
4029            f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
4030            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4031        );
4032        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
4033        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
4034        assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
4035        assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
4036        // The encoding survives too, since the payload names the plainest legal
4037        // type and the loader puts the value back on the rung it belongs on.
4038        assert_eq!(
4039            f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
4040            f.run(&[b"OBJECT", b"ENCODING", b"t"])
4041        );
4042    }
4043
4044    #[test]
4045    fn a_dumped_hash_keeps_its_field_deadlines() {
4046        let mut f = Fixture::new();
4047        f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
4048        assert_eq!(
4049            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
4050            "*1\r\n:1\r\n"
4051        );
4052        let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
4053        assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
4054        assert_eq!(
4055            f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
4056            "*2\r\n:-1\r\n:100\r\n"
4057        );
4058    }
4059
4060    #[test]
4061    fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
4062        let mut f = Fixture::new();
4063        f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
4064        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
4065        assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
4066        assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
4067        assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
4068        assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
4069        // An absolute deadline that has already gone is not an error. The key is
4070        // not created and the reply is the same OK a live one gets.
4071        assert_eq!(
4072            f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
4073            "+OK\r\n"
4074        );
4075        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
4076    }
4077
4078    #[test]
4079    fn dump_answers_nothing_for_a_key_that_is_not_there() {
4080        let mut f = Fixture::new();
4081        assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
4082        f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
4083        f.advance(50);
4084        assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
4085    }
4086
4087    #[test]
4088    fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
4089        let mut f = Fixture::new();
4090        f.run(&[b"SET", b"a", b"first"]);
4091        f.run(&[b"SET", b"b", b"second"]);
4092        let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
4093        assert_eq!(
4094            f.run(&[b"RESTORE", b"a", b"0", &bytes]),
4095            "-BUSYKEY Target key name already exists.\r\n"
4096        );
4097        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
4098        assert_eq!(
4099            f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
4100            "+OK\r\n"
4101        );
4102        assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
4103    }
4104
4105    /// The busy key comes before the payload, which is not the order the
4106    /// arguments read in. Whether a key is taken should not depend on whether
4107    /// the bytes behind it happened to be good.
4108    #[test]
4109    fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
4110        let mut f = Fixture::new();
4111        f.run(&[b"SET", b"a", b"v"]);
4112        assert_eq!(
4113            f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
4114            "-BUSYKEY Target key name already exists.\r\n"
4115        );
4116        // And the options come before even that, so a bad FREQ beats the busy
4117        // key the same way a bad DB beats a missing source in COPY.
4118        assert_eq!(
4119            f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
4120            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
4121        );
4122    }
4123
4124    #[test]
4125    fn restore_can_tell_a_bad_footer_from_bad_bytes() {
4126        let mut f = Fixture::new();
4127        f.run(&[b"SET", b"a", b"hello"]);
4128        let good = payload(&f.raw(&[b"DUMP", b"a"]));
4129
4130        let mut flipped = good.clone();
4131        flipped[2] ^= 0x40;
4132        assert_eq!(
4133            f.run(&[b"RESTORE", b"b", b"0", &flipped]),
4134            "-ERR DUMP payload version or checksum are wrong\r\n"
4135        );
4136        assert_eq!(
4137            f.run(&[b"RESTORE", b"b", b"0", b"short"]),
4138            "-ERR DUMP payload version or checksum are wrong\r\n"
4139        );
4140        // A footer that is right over a body that is not. The type byte says
4141        // string and there is nothing behind it, so the checksum agrees and the
4142        // value does not exist.
4143        let mut truncated = good[..1].to_vec();
4144        truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
4145        let crc = yo_common::crc::crc64(0, &truncated);
4146        truncated.extend_from_slice(&crc.to_le_bytes());
4147        assert_eq!(
4148            f.run(&[b"RESTORE", b"b", b"0", &truncated]),
4149            "-ERR Bad data format\r\n"
4150        );
4151        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
4152    }
4153
4154    #[test]
4155    fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
4156        let mut f = Fixture::new();
4157        f.run(&[b"SET", b"a", b"v"]);
4158        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
4159        assert_eq!(
4160            f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
4161            "-ERR Invalid TTL value, must be >= 0\r\n"
4162        );
4163        assert_eq!(
4164            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
4165            "-ERR Invalid IDLETIME value, must be >= 0\r\n"
4166        );
4167        assert_eq!(
4168            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
4169            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
4170        );
4171        // Both are accepted and both are then dropped, which is D-26.
4172        assert_eq!(
4173            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
4174            "+OK\r\n"
4175        );
4176        assert_eq!(
4177            f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
4178            "+OK\r\n"
4179        );
4180    }
4181
4182    /// Neither word is refused for being the wrong one. Each is only accepted
4183    /// while the other is unset, so the second of the two falls through to the
4184    /// plain syntax error rather than getting a message of its own.
4185    #[test]
4186    fn restore_takes_idletime_or_freq_and_not_both() {
4187        let mut f = Fixture::new();
4188        f.run(&[b"SET", b"a", b"v"]);
4189        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
4190        assert_eq!(
4191            f.run(&[
4192                b"RESTORE",
4193                b"b",
4194                b"0",
4195                &bytes,
4196                b"IDLETIME",
4197                b"1",
4198                b"FREQ",
4199                b"2"
4200            ]),
4201            "-ERR syntax error\r\n"
4202        );
4203        assert_eq!(
4204            f.run(&[
4205                b"RESTORE",
4206                b"b",
4207                b"0",
4208                &bytes,
4209                b"FREQ",
4210                b"2",
4211                b"IDLETIME",
4212                b"1"
4213            ]),
4214            "-ERR syntax error\r\n"
4215        );
4216        assert_eq!(
4217            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
4218            "-ERR syntax error\r\n"
4219        );
4220        assert_eq!(
4221            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
4222            "-ERR syntax error\r\n"
4223        );
4224    }
4225
4226    #[test]
4227    fn copy_checks_its_options_before_it_looks_for_anything() {
4228        let mut f = Fixture::new();
4229        // No key exists at all, and every one of these is still the option
4230        // complaint rather than a zero, which is the order a real server uses.
4231        assert_eq!(
4232            f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
4233            "-ERR DB index is out of range\r\n"
4234        );
4235        assert_eq!(
4236            f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
4237            "-ERR DB index is out of range\r\n"
4238        );
4239        assert_eq!(
4240            f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
4241            "-ERR value is not an integer or out of range\r\n"
4242        );
4243        assert_eq!(
4244            f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
4245            "-ERR syntax error\r\n"
4246        );
4247        assert_eq!(
4248            f.run(&[b"COPY", b"a", b"a"]),
4249            "-ERR source and destination objects are the same\r\n"
4250        );
4251        // Repeated, reordered and lowercased, and the last DB wins.
4252        assert_eq!(
4253            f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
4254            ":0\r\n"
4255        );
4256    }
4257
4258    #[test]
4259    fn time_is_two_bulk_strings_and_moves() {
4260        let mut f = Fixture::new();
4261        let first = f.run(&[b"TIME"]);
4262        assert!(first.starts_with("*2\r\n$"), "got {first}");
4263        let parts: Vec<&str> = first.split("\r\n").collect();
4264        let secs: i64 = parts[2].parse().expect("seconds as decimal text");
4265        let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
4266        assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
4267        assert!((0..1_000_000).contains(&micros), "got {micros}");
4268        // The coarse clock the keyspace uses is a cached millisecond that a
4269        // background tick refreshes, so a TIME built on it would answer the
4270        // same microsecond twice in a row here.
4271        assert_ne!(first, f.run(&[b"TIME"]));
4272    }
4273
4274    #[test]
4275    fn a_keyspace_scan_walks_every_key_once() {
4276        // The count below is thirty two, so ninety six keys is three pages of
4277        // cursor and says the same thing as five hundred at a fifth of the
4278        // interpreted work.
4279        let n = if cfg!(miri) { 96 } else { 500 };
4280        let mut f = Fixture::new();
4281        for i in 0..n {
4282            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
4283        }
4284
4285        let mut seen: Vec<String> = Vec::new();
4286        let mut cursor = "0".to_owned();
4287        let mut calls = 0;
4288        loop {
4289            let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
4290            seen.extend(keys);
4291            cursor = next;
4292            calls += 1;
4293            assert!(calls < 10_000, "the cursor is not advancing");
4294            if cursor == "0" {
4295                break;
4296            }
4297        }
4298
4299        seen.sort();
4300        seen.dedup();
4301        assert_eq!(seen.len(), n, "every key once and only once");
4302        // And more than one call to get them, or the COUNT is being ignored and
4303        // the loop above proved nothing about resuming.
4304        assert!(calls > 1, "{n} keys came back in one batch");
4305    }
4306
4307    #[test]
4308    fn a_scan_narrows_by_pattern_and_by_type() {
4309        let mut f = Fixture::new();
4310        f.run(&[b"SET", b"str", b"v"]);
4311        f.run(&[b"SADD", b"members", b"a"]);
4312        f.run(&[b"HSET", b"fields", b"f", b"v"]);
4313
4314        let all = |f: &mut Fixture, args: &[&[u8]]| {
4315            let mut out: Vec<String> = Vec::new();
4316            let mut cursor = "0".to_owned();
4317            loop {
4318                let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
4319                line.extend_from_slice(args);
4320                let (next, keys) = scan_reply(&f.run(&line));
4321                out.extend(keys);
4322                cursor = next;
4323                if cursor == "0" {
4324                    break;
4325                }
4326            }
4327            out.sort();
4328            out
4329        };
4330
4331        assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
4332        assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
4333        assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
4334        // Case insensitive, the same as Redis's own comparison.
4335        assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
4336        // A type nothing can hold is not an error, it just matches nothing.
4337        assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
4338        assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
4339        // Both filters at once, and they are an and rather than an or.
4340        assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
4341    }
4342
4343    #[test]
4344    fn a_scan_says_what_is_wrong_with_it() {
4345        let mut f = Fixture::new();
4346        assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
4347        assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
4348        assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
4349        assert_eq!(
4350            f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
4351            "-ERR syntax error\r\n"
4352        );
4353        assert_eq!(
4354            f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
4355            "-ERR value is not an integer or out of range\r\n"
4356        );
4357        assert_eq!(
4358            f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
4359            "-ERR syntax error\r\n"
4360        );
4361        // A cursor the client made up is a cursor. It resumes somewhere
4362        // arbitrary and answers whatever is there, which is what Redis does and
4363        // is the only behaviour that does not need the server to remember every
4364        // cursor it has handed out.
4365        assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
4366    }
4367
4368    #[test]
4369    fn keys_and_randomkey_look_at_the_whole_database() {
4370        let mut f = Fixture::new();
4371        assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
4372        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
4373
4374        for name in ["one", "two", "three"] {
4375            f.run(&[b"SET", name.as_bytes(), b"v"]);
4376        }
4377        assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
4378        assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
4379        assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
4380
4381        for _ in 0..50 {
4382            let got = f.run(&[b"RANDOMKEY"]);
4383            assert!(
4384                ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
4385                "got {got}"
4386            );
4387        }
4388    }
4389
4390    #[test]
4391    fn a_walk_does_not_answer_keys_that_have_expired() {
4392        let mut f = Fixture::new();
4393        f.run(&[b"SET", b"alive", b"v"]);
4394        f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
4395        f.server.advance_clock_ms(2);
4396        assert_eq!(
4397            f.run(&[b"DBSIZE"]),
4398            ":2\r\n",
4399            "nothing has collected it yet"
4400        );
4401
4402        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
4403        let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
4404        assert_eq!(keys, ["alive"]);
4405        for _ in 0..20 {
4406            assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
4407        }
4408        // The walk collected it on the way past, which is what makes DBSIZE
4409        // here answer what Redis answers once its own cycle has been round.
4410        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4411    }
4412
4413    #[test]
4414    fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
4415        let mut f = Fixture::new();
4416        f.run(&[b"SET", b"k", b"v"]);
4417        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
4418        assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
4419
4420        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
4421        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4422        let ms = int(&f.run(&[b"PTTL", b"k"]));
4423        assert!((99_000..=100_000).contains(&ms), "got {ms}");
4424
4425        // The absolute pair, derived from the same one number the store kept.
4426        let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
4427        let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4428        assert_eq!(at, (at_ms + 500) / 1000);
4429        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
4430
4431        assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
4432        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
4433        assert_eq!(
4434            f.run(&[b"PERSIST", b"k"]),
4435            ":0\r\n",
4436            "nothing to take off the second time"
4437        );
4438        assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
4439        assert_eq!(
4440            f.run(&[b"GET", b"k"]),
4441            "$1\r\nv\r\n",
4442            "and the value went through all of that untouched"
4443        );
4444    }
4445
4446    #[test]
4447    fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
4448        let mut f = Fixture::new();
4449        f.run(&[b"SET", b"str", b"v"]);
4450        f.run(&[b"SADD", b"set", b"a", b"b"]);
4451        f.run(&[b"HSET", b"hash", b"f", b"v"]);
4452
4453        for key in [b"str".as_slice(), b"set", b"hash"] {
4454            assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
4455            assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
4456        }
4457        // The body is not touched by any of that, which is the whole reason the
4458        // deadline lives in the record and the body lives somewhere else.
4459        assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
4460        assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
4461        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
4462    }
4463
4464    #[test]
4465    fn a_deadline_that_has_already_gone_deletes_the_key_now() {
4466        let mut f = Fixture::new();
4467        for key in [b"a".as_slice(), b"b", b"c", b"d"] {
4468            f.run(&[b"SET", key, b"v"]);
4469        }
4470        // Four ways of naming a moment that has passed, and all four are a
4471        // delete answering 1 rather than an error. Zero is a moment, minus one
4472        // is a moment, and the hash field commands refuse the negative one.
4473        assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
4474        assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
4475        assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
4476        assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
4477        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4478        assert_eq!(
4479            f.run(&[b"EXPIRE", b"a", b"100"]),
4480            ":0\r\n",
4481            "and the key really went, so there is nothing to put a deadline on"
4482        );
4483    }
4484
4485    #[test]
4486    fn the_four_conditions_decide_whether_the_deadline_moves() {
4487        let mut f = Fixture::new();
4488        f.run(&[b"SET", b"k", b"v"]);
4489
4490        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
4491        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
4492        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
4493        assert_eq!(
4494            f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
4495            ":1\r\n",
4496            "no deadline reads as infinitely far away, so LT passes where GT fails"
4497        );
4498
4499        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
4500        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
4501        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4502        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
4503        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
4504        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4505
4506        // The condition is answered before the past check, so this is a 0 and
4507        // the key survives. The other order would delete it.
4508        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
4509        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
4510        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
4511        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
4512    }
4513
4514    #[test]
4515    fn the_conditions_are_a_set_and_not_a_keyword() {
4516        let mut f = Fixture::new();
4517        f.run(&[b"SET", b"k", b"v"]);
4518
4519        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
4520        assert_eq!(
4521            f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
4522            ":0\r\n",
4523            "the same keyword twice means it once, and NX now has a deadline to fail on"
4524        );
4525
4526        // XX with LT is the one pair that is not either of them on its own: LT
4527        // alone would accept a key with no deadline and this does not.
4528        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
4529        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4530        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
4531        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
4532        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4533        f.run(&[b"PERSIST", b"k"]);
4534        assert_eq!(
4535            f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
4536            ":0\r\n",
4537            "where LT on its own would have taken it"
4538        );
4539        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
4540    }
4541
4542    #[test]
4543    fn a_key_is_gone_once_its_moment_passes() {
4544        let mut f = Fixture::new();
4545        f.run(&[b"SET", b"k", b"v"]);
4546        f.run(&[b"EXPIRE", b"k", b"100"]);
4547
4548        let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4549        f.server.set_clock_ms(at as u64 + 1);
4550        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4551        assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
4552        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
4553        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4554    }
4555
4556    #[test]
4557    fn the_expiry_commands_refuse_what_a_real_server_refuses() {
4558        let mut f = Fixture::new();
4559        f.run(&[b"SET", b"k", b"v"]);
4560        for (bad, want) in [
4561            (
4562                &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
4563                "-ERR value is not an integer or out of range\r\n",
4564            ),
4565            (
4566                &[b"EXPIRE", b"k", b"100", b"MAYBE"],
4567                "-ERR Unsupported option MAYBE\r\n",
4568            ),
4569            (
4570                &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
4571                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4572            ),
4573            (
4574                &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
4575                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4576            ),
4577            (
4578                &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
4579                "-ERR GT and LT options at the same time are not compatible\r\n",
4580            ),
4581            // Seconds that overflow when multiplied into milliseconds. Every
4582            // message names the command it came from.
4583            (
4584                &[b"EXPIRE", b"k", b"9223372036854775807"],
4585                "-ERR invalid expire time in 'expire' command\r\n",
4586            ),
4587            (
4588                &[b"EXPIREAT", b"k", b"9223372036854775807"],
4589                "-ERR invalid expire time in 'expireat' command\r\n",
4590            ),
4591            (
4592                &[b"PEXPIRE", b"k", b"9223372036854775807"],
4593                "-ERR invalid expire time in 'pexpire' command\r\n",
4594            ),
4595        ] {
4596            assert_eq!(f.run(bad), want, "for {bad:?}");
4597        }
4598        assert_eq!(
4599            f.run(&[b"TTL", b"k"]),
4600            ":-1\r\n",
4601            "and none of those put a deadline on anything"
4602        );
4603
4604        // The one of the four that has no arithmetic to overflow. Redis takes
4605        // it and holds the number as given, and a record here holds forty six
4606        // bits, so it lands in the year 4199 instead. D-17.
4607        assert_eq!(
4608            f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
4609            ":1\r\n"
4610        );
4611        assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
4612    }
4613
4614    #[test]
4615    fn flushing_empties_this_database_or_every_one_of_them() {
4616        let mut f = Fixture::new();
4617        f.run(&[b"SELECT", b"0"]);
4618        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4619        f.run(&[b"SELECT", b"1"]);
4620        f.run(&[b"SET", b"c", b"3"]);
4621        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4622        // ASYNC and SYNC are both taken and neither changes anything, since the
4623        // keyspace is empty before the OK goes out either way.
4624        assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
4625        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4626        // Only database one was emptied.
4627        f.run(&[b"SELECT", b"0"]);
4628        assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
4629        assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
4630        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4631        f.run(&[b"SELECT", b"1"]);
4632        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4633        // Anything else after the name is a syntax error, and so is a third
4634        // argument even when the second one is a word we take.
4635        assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
4636        assert_eq!(
4637            f.run(&[b"FLUSHDB", b"sync", b"sync"]),
4638            "-ERR syntax error\r\n"
4639        );
4640    }
4641
4642    #[test]
4643    fn the_script_cache_and_the_library_set_answer_for_being_empty() {
4644        let mut f = Fixture::new();
4645        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4646        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
4647        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
4648        // Nothing is cached, so nothing is there, one answer per hash asked
4649        // about.
4650        assert_eq!(
4651            f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
4652            "*2\r\n:0\r\n:0\r\n"
4653        );
4654        assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
4655        assert_eq!(
4656            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
4657            "*0\r\n"
4658        );
4659        assert_eq!(
4660            f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
4661            "-ERR Library not found\r\n"
4662        );
4663
4664        // Redis's two messages here are its own, one per container, and one of
4665        // them reads like a typo.
4666        assert_eq!(
4667            f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
4668            "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
4669        );
4670        assert_eq!(
4671            f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
4672            "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
4673        );
4674        // A second argument after the mode is the generic one instead, because
4675        // the count is checked before the word is looked at. The subcommand in
4676        // the sentence is the client's own spelling and not the canonical one,
4677        // which is the same thing `unknown subcommand` does.
4678        assert_eq!(
4679            f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
4680            "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
4681        );
4682        assert_eq!(
4683            f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
4684            "-ERR Unknown argument bogus\r\n"
4685        );
4686        assert_eq!(
4687            f.run(&[b"SCRIPT", b"EXISTS"]),
4688            "-ERR wrong number of arguments for 'script|exists' command\r\n"
4689        );
4690
4691        assert_eq!(
4692            f.run(&[b"FUNCTION", b"NOPE"]),
4693            "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
4694        );
4695    }
4696
4697    #[test]
4698    fn the_script_cache_holds_what_was_loaded_into_it() {
4699        let mut f = Fixture::new();
4700        // The hash is the sha1 of the body and nothing else, so it is the same
4701        // number a real server answers and a client can compute it itself.
4702        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4703        assert_eq!(
4704            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4705            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4706        );
4707        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4708        assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
4709        // Loading is idempotent and a body that will not parse is refused
4710        // where it was written rather than where it is called.
4711        assert_eq!(
4712            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4713            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4714        );
4715        assert!(
4716            f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
4717                .starts_with("-ERR Error compiling script"),
4718        );
4719
4720        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4721        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
4722        assert_eq!(
4723            f.run(&[b"EVALSHA", sha, b"0"]),
4724            "-NOSCRIPT No matching script. Please use EVAL.\r\n"
4725        );
4726
4727        // Running the body puts it in the cache too, which is what makes the
4728        // load then call then fall back to load pattern a client uses work.
4729        assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
4730        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4731
4732        // Nothing here can run long enough to be killed, which is D-101, so
4733        // the answer is the one a real server gives when nothing is stuck.
4734        assert_eq!(
4735            f.run(&[b"SCRIPT", b"KILL"]),
4736            "-NOTBUSY No scripts in execution right now.\r\n"
4737        );
4738        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
4739        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
4740        assert_eq!(
4741            f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
4742            "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
4743        );
4744    }
4745
4746    #[test]
4747    fn eval_counts_its_keys_before_it_compiles_anything() {
4748        let mut f = Fixture::new();
4749        assert_eq!(
4750            f.run(&[b"EVAL", b"return 1"]),
4751            "-ERR wrong number of arguments for 'eval' command\r\n"
4752        );
4753        assert_eq!(
4754            f.run(&[b"EVAL", b"return 1", b"abc"]),
4755            "-ERR value is not an integer or out of range\r\n"
4756        );
4757        assert_eq!(
4758            f.run(&[b"EVAL", b"return 1", b"-1"]),
4759            "-ERR Number of keys can't be negative\r\n"
4760        );
4761        assert_eq!(
4762            f.run(&[b"EVAL", b"return 1", b"1"]),
4763            "-ERR Number of keys can't be greater than number of args\r\n"
4764        );
4765        // The count splits the tail, and everything past the keys is ARGV.
4766        assert_eq!(
4767            f.run(&[
4768                b"EVAL",
4769                b"return {KEYS[1],KEYS[2],ARGV[1]}",
4770                b"2",
4771                b"a",
4772                b"b",
4773                b"c"
4774            ]),
4775            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4776        );
4777        assert_eq!(
4778            f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
4779            ":0\r\n"
4780        );
4781        assert_eq!(
4782            f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
4783            ":2\r\n"
4784        );
4785    }
4786
4787    #[test]
4788    fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
4789        let mut f = Fixture::new();
4790        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4791
4792        // A number is truncated toward zero rather than rounded, and the two
4793        // ends of the range saturate the way the cast does.
4794        assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
4795        assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
4796        assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
4797        assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
4798        assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
4799        assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
4800        assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
4801
4802        assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
4803        assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
4804        // Everything that is not there is the same nothing.
4805        assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
4806        assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
4807        assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
4808        assert_eq!(eval(&mut f, b""), "$-1\r\n");
4809
4810        // A table is an array that stops at the first hole, which is what makes
4811        // a script build a reply by appending rather than by indexing.
4812        assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
4813        assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
4814        assert_eq!(
4815            eval(&mut f, b"return {1,'a',{2}}"),
4816            "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
4817        );
4818
4819        // The named fields, in the order a real server looks for them.
4820        assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
4821        assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
4822        assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
4823        assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
4824        // A line break inside one of them becomes a space, because the reply is
4825        // a single line and a client that saw the break would lose the frame.
4826        assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a  b\r\n");
4827        // A field of the wrong type is not that kind of reply at all, and falls
4828        // through to the array walk, which finds nothing.
4829        assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
4830        assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
4831    }
4832
4833    #[test]
4834    fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
4835        let mut f = Fixture::new();
4836        // Under RESP2 the four typed tables have to come back as something a
4837        // client that only knows RESP2 can read.
4838        assert_eq!(
4839            f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
4840            "$3\r\n3.5\r\n"
4841        );
4842        assert_eq!(
4843            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4844            "$3\r\n123\r\n"
4845        );
4846        assert_eq!(
4847            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4848            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4849        );
4850        assert_eq!(
4851            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4852            "*1\r\n$1\r\na\r\n"
4853        );
4854        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
4855
4856        f.out = Out::new(Proto::Resp3);
4857        assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
4858        assert_eq!(
4859            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4860            "(123\r\n"
4861        );
4862        assert_eq!(
4863            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4864            "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
4865        );
4866        assert_eq!(
4867            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4868            "~1\r\n$1\r\na\r\n"
4869        );
4870        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
4871    }
4872
4873    #[test]
4874    fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
4875        let mut f = Fixture::new();
4876        f.run(&[b"SET", b"s", b"hello"]);
4877        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4878        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4879
4880        assert_eq!(
4881            eval(&mut f, b"return type(redis.call('get','s'))"),
4882            "$6\r\nstring\r\n"
4883        );
4884        assert_eq!(
4885            eval(&mut f, b"return type(redis.call('llen','l'))"),
4886            "$6\r\nnumber\r\n"
4887        );
4888        assert_eq!(
4889            eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
4890            "$5\r\ntable\r\n"
4891        );
4892        // A status is a table with one field, which is what lets a script pass
4893        // one straight back out again.
4894        assert_eq!(
4895            eval(&mut f, b"return redis.call('set','s','v')['ok']"),
4896            "$2\r\nOK\r\n"
4897        );
4898        // A missing key is false under RESP2 and nil once the script asks for
4899        // RESP3, which is the one conversion the script gets to choose.
4900        assert_eq!(
4901            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4902            "$5\r\nfalse\r\n"
4903        );
4904        assert_eq!(
4905            eval(
4906                &mut f,
4907                b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
4908            ),
4909            "$3\r\nnil\r\n"
4910        );
4911        // The choice does not outlive the script that made it.
4912        assert_eq!(
4913            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4914            "$5\r\nfalse\r\n"
4915        );
4916    }
4917
4918    #[test]
4919    fn an_error_from_a_script_names_the_line_it_came_from() {
4920        let mut f = Fixture::new();
4921        // The position is the script's own, not the prelude's, and the suffix
4922        // names the script so a client can find it in the cache.
4923        assert_eq!(
4924            f.run(&[b"EVAL", b"error('boom')", b"0"]),
4925            "-ERR user_script:1: boom script: \
4926             82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
4927        );
4928        // Level zero says the message already knows where it came from.
4929        assert_eq!(
4930            f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
4931            "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
4932        );
4933        // A table with an err field keeps its own text and gets the suffix.
4934        assert!(
4935            f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
4936                .starts_with("-structured script: "),
4937        );
4938        // A script that will not parse is refused before it runs, so there is
4939        // no script and nothing to name.
4940        assert_eq!(
4941            f.run(&[b"EVAL", b"return this is not lua", b"0"]),
4942            "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
4943        );
4944
4945        // A table that came out of pcall is a string by the time the script
4946        // sees it, which is a real server's own wrapping and not Lua's.
4947        assert_eq!(
4948            f.run(&[
4949                b"EVAL",
4950                b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
4951                b"0"
4952            ]),
4953            "$8\r\nstring:z\r\n"
4954        );
4955        assert_eq!(
4956            f.run(&[
4957                b"EVAL",
4958                b"local a, b = pcall(function() error({a=1}) end) return type(b)",
4959                b"0"
4960            ]),
4961            "$5\r\ntable\r\n"
4962        );
4963    }
4964
4965    #[test]
4966    fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
4967        let mut f = Fixture::new();
4968        let sentence = |f: &mut Fixture, body: &[u8]| {
4969            let reply = f.run(&[b"EVAL", body, b"0"]);
4970            reply.split(" script: ").next().unwrap().to_owned()
4971        };
4972
4973        assert_eq!(
4974            sentence(&mut f, b"return redis.call()"),
4975            "-ERR Please specify at least one argument for this redis lib call"
4976        );
4977        assert_eq!(
4978            sentence(&mut f, b"return redis.call('get', {})"),
4979            "-ERR Lua redis lib command arguments must be strings or integers"
4980        );
4981        assert_eq!(
4982            sentence(&mut f, b"return redis.call('nosuchcmd')"),
4983            "-ERR Unknown Redis command called from script"
4984        );
4985        assert_eq!(
4986            sentence(&mut f, b"return redis.call('get')"),
4987            "-ERR Wrong number of args calling Redis command from script"
4988        );
4989        // The commands that make no sense inside a script are refused by name
4990        // rather than by not being implemented, so the sentence is the same one
4991        // a real server writes for each of them.
4992        for name in [
4993            &b"return redis.call('multi')"[..],
4994            b"return redis.call('exec')",
4995            b"return redis.call('watch','k')",
4996            b"return redis.call('subscribe','c')",
4997            b"return redis.call('debug','jmap')",
4998            b"return redis.call('eval','return 1',0)",
4999            b"return redis.call('config','get','maxmemory')",
5000        ] {
5001            assert_eq!(
5002                sentence(&mut f, name),
5003                "-ERR This Redis command is not allowed from script",
5004                "for {}",
5005                String::from_utf8_lossy(name)
5006            );
5007        }
5008        // HELP is the one subcommand of a refused container that is allowed,
5009        // because it reads nothing and changes nothing.
5010        assert!(
5011            f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
5012                .starts_with('*'),
5013        );
5014
5015        // pcall answers the same sentence as a value instead of raising it, and
5016        // the value has an err field a script can read.
5017        assert_eq!(
5018            f.run(&[
5019                b"EVAL",
5020                b"local x = redis.pcall('nosuchcmd') return x.err",
5021                b"0"
5022            ]),
5023            "$44\r\nERR Unknown Redis command called from script\r\n"
5024        );
5025        // Returning it unread raises it, because the table has an err field.
5026        assert_eq!(
5027            f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
5028            "-ERR Unknown Redis command called from script\r\n"
5029        );
5030    }
5031
5032    #[test]
5033    fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
5034        let mut f = Fixture::new();
5035        f.run(&[b"SET", b"k", b"v"]);
5036        assert_eq!(
5037            f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
5038            "$1\r\nv\r\n"
5039        );
5040        assert!(
5041            f.run(&[
5042                b"EVAL_RO",
5043                b"return redis.call('set', KEYS[1], 'x')",
5044                b"1",
5045                b"k"
5046            ])
5047            .starts_with("-ERR Write commands are not allowed from read-only scripts."),
5048        );
5049        // The write did not happen, and the same body under EVAL does.
5050        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
5051        assert_eq!(
5052            f.run(&[
5053                b"EVAL",
5054                b"return redis.call('set', KEYS[1], 'x')",
5055                b"1",
5056                b"k"
5057            ]),
5058            "+OK\r\n"
5059        );
5060        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
5061
5062        // EVALSHA_RO runs a cached body under the same rule.
5063        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
5064        f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
5065        assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
5066    }
5067
5068    #[test]
5069    fn a_script_cannot_leave_anything_behind_for_the_next_one() {
5070        let mut f = Fixture::new();
5071        // A plain global write and a write through a name on the redis table
5072        // both raise, with the position the script wrote them at.
5073        for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
5074            let reply = f.run(&[b"EVAL", body, b"0"]);
5075            assert!(
5076                reply
5077                    .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
5078                "{body:?} gave {reply}",
5079            );
5080        }
5081        // Walking round the guard with rawset or setmetatable raises too, and
5082        // without the position, which is where a real server raises it from.
5083        for body in [
5084            &b"rawset(redis, 'call', 1)"[..],
5085            b"rawset(_G, 'zz', 1)",
5086            b"setmetatable(_G, {})",
5087            b"setmetatable(redis, {})",
5088        ] {
5089            let reply = f.run(&[b"EVAL", body, b"0"]);
5090            assert!(
5091                reply.starts_with("-ERR Attempt to modify a readonly table script: "),
5092                "{body:?} gave {reply}",
5093            );
5094        }
5095        // Reading a name that is not there is a mistake rather than a nil, so a
5096        // misspelled global stops the script instead of doing nothing quietly.
5097        assert!(
5098            f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
5099                .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
5100        );
5101        // Reading a name that is not on the redis table is a nil, which is how
5102        // a script tests for a helper that an older server does not have.
5103        assert_eq!(
5104            f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
5105            "$3\r\nnil\r\n"
5106        );
5107
5108        // The one write that lands, D-103, is taken back out before the next
5109        // script starts, so nothing a script does reaches the one after it.
5110        assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
5111        assert_eq!(
5112            f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
5113            "$8\r\nfunction\r\n"
5114        );
5115        assert_eq!(
5116            f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
5117            "$8\r\nfunction\r\n"
5118        );
5119    }
5120
5121    #[test]
5122    fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
5123        let mut f = Fixture::new();
5124        // The guard in front of the table is empty, so the three base library
5125        // readers that skip a metatable are pointed at the real table behind
5126        // it. A script counts what a real server counts.
5127        assert_eq!(
5128            f.run(&[
5129                b"EVAL",
5130                b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
5131                b"0",
5132            ]),
5133            ":23\r\n"
5134        );
5135        assert_eq!(
5136            f.run(&[
5137                b"EVAL",
5138                b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
5139                  table.sort(t) return table.concat(t, ' ')",
5140                b"0",
5141            ]),
5142            "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
5143             REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
5144             acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
5145             set_repl setresp sha1hex status_reply\r\n"
5146        );
5147        // The loop hands over the values as well as the names, so the twelve
5148        // helpers are callable from inside a traversal and not just findable.
5149        assert_eq!(
5150            f.run(&[
5151                b"EVAL",
5152                b"local n = 0 for k, v in pairs(redis) do \
5153                  if type(v) == 'function' then n = n + 1 end end return n",
5154                b"0",
5155            ]),
5156            ":12\r\n"
5157        );
5158        // The other two readers agree with it.
5159        assert_eq!(
5160            f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
5161            "$6\r\nstring\r\n"
5162        );
5163        assert_eq!(
5164            f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
5165            "$8\r\nfunction\r\n"
5166        );
5167        assert_eq!(
5168            f.run(&[
5169                b"EVAL",
5170                b"return tostring(rawget(redis, 'nosuchfield'))",
5171                b"0",
5172            ]),
5173            "$3\r\nnil\r\n"
5174        );
5175        // Reading round the guard is the only thing that was given back. A
5176        // write still lands on the guard and still raises.
5177        for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
5178            assert!(
5179                f.run(&[b"EVAL", body, b"0"])
5180                    .contains("Attempt to modify a readonly table script: "),
5181                "{body:?}",
5182            );
5183        }
5184        // A table nobody guards walks the way it always did, whether a script
5185        // made it or the standard library did.
5186        assert_eq!(
5187            f.run(&[
5188                b"EVAL",
5189                b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
5190                b"0",
5191            ]),
5192            ":2\r\n"
5193        );
5194        assert_eq!(
5195            f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
5196            "$3\r\nnil\r\n"
5197        );
5198        assert_eq!(
5199            f.run(&[
5200                b"EVAL",
5201                b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
5202                  return type(f)",
5203                b"0",
5204            ]),
5205            "$8\r\nfunction\r\n"
5206        );
5207    }
5208
5209    #[test]
5210    fn a_script_gets_the_bit_library_a_real_server_carries() {
5211        let mut f = Fixture::new();
5212        // Every answer is a signed word, which is why the ones past two to the
5213        // thirty one come back negative.
5214        for (body, want) in [
5215            ("bit.tobit(1)", ":1\r\n"),
5216            ("bit.tobit(2^32 + 1)", ":1\r\n"),
5217            ("bit.tobit(2^31)", ":-2147483648\r\n"),
5218            ("bit.tobit(0xffffffff)", ":-1\r\n"),
5219            // The rounding is to the nearest and not toward zero.
5220            ("bit.tobit(1.5)", ":2\r\n"),
5221            ("bit.tobit(2.5)", ":2\r\n"),
5222            ("bit.bnot(0)", ":-1\r\n"),
5223            ("bit.band(0xff, 0x0f)", ":15\r\n"),
5224            ("bit.band(1, 2, 3)", ":0\r\n"),
5225            ("bit.bor(1, 2, 4)", ":7\r\n"),
5226            ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
5227            // Only the low five bits of a count are read.
5228            ("bit.lshift(1, 31)", ":-2147483648\r\n"),
5229            ("bit.lshift(1, 32)", ":1\r\n"),
5230            ("bit.lshift(1, 33)", ":2\r\n"),
5231            ("bit.rshift(-1, 1)", ":2147483647\r\n"),
5232            ("bit.arshift(-1, 1)", ":-1\r\n"),
5233            ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
5234            ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
5235            ("bit.bswap(0x12345678)", ":2018915346\r\n"),
5236            // A string that reads as a number is a number, which is Lua's rule
5237            // and not a courtesy of this library.
5238            ("bit.tobit('0x10')", ":16\r\n"),
5239        ] {
5240            let script = format!("return {body}");
5241            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5242        }
5243        // The digits are the low ones, a negative count asks for upper case,
5244        // and a count outside eight is brought back to it.
5245        for (body, want) in [
5246            ("bit.tohex(1)", "00000001"),
5247            ("bit.tohex(-1)", "ffffffff"),
5248            ("bit.tohex(255, 2)", "ff"),
5249            ("bit.tohex(255, -8)", "000000FF"),
5250            ("bit.tohex(0x87654321, 4)", "4321"),
5251            ("bit.tohex(1, 0)", ""),
5252            ("bit.tohex(1, 9)", "00000001"),
5253        ] {
5254            let script = format!("return {body}");
5255            assert_eq!(
5256                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5257                format!("${}\r\n{want}\r\n", want.len()),
5258                "{body}",
5259            );
5260        }
5261        // A bad argument names the position, the function and what was passed,
5262        // and the line in front of it is the script's own.
5263        for (body, want) in [
5264            (
5265                "return bit.band()",
5266                "bad argument #1 to 'band' (number expected, got no value)",
5267            ),
5268            (
5269                "return bit.band('x')",
5270                "bad argument #1 to 'band' (number expected, got string)",
5271            ),
5272            (
5273                "return bit.tobit(true)",
5274                "bad argument #1 to 'tobit' (number expected, got boolean)",
5275            ),
5276            (
5277                "return bit.lshift(1)",
5278                "bad argument #2 to 'lshift' (number expected, got no value)",
5279            ),
5280        ] {
5281            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5282            assert!(
5283                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5284                "{body} gave {reply}",
5285            );
5286        }
5287        // The name in the message is the one the call site used, so a call that
5288        // went through `pcall` has no name to report.
5289        assert_eq!(
5290            f.run(&[
5291                b"EVAL",
5292                b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
5293                b"0",
5294            ]),
5295            "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
5296        );
5297        // The table is readable and not writable, the same as `redis`.
5298        assert_eq!(
5299            f.run(&[
5300                b"EVAL",
5301                b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
5302                  table.sort(t) return table.concat(t, ' ')",
5303                b"0",
5304            ]),
5305            "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
5306        );
5307        for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
5308            assert!(
5309                f.run(&[b"EVAL", body, b"0"])
5310                    .contains("Attempt to modify a readonly table script: "),
5311                "{body:?}",
5312            );
5313        }
5314    }
5315
5316    #[test]
5317    fn a_script_gets_the_cjson_library_a_real_server_carries() {
5318        let mut f = Fixture::new();
5319        // Encoding, including the three shapes nobody guesses right: an empty
5320        // table is an object, a number is fourteen significant digits, and a
5321        // hole in an array is a null rather than a shorter array.
5322        for (body, want) in [
5323            ("cjson.encode(nil)", "null"),
5324            ("cjson.encode(true)", "true"),
5325            ("cjson.encode(cjson.null)", "null"),
5326            ("cjson.encode(100)", "100"),
5327            ("cjson.encode(1/3)", "0.33333333333333"),
5328            ("cjson.encode(1e300)", "1e+300"),
5329            ("cjson.encode(2^53)", "9.007199254741e+15"),
5330            ("cjson.encode({})", "{}"),
5331            ("cjson.encode({1,2,3})", "[1,2,3]"),
5332            ("cjson.encode({a=1})", "{\"a\":1}"),
5333            ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
5334            ("cjson.encode({[0]=1})", "{\"0\":1}"),
5335            ("cjson.encode('a\\nb')", "\"a\\nb\""),
5336            // A tab and a backslash have short escapes, a vertical tab does not.
5337            ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
5338            ("cjson.encode('\\11')", "\"\\u000b\""),
5339            // Reading and writing again is the shortest way to say the decoder
5340            // built what the encoder expected.
5341            (
5342                "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
5343                "[1,[2,{\"a\":null}]]",
5344            ),
5345            // An empty array comes back as an object, because a table with
5346            // nothing in it has nothing to say about which it was.
5347            ("cjson.encode(cjson.decode('[]'))", "{}"),
5348        ] {
5349            let script = format!("return {body}");
5350            assert_eq!(
5351                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5352                format!("${}\r\n{want}\r\n", want.len()),
5353                "{body}",
5354            );
5355        }
5356        // Decoding, where the leniency about numbers is on by default and a
5357        // null is a value of its own rather than a missing key.
5358        for (body, want) in [
5359            ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
5360            ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
5361            ("cjson.decode('0x10')", ":16\r\n"),
5362            ("cjson.decode('+1')", ":1\r\n"),
5363            ("cjson.decode('01')", ":1\r\n"),
5364            ("cjson.decode(1) + 1", ":2\r\n"),
5365            // A long bracket, because Lua 5.1 would eat the backslash first.
5366            ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
5367            ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
5368            ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
5369        ] {
5370            let script = format!("return {body}");
5371            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5372        }
5373        // The settings, each of which answers with what it now holds.
5374        for (body, want) in [
5375            (
5376                "cjson.encode_number_precision(3) return cjson.encode(1/3)",
5377                "0.333",
5378            ),
5379            (
5380                "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
5381                "null",
5382            ),
5383            (
5384                "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
5385                "inf",
5386            ),
5387            (
5388                "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
5389                "{\"1\":1,\"100\":1}",
5390            ),
5391            (
5392                "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
5393                "[]",
5394            ),
5395            ("return tostring(cjson.encode_max_depth())", "1000"),
5396            ("return tostring(cjson.encode_keep_buffer(false))", "false"),
5397            ("return tostring(cjson.encode_sparse_array())", "false"),
5398            // A setting one script changed is not a setting the next one sees,
5399            // which is D-105.
5400            ("return tostring(cjson.encode_number_precision())", "14"),
5401        ] {
5402            assert_eq!(
5403                f.run(&[b"EVAL", body.as_bytes(), b"0"]),
5404                format!("${}\r\n{want}\r\n", want.len()),
5405                "{body}",
5406            );
5407        }
5408        // A failure names what stopped it and, when it was the text, where.
5409        for (body, want) in [
5410            (
5411                "return cjson.encode(1/0)",
5412                "Cannot serialise number: must not be NaN or Inf",
5413            ),
5414            (
5415                "return cjson.encode({[1]=1,[100]=1})",
5416                "Cannot serialise table: excessively sparse array",
5417            ),
5418            (
5419                "return cjson.encode({[true]=1})",
5420                "Cannot serialise boolean: table key must be a number or string",
5421            ),
5422            (
5423                "return cjson.encode(tostring)",
5424                "Cannot serialise function: type not supported",
5425            ),
5426            (
5427                "return cjson.encode()",
5428                "bad argument #1 to 'encode' (expected 1 argument)",
5429            ),
5430            (
5431                "return cjson.decode('[1,2')",
5432                "Expected comma or array end but found T_END at character 5",
5433            ),
5434            (
5435                "return cjson.decode('{\"a\" 1}')",
5436                "Expected colon but found T_NUMBER at character 6",
5437            ),
5438            (
5439                "return cjson.decode('tru')",
5440                "Expected value but found invalid token at character 1",
5441            ),
5442            (
5443                "return cjson.decode('[1] 2')",
5444                "Expected the end but found T_NUMBER at character 5",
5445            ),
5446            (
5447                "return cjson.encode_max_depth(0)",
5448                "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
5449            ),
5450            (
5451                "return cjson.encode_invalid_numbers('yes')",
5452                "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
5453            ),
5454            (
5455                "return cjson.encode_max_depth(1, 2)",
5456                "bad argument #2 to 'encode_max_depth' (found too many arguments)",
5457            ),
5458        ] {
5459            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5460            assert!(
5461                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5462                "{body} gave {reply}",
5463            );
5464        }
5465        // A module of its own, with settings of its own and no guard on it,
5466        // which is what a real server hands back.
5467        assert_eq!(
5468            f.run(&[
5469                b"EVAL",
5470                b"local n = cjson.new() n.encode_number_precision(3) \
5471                  return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
5472                b"0",
5473            ]),
5474            "$22\r\n0.33333333333333 0.333\r\n"
5475        );
5476        // The table is readable and not writable, the same as `redis`.
5477        let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
5478                     decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
5479                     encode_max_depth encode_number_precision encode_sparse_array new null";
5480        assert_eq!(
5481            f.run(&[
5482                b"EVAL",
5483                b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
5484                  table.sort(t) return table.concat(t, ' ')",
5485                b"0",
5486            ]),
5487            format!("${}\r\n{names}\r\n", names.len())
5488        );
5489        for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
5490            assert!(
5491                f.run(&[b"EVAL", body, b"0"])
5492                    .contains("Attempt to modify a readonly table script: "),
5493                "{body:?}",
5494            );
5495        }
5496    }
5497
5498    #[test]
5499    fn a_script_gets_the_struct_library_a_real_server_carries() {
5500        let mut f = Fixture::new();
5501        // Packing, where the sizes are the ones a sixty four bit build gives
5502        // and the order is the machine's own unless the format says otherwise.
5503        for (body, want) in [
5504            ("#struct.pack('i4', 1)", ":4\r\n"),
5505            ("#struct.pack('l', 1)", ":8\r\n"),
5506            ("#struct.pack('d', 1)", ":8\r\n"),
5507            ("#struct.pack('f', 1)", ":4\r\n"),
5508            ("#struct.pack('s', 'abc')", ":4\r\n"),
5509            ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
5510            ("#struct.pack('x')", ":1\r\n"),
5511            ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
5512            ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
5513            ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
5514            // Past eight bytes the C shifts an unsigned long off the end, so
5515            // the rest of the bytes are zero and a negative is not carried.
5516            ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
5517            ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
5518            // A count of zero on `c` writes the whole string, `s` adds the
5519            // terminator, and `x` writes a zero byte nobody reads back.
5520            ("#struct.pack('c0', 'abcd')", ":4\r\n"),
5521            ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
5522            ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
5523        ] {
5524            let script = format!("return {body}");
5525            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5526        }
5527        // Sizes, including the two the C is lenient about: an unknown letter
5528        // and a bare digit are both nothing at all rather than a complaint.
5529        for (body, want) in [
5530            ("struct.size('i')", ":4\r\n"),
5531            ("struct.size('l')", ":8\r\n"),
5532            ("struct.size('T')", ":8\r\n"),
5533            ("struct.size('h')", ":2\r\n"),
5534            ("struct.size('c10')", ":10\r\n"),
5535            ("struct.size('ic')", ":5\r\n"),
5536            ("struct.size('!8ic')", ":5\r\n"),
5537            ("struct.size('!4i')", ":4\r\n"),
5538            // Nothing is padded until `!` turns alignment on, and then a
5539            // double is pushed out to the next eight byte boundary.
5540            ("struct.size('bd')", ":9\r\n"),
5541            ("struct.size('!bd')", ":16\r\n"),
5542            ("struct.size('A')", ":0\r\n"),
5543            ("struct.size('7')", ":0\r\n"),
5544        ] {
5545            let script = format!("return {body}");
5546            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5547        }
5548        // Unpacking, which hands back the values and then where it stopped, so
5549        // the last number can be passed straight back in as the next offset.
5550        for (body, want) in [
5551            ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
5552            ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
5553            ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
5554            ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
5555            ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
5556            (
5557                "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
5558                ":-70000\r\n",
5559            ),
5560            ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
5561            // A `c0` takes its length from the value read just before it and
5562            // swallows it, so one byte says how long the next three are and
5563            // only the string and the position come back.
5564            ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
5565            ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
5566        ] {
5567            let script = format!("return {body}");
5568            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5569        }
5570        for (body, want) in [
5571            ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
5572            ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
5573            ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
5574        ] {
5575            let script = format!("return {body}");
5576            assert_eq!(
5577                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5578                format!("${}\r\n{want}\r\n", want.len()),
5579                "{body}",
5580            );
5581        }
5582        // A failure names the argument the C names, which is not always the
5583        // argument a reader would pick.
5584        for (body, want) in [
5585            (
5586                "return struct.pack()",
5587                "bad argument #1 to 'pack' (string expected, got no value)",
5588            ),
5589            // The C pushes a nil before it reads anything, so a missing value
5590            // is a nil rather than nothing at all.
5591            (
5592                "return struct.pack('i4')",
5593                "bad argument #2 to 'pack' (number expected, got nil)",
5594            ),
5595            // And it reads the string with a post increment before it checks
5596            // the length, so the number here is one past the real argument.
5597            (
5598                "return struct.pack('c6', 'abc')",
5599                "bad argument #3 to 'pack' (string too short)",
5600            ),
5601            (
5602                "return struct.pack('A', 'x')",
5603                "bad argument #1 to 'pack' (invalid format option 'A')",
5604            ),
5605            (
5606                "return struct.pack('i33', 1)",
5607                "integral size 33 is larger than limit of 32",
5608            ),
5609            (
5610                "return struct.pack('!3i', 1)",
5611                "alignment 3 is not a power of 2",
5612            ),
5613            (
5614                "return struct.unpack()",
5615                "bad argument #1 to 'unpack' (string expected, got no value)",
5616            ),
5617            (
5618                "return struct.unpack('i4')",
5619                "bad argument #2 to 'unpack' (string expected, got no value)",
5620            ),
5621            (
5622                "return struct.unpack('i4', 'ab')",
5623                "bad argument #2 to 'unpack' (data string too short)",
5624            ),
5625            (
5626                "return struct.unpack('i1', 'abc', 0)",
5627                "bad argument #3 to 'unpack' (offset must be 1 or greater)",
5628            ),
5629            (
5630                "return struct.unpack('c0', 'abc')",
5631                "format 'c0' needs a previous size",
5632            ),
5633            (
5634                "return struct.unpack('s', 'abc')",
5635                "unfinished string in data",
5636            ),
5637            (
5638                "return struct.size()",
5639                "bad argument #1 to 'size' (string expected, got no value)",
5640            ),
5641            (
5642                "return struct.size('s')",
5643                "bad argument #1 to 'size' (option 's' has no fixed size)",
5644            ),
5645            (
5646                "return struct.size('c0')",
5647                "bad argument #1 to 'size' (option 'c0' has no fixed size)",
5648            ),
5649        ] {
5650            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5651            assert!(
5652                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5653                "{body} gave {reply}",
5654            );
5655        }
5656        // Three members and no version, which is all the C registers.
5657        let names = "pack size unpack";
5658        assert_eq!(
5659            f.run(&[
5660                b"EVAL",
5661                b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
5662                  table.sort(t) return table.concat(t, ' ')",
5663                b"0",
5664            ]),
5665            format!("${}\r\n{names}\r\n", names.len())
5666        );
5667        for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
5668            assert!(
5669                f.run(&[b"EVAL", body, b"0"])
5670                    .contains("Attempt to modify a readonly table script: "),
5671                "{body:?}",
5672            );
5673        }
5674    }
5675
5676    #[test]
5677    fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
5678        let mut f = Fixture::new();
5679        // Every value goes out in the shortest form that holds it, and several
5680        // arguments are packed one after another into one string.
5681        let hex = "local function hx(s) return (string.gsub(s, '.', \
5682                   function(c) return string.format('%02x', string.byte(c)) end)) end ";
5683        for (body, want) in [
5684            ("cmsgpack.pack(nil)", "c0"),
5685            ("cmsgpack.pack(true)", "c3"),
5686            ("cmsgpack.pack(false)", "c2"),
5687            ("cmsgpack.pack(0)", "00"),
5688            ("cmsgpack.pack(127)", "7f"),
5689            ("cmsgpack.pack(128)", "cc80"),
5690            ("cmsgpack.pack(-1)", "ff"),
5691            ("cmsgpack.pack(-33)", "d0df"),
5692            ("cmsgpack.pack(65535)", "cdffff"),
5693            ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
5694            ("cmsgpack.pack(2^53)", "cf0020000000000000"),
5695            ("cmsgpack.pack(-2^63)", "d38000000000000000"),
5696            // Past what an integer holds it is a number again, and a number
5697            // goes out narrow whenever four bytes give it back unchanged.
5698            ("cmsgpack.pack(2^64)", "ca5f800000"),
5699            ("cmsgpack.pack(1.5)", "ca3fc00000"),
5700            ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
5701            ("cmsgpack.pack('abc')", "a3616263"),
5702            ("cmsgpack.pack('')", "a0"),
5703            ("cmsgpack.pack({})", "90"),
5704            ("cmsgpack.pack({1, 2})", "920102"),
5705            ("cmsgpack.pack({a = 1})", "81a16101"),
5706            ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
5707            // Sixteen levels of table are packed and the seventeenth is a nil,
5708            // which is what the C does rather than refusing the whole thing.
5709            (
5710                "(function() local t = {} local c = t \
5711                 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
5712                "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
5713                 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
5714            ),
5715        ] {
5716            let script = format!("{hex} return hx({body})");
5717            assert_eq!(
5718                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5719                format!("${}\r\n{want}\r\n", want.len()),
5720                "{body}",
5721            );
5722        }
5723        // Unpacking reads the whole stream, so a string holding three values
5724        // hands back three. The two that take an offset put where they got to
5725        // in front of the values, and answer minus one when nothing is left.
5726        for (body, want) in [
5727            ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
5728            ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
5729            ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
5730            ("select('#', cmsgpack.unpack(''))", 0),
5731            ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
5732            ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5733            ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5734            ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
5735            ("select(1, cmsgpack.unpack_one('\\1'))", -1),
5736            ("select(1, cmsgpack.unpack_one('', 0))", -1),
5737            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
5738            ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
5739            // A limit of nothing at all takes the read everything path, which
5740            // has no offset in front of it.
5741            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
5742            ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
5743        ] {
5744            let script = format!("return {body}");
5745            assert_eq!(
5746                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5747                format!(":{want}\r\n"),
5748                "{body}",
5749            );
5750        }
5751        for (body, want) in [
5752            ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
5753            ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
5754            ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
5755            (
5756                "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
5757                "inf",
5758            ),
5759            ("cmsgpack._NAME", "cmsgpack"),
5760            ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
5761            (
5762                "cmsgpack._COPYRIGHT",
5763                "Copyright (C) 2012, Salvatore Sanfilippo",
5764            ),
5765            (
5766                "cmsgpack._DESCRIPTION",
5767                "MessagePack C implementation for Lua",
5768            ),
5769        ] {
5770            let script = format!("return {body}");
5771            assert_eq!(
5772                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5773                format!("${}\r\n{want}\r\n", want.len()),
5774                "{body}",
5775            );
5776        }
5777        for (body, want) in [
5778            // The C counts the arguments before it reads any of them, so the
5779            // one it names when there are none is the one before the first.
5780            (
5781                "return cmsgpack.pack()",
5782                "bad argument #0 to 'pack' (MessagePack pack needs input.)",
5783            ),
5784            (
5785                "return cmsgpack.unpack()",
5786                "bad argument #1 to 'unpack' (string expected, got no value)",
5787            ),
5788            (
5789                "return cmsgpack.unpack(string.char(193))",
5790                "Bad data format in input.",
5791            ),
5792            (
5793                "return cmsgpack.unpack(string.char(204))",
5794                "Missing bytes in input.",
5795            ),
5796            (
5797                "return cmsgpack.unpack(string.char(146, 1))",
5798                "Missing bytes in input.",
5799            ),
5800            (
5801                "return cmsgpack.unpack_one('\\1', 5)",
5802                "Start offset 5 greater than input length 1.",
5803            ),
5804            (
5805                "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
5806                "Start offset 5 greater than input length 2.",
5807            ),
5808            // The second number here is the length of the input rather than
5809            // the limit, which is a mixed up argument in the C kept on purpose.
5810            (
5811                "return cmsgpack.unpack_one('\\1', -1)",
5812                "Invalid request to unpack with offset of -1 and limit of 1.",
5813            ),
5814            (
5815                "return cmsgpack.unpack_limit('\\1', -1, 0)",
5816                "Invalid request to unpack with offset of 0 and limit of 1.",
5817            ),
5818        ] {
5819            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5820            assert!(
5821                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5822                "{body} gave {reply}",
5823            );
5824        }
5825        // Four calls and the four names the C sets on the table beside them.
5826        let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
5827        assert_eq!(
5828            f.run(&[
5829                b"EVAL",
5830                b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
5831                  table.sort(t) return table.concat(t, ' ')",
5832                b"0",
5833            ]),
5834            format!("${}\r\n{names}\r\n", names.len())
5835        );
5836        for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
5837            assert!(
5838                f.run(&[b"EVAL", body, b"0"])
5839                    .contains("Attempt to modify a readonly table script: "),
5840                "{body:?}",
5841            );
5842        }
5843        // A library is a table like any other from a script's side, so packing
5844        // one walks its members rather than finding the guard in front empty.
5845        assert_eq!(
5846            f.run(&[
5847                b"EVAL",
5848                b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
5849                b"0",
5850            ]),
5851            "$8\r\ncmsgpack\r\n"
5852        );
5853    }
5854
5855    /// The library used by most of the function tests below.
5856    ///
5857    /// Written out once because every one of them wants a library that has
5858    /// something to call, and because the line numbers in the failures a couple
5859    /// of them check are line numbers in this.
5860    const LIB: &[u8] = b"#!lua name=mylib\n\
5861        local counter = 0\n\
5862        redis.register_function{function_name = 'ping', description = 'says pong',\n\
5863        callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
5864        redis.register_function('count', function() counter = counter + 1 return counter end)\n\
5865        redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
5866        redis.register_function('setit', function(keys, args) \
5867        return redis.call('SET', keys[1], args[1]) end)\n\
5868        redis.register_function('raise', function() error('boom') end)\n";
5869
5870    /// A second library, for the tests that need two of them.
5871    const OTHER: &[u8] = b"#!lua name=other\n\
5872        redis.register_function('twice', function(keys, args) return 2 end)\n";
5873
5874    #[test]
5875    fn a_library_is_loaded_once_and_called_by_name_forever_after() {
5876        let mut f = Fixture::new();
5877        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5878        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5879        // The dictionary FCALL looks in is one for the whole server and it does
5880        // not care about case, which is why this finds the same function.
5881        assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
5882        // Keys and arguments arrive as the two arguments of the callback rather
5883        // than as globals, and a function that reads KEYS is reading a name
5884        // that is not there.
5885        assert_eq!(
5886            f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
5887            "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5888        );
5889        assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
5890        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
5891        // A library's own local outlives the call that made it, which is the
5892        // whole reason a library is not a script.
5893        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5894        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
5895        // The name a failure ends with is the function's, where a script's is
5896        // its digest, and the line is a line in the library.
5897        assert_eq!(
5898            f.run(&[b"FCALL", b"raise", b"0"]),
5899            "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
5900        );
5901        // Deleting is by the exact name, so the upper case spelling that found
5902        // the function a moment ago does not find the library.
5903        assert_eq!(
5904            f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
5905            "-ERR Library not found\r\n"
5906        );
5907        assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
5908        assert_eq!(
5909            f.run(&[b"FCALL", b"ping", b"0"]),
5910            "-ERR Function not found\r\n"
5911        );
5912    }
5913
5914    #[test]
5915    fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
5916        let mut f = Fixture::new();
5917        for (code, want) in [
5918            (&b"return 1"[..], "ERR Missing library metadata"),
5919            (b"#!lua name=x", "ERR Invalid library metadata"),
5920            (b"#!\n", "ERR Library name was not given"),
5921            (b"#!lua\nx", "ERR Library name was not given"),
5922            (
5923                b"#!lua name=a name=b\nx",
5924                "ERR Invalid metadata value, name argument was given multiple times",
5925            ),
5926            (
5927                b"#!lua nome=a\nx",
5928                "ERR Invalid metadata value given: nome=a",
5929            ),
5930            (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
5931            (
5932                b"#!lua name=a-b\nx",
5933                "ERR Library names can only contain letters, numbers, or underscores(_) \
5934                 and must be at least one character long",
5935            ),
5936            (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
5937            (
5938                b"#!lua name=c\nthis is not lua",
5939                "ERR Error compiling function: user_function:2: '=' expected near 'is'",
5940            ),
5941            // Nothing at all is on the global table during a load except one
5942            // table with eight names on it, so `error` is as absent as anything
5943            // a library misspelled would be.
5944            (
5945                b"#!lua name=r\nerror('boom')",
5946                "ERR Error registering functions: ERR user_function:2: \
5947                 Script attempted to access nonexistent global variable 'error'",
5948            ),
5949            // And `redis` is there but `redis.call` is not, so the name the
5950            // complaint gives is `call` and not `redis`.
5951            (
5952                b"#!lua name=r\nredis.call('PING')",
5953                "ERR Error registering functions: ERR user_function:2: \
5954                 Script attempted to access nonexistent global variable 'call'",
5955            ),
5956            (
5957                b"#!lua name=r\nx = 1",
5958                "ERR Error registering functions: ERR user_function:2: \
5959                 Attempt to modify a readonly table",
5960            ),
5961            (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
5962        ] {
5963            assert_eq!(
5964                f.run(&[b"FUNCTION", b"LOAD", code]),
5965                format!("-{want}\r\n"),
5966                "{}",
5967                String::from_utf8_lossy(code),
5968            );
5969        }
5970    }
5971
5972    #[test]
5973    fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
5974        let mut f = Fixture::new();
5975        for (call, want) in [
5976            (
5977                &b"redis.register_function()"[..],
5978                "wrong number of arguments to redis.register_function",
5979            ),
5980            (
5981                b"redis.register_function('a', function() end, 1)",
5982                "wrong number of arguments to redis.register_function",
5983            ),
5984            (
5985                b"redis.register_function('a')",
5986                "calling redis.register_function with a single argument is only \
5987                 applicable to Lua table (representing named arguments).",
5988            ),
5989            (
5990                b"redis.register_function({foo = 'a'})",
5991                "unknown argument given to redis.register_function",
5992            ),
5993            (
5994                b"redis.register_function({callback = function() end})",
5995                "redis.register_function must get a function name argument",
5996            ),
5997            (
5998                b"redis.register_function({function_name = 'a'})",
5999                "redis.register_function must get a callback argument",
6000            ),
6001            (
6002                b"redis.register_function({function_name = {}, callback = function() end})",
6003                "function_name argument given to redis.register_function must be a string",
6004            ),
6005            (
6006                b"redis.register_function({function_name = 'a', description = {}, \
6007                  callback = function() end})",
6008                "description argument given to redis.register_function must be a string",
6009            ),
6010            (
6011                b"redis.register_function({function_name = 'a', callback = 1})",
6012                "callback argument given to redis.register_function must be a function",
6013            ),
6014            (
6015                b"redis.register_function({function_name = 'a', callback = function() end, \
6016                  flags = 1})",
6017                "flags argument to redis.register_function must be a table \
6018                 representing function flags",
6019            ),
6020            (
6021                b"redis.register_function({function_name = 'a', callback = function() end, \
6022                  flags = {'zz'}})",
6023                "unknown flag given",
6024            ),
6025            (
6026                b"redis.register_function({}, function() end)",
6027                "first argument to redis.register_function must be a string",
6028            ),
6029            (
6030                b"redis.register_function('a', 1)",
6031                "second argument to redis.register_function must be a function",
6032            ),
6033            (
6034                b"redis.register_function('a-b', function() end)",
6035                "Library names can only contain letters, numbers, or underscores(_) \
6036                 and must be at least one character long",
6037            ),
6038            (
6039                b"redis.register_function('d', function() end) \
6040                  redis.register_function('d', function() end)",
6041                "Function already exists in the library",
6042            ),
6043        ] {
6044            let mut code = b"#!lua name=e\n".to_vec();
6045            code.extend_from_slice(call);
6046            // Two `ERR` in a row on purpose. The sentence comes back as a table
6047            // with the code already on it, which is what keeps the position off
6048            // the front of it, and then the code goes on the line as well.
6049            assert_eq!(
6050                f.run(&[b"FUNCTION", b"LOAD", &code]),
6051                format!("-ERR Error registering functions: ERR {want}\r\n"),
6052                "{}",
6053                String::from_utf8_lossy(call),
6054            );
6055        }
6056        // A number is a name, because the C reads an argument that should be a
6057        // string through a helper that takes a number and prints it.
6058        assert_eq!(
6059            f.run(&[
6060                b"FUNCTION",
6061                b"LOAD",
6062                b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
6063            ]),
6064            "$1\r\nn\r\n"
6065        );
6066        assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
6067        // The dictionary inside one library is case sensitive where the one
6068        // across libraries is not, so these are two functions.
6069        assert_eq!(
6070            f.run(&[
6071                b"FUNCTION",
6072                b"LOAD",
6073                b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
6074                  redis.register_function('D', function() return 2 end)",
6075            ]),
6076            "$1\r\nc\r\n"
6077        );
6078    }
6079
6080    #[test]
6081    fn a_library_cannot_take_a_name_another_library_already_has() {
6082        let mut f = Fixture::new();
6083        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6084        assert_eq!(
6085            f.run(&[b"FUNCTION", b"LOAD", LIB]),
6086            "-ERR Library 'mylib' already exists\r\n"
6087        );
6088        // A different library that registers a name the first one already has,
6089        // which is checked without regard to case because the dictionary it is
6090        // checked against is.
6091        assert_eq!(
6092            f.run(&[
6093                b"FUNCTION",
6094                b"LOAD",
6095                b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
6096            ]),
6097            "-ERR Function PING already exists\r\n"
6098        );
6099        // REPLACE reloads a library over itself, and the collision check leaves
6100        // the library being replaced out or nothing could ever be reloaded.
6101        assert_eq!(
6102            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
6103            "$5\r\nmylib\r\n"
6104        );
6105        // The counter went back to zero with the reload, since the library is a
6106        // new one and its locals are new with it.
6107        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
6108        assert_eq!(
6109            f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
6110            "-ERR Unknown option given: NOPE\r\n"
6111        );
6112        // The loop that reads the options stops one short of the end, so the
6113        // last argument is the code whatever it looks like.
6114        assert_eq!(
6115            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
6116            "-ERR Missing library metadata\r\n"
6117        );
6118        assert_eq!(
6119            f.run(&[b"FUNCTION", b"LOAD"]),
6120            "-ERR wrong number of arguments for 'function|load' command\r\n"
6121        );
6122    }
6123
6124    #[test]
6125    fn fcall_checks_the_name_before_it_looks_at_anything_else() {
6126        let mut f = Fixture::new();
6127        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6128        for (args, want) in [
6129            (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
6130            (&[b"ping", b"x"], "ERR Bad number of keys provided"),
6131            (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
6132            (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
6133            (
6134                &[b"ping", b"99999999999999999999"],
6135                "ERR Bad number of keys provided",
6136            ),
6137            (
6138                &[b"ping", b"3", b"a"],
6139                "ERR Number of keys can't be greater than number of args",
6140            ),
6141            (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
6142        ] {
6143            let mut wire: Vec<&[u8]> = vec![b"FCALL"];
6144            wire.extend_from_slice(args);
6145            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
6146        }
6147        // The read-only spelling refuses a function the library did not mark
6148        // no-writes, and it refuses it before anything runs.
6149        assert_eq!(
6150            f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
6151            "-ERR Can not execute a script with write flag using *_ro command.\r\n"
6152        );
6153        assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
6154        assert_eq!(
6155            f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
6156            "-ERR Function not found\r\n"
6157        );
6158        // And a function that was marked no-writes is held to it whichever
6159        // spelling called it.
6160        assert_eq!(
6161            f.run(&[
6162                b"FUNCTION",
6163                b"LOAD",
6164                b"#!lua name=w\nredis.register_function{function_name = 'w', \
6165                  flags = {'no-writes'}, callback = function(keys) \
6166                  return redis.call('SET', keys[1], 'x') end}",
6167            ]),
6168            "$1\r\nw\r\n"
6169        );
6170        assert!(
6171            f.run(&[b"FCALL", b"w", b"1", b"k"])
6172                .starts_with("-ERR Write commands are not allowed from read-only scripts."),
6173        );
6174    }
6175
6176    #[test]
6177    fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
6178        let mut f = Fixture::new();
6179        // The three names on the `redis` table that only mean something inside
6180        // EVAL are not there, and neither is the error handler EVAL installs.
6181        let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
6182                     REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
6183                     acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
6184                     status_reply";
6185        let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
6186                       gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
6187                       rawequal rawget rawset redis select setmetatable string struct table \
6188                       tonumber tostring type unpack xpcall";
6189        assert_eq!(
6190            f.run(&[
6191                b"FUNCTION",
6192                b"LOAD",
6193                b"#!lua name=g\n\
6194                  local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
6195                  table.sort(o) return table.concat(o, ' ') end\n\
6196                  redis.register_function('names', function() return sorted(redis) end)\n\
6197                  redis.register_function('globals', function() return sorted(_G) end)\n\
6198                  redis.register_function('keysg', function() return KEYS[1] end)\n\
6199                  redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
6200                  redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
6201                  redis.register_function('gwr', function() _G.pcall = 1 end)\n",
6202            ]),
6203            "$1\r\ng\r\n"
6204        );
6205        assert_eq!(
6206            f.run(&[b"FCALL", b"names", b"0"]),
6207            format!("${}\r\n{names}\r\n", names.len())
6208        );
6209        assert_eq!(
6210            f.run(&[b"FCALL", b"globals", b"0"]),
6211            format!("${}\r\n{globals}\r\n", globals.len())
6212        );
6213        // No `KEYS`, and reading a global that is not there is a mistake rather
6214        // than a nil, so this is the sandbox's own complaint.
6215        assert!(
6216            f.run(&[b"FCALL", b"keysg", b"1", b"k"])
6217                .contains("nonexistent global variable 'KEYS'"),
6218        );
6219        // The `redis` table has no error metatable on it, unlike the global
6220        // table, so a name that is not on it is a nil and not a complaint.
6221        assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
6222        // The global table cannot be written to either way round, which is a
6223        // stricter rule than the one a script runs under.
6224        for name in [&b"wr"[..], b"gwr"] {
6225            assert!(
6226                f.run(&[b"FCALL", name, b"0"])
6227                    .contains("Attempt to modify a readonly table"),
6228                "{}",
6229                String::from_utf8_lossy(name),
6230            );
6231        }
6232    }
6233
6234    #[test]
6235    fn function_list_says_what_every_library_registered() {
6236        let mut f = Fixture::new();
6237        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6238        // One map per library on RESP3, and the functions inside it in the
6239        // order the library registered them, which is D-109.
6240        f.out = Out::new(Proto::Resp3);
6241        let listed = f.run(&[b"FUNCTION", b"LIST"]);
6242        assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
6243        assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
6244        assert!(listed.contains(
6245            "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
6246             $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
6247        ));
6248        // A function with no description gets a null rather than an empty
6249        // string, and no flags is an empty set rather than a missing field.
6250        assert!(listed.contains(
6251            "$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"
6252        ));
6253        assert!(!listed.contains("library_code"));
6254        assert!(
6255            f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
6256                .contains("library_code")
6257        );
6258        // The pattern is matched without regard to case, which is a third rule
6259        // again next to the two the two dictionaries use.
6260        assert!(
6261            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
6262                .starts_with("*1\r\n")
6263        );
6264        assert_eq!(
6265            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
6266            "*0\r\n"
6267        );
6268        // On RESP2 the same reply is a flat array of six, which is what `map`
6269        // means on a protocol that has no map.
6270        f.out = Out::new(Proto::Resp2);
6271        assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
6272        for (args, want) in [
6273            (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
6274            (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
6275            (
6276                &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
6277                "ERR Unknown argument LIBRARYNAME",
6278            ),
6279            (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
6280        ] {
6281            let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
6282            wire.extend_from_slice(args);
6283            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
6284        }
6285    }
6286
6287    #[test]
6288    fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
6289        let mut f = Fixture::new();
6290        f.out = Out::new(Proto::Resp3);
6291        assert_eq!(
6292            f.run(&[b"FUNCTION", b"STATS"]),
6293            "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
6294             %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
6295        );
6296        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6297        assert!(
6298            f.run(&[b"FUNCTION", b"STATS"])
6299                .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
6300        );
6301        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6302        assert!(
6303            f.run(&[b"FUNCTION", b"STATS"])
6304                .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
6305        );
6306    }
6307
6308    #[test]
6309    fn every_function_subcommand_complains_about_its_own_arity() {
6310        let mut f = Fixture::new();
6311        for (args, want) in [
6312            (
6313                &[&b"STATS"[..], b"X"][..],
6314                "ERR wrong number of arguments for 'function|stats' command",
6315            ),
6316            (
6317                &[b"KILL", b"X"],
6318                "ERR wrong number of arguments for 'function|kill' command",
6319            ),
6320            (
6321                &[b"HELP", b"X"],
6322                "ERR wrong number of arguments for 'function|help' command",
6323            ),
6324            (
6325                &[b"DELETE"],
6326                "ERR wrong number of arguments for 'function|delete' command",
6327            ),
6328            (
6329                &[b"DELETE", b"a", b"b"],
6330                "ERR wrong number of arguments for 'function|delete' command",
6331            ),
6332            (
6333                &[b"DUMP", b"X"],
6334                "ERR wrong number of arguments for 'function|dump' command",
6335            ),
6336            (
6337                &[b"RESTORE"],
6338                "ERR wrong number of arguments for 'function|restore' command",
6339            ),
6340            // RESTORE is the other one that falls through to the generic
6341            // sentence, and for the same reason FLUSH does.
6342            (
6343                &[b"RESTORE", b"a", b"FLUSH", b"X"],
6344                "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
6345                 Try FUNCTION HELP.",
6346            ),
6347            (
6348                &[b"RESTORE", b"a", b"ZZ"],
6349                "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
6350                 or REPLACE.",
6351            ),
6352            // FLUSH is the one that does not, because it checks the count
6353            // itself before it looks at the argument.
6354            (
6355                &[b"FLUSH", b"SYNC", b"X"],
6356                "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
6357                 Try FUNCTION HELP.",
6358            ),
6359            (
6360                &[b"FLUSH", b"ZZ"],
6361                "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
6362            ),
6363            (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
6364        ] {
6365            let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
6366            wire.extend_from_slice(args);
6367            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
6368        }
6369        assert_eq!(
6370            f.run(&[b"FUNCTION"]),
6371            "-ERR wrong number of arguments for 'function' command\r\n"
6372        );
6373        assert_eq!(
6374            f.run(&[b"FUNCTION", b"KILL"]),
6375            "-NOTBUSY No scripts in execution right now.\r\n"
6376        );
6377    }
6378
6379    /// The two ends of the same pipe, so they are tested as one.
6380    ///
6381    /// An empty server dumps ten bytes rather than nothing, because the footer
6382    /// is there whether or not a library is in front of it, and restoring those
6383    /// ten bytes is a working no op.
6384    #[test]
6385    fn a_library_survives_a_dump_and_a_restore() {
6386        let mut f = Fixture::new();
6387        let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6388        assert_eq!(empty.len(), 10);
6389        assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
6390
6391        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6392        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6393        assert!(full.len() > empty.len());
6394
6395        // The default policy is APPEND, so restoring onto the library the
6396        // payload came from is a name collision and not a quiet replacement.
6397        assert_eq!(
6398            f.run(&[b"FUNCTION", b"RESTORE", &full]),
6399            "-ERR Library mylib already exists\r\n"
6400        );
6401        assert_eq!(
6402            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6403            "+OK\r\n"
6404        );
6405        assert_eq!(
6406            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
6407            "+OK\r\n"
6408        );
6409        // Whichever way it went back, the functions in it still run.
6410        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6411
6412        // FLUSH keeps only what the payload held, so a library that was there
6413        // and is not in the payload is gone.
6414        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
6415        assert_eq!(
6416            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
6417            "+OK\r\n"
6418        );
6419        assert_eq!(
6420            f.run(&[b"FUNCTION", b"DELETE", b"other"]),
6421            "-ERR Library not found\r\n"
6422        );
6423    }
6424
6425    /// A payload that is going to be refused has to leave the server alone.
6426    ///
6427    /// Every one of these is refused for a different reason and at a different
6428    /// depth, from bytes that are not a payload at all down to a library that
6429    /// compiles and then collides, and the library that was already there has to
6430    /// still be there afterwards in every case.
6431    #[test]
6432    fn a_restore_that_fails_changes_nothing() {
6433        let mut f = Fixture::new();
6434        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6435        let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6436
6437        // Put the footer back on, so that each of these is refused for the
6438        // reason it is meant to be testing rather than for a checksum the edit
6439        // broke on the way.
6440        let reseal = |body: &[u8], version: u16| {
6441            let mut out = body.to_vec();
6442            out.extend_from_slice(&version.to_le_bytes());
6443            let crc = yo_common::crc::crc64(0, &out);
6444            out.extend_from_slice(&crc.to_le_bytes());
6445            out
6446        };
6447        let body = &good[..good.len() - 10];
6448
6449        let mut torn = good.clone();
6450        let n = torn.len();
6451        torn[n - 1] ^= 0xff;
6452        let future = reseal(body, 999);
6453        // The opcode in front of the one library, changed to the one the 7.0
6454        // release candidates wrote and then to one that is not a library at all.
6455        let mut pre_ga = body.to_vec();
6456        pre_ga[0] = 246;
6457        let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
6458        let mut other = body.to_vec();
6459        other[0] = 0;
6460        let other = reseal(&other, yo_kv::rdb::VERSION);
6461        // A library whose length says there is more of it than there is.
6462        let mut cut = body.to_vec();
6463        cut.truncate(body.len() - 1);
6464        let cut = reseal(&cut, yo_kv::rdb::VERSION);
6465
6466        for (bytes, want) in [
6467            (vec![], "ERR DUMP payload version or checksum are wrong"),
6468            (
6469                b"0123456789".to_vec(),
6470                "ERR DUMP payload version or checksum are wrong",
6471            ),
6472            (torn, "ERR DUMP payload version or checksum are wrong"),
6473            (future, "ERR DUMP payload version or checksum are wrong"),
6474            (pre_ga, "ERR Pre-GA function format not supported"),
6475            (other, "ERR given type is not a function"),
6476            (cut, "ERR Failed loading library payload"),
6477        ] {
6478            assert_eq!(
6479                f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
6480                format!("-{want}\r\n")
6481            );
6482        }
6483
6484        // Still exactly the one library, and it still runs.
6485        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6486        let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6487        assert_eq!(again, good);
6488    }
6489
6490    /// A REPLACE takes a library's name off another library and still refuses to
6491    /// take a function name off one it is leaving alone.
6492    #[test]
6493    fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
6494        let mut f = Fixture::new();
6495        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6496        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6497        // A second library registering the name the payload's library uses.
6498        let clash =
6499            b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
6500                .as_slice();
6501        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6502        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
6503        assert_eq!(
6504            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6505            "-ERR Function ping already exists\r\n"
6506        );
6507        // Untouched, so the name still belongs to the library that had it.
6508        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
6509    }
6510
6511    #[test]
6512    fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
6513        let mut f = Fixture::new();
6514        assert_eq!(
6515            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
6516            "*1\r\n$1\r\nk\r\n"
6517        );
6518        assert_eq!(
6519            f.run(&[
6520                b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
6521            ]),
6522            "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
6523        );
6524        // None is a real answer for a script and the arguments past the count
6525        // are not keys, so they are not listed.
6526        assert_eq!(
6527            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
6528            "*0\r\n"
6529        );
6530        // A count that makes no sense finds no keys rather than being an error,
6531        // which is what a real server's key spec does with it.
6532        assert_eq!(
6533            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
6534            "*0\r\n"
6535        );
6536        assert_eq!(
6537            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
6538            "*0\r\n"
6539        );
6540        assert_eq!(
6541            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
6542            "*0\r\n"
6543        );
6544        // The count itself has to be there, and that is an arity question.
6545        assert_eq!(
6546            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
6547            "-ERR Invalid number of arguments specified for command\r\n"
6548        );
6549    }
6550
6551    #[test]
6552    fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
6553        let mut f = Fixture::new();
6554        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
6555
6556        assert_eq!(
6557            eval(&mut f, b"return redis.sha1hex('')"),
6558            "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
6559        );
6560        assert_eq!(
6561            eval(&mut f, b"return redis.sha1hex('return 1')"),
6562            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
6563        );
6564        // A message with no space in it gets the generic code in front, and one
6565        // that already looks like a coded error is left alone.
6566        assert_eq!(
6567            eval(&mut f, b"return redis.error_reply('boom')"),
6568            "-ERR boom\r\n"
6569        );
6570        assert_eq!(
6571            eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
6572            "-WRONGTYPE nope\r\n"
6573        );
6574        assert_eq!(
6575            eval(&mut f, b"return redis.status_reply('fine')"),
6576            "+fine\r\n"
6577        );
6578        // Neither of them raises when it is called wrongly, they answer a value
6579        // that is an error, which is a difference a script can see.
6580        assert_eq!(
6581            eval(&mut f, b"return redis.error_reply(1)"),
6582            "-ERR wrong number or type of arguments\r\n"
6583        );
6584        assert_eq!(
6585            eval(&mut f, b"local x = redis.status_reply() return x.err"),
6586            "$37\r\nERR wrong number or type of arguments\r\n"
6587        );
6588
6589        // The constants a script branches on.
6590        assert_eq!(
6591            eval(
6592                &mut f,
6593                b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
6594            ),
6595            "$4\r\n0123\r\n"
6596        );
6597        assert_eq!(
6598            eval(
6599                &mut f,
6600                b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
6601            ),
6602            "$5\r\n01223\r\n"
6603        );
6604        // The calls that exist so an old script keeps working.
6605        assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
6606        assert_eq!(
6607            eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
6608            ":1\r\n"
6609        );
6610        assert_eq!(
6611            eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
6612            ":1\r\n"
6613        );
6614        assert_eq!(
6615            eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
6616            ":1\r\n"
6617        );
6618        // Each of those checks its arguments the way a real server does.
6619        assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
6620        assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
6621        assert!(
6622            eval(&mut f, b"redis.log('x', 'y')")
6623                .contains("First argument must be a number (log level)."),
6624        );
6625        assert!(
6626            eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
6627                .contains("Invalid command passed to redis.acl_check_cmd()"),
6628        );
6629        assert!(
6630            eval(&mut f, b"return redis.acl_check_cmd('get')")
6631                .contains("Wrong number of args for redis.acl_check_cmd()"),
6632        );
6633    }
6634
6635    #[test]
6636    fn a_counter_is_an_integer_and_not_a_string_of_digits() {
6637        let mut f = Fixture::new();
6638        assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
6639        assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
6640        assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
6641        // Read back as a string it is still an integer, written out as digits
6642        // only because somebody asked for them.
6643        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
6644        assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
6645        // A counter that is not a number is the error the store raises and this
6646        // layer only spells, which is the whole point of the split.
6647        f.run(&[b"SET", b"k", b"hello"]);
6648        assert_eq!(
6649            f.run(&[b"INCR", b"k"]),
6650            "-ERR value is not an integer or out of range\r\n"
6651        );
6652        assert_eq!(
6653            f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
6654            "-ERR increment would produce NaN or Infinity\r\n"
6655        );
6656    }
6657
6658    /// Every one of these was read off a running 8.8. They are the answers a
6659    /// client library's own test suite checks, and the shapes are not
6660    /// guessable: `DIGEST` is hexadecimal in a bulk string, `MSETEX` is an
6661    /// integer, `INCREX` is a pair.
6662    #[test]
6663    fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
6664        let mut f = Fixture::new();
6665        assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
6666        // The same digest a real 8.8 answers for the same five bytes, which is
6667        // what makes `IFDEQ` usable against a mixed deployment.
6668        assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
6669        assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
6670        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
6671        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
6672        assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
6673        assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
6674        assert_eq!(
6675            f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
6676            "*2\r\n:1\r\n:0\r\n",
6677            "a refused increment reports the value it left alone and applied nothing"
6678        );
6679        assert_eq!(
6680            f.run(&[
6681                b"INCREX",
6682                b"n",
6683                b"BYINT",
6684                b"5",
6685                b"UBOUND",
6686                b"3",
6687                b"SATURATE"
6688            ]),
6689            "*2\r\n:3\r\n:2\r\n"
6690        );
6691        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
6692        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
6693    }
6694
6695    #[test]
6696    fn the_same_answers_come_out_in_resp3_spelling() {
6697        let mut f = Fixture::new();
6698        assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
6699        assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
6700        // A float counter is a double on RESP3 and the digits in a bulk string
6701        // on RESP2, and `INCRBYFLOAT` is a bulk string on both.
6702        assert_eq!(
6703            f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
6704            "*2\r\n,1.5\r\n,1.5\r\n"
6705        );
6706        assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
6707        // `RESET` puts the protocol back, which is the part that is easy to
6708        // miss and leaves a pooled connection speaking the wrong one.
6709        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6710        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
6711    }
6712
6713    #[test]
6714    fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
6715        let mut f = Fixture::new();
6716        let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
6717        assert_eq!(flow, Flow::Continue);
6718        assert_eq!(
6719            reply,
6720            "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
6721        );
6722        // A name with a line ending in it cannot write its own frame into the
6723        // stream, which is the reason the error writer maps them to spaces.
6724        let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
6725        assert_eq!(reply.matches("\r\n").count(), 1);
6726    }
6727
6728    #[test]
6729    fn arity_is_checked_before_the_command_is() {
6730        let mut f = Fixture::new();
6731        assert_eq!(
6732            f.run(&[b"GET"]),
6733            "-ERR wrong number of arguments for 'get' command\r\n"
6734        );
6735        assert_eq!(
6736            f.run(&[b"MSET", b"k"]),
6737            "-ERR wrong number of arguments for 'mset' command\r\n"
6738        );
6739        // The table says `PING` takes one or more and a real server then
6740        // refuses three, which is the sort of thing that only shows up against
6741        // the real thing.
6742        assert_eq!(
6743            f.run(&[b"PING", b"a", b"b"]),
6744            "-ERR wrong number of arguments for 'ping' command\r\n"
6745        );
6746        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
6747        assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
6748        // `DELEX` takes two or four and nothing between.
6749        assert_eq!(
6750            f.run(&[b"DELEX", b"k", b"IFEQ"]),
6751            "-ERR wrong number of arguments for 'delex' command\r\n"
6752        );
6753    }
6754
6755    /// The option rules, all of them measured against 8.8 rather than read off
6756    /// the documentation. The surprising one is that `SET` accepts the same
6757    /// keyword twice and `INCREX` does not.
6758    #[test]
6759    fn the_option_combinations_are_the_ones_a_real_server_accepts() {
6760        let mut f = Fixture::new();
6761        let syntax = "-ERR syntax error\r\n";
6762        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
6763        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
6764        assert_eq!(
6765            f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
6766            syntax
6767        );
6768        assert_eq!(
6769            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
6770            syntax
6771        );
6772        assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
6773        // Twice is fine, and the last one wins.
6774        assert_eq!(
6775            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
6776            "+OK\r\n"
6777        );
6778        assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
6779        assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
6780        // `INCREX` refuses what `SET` allows.
6781        assert_eq!(
6782            f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
6783            syntax
6784        );
6785        assert_eq!(
6786            f.run(&[b"INCREX", b"n", b"ENX"]),
6787            "-ERR ENX flag requires an expiration\r\n"
6788        );
6789        assert_eq!(
6790            f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
6791            "-ERR UBOUND is not an integer or out of range\r\n"
6792        );
6793        assert_eq!(
6794            f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
6795            "-ERR LBOUND can't be greater than UBOUND\r\n"
6796        );
6797        assert_eq!(
6798            f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
6799            "-ERR If you want both the length and indexes, please just use IDX.\r\n"
6800        );
6801    }
6802
6803    /// Where the expiration rules bite. The one worth the test is `GETEX` on a
6804    /// key that is not there, which answers null without ever looking at the
6805    /// expiration it was given.
6806    #[test]
6807    fn the_expiry_rules_are_redis_own() {
6808        let mut f = Fixture::new();
6809        let bad = "-ERR invalid expire time in 'set' command\r\n";
6810        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
6811        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
6812        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
6813        assert_eq!(
6814            f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
6815            bad
6816        );
6817        assert_eq!(
6818            f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
6819            "-ERR value is not an integer or out of range\r\n"
6820        );
6821        assert_eq!(
6822            f.run(&[b"SETEX", b"k", b"0", b"v"]),
6823            "-ERR invalid expire time in 'setex' command\r\n"
6824        );
6825        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
6826        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
6827        assert_eq!(
6828            f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
6829            "-ERR syntax error\r\n",
6830            "the option list is still checked before the key is looked up"
6831        );
6832        // A deadline in the past is accepted and the key goes with it.
6833        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6834        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
6835        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6836    }
6837
6838    #[test]
6839    fn mset_takes_its_pairs_from_the_read_buffer() {
6840        let mut f = Fixture::new();
6841        assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
6842        assert_eq!(
6843            f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
6844            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
6845        );
6846        assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
6847        assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
6848        assert_eq!(
6849            f.run(&[b"MSETEX", b"2", b"e", b"5"]),
6850            "-ERR wrong number of key-value pairs\r\n"
6851        );
6852        assert_eq!(
6853            f.run(&[b"MSETEX", b"0", b"e", b"5"]),
6854            "-ERR invalid numkeys value\r\n"
6855        );
6856        assert_eq!(
6857            f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
6858            "-ERR invalid numkeys value\r\n"
6859        );
6860    }
6861
6862    #[test]
6863    fn lcs_answers_the_length_the_string_and_the_runs() {
6864        let mut f = Fixture::new();
6865        f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
6866        assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
6867        assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
6868        assert_eq!(
6869            f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
6870            "*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"
6871        );
6872        // Without `IDX` the two options that only mean something with it are
6873        // accepted and ignored, which is what a real server does.
6874        assert_eq!(
6875            f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
6876            "$6\r\nmytext\r\n"
6877        );
6878    }
6879
6880    #[test]
6881    fn select_moves_the_connection_and_the_databases_stay_apart() {
6882        let mut f = Fixture::new();
6883        f.run(&[b"SET", b"k", b"zero"]);
6884        assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
6885        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6886        f.run(&[b"SET", b"k", b"four"]);
6887        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
6888        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6889        assert_eq!(
6890            f.run(&[b"SELECT", b"99"]),
6891            "-ERR DB index is out of range\r\n"
6892        );
6893        assert_eq!(
6894            f.run(&[b"SELECT", b"-1"]),
6895            "-ERR DB index is out of range\r\n"
6896        );
6897        assert_eq!(
6898            f.run(&[b"SELECT", b"abc"]),
6899            "-ERR value is not an integer or out of range\r\n"
6900        );
6901        // `RESET` brings it back to zero.
6902        f.run(&[b"SELECT", b"4"]);
6903        f.run(&[b"RESET"]);
6904        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6905    }
6906
6907    #[test]
6908    fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
6909        let mut f = Fixture::new();
6910        let reply = f.run(&[b"HELLO"]);
6911        assert!(reply.starts_with("*14\r\n"), "{reply}");
6912        assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
6913        assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
6914        assert!(
6915            reply.contains(":7\r\n"),
6916            "the connection id is in there: {reply}"
6917        );
6918        assert_eq!(
6919            f.run(&[b"HELLO", b"4"]),
6920            "-NOPROTO unsupported protocol version\r\n"
6921        );
6922        assert_eq!(
6923            f.run(&[b"HELLO", b"abc"]),
6924            "-ERR Protocol version is not an integer or out of range\r\n"
6925        );
6926        assert_eq!(
6927            f.run(&[b"HELLO", b"3", b"SETNAME"]),
6928            "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
6929        );
6930        assert!(
6931            f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
6932                .starts_with("%7\r\n")
6933        );
6934        assert_eq!(f.session.name(), b"bob");
6935        f.run(&[b"RESET"]);
6936        assert_eq!(f.session.name(), b"");
6937    }
6938
6939    #[test]
6940    fn command_describes_this_server_in_the_shape_a_driver_reads() {
6941        let mut f = Fixture::new();
6942        let count = format!(":{}\r\n", COMMANDS.len());
6943        assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
6944        let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
6945        assert_eq!(
6946            info,
6947            "*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\
6948             *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
6949        );
6950        // A null in the list, and the plain one: `$-1` and not `*-1`.
6951        assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
6952        assert_eq!(
6953            f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
6954            "*1\r\n$8\r\ngetrange\r\n"
6955        );
6956        assert_eq!(
6957            f.run(&[b"COMMAND", b"NOPE"]),
6958            "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
6959        );
6960    }
6961
6962    /// A cluster aware client asks this question and then routes on the
6963    /// answer, so `MSETEX`, whose keys are not where the table says, is the one
6964    /// that matters.
6965    #[test]
6966    fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
6967        let mut f = Fixture::new();
6968        assert_eq!(
6969            f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
6970            "*1\r\n$1\r\nk\r\n"
6971        );
6972        assert_eq!(
6973            f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
6974            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6975        );
6976        assert_eq!(
6977            f.run(&[
6978                b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
6979            ]),
6980            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6981        );
6982        assert_eq!(
6983            f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
6984            "-ERR The command has no key arguments\r\n"
6985        );
6986        assert_eq!(
6987            f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
6988            "-ERR Invalid number of arguments specified for command\r\n"
6989        );
6990    }
6991
6992    #[test]
6993    fn config_answers_what_it_can_and_refuses_what_it_cannot() {
6994        let mut f = Fixture::new();
6995        assert_eq!(
6996            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6997            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
6998        );
6999        // A pattern matches more than one, and a setting two patterns both ask
7000        // for is still sent once.
7001        let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
7002        assert!(both.starts_with("*6\r\n"), "{both}");
7003        assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
7004        assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
7005        assert_eq!(
7006            f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
7007            "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
7008        );
7009        assert_eq!(
7010            f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
7011            "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
7012        );
7013        assert_eq!(
7014            f.run(&[b"CONFIG", b"GET"]),
7015            "-ERR wrong number of arguments for 'config|get' command\r\n"
7016        );
7017        // Too few arguments and an odd number of them are different
7018        // complaints, which is the sort of thing only the real server tells
7019        // you.
7020        assert_eq!(
7021            f.run(&[b"CONFIG", b"SET", b"appendonly"]),
7022            "-ERR wrong number of arguments for 'config|set' command\r\n"
7023        );
7024        assert_eq!(
7025            f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
7026            "-ERR syntax error\r\n"
7027        );
7028        assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
7029        assert_eq!(
7030            f.run(&[b"CONFIG", b"REWRITE"]),
7031            "-ERR The server is running without a config file\r\n"
7032        );
7033    }
7034
7035    #[test]
7036    fn the_eviction_policy_reads_back_what_was_written_to_it() {
7037        let mut f = Fixture::new();
7038        assert_eq!(
7039            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
7040            "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
7041        );
7042        assert_eq!(
7043            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
7044            "+OK\r\n",
7045            "the name is matched without regard to case, like every other one"
7046        );
7047        assert_eq!(
7048            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
7049            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
7050        );
7051        // And INFO agrees with CONFIG, which it did not when it was a literal.
7052        assert!(
7053            f.run(&[b"INFO", b"memory"])
7054                .contains("maxmemory_policy:allkeys-lfu"),
7055            "INFO and CONFIG disagree about the policy"
7056        );
7057        // The refusal names every legal value in the order the real server's
7058        // enum table lists them, because a client comparing the message compares
7059        // the whole string.
7060        assert_eq!(
7061            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
7062            "-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"
7063        );
7064        // A bad pair leaves the good one in the same command alone, and the
7065        // policy is checked by the same pass that checks the numbers.
7066        assert_eq!(
7067            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
7068            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
7069        );
7070        f.run(&[
7071            b"CONFIG",
7072            b"SET",
7073            b"hash-max-listpack-entries",
7074            b"7",
7075            b"maxmemory-policy",
7076            b"nonsense",
7077        ]);
7078        assert_eq!(
7079            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7080            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
7081        );
7082    }
7083
7084    #[test]
7085    fn the_three_eviction_numbers_read_back_too() {
7086        let mut f = Fixture::new();
7087        for (name, default, set) in [
7088            ("maxmemory-samples", "5", "12"),
7089            ("lfu-log-factor", "10", "3"),
7090            ("lfu-decay-time", "1", "60"),
7091        ] {
7092            let get = || {
7093                format!(
7094                    "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
7095                    name.len(),
7096                    default.len()
7097                )
7098            };
7099            assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
7100            assert_eq!(
7101                f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
7102                "+OK\r\n"
7103            );
7104            assert_eq!(
7105                f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
7106                format!(
7107                    "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
7108                    name.len(),
7109                    set.len()
7110                )
7111            );
7112            // A number that is not a number is refused with the same sentence
7113            // every other number gets, which names the setting the client typed.
7114            assert_eq!(
7115                f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
7116                format!(
7117                    "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
7118                )
7119            );
7120        }
7121    }
7122
7123    #[test]
7124    fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
7125        let mut f = Fixture::new();
7126        assert_eq!(
7127            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
7128            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
7129            "no limit is the default"
7130        );
7131        // The pairing is Redis's and it is a trap: the bare letter is a power of
7132        // ten and the one with the b is a power of two.
7133        for (typed, bytes) in [
7134            (&b"1024"[..], "1024"),
7135            (b"1k", "1000"),
7136            (b"1kb", "1024"),
7137            (b"1M", "1000000"),
7138            (b"1Mb", "1048576"),
7139            (b"1gb", "1073741824"),
7140            (b"100mb", "104857600"),
7141        ] {
7142            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
7143            assert_eq!(
7144                f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
7145                format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
7146                "set {}",
7147                String::from_utf8_lossy(typed)
7148            );
7149        }
7150        assert!(
7151            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
7152            "the report agrees with the setting"
7153        );
7154
7155        // A unit nobody has heard of, and a negative number, which is not a very
7156        // large one however it is spelled.
7157        for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
7158            assert_eq!(
7159                f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
7160                "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
7161                "refused {}",
7162                String::from_utf8_lossy(bad)
7163            );
7164        }
7165        assert!(
7166            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
7167            "and the refusal left the old one alone"
7168        );
7169    }
7170
7171    #[test]
7172    fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
7173        let mut f = Fixture::new();
7174        f.run(&[b"SET", b"here", b"already"]);
7175        // A byte, which is under what an empty server holds, so nothing this
7176        // command could do would get it under. The default policy is
7177        // `noeviction`, so nothing is what it does.
7178        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
7179        assert_eq!(
7180            f.run(&[b"SET", b"k", b"v"]),
7181            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
7182        );
7183        assert_eq!(
7184            f.run(&[b"LPUSH", b"l", b"v"]),
7185            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
7186        );
7187        // Reading is allowed, and so is the one thing that would help.
7188        assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
7189        assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
7190        assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
7191
7192        // Taking the limit away lets the write through again.
7193        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
7194        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
7195    }
7196
7197    /// Not under Miri, for the reason in `filled`: what it is watching is a
7198    /// whole two megabyte segment going back, so the megabytes are the claim
7199    /// and there is no smaller version of it that says the same thing.
7200    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
7201    #[test]
7202    fn an_allkeys_policy_makes_room_instead_of_refusing() {
7203        let mut f = Fixture::new();
7204        let val = vec![b'v'; 256];
7205        for i in 0..24000u32 {
7206            let k = format!("key:{i:08}");
7207            f.run(&[b"SET", k.as_bytes(), &val]);
7208        }
7209        let full = f.server.memory_bytes();
7210        assert!(
7211            full > 3 * 1024 * 1024,
7212            "the arena is several segments: {full}"
7213        );
7214
7215        // Two megabytes under what it is holding, which is one segment's worth,
7216        // so getting there means giving a whole segment back and not just
7217        // dropping a few records.
7218        let limit = full - 2 * 1024 * 1024;
7219        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
7220        f.run(&[
7221            b"CONFIG",
7222            b"SET",
7223            b"maxmemory",
7224            limit.to_string().as_bytes(),
7225        ]);
7226
7227        // Writes keep working the whole way down. The budget means one command
7228        // does not do it all, so this runs until the server has settled and
7229        // checks that nothing was refused on the way.
7230        for i in 0..2000u32 {
7231            let k = format!("new:{i:08}");
7232            assert_eq!(
7233                f.run(&[b"SET", k.as_bytes(), &val]),
7234                "+OK\r\n",
7235                "write {i} was refused"
7236            );
7237            f.server.refresh_memory();
7238            if f.server.memory_bytes() <= limit {
7239                break;
7240            }
7241        }
7242        assert!(
7243            f.server.memory_bytes() <= limit,
7244            "it never got under: {} against {limit}",
7245            f.server.memory_bytes()
7246        );
7247        let info = f.run(&[b"INFO", b"stats"]);
7248        assert!(!info.contains("evicted_keys:0"), "{info}");
7249        assert!(
7250            f.run(&[b"DBSIZE"]) != ":0\r\n",
7251            "and it did not empty the database to get there"
7252        );
7253    }
7254
7255    /// Not under Miri. Every round is eleven commands over six collections
7256    /// holding two hundred byte values, which is a third of a second each
7257    /// interpreted, and the rounds cannot come down far: one in seven takes an
7258    /// entry back out, so under about a hundred and seventy of them the
7259    /// collections never reach the hundred and twenty eight entries where the
7260    /// small representations give up and become the big ones, and a
7261    /// representation changing under the running total is one of the five
7262    /// things this is here to watch. What is left is an hour, for an accounting
7263    /// claim rather than a safety one, and the commands it sends are sent a few
7264    /// at a time by the tests around it.
7265    #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
7266    #[test]
7267    fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
7268        // The limit is judged against a number kept as the collections move,
7269        // rather than found by asking all of them, and the two have to be the
7270        // same number or the limit is enforced against a fiction. This does the
7271        // things that move it, which is growing a collection, shrinking one,
7272        // changing its representation, deleting it and reusing its slot, across
7273        // all five types, and checks the two against each other as it goes.
7274        let mut f = Fixture::new();
7275        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
7276        let big = vec![b'v'; 200];
7277
7278        for i in 0..400u32 {
7279            let n = i.to_string();
7280            let n = n.as_bytes();
7281            f.run(&[b"SADD", b"s", n]);
7282            f.run(&[b"SADD", b"s2", &big]);
7283            f.run(&[b"HSET", b"h", n, &big]);
7284            f.run(&[b"RPUSH", b"l", &big]);
7285            f.run(&[b"ZADD", b"z", n, n]);
7286            f.run(&[b"ARSET", b"a", n, &big]);
7287            if i % 7 == 0 {
7288                f.run(&[b"SREM", b"s", n]);
7289                f.run(&[b"HDEL", b"h", n]);
7290                f.run(&[b"LPOP", b"l"]);
7291                f.run(&[b"ZREM", b"z", n]);
7292                f.run(&[b"ARDEL", b"a", n]);
7293            }
7294            if i % 53 == 0 {
7295                // Every type deleted and made again, so a slot goes on the free
7296                // list and comes back holding something else.
7297                f.run(&[b"DEL", b"s2"]);
7298            }
7299            assert_eq!(
7300                f.server.settled_memory(),
7301                f.server.memory_bytes(),
7302                "after round {i}"
7303            );
7304        }
7305
7306        // The run has to have built something, or the two numbers agreeing is
7307        // two zeroes agreeing.
7308        assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
7309        assert!(
7310            f.server.memory_bytes() > 512 * 1024,
7311            "{}",
7312            f.server.memory_bytes()
7313        );
7314
7315        // And it survives the collections going away entirely.
7316        f.run(&[b"FLUSHALL"]);
7317        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
7318    }
7319
7320    #[test]
7321    fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
7322        // A server with no limit does not keep the running total, so setting a
7323        // limit on a database that is already full has to start it from a walk.
7324        // If it did not, the first reading would be zero and the server would
7325        // think it had all the room in the world.
7326        let mut f = Fixture::new();
7327        for i in 0..200u32 {
7328            let n = i.to_string();
7329            f.run(&[b"SADD", b"s", n.as_bytes()]);
7330            f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
7331        }
7332        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
7333        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
7334
7335        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
7336        for i in 200..400u32 {
7337            let n = i.to_string();
7338            f.run(&[b"SADD", b"s", n.as_bytes()]);
7339        }
7340        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
7341        assert_eq!(
7342            f.server.settled_memory(),
7343            f.server.memory_bytes(),
7344            "the writes it was not watching are in the number it started from"
7345        );
7346    }
7347
7348    #[test]
7349    fn evicted_keys_and_expired_keys_are_different_numbers() {
7350        let mut f = Fixture::new();
7351        // Nothing has been evicted and nothing can be under the default policy,
7352        // so this stays at zero while the other one moves.
7353        f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
7354        f.server.advance_clock_ms(20);
7355        f.run(&[b"GET", b"gone"]);
7356        let info = f.run(&[b"INFO", b"stats"]);
7357        assert!(info.contains("expired_keys:1"), "{info}");
7358        assert!(info.contains("evicted_keys:0"), "{info}");
7359    }
7360
7361    #[test]
7362    fn the_two_counters_count_the_reads_and_nothing_else() {
7363        let mut f = Fixture::new();
7364        f.run(&[b"SET", b"k", b"v"]);
7365        f.run(&[b"GET", b"k"]);
7366        f.run(&[b"GET", b"nope"]);
7367        f.run(&[b"EXISTS", b"k", b"nope"]);
7368        // The write at the top is not in either number, and the three reads
7369        // under it are, once for each key each of them names.
7370        let info = f.run(&[b"INFO", b"stats"]);
7371        assert!(info.contains("keyspace_hits:2"), "{info}");
7372        assert!(info.contains("keyspace_misses:2"), "{info}");
7373
7374        f.run(&[b"CONFIG", b"RESETSTAT"]);
7375        let info = f.run(&[b"INFO", b"stats"]);
7376        assert!(info.contains("keyspace_hits:0"), "{info}");
7377        assert!(info.contains("keyspace_misses:0"), "{info}");
7378    }
7379
7380    /// The shapes that look one key up more than once, which a real server
7381    /// counts once because it only looks once. See `misses::reading`.
7382    #[test]
7383    fn a_read_that_visits_its_key_twice_is_counted_once() {
7384        let mut f = Fixture::new();
7385        f.run(&[b"ZADD", b"z", b"1", b"m"]);
7386        f.run(&[b"ZRANGE", b"z", b"0", b"-1"]);
7387        f.run(&[b"ZMSCORE", b"z", b"m", b"gone", b"also gone"]);
7388        f.run(&[b"OBJECT", b"ENCODING", b"z"]);
7389        f.run(&[b"DUMP", b"z"]);
7390        let info = f.run(&[b"INFO", b"stats"]);
7391        assert!(info.contains("keyspace_hits:4"), "{info}");
7392        // A member that is not in the sorted set is not a miss. Only a key that
7393        // is not there is one.
7394        assert!(info.contains("keyspace_misses:0"), "{info}");
7395    }
7396
7397    /// A lookup on the way to a write is not a read, which is the other half of
7398    /// what `lookups::quiet` is for.
7399    #[test]
7400    fn the_key_a_read_writes_afterwards_is_not_counted() {
7401        let mut f = Fixture::new();
7402        f.run(&[b"SET", b"s", b"v"]);
7403        f.run(&[b"COPY", b"s", b"dst"]);
7404        f.run(&[b"GETEX", b"s", b"EX", b"100"]);
7405        f.run(&[b"BITOP", b"AND", b"into", b"s", b"nope"]);
7406        let info = f.run(&[b"INFO", b"stats"]);
7407        // The source of the copy, the key `GETEX` answers with, and one of the
7408        // two sources of the operation. The three destinations are written and
7409        // never read, so none of them is in here.
7410        assert!(info.contains("keyspace_hits:3"), "{info}");
7411        assert!(info.contains("keyspace_misses:1"), "{info}");
7412    }
7413
7414    #[test]
7415    fn the_object_subcommands_follow_the_policy() {
7416        let mut f = Fixture::new();
7417        f.run(&[b"SET", b"s", b"v"]);
7418        // Under the default the clock is kept and the counter is not, and under
7419        // an LFU policy it is the other way round. Each subcommand refuses on
7420        // the side where its reading of the three bytes means nothing.
7421        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
7422        assert!(
7423            f.run(&[b"OBJECT", b"FREQ", b"s"])
7424                .starts_with("-ERR An LFU maxmemory policy is not selected"),
7425        );
7426
7427        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
7428        assert!(
7429            f.run(&[b"OBJECT", b"IDLETIME", b"s"])
7430                .starts_with("-ERR An LFU maxmemory policy is selected"),
7431        );
7432        // The key was written under a clock policy, so what comes back is that
7433        // clock read as a counter. It is a number and not an error, which is the
7434        // point: switching at runtime does not invalidate anything, it only makes
7435        // the old field mean something else until the key is used again.
7436        assert!(
7437            f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
7438            "FREQ should answer under an LFU policy"
7439        );
7440    }
7441
7442    #[test]
7443    fn object_says_which_rung_of_the_ladder_a_key_is_on() {
7444        let mut f = Fixture::new();
7445        f.run(&[b"SET", b"s", b"hello"]);
7446        f.run(&[b"SET", b"n", b"123"]);
7447        f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
7448        f.run(&[b"SADD", b"ss", b"a", b"b"]);
7449        f.run(&[b"HSET", b"h", b"f", b"v"]);
7450        for (key, want) in [
7451            (b"s".as_slice(), "embstr"),
7452            (b"n", "int"),
7453            (b"si", "intset"),
7454            (b"ss", "listpack"),
7455            (b"h", "listpack"),
7456        ] {
7457            let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
7458            assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
7459        }
7460
7461        // A field deadline widens the blob rather than promoting it, and this
7462        // is the only place a client can see that happen.
7463        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
7464        assert_eq!(
7465            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7466            "$10\r\nlistpackex\r\n"
7467        );
7468
7469        assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
7470        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
7471        assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
7472    }
7473
7474    #[test]
7475    fn object_answers_nil_for_a_key_that_is_not_there() {
7476        let mut f = Fixture::new();
7477        for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
7478            assert_eq!(
7479                f.run(&[b"OBJECT", sub, b"nokey"]),
7480                "$-1\r\n",
7481                "a nil and not an error, which is what 8.10.1 does"
7482            );
7483        }
7484        // And the key is looked up before FREQ has its complaint, so the
7485        // complaint only reaches a key that exists.
7486        f.run(&[b"SET", b"s", b"v"]);
7487        assert!(
7488            f.run(&[b"OBJECT", b"FREQ", b"s"])
7489                .starts_with("-ERR An LFU maxmemory policy is not"),
7490        );
7491        assert_eq!(
7492            f.run(&[b"OBJECT", b"NOPE", b"s"]),
7493            "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
7494        );
7495        assert_eq!(
7496            f.run(&[b"OBJECT", b"ENCODING"]),
7497            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7498        );
7499        assert_eq!(
7500            f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
7501            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7502        );
7503        assert_eq!(
7504            f.run(&[b"OBJECT"]),
7505            "-ERR wrong number of arguments for 'object' command\r\n"
7506        );
7507    }
7508
7509    #[test]
7510    fn config_moves_the_ladder_and_object_encoding_agrees() {
7511        let mut f = Fixture::new();
7512        assert_eq!(
7513            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7514            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7515            "512 and not the 128 everyone remembers, which is what 8.10.1 says"
7516        );
7517        // The old spelling is the same number under a different name, and a
7518        // glob that catches both sends both.
7519        assert_eq!(
7520            f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
7521            "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
7522        );
7523        assert!(
7524            f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
7525                .starts_with("*8\r\n")
7526        );
7527        assert!(
7528            f.run(&[b"CONFIG", b"GET", b"set-max-*"])
7529                .starts_with("*6\r\n")
7530        );
7531
7532        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
7533        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
7534
7535        assert_eq!(
7536            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
7537            "+OK\r\n",
7538            "written under the old name and read back under the new one"
7539        );
7540        assert_eq!(
7541            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7542            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
7543        );
7544        assert_eq!(
7545            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7546            "$8\r\nlistpack\r\n",
7547            "the hash that already exists is left exactly where it was"
7548        );
7549        f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
7550        assert_eq!(
7551            f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
7552            "$9\r\nhashtable\r\n",
7553            "and the next one built goes straight to a table"
7554        );
7555
7556        // The set has three of these and all three move.
7557        f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
7558        f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
7559        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
7560        f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
7561        f.run(&[b"SADD", b"s2", b"abcdefgh"]);
7562        assert_eq!(
7563            f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
7564            "$9\r\nhashtable\r\n"
7565        );
7566    }
7567
7568    #[test]
7569    fn config_set_takes_all_of_the_ladder_or_none_of_it() {
7570        let mut f = Fixture::new();
7571        assert_eq!(
7572            f.run(&[
7573                b"CONFIG",
7574                b"SET",
7575                b"hash-max-listpack-entries",
7576                b"7",
7577                b"set-max-listpack-entries",
7578                b"abc"
7579            ]),
7580            "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
7581        );
7582        assert_eq!(
7583            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7584            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7585            "the pair in front of the bad one did not go in"
7586        );
7587        // The name in the complaint is the one that was typed, so the old
7588        // spelling comes back as the old spelling.
7589        assert_eq!(
7590            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
7591            "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
7592        );
7593        assert_eq!(
7594            f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
7595            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
7596        );
7597        // A number past what an i64 holds is the parse complaint and not the
7598        // range one, which is upstream reading it before it checks it.
7599        assert_eq!(
7600            f.run(&[
7601                b"CONFIG",
7602                b"SET",
7603                b"set-max-intset-entries",
7604                b"99999999999999999999"
7605            ]),
7606            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
7607        );
7608        assert_eq!(
7609            f.run(&[
7610                b"CONFIG",
7611                b"SET",
7612                b"set-max-intset-entries",
7613                b"9223372036854775807"
7614            ]),
7615            "+OK\r\n"
7616        );
7617    }
7618
7619    #[test]
7620    fn a_setting_moved_on_one_database_moved_on_all_of_them() {
7621        let mut f = Fixture::new();
7622        f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
7623        f.run(&[b"SELECT", b"3"]);
7624        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7625        assert_eq!(
7626            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7627            "$9\r\nhashtable\r\n",
7628            "these are one server wide number in Redis, whatever a Keyspace carries"
7629        );
7630    }
7631
7632    #[test]
7633    fn info_reports_the_numbers_it_can_stand_behind() {
7634        let mut f = Fixture::new();
7635        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
7636        let all = f.run(&[b"INFO"]);
7637        assert!(all.contains("redis_version:8.8.0"), "{all}");
7638        assert!(
7639            all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
7640            "{all}"
7641        );
7642        assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
7643        assert!(all.contains("role:master"), "{all}");
7644        // One section is one section.
7645        let clients = f.run(&[b"INFO", b"clients"]);
7646        assert!(clients.contains("connected_clients:0"), "{clients}");
7647        assert!(!clients.contains("redis_version"), "{clients}");
7648        assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
7649    }
7650
7651    /// The sections a bare `INFO` gives back, and the ones you have to ask for.
7652    ///
7653    /// This is Redis's `unit/info-command` written against the fixture. Every
7654    /// assertion in it is one of theirs, in their order, and the two fields it
7655    /// turns on are the two that suite was failing on: `master_repl_offset`,
7656    /// which is in the default set, and `rejected_calls`, which is not.
7657    #[test]
7658    fn commandstats_is_asked_for_and_replication_is_not() {
7659        let mut f = Fixture::new();
7660        for arg in ["", "all", "default", "everything"] {
7661            let info = if arg.is_empty() {
7662                f.run(&[b"INFO"])
7663            } else {
7664                f.run(&[b"INFO", arg.as_bytes()])
7665            };
7666            assert!(info.contains("redis_version"), "{arg}: {info}");
7667            assert!(info.contains("used_cpu_user"), "{arg}: {info}");
7668            assert!(info.contains("used_memory"), "{arg}: {info}");
7669            assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
7670            let asked = arg == "all" || arg == "everything";
7671            assert_eq!(
7672                info.contains("rejected_calls"),
7673                asked,
7674                "{arg} should{} carry the command counters: {info}",
7675                if asked { "" } else { " not" }
7676            );
7677        }
7678
7679        let cpu = f.run(&[b"INFO", b"cpu"]);
7680        assert!(cpu.contains("used_cpu_user"), "{cpu}");
7681        assert!(!cpu.contains("used_memory"), "{cpu}");
7682
7683        // Their case, to make the point that a section name is not case
7684        // sensitive any more than a command name is.
7685        let stats = f.run(&[b"INFO", b"commandSTATS"]);
7686        assert!(!stats.contains("used_memory"), "{stats}");
7687        assert!(stats.contains("rejected_calls"), "{stats}");
7688
7689        // Two sections named, and neither of them pulls in a third.
7690        let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
7691        assert!(pair.contains("used_cpu_user"), "{pair}");
7692        assert!(!pair.contains("master_repl_offset"), "{pair}");
7693
7694        let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
7695        assert!(with_all.contains("used_memory"), "{with_all}");
7696        assert!(with_all.contains("master_repl_offset"), "{with_all}");
7697        assert!(with_all.contains("rejected_calls"), "{with_all}");
7698        // A section named twice is still written once.
7699        assert_eq!(
7700            with_all.matches("used_cpu_user_children").count(),
7701            1,
7702            "{with_all}"
7703        );
7704
7705        let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
7706        assert!(with_default.contains("used_memory"), "{with_default}");
7707        assert!(
7708            with_default.contains("master_repl_offset"),
7709            "{with_default}"
7710        );
7711        assert!(!with_default.contains("rejected_calls"), "{with_default}");
7712        assert_eq!(
7713            with_default.matches("used_cpu_user_children").count(),
7714            1,
7715            "{with_default}"
7716        );
7717    }
7718
7719    /// The memory section says what this process may use, not what the machine
7720    /// has.
7721    ///
7722    /// The distinction is the whole point of it. A server inside a container
7723    /// that reports the host's memory is a server whose operator sizes it for
7724    /// memory it will be killed for touching, so all three numbers are there:
7725    /// what the machine has, what the cgroup allows, and the quarter of the
7726    /// tighter one that pools are sized from.
7727    #[test]
7728    fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
7729        let mut f = Fixture::new();
7730        let info = f.run(&[b"INFO", b"memory"]);
7731        for field in [
7732            "total_system_memory:",
7733            "mem_cgroup_limit:",
7734            "mem_limit:",
7735            "mem_budget:",
7736        ] {
7737            assert!(info.contains(field), "no {field} in {info}");
7738        }
7739
7740        let field = |name: &str| -> u64 {
7741            info.lines()
7742                .find_map(|l| l.strip_prefix(name))
7743                .unwrap_or_else(|| panic!("no {name} in {info}"))
7744                .trim()
7745                .parse()
7746                .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
7747        };
7748        let limit = field("mem_limit:");
7749        assert_eq!(field("mem_budget:"), limit / 4, "{info}");
7750        // Zero means there is no limit to report, which is a real answer on a
7751        // machine with no cgroups and no way to ask how big it is.
7752        if limit != 0 {
7753            let host = field("total_system_memory:");
7754            let cgroup = field("mem_cgroup_limit:");
7755            assert!(
7756                limit == host || limit == cgroup,
7757                "the limit came from neither number: {info}"
7758            );
7759        }
7760    }
7761
7762    /// The three counters, each on the path that raises it.
7763    ///
7764    /// `calls` on a command that worked, `failed_calls` on one that ran and
7765    /// answered with an error, and `rejected_calls` on one that never ran at
7766    /// all. The last two are the pair that is easy to collapse into one number
7767    /// and that Redis keeps apart, because a client sending the wrong number of
7768    /// arguments and a client asking for a list element that is not there are
7769    /// not the same problem.
7770    #[test]
7771    fn a_command_counts_what_it_did_separately_from_what_it_refused() {
7772        let mut f = Fixture::new();
7773        f.run(&[b"SET", b"k", b"v"]);
7774        f.run(&[b"SET", b"k", b"w"]);
7775        // Ran, and answered with an error, because `k` is not a list.
7776        f.run(&[b"LPUSH", b"k", b"x"]);
7777        // Never ran: `LPUSH` takes at least three arguments.
7778        f.run(&[b"LPUSH", b"k"]);
7779
7780        let stats = f.run(&[b"INFO", b"commandstats"]);
7781        assert!(
7782            stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
7783            "{stats}"
7784        );
7785        assert!(
7786            stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
7787            "{stats}"
7788        );
7789        assert!(
7790            !stats.contains("cmdstat_zadd"),
7791            "a command nobody has sent has no row: {stats}"
7792        );
7793    }
7794
7795    /// A cache that writes with a deadline and never reads back used to hold
7796    /// every key it had ever written, because lazy expiry needs somebody to walk
7797    /// past a key before it can reclaim it and nobody ever did.
7798    #[test]
7799    fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
7800        // Four thousand keys is four thousand trips through dispatch, and what
7801        // Miri charges for is trips rather than keys, so this was over five
7802        // minutes there. An eighth of each keeps everything the test is about,
7803        // which is three keys with a deadline for every one without and a
7804        // sweep that has to reclaim all of the first kind and none of the
7805        // second.
7806        let (dead, live) = if cfg!(miri) {
7807            (375, 125)
7808        } else {
7809            (3_000, 1_000)
7810        };
7811        let mut f = Fixture::new();
7812        for i in 0..dead {
7813            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7814        }
7815        for i in 0..live {
7816            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7817        }
7818        let all = format!(":{}\r\n", dead + live);
7819        assert_eq!(f.run(&[b"DBSIZE"]), all);
7820        f.advance(100);
7821        assert_eq!(
7822            f.run(&[b"DBSIZE"]),
7823            all,
7824            "DBSIZE counts records and nothing has read past the dead ones yet"
7825        );
7826
7827        // What the shard loop does, one slice at a time.
7828        let rest = format!(":{live}\r\n");
7829        let mut spent = 0;
7830        for _ in 0..2_000 {
7831            spent += f.server.expire_step(4096);
7832            if f.run(&[b"DBSIZE"]) == rest {
7833                break;
7834            }
7835        }
7836        assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
7837        assert!(
7838            f.run(&[b"INFO", b"stats"])
7839                .contains(&format!("expired_keys:{dead}"))
7840        );
7841        for i in 0..live {
7842            assert_eq!(
7843                f.run(&[b"GET", format!("k{i}").as_bytes()]),
7844                "$1\r\nv\r\n",
7845                "it took a key that had no deadline"
7846            );
7847        }
7848    }
7849
7850    #[test]
7851    fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
7852        // The keys are only here so that the database the sweep walks is not an
7853        // empty one. Two hundred of them fills as many slots as a sweep looks
7854        // at and is a tenth of the interpreted work.
7855        let n = if cfg!(miri) { 200 } else { 2_000 };
7856        let mut f = Fixture::new();
7857        for i in 0..n {
7858            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7859        }
7860        assert_eq!(f.server.expire_step(4096), 0);
7861        // And one database having them does not make the other fifteen pay.
7862        f.run(&[b"SELECT", b"3"]);
7863        f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
7864        f.advance(100);
7865        for _ in 0..64 {
7866            f.server.expire_step(4096);
7867        }
7868        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7869        f.run(&[b"SELECT", b"0"]);
7870        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
7871        assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
7872    }
7873
7874    /// The gate, which is what stops a maintenance slice that runs every hundred
7875    /// nanoseconds from drawing a sample every hundred nanoseconds.
7876    #[test]
7877    fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
7878        let mut f = Fixture::new();
7879        for i in 0..500u32 {
7880            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7881        }
7882        f.advance(100);
7883        let at = f.server.striped(0).now_ms();
7884        f.server.set_clock_ms(at);
7885        // A small budget, so that one slice cannot finish the job and a second
7886        // one having nothing to do would mean the gate and not an empty
7887        // database.
7888        assert!(f.server.expire_slice(8) > 0, "the first one works");
7889        for _ in 0..1_000 {
7890            assert_eq!(
7891                f.server.expire_slice(8),
7892                0,
7893                "the millisecond has not moved and neither should this"
7894            );
7895        }
7896        assert!(
7897            f.server.striped(0).expires() > 400,
7898            "there is plenty left to take"
7899        );
7900        f.server.set_clock_ms(at + 1);
7901        assert!(f.server.expire_slice(8) > 0, "and then it goes again");
7902    }
7903
7904    /// `expires=` used to be a hardcoded zero, which meant a dashboard watching
7905    /// how much of a cache is volatile was reading a constant.
7906    #[test]
7907    fn info_keyspace_counts_the_keys_that_have_a_deadline() {
7908        let mut f = Fixture::new();
7909        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
7910        assert!(
7911            f.run(&[b"INFO", b"keyspace"])
7912                .contains("db0:keys=3,expires=0"),
7913            "none of them has one yet"
7914        );
7915        f.run(&[b"EXPIRE", b"a", b"1000"]);
7916        f.run(&[b"EXPIRE", b"b", b"1000"]);
7917        let two = f.run(&[b"INFO", b"keyspace"]);
7918        assert!(two.contains("db0:keys=3,expires=2"), "{two}");
7919        f.run(&[b"PERSIST", b"a"]);
7920        f.run(&[b"DEL", b"b"]);
7921        let none = f.run(&[b"INFO", b"keyspace"]);
7922        assert!(none.contains("db0:keys=2,expires=0"), "{none}");
7923
7924        // Each database answers for itself, the way Redis reports it.
7925        f.run(&[b"SELECT", b"1"]);
7926        f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
7927        let both = f.run(&[b"INFO", b"keyspace"]);
7928        assert!(both.contains("db0:keys=2,expires=0"), "{both}");
7929        assert!(both.contains("db1:keys=1,expires=1"), "{both}");
7930    }
7931
7932    /// Not under Miri, which reads a zero on purpose because it has no
7933    /// `getrusage` to call, so the second half of this would burn a billion
7934    /// interpreted multiplications waiting for a number that is never going to
7935    /// move. The first half, that the section is there and has the fields Redis
7936    /// clients look for, is checked by the `INFO` tests above as well, and
7937    /// those do run there.
7938    #[cfg(unix)]
7939    #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
7940    #[test]
7941    fn info_cpu_reports_processor_time_that_was_really_measured() {
7942        let mut f = Fixture::new();
7943        let cpu = f.run(&[b"INFO", b"cpu"]);
7944        assert!(cpu.contains("# CPU"), "{cpu}");
7945        // Redis's unit/info-command asks for this one by name in three tests.
7946        assert!(cpu.contains("used_cpu_user:"), "{cpu}");
7947        assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
7948        assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
7949        assert!(!cpu.contains("redis_version"), "{cpu}");
7950
7951        // It is a measurement and not a constant, so it goes up when work
7952        // happens. A tight loop rather than a sleep, because sleeping is the
7953        // one thing that does not move this number.
7954        let before = used_cpu_user(&cpu);
7955        let mut n = 0u64;
7956        let mut rounds = 0;
7957        while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
7958            for i in 0..1_000_000u64 {
7959                n = n.wrapping_add(i.wrapping_mul(i));
7960            }
7961            rounds += 1;
7962            // A bound rather than a spin, so a platform where this number does
7963            // not move fails here instead of hanging. Even a clock with whole
7964            // millisecond granularity gets there in the first round or two.
7965            assert!(rounds < 1_000, "cpu time never moved, n is {n}");
7966        }
7967    }
7968
7969    /// Pull `used_cpu_user` back out of an `INFO cpu` reply.
7970    #[cfg(unix)]
7971    fn used_cpu_user(info: &str) -> f64 {
7972        info.lines()
7973            .find_map(|l| l.strip_prefix("used_cpu_user:"))
7974            .expect("no used_cpu_user in the reply")
7975            .trim()
7976            .parse()
7977            .expect("used_cpu_user is not a number")
7978    }
7979
7980    /// The safety net under the rule that a body checks its arguments before
7981    /// it writes anything. `MGET` writes its array header first and then reads
7982    /// each key, so if a later argument could fail the header would already be
7983    /// out. Nothing in the string group does that today and this is what would
7984    /// catch the first one that did.
7985    #[test]
7986    fn a_command_that_fails_leaves_nothing_half_written() {
7987        let mut f = Fixture::new();
7988        let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
7989        assert_eq!(reply, "-ERR offset is out of range\r\n");
7990        assert!(!reply.contains(':'), "no integer went out in front of it");
7991    }
7992
7993    #[test]
7994    fn quit_answers_first_and_closes_after() {
7995        let mut f = Fixture::new();
7996        let (flow, reply) = f.flow(&[b"QUIT"]);
7997        assert_eq!(reply, "+OK\r\n");
7998        assert_eq!(flow, Flow::Close);
7999    }
8000
8001    /// A server that has not been asked to stop is not stopping, and one that
8002    /// has says so without writing anything back.
8003    ///
8004    /// The empty reply is the point. Redis answers nothing at all here and the
8005    /// client sees the socket close, and an `OK` would be a promise from a
8006    /// process that is about to not exist.
8007    #[test]
8008    fn shutdown_writes_nothing_and_sets_the_flag() {
8009        let mut f = Fixture::new();
8010        assert!(!f.server.stopping(), "nobody has asked yet");
8011
8012        let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
8013        assert_eq!(reply, "");
8014        assert_eq!(flow, Flow::Close);
8015        assert!(f.server.stopping());
8016    }
8017
8018    /// Every flag combination 8.10.1 takes, and every one it refuses.
8019    ///
8020    /// The refusals are the half worth pinning down. `SAVE` and `NOSAVE`
8021    /// contradict each other, `ABORT` says to do nothing so it cannot be
8022    /// combined with a word about how to do it, and repeating any one of them
8023    /// is fine. All of it was read off a running 8.10.1 rather than worked out
8024    /// from the documentation, which does not say.
8025    #[test]
8026    fn shutdown_takes_the_flags_redis_takes() {
8027        for flags in [
8028            &[b"NOSAVE".as_slice()][..],
8029            &[b"SAVE"],
8030            &[b"NOW"],
8031            &[b"FORCE"],
8032            &[b"nosave"],
8033            &[b"NOW", b"NOW"],
8034            &[b"SAVE", b"SAVE"],
8035            &[b"NOSAVE", b"NOW", b"FORCE"],
8036        ] {
8037            let mut f = Fixture::new();
8038            let mut parts = vec![b"SHUTDOWN".as_slice()];
8039            parts.extend_from_slice(flags);
8040            let (flow, reply) = f.flow(&parts);
8041            assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
8042            assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
8043            assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
8044        }
8045
8046        for flags in [
8047            &[b"BOGUS".as_slice()][..],
8048            &[b"SAVE", b"NOSAVE"],
8049            &[b"NOSAVE", b"SAVE"],
8050            &[b"ABORT", b"NOW"],
8051            &[b"NOSAVE", b"ABORT"],
8052            &[b"NOW", b"FORCE", b"ABORT"],
8053        ] {
8054            let mut f = Fixture::new();
8055            let mut parts = vec![b"SHUTDOWN".as_slice()];
8056            parts.extend_from_slice(flags);
8057            assert_eq!(
8058                f.run(&parts),
8059                "-ERR syntax error\r\n",
8060                "SHUTDOWN {flags:?} was accepted"
8061            );
8062            assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
8063        }
8064    }
8065
8066    /// `ABORT` has nothing to call off, ever.
8067    ///
8068    /// A shutdown here is decided and done inside one turn of the loop, so
8069    /// there is no window in which one is in progress. That makes Redis's
8070    /// message for a cancel with nothing to cancel the right answer every time
8071    /// rather than only when nothing happens to be pending. Two `ABORT`s is
8072    /// still one `ABORT`, which is what 8.10.1 does.
8073    #[test]
8074    fn shutdown_abort_never_has_anything_to_abort() {
8075        let mut f = Fixture::new();
8076        for parts in [
8077            &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
8078            &[b"SHUTDOWN", b"ABORT", b"ABORT"],
8079        ] {
8080            assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
8081            assert!(!f.server.stopping(), "an abort stopped the server");
8082        }
8083    }
8084
8085    /// A fixture whose server writes into a directory of its own.
8086    ///
8087    /// Every test here really writes files, because the whole point of the
8088    /// command is the files and a backup that is only a state machine would
8089    /// pass a test suite and fail the first person who tried to restore one.
8090    /// The directory carries the test's name so that the suite can run its
8091    /// tests in parallel the way it always does.
8092    struct Backups {
8093        f: Fixture,
8094        dir: PathBuf,
8095    }
8096
8097    impl Backups {
8098        fn new(name: &str) -> Backups {
8099            let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
8100            let _ = std::fs::remove_dir_all(&dir);
8101            std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
8102            let mut f = Fixture::new();
8103            f.server.set_dir(dir.clone());
8104            Backups { f, dir }
8105        }
8106
8107        fn run(&mut self, parts: &[&[u8]]) -> String {
8108            self.f.run(parts)
8109        }
8110
8111        /// The names in `backupdir`, sorted, so a test can say what is on disk.
8112        fn files(&self) -> Vec<String> {
8113            let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
8114                Ok(entries) => entries
8115                    .filter_map(|e| e.ok())
8116                    .map(|e| e.file_name().to_string_lossy().into_owned())
8117                    .collect(),
8118                Err(_) => Vec::new(),
8119            };
8120            names.sort();
8121            names
8122        }
8123
8124        fn read(&self, name: &str) -> Vec<u8> {
8125            std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
8126        }
8127    }
8128
8129    impl Drop for Backups {
8130        fn drop(&mut self) {
8131            let _ = std::fs::remove_dir_all(&self.dir);
8132        }
8133    }
8134
8135    /// The four states and the moves between them, in the order a client walks
8136    /// them, with the files checked at every step.
8137    #[test]
8138    fn backup_walks_the_states_the_reference_walks() {
8139        let mut b = Backups::new("states");
8140        let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
8141
8142        assert!(status(&mut b).contains("idle"));
8143        assert!(b.files().is_empty(), "an idle server has written a backup");
8144
8145        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
8146        assert!(status(&mut b).contains("incrementing"));
8147        assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
8148
8149        assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
8150        assert!(status(&mut b).contains("sealed"));
8151        assert_eq!(
8152            b.files(),
8153            [
8154                "appendonly.aof.1.base.rdb",
8155                "appendonly.aof.1.incr.aof",
8156                "appendonly.aof.manifest",
8157            ]
8158        );
8159
8160        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
8161        assert!(status(&mut b).contains("idle"));
8162        assert!(b.files().is_empty(), "cleanup left something behind");
8163    }
8164
8165    /// Every move that is refused, in the reference's words.
8166    #[test]
8167    fn backup_refuses_the_moves_the_reference_refuses() {
8168        let mut b = Backups::new("refusals");
8169
8170        assert_eq!(
8171            b.run(&[b"BACKUP", b"SEAL"]),
8172            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
8173        );
8174        assert_eq!(
8175            b.run(&[b"BACKUP", b"ABORT"]),
8176            "-ERR No backup in progress\r\n"
8177        );
8178        // Cleanup from idle is not an error, it is a way of saying there was
8179        // nothing to clean up.
8180        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
8181
8182        b.run(&[b"BACKUP", b"START"]);
8183        assert_eq!(
8184            b.run(&[b"BACKUP", b"START"]),
8185            "-ERR A backup is already in progress, ABORT it first\r\n"
8186        );
8187        assert_eq!(
8188            b.run(&[b"BACKUP", b"CLEANUP"]),
8189            "-ERR Backup is in progress\r\n"
8190        );
8191
8192        b.run(&[b"BACKUP", b"SEAL"]);
8193        assert_eq!(
8194            b.run(&[b"BACKUP", b"START"]),
8195            "-ERR A sealed backup exists, CLEANUP it first\r\n"
8196        );
8197        assert_eq!(
8198            b.run(&[b"BACKUP", b"SEAL"]),
8199            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
8200        );
8201        assert_eq!(
8202            b.run(&[b"BACKUP", b"ABORT"]),
8203            "-ERR No backup in progress\r\n"
8204        );
8205    }
8206
8207    /// An abort takes the base file away and leaves a state saying who did it.
8208    ///
8209    /// The next backup takes the next sequence number rather than reusing the
8210    /// one whose files were just thrown away, so a directory somebody copied a
8211    /// half finished backup out of cannot end up with two different files under
8212    /// one name.
8213    #[test]
8214    fn backup_abort_removes_the_file_and_says_who_did_it() {
8215        let mut b = Backups::new("abort");
8216        b.run(&[b"BACKUP", b"START"]);
8217        assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
8218
8219        let status = b.run(&[b"BACKUP", b"STATUS"]);
8220        assert!(status.contains("failed"), "{status}");
8221        assert!(status.contains("aborted by user"), "{status}");
8222        assert!(b.files().is_empty(), "abort left the base file behind");
8223        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
8224
8225        // A start from failed works, and is the second backup.
8226        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
8227        assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
8228        let status = b.run(&[b"BACKUP", b"STATUS"]);
8229        assert!(status.contains("incrementing"), "{status}");
8230        assert!(!status.contains("aborted"), "the old error was kept");
8231    }
8232
8233    /// `LIST` names nothing, then one file, then three, and they are absolute.
8234    #[test]
8235    fn backup_list_names_the_files_that_are_pinned_so_far() {
8236        let mut b = Backups::new("list");
8237        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
8238
8239        b.run(&[b"BACKUP", b"START"]);
8240        let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
8241        let base = base.to_string_lossy().into_owned();
8242        assert_eq!(
8243            b.run(&[b"BACKUP", b"LIST"]),
8244            format!("*1\r\n${}\r\n{base}\r\n", base.len())
8245        );
8246
8247        b.run(&[b"BACKUP", b"SEAL"]);
8248        let listed = b.run(&[b"BACKUP", b"LIST"]);
8249        assert!(listed.starts_with("*3\r\n"), "{listed}");
8250        // The order is the manifest's order, base then incremental then the
8251        // manifest itself, which is the order a restore needs them in.
8252        let names: Vec<&str> = listed
8253            .lines()
8254            .filter(|l| l.starts_with('/') || l.contains(":\\"))
8255            .collect();
8256        assert_eq!(names.len(), 3, "{listed}");
8257        assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
8258        assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
8259        assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
8260    }
8261
8262    /// The base file is the dataset as it was at `START` and not at `SEAL`.
8263    ///
8264    /// That is D-46 and it is the one thing about this a client can notice, so
8265    /// it is pinned here rather than left to be discovered by whoever restores
8266    /// one. The incremental file is empty for the same reason: there is no
8267    /// append only log underneath this server to copy the writes in between out
8268    /// of.
8269    #[test]
8270    fn a_backup_holds_the_dataset_as_it_was_at_start() {
8271        let mut b = Backups::new("contents");
8272        b.run(&[b"SET", b"bk", b"v1"]);
8273        b.run(&[b"BACKUP", b"START"]);
8274        b.run(&[b"SET", b"bk", b"v2"]);
8275        b.run(&[b"BACKUP", b"SEAL"]);
8276
8277        let base = b.read("appendonly.aof.1.base.rdb");
8278        assert!(base.starts_with(b"REDIS"), "not an RDB file");
8279        assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
8280        assert!(
8281            !base.windows(2).any(|w| w == b"v2"),
8282            "the base file moved on after START"
8283        );
8284        // The aux field a loader acts on, and the one that says this file is
8285        // the base of an append only file rather than a standalone dump. Its
8286        // value is the one byte string 1, which the encoder writes as an
8287        // integer the way a real server writes it.
8288        let at = base
8289            .windows(8)
8290            .position(|w| w == b"aof-base")
8291            .expect("no aof-base aux field");
8292        assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
8293
8294        assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
8295        assert_eq!(
8296            String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
8297            "file appendonly.aof.1.base.rdb seq 1 type b\n\
8298             file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
8299        );
8300    }
8301
8302    /// `STATUS` is a map of four pairs on RESP3 and the same pairs flat on
8303    /// RESP2, which is what every other map shaped reply in this server does.
8304    #[test]
8305    fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
8306        let mut b = Backups::new("status");
8307        b.f.server.set_clock_ms(1_700_000_000_000);
8308
8309        assert_eq!(
8310            b.run(&[b"BACKUP", b"STATUS"]),
8311            "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
8312             $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
8313        );
8314
8315        b.f.out = Out::new(Proto::Resp3);
8316        b.run(&[b"BACKUP", b"START"]);
8317        assert_eq!(
8318            b.run(&[b"BACKUP", b"STATUS"]),
8319            "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
8320             $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
8321        );
8322
8323        b.run(&[b"BACKUP", b"SEAL"]);
8324        let sealed = b.run(&[b"BACKUP", b"STATUS"]);
8325        assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
8326    }
8327
8328    /// A sealed backup that nobody cleans up goes away on its own once
8329    /// `backup-sealed-ttl` seconds have passed since the seal.
8330    #[test]
8331    fn a_sealed_backup_is_swept_away_after_the_timeout() {
8332        let mut b = Backups::new("ttl");
8333        b.f.server.set_clock_ms(1_000_000);
8334        assert_eq!(
8335            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
8336            "+OK\r\n"
8337        );
8338        b.run(&[b"BACKUP", b"START"]);
8339        b.run(&[b"BACKUP", b"SEAL"]);
8340
8341        // A minute short of the deadline, nothing happens.
8342        b.f.server.set_clock_ms(1_000_000 + 59_000);
8343        b.f.server.backup_expire();
8344        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
8345        assert_eq!(b.files().len(), 3);
8346
8347        b.f.server.set_clock_ms(1_000_000 + 60_000);
8348        b.f.server.backup_expire();
8349        let status = b.run(&[b"BACKUP", b"STATUS"]);
8350        assert!(status.contains("idle"), "{status}");
8351        assert!(b.files().is_empty(), "the timeout left the files behind");
8352
8353        // Zero is the default and means a sealed backup is kept for ever.
8354        b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
8355        b.run(&[b"BACKUP", b"START"]);
8356        b.run(&[b"BACKUP", b"SEAL"]);
8357        b.f.server.set_clock_ms(9_000_000_000);
8358        b.f.server.backup_expire();
8359        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
8360    }
8361
8362    /// The three settings around the command, read and written the way 8.10.1
8363    /// reads and writes them.
8364    #[test]
8365    fn the_backup_settings_behave_the_way_the_reference_does() {
8366        let mut b = Backups::new("config");
8367        let dir = b.dir.to_string_lossy().into_owned();
8368
8369        assert_eq!(
8370            b.run(&[b"CONFIG", b"GET", b"dir"]),
8371            format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
8372        );
8373        assert_eq!(
8374            b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
8375            "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
8376        );
8377        assert_eq!(
8378            b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
8379            "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
8380        );
8381
8382        // `dir` is a protected config, so it is refused even for the value it
8383        // already holds, and `backupdirname` is immutable.
8384        assert_eq!(
8385            b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
8386            "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
8387        );
8388        assert_eq!(
8389            b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
8390            "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
8391        );
8392        assert!(
8393            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
8394                .contains("argument couldn't be parsed into an integer")
8395        );
8396        assert!(
8397            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
8398                .contains("argument must be between 0 and 9223372036854775807 inclusive")
8399        );
8400    }
8401
8402    /// The help text, which has `HELP` in it twice because the reference's does.
8403    #[test]
8404    fn backup_help_is_the_text_the_reference_sends() {
8405        let mut f = Fixture::new();
8406        let help = f.run(&[b"BACKUP", b"HELP"]);
8407        assert!(help.starts_with("*17\r\n"), "{help}");
8408        assert!(
8409            help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
8410        );
8411        assert!(help.contains("+    Start a new backup into the configured 'backupdirname'.\r\n"));
8412        assert!(help.contains("+    Freeze the current backup (BASE + INCR + manifest).\r\n"));
8413        assert!(help.contains("+    Return this help.\r\n+HELP\r\n+    Print this help.\r\n"));
8414    }
8415
8416    /// What a mistyped `BACKUP` gets told.
8417    ///
8418    /// The arity error names `backup` where the reference names `backup|start`,
8419    /// which is D-46: the table reports one arity for the container the way the
8420    /// reference does, and the per subcommand table that would carry the better
8421    /// name is not built yet. Every subcommand is exactly two words, so nothing
8422    /// legal is refused by it.
8423    #[test]
8424    fn backup_refuses_what_it_cannot_read() {
8425        let mut f = Fixture::new();
8426        assert_eq!(
8427            f.run(&[b"BACKUP"]),
8428            "-ERR wrong number of arguments for 'backup' command\r\n"
8429        );
8430        assert_eq!(
8431            f.run(&[b"BACKUP", b"START", b"x"]),
8432            "-ERR wrong number of arguments for 'backup' command\r\n"
8433        );
8434        assert_eq!(
8435            f.run(&[b"BACKUP", b"NOPE"]),
8436            "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
8437        );
8438    }
8439
8440    #[test]
8441    fn the_command_counter_counts_every_command_including_the_bad_ones() {
8442        let mut f = Fixture::new();
8443        f.run(&[b"PING"]);
8444        f.run(&[b"NOPE"]);
8445        f.run(&[b"GET"]);
8446        assert_eq!(f.server.totals().commands, 3);
8447    }
8448
8449    #[test]
8450    fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
8451        let mut server = Server::new();
8452        server.set_threads(2);
8453        // A fresh server has every database on the turn's list, so start from
8454        // nothing to see the one mark arrive.
8455        server.mine().turn.store(0, Relaxed);
8456        server.locals[1].mark(1 << 9);
8457        server.collect_marks();
8458        assert!(server.mine().wanted(9));
8459        // And taken once rather than left to be taken again next turn.
8460        assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
8461    }
8462
8463    #[test]
8464    fn what_two_threads_counted_is_added_up_when_info_asks() {
8465        let mut server = Server::new();
8466        server.set_threads(2);
8467        // Written into the two sets by hand, because what is under test is the
8468        // adding up and not the claiming, and one test thread can only ever
8469        // claim one set.
8470        let ping = lookup(b"PING").expect("PING is a command");
8471        for (at, calls) in [(0, 2), (1, 3)] {
8472            let counters = &server.locals[at];
8473            for _ in 0..calls {
8474                counters.stats.commands.bump();
8475                counters.cmdstats.at(ping).calls.bump();
8476            }
8477            counters.stats.opened();
8478        }
8479        assert_eq!(server.totals().commands, 5);
8480        assert_eq!(server.totals().clients, 2);
8481        assert_eq!(server.totals().connections, 2);
8482        let rows: Vec<_> = server.command_stats().collect();
8483        assert_eq!(rows.len(), 1);
8484        assert_eq!(rows[0].0, "ping");
8485        assert_eq!(rows[0].1.calls, 5);
8486        // A reset takes the totals and leaves the open connections, which are
8487        // still open.
8488        server.reset_stats();
8489        assert_eq!(server.totals().commands, 0);
8490        assert_eq!(server.totals().connections, 0);
8491        assert_eq!(server.totals().clients, 2);
8492    }
8493
8494    #[test]
8495    fn the_parked_count_says_what_the_waiter_list_says() {
8496        let mut f = Fixture::new();
8497        assert_eq!(f.server.parked(), 0);
8498        for client in 1..=3u64 {
8499            f.session = Session::new(client);
8500            assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
8501        }
8502        assert_eq!(f.server.parked(), 3);
8503        assert_eq!(f.server.waiters().len(), 3);
8504
8505        // The three ways the list gets shorter, each of which has to move the
8506        // number with it, because a number left behind is either a walk of the
8507        // list that never happens or one that runs off the end of it.
8508        f.server.forget_waiters(2);
8509        assert_eq!(f.server.parked(), f.server.waiters().len());
8510        f.server.forget_waiters(1);
8511        assert_eq!(f.server.parked(), f.server.waiters().len());
8512        f.run(&[b"RPUSH", b"q", b"v"]);
8513        let mut out = Out::new(Proto::Resp2);
8514        assert!(f.server.serve_waiter(3, 0, &mut out));
8515        f.server.forget_waiters(3);
8516        assert_eq!(f.server.parked(), 0);
8517        assert!(f.server.waiters().is_empty());
8518    }
8519
8520    #[test]
8521    fn a_set_goes_from_bytes_to_bytes() {
8522        let mut f = Fixture::new();
8523        assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
8524        assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
8525        assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
8526        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
8527        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
8528        assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
8529        assert_eq!(
8530            f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
8531            "*3\r\n:1\r\n:0\r\n:1\r\n"
8532        );
8533        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
8534        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8535    }
8536
8537    #[test]
8538    fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
8539        let mut f = Fixture::new();
8540        assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
8541        assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
8542        assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
8543        assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
8544        assert_eq!(
8545            f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
8546            "*2\r\n:0\r\n:0\r\n"
8547        );
8548        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
8549    }
8550
8551    #[test]
8552    fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
8553        // Not cosmetic. A RESP3 client that gets a `~` hands the caller a set
8554        // and one that gets a `*` hands it a list, without either of them being
8555        // told which command was sent.
8556        let mut f = Fixture::new();
8557        f.run(&[b"SADD", b"s", b"one"]);
8558        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
8559
8560        f.run(&[b"HELLO", b"3"]);
8561        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
8562    }
8563
8564    #[test]
8565    fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
8566        // An intset holds the number, so these digits exist for the first time
8567        // in the reply buffer.
8568        let mut f = Fixture::new();
8569        f.run(&[b"SADD", b"s", b"42"]);
8570        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
8571        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
8572        assert_eq!(
8573            f.run(&[b"SISMEMBER", b"s", b"042"]),
8574            ":0\r\n",
8575            "the member is the bytes and not the number they parse to"
8576        );
8577    }
8578
8579    #[test]
8580    fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
8581        let mut f = Fixture::new();
8582        f.run(&[b"SET", b"str", b"v"]);
8583        f.run(&[b"SADD", b"set", b"a"]);
8584
8585        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8586        assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
8587        assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
8588        assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
8589        assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
8590        assert_eq!(f.run(&[b"GET", b"set"]), wrong);
8591        assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
8592        assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
8593        assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
8594
8595        // MGET is the one that does not, because Redis gives nil for the odd
8596        // key out rather than failing the good keys next to it.
8597        assert_eq!(
8598            f.run(&[b"MGET", b"str", b"set", b"nope"]),
8599            "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
8600        );
8601        // And plain SET overwrites any type, which takes the body with it.
8602        assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
8603        assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
8604    }
8605
8606    #[test]
8607    fn a_wrongtype_leaves_nothing_half_written() {
8608        // SMISMEMBER writes an array header and then one reply per member, so
8609        // it is the first command in the server that could get a header out in
8610        // front of an error if it checked its key in the wrong order.
8611        let mut f = Fixture::new();
8612        f.run(&[b"SET", b"k", b"v"]);
8613        let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
8614        assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
8615        assert!(!reply.contains('*'), "an array header went out in front");
8616    }
8617
8618    #[test]
8619    fn emptying_a_set_takes_the_key_with_it() {
8620        let mut f = Fixture::new();
8621        f.run(&[b"SADD", b"s", b"a", b"b"]);
8622        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
8623        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
8624        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8625        assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
8626        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8627    }
8628
8629    /// Pull the cursor and the members out of one `SSCAN` reply.
8630    ///
8631    /// Crude on purpose. A test that walked a set through a real client would
8632    /// be testing the client, and what these tests are about is the shape of
8633    /// the bytes and the fact that a walk sees every member once.
8634    fn split_scan(reply: &str) -> (String, Vec<String>) {
8635        let mut lines = reply.split("\r\n");
8636        assert_eq!(lines.next(), Some("*2"), "got {reply}");
8637        lines.next().expect("the cursor header");
8638        let cursor = lines.next().expect("the cursor").to_owned();
8639        let header = lines.next().expect("the member header");
8640        let n: usize = header[1..].parse().expect("a member count");
8641        let mut members = Vec::with_capacity(n);
8642        for _ in 0..n {
8643            lines.next().expect("a member header");
8644            members.push(lines.next().expect("a member").to_owned());
8645        }
8646        (cursor, members)
8647    }
8648
8649    #[test]
8650    fn popping_takes_a_member_off_the_set_and_hands_it_back() {
8651        let mut f = Fixture::new();
8652        f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
8653
8654        let one = f.run(&[b"SPOP", b"s"]);
8655        assert!(
8656            ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
8657            "got {one}"
8658        );
8659        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8660
8661        // A count takes that many, and the last one takes the key with it.
8662        let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
8663        assert!(rest.starts_with("*3\r\n"), "got {rest}");
8664        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8665        // And a pop at a key that is not there is a nil, not an empty bulk.
8666        assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
8667        assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
8668    }
8669
8670    #[test]
8671    fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
8672        // The one place in the server where the reply type carries something
8673        // the command name does not. SPOP's members are distinct so a RESP3
8674        // client can build a set out of them. SRANDMEMBER with a negative count
8675        // can hand back the same member three times, and a set would lose two.
8676        let mut f = Fixture::new();
8677        f.run(&[b"HELLO", b"3"]);
8678        f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
8679
8680        assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
8681        // And a positive count is an array too, since Redis makes it one.
8682        assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
8683
8684        // A negative count against a set of one is where the difference bites:
8685        // the same member three times, which is a three element reply and would
8686        // have been a one element reply if it had gone out as a set.
8687        f.run(&[b"SADD", b"one", b"z"]);
8688        assert_eq!(
8689            f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
8690            "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
8691        );
8692    }
8693
8694    #[test]
8695    fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
8696        let mut f = Fixture::new();
8697        f.run(&[b"SADD", b"s", b"only"]);
8698        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8699        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8700        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
8701
8702        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
8703        // The count form answers an empty array rather than a nil, which is the
8704        // pair of answers Redis gives and is not the pair it looks like.
8705        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
8706        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
8707        // Asking for more than is there answers all of it once and not padding.
8708        assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
8709    }
8710
8711    #[test]
8712    fn a_pop_count_that_is_not_a_positive_number_says_so() {
8713        let mut f = Fixture::new();
8714        f.run(&[b"SADD", b"s", b"a"]);
8715        let bad = "-ERR value is out of range, must be positive\r\n";
8716        assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
8717        assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
8718        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
8719        // Zero is allowed and is a real answer rather than an error.
8720        assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
8721        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
8722    }
8723
8724    #[test]
8725    fn a_scan_walks_a_set_of_any_size_exactly_once() {
8726        let mut f = Fixture::new();
8727        let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
8728        let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
8729            .into_iter()
8730            .chain(members.iter().map(Vec::as_slice))
8731            .collect();
8732        f.run(&args);
8733
8734        let mut seen = Vec::new();
8735        let mut cursor = "0".to_owned();
8736        loop {
8737            let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
8738            let (next, got) = split_scan(&reply);
8739            seen.extend(got);
8740            cursor = next;
8741            if cursor == "0" {
8742                break;
8743            }
8744        }
8745        seen.sort();
8746        seen.dedup();
8747        assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
8748
8749        // A set small enough to be a listpack answers in one call whatever
8750        // cursor it was handed, which is what Redis does for that encoding.
8751        f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
8752        let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
8753        assert_eq!(cursor, "0");
8754        assert_eq!(got.len(), 3);
8755        // And a key that is not there is a finished scan of nothing.
8756        assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
8757    }
8758
8759    #[test]
8760    fn a_scan_takes_match_and_count_and_refuses_anything_else() {
8761        let mut f = Fixture::new();
8762        f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
8763
8764        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
8765        let mut got = got;
8766        got.sort();
8767        assert_eq!(got, ["aa", "ab"]);
8768
8769        // An integer member has no digits stored anywhere, so MATCH is the one
8770        // place a scan pays to write some.
8771        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
8772        let mut got = got;
8773        got.sort();
8774        assert_eq!(got, ["12", "13"]);
8775
8776        assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
8777        assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
8778        assert_eq!(
8779            f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
8780            "-ERR syntax error\r\n"
8781        );
8782        // A count under one is a syntax error and not a range error, which is
8783        // the odder of Redis's two answers and the reason it is copied exactly.
8784        assert_eq!(
8785            f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
8786            "-ERR syntax error\r\n"
8787        );
8788    }
8789
8790    #[test]
8791    fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
8792        let mut f = Fixture::new();
8793        f.run(&[b"SADD", b"src", b"a", b"b"]);
8794        f.run(&[b"SADD", b"dst", b"c"]);
8795
8796        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
8797        assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
8798        assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
8799        // A member that is not in the source is a zero and moves nothing.
8800        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
8801        assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
8802
8803        // A destination that does not exist gets made, and a source that runs
8804        // out goes away.
8805        assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
8806        assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
8807        assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
8808    }
8809
8810    #[test]
8811    fn moving_checks_the_types_in_the_order_redis_checks_them() {
8812        // Not the order it looks like it should be. A source that is not there
8813        // answers zero without ever looking at the destination, so this is a
8814        // zero and not a WRONGTYPE even though the destination is a string.
8815        let mut f = Fixture::new();
8816        f.run(&[b"SET", b"str", b"v"]);
8817        f.run(&[b"SADD", b"set", b"a"]);
8818
8819        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8820        assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
8821        assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
8822        assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
8823        assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
8824        assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
8825        assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
8826        assert_eq!(
8827            f.run(&[b"SISMEMBER", b"set", b"a"]),
8828            ":1\r\n",
8829            "and none of that moved anything"
8830        );
8831    }
8832
8833    #[test]
8834    fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8835        // SSCAN writes an outer array header before it walks, so it is the
8836        // command most likely to get bytes out in front of an error.
8837        let mut f = Fixture::new();
8838        f.run(&[b"SADD", b"s", b"a"]);
8839        for bad in [
8840            &[b"SSCAN".as_slice(), b"s", b"abc"][..],
8841            &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
8842            &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
8843        ] {
8844            let reply = f.run(bad);
8845            assert!(reply.starts_with("-ERR"), "got {reply}");
8846            assert!(!reply.contains('*'), "an array header went out in front");
8847        }
8848    }
8849
8850    #[test]
8851    fn a_hash_writes_reads_and_deletes_its_fields() {
8852        let mut f = Fixture::new();
8853        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
8854        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
8855        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8856        assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
8857        assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
8858        assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
8859        assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
8860        assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
8861        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
8862        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
8863
8864        // The value the client sent is `9`, so HGET h b must not find the `2`
8865        // that is a value. A search with a step of one would have.
8866        assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
8867
8868        assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
8869        assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
8870        assert_eq!(
8871            f.run(&[b"EXISTS", b"h"]),
8872            ":0\r\n",
8873            "and losing the last field lost the key"
8874        );
8875    }
8876
8877    #[test]
8878    fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
8879        let mut f = Fixture::new();
8880        f.run(&[b"HSET", b"h", b"a", b"1"]);
8881        assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
8882        assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
8883        assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
8884        assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
8885        assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
8886
8887        f.run(&[b"HELLO", b"3"]);
8888        assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
8889        assert_eq!(
8890            f.run(&[b"HGETALL", b"nokey"]),
8891            "%0\r\n",
8892            "a missing key is the empty hash and never a nil"
8893        );
8894        assert_eq!(
8895            f.run(&[b"HKEYS", b"h"]),
8896            "*1\r\n$1\r\na\r\n",
8897            "and the two that answer one side stay arrays"
8898        );
8899    }
8900
8901    #[test]
8902    fn hmget_answers_once_per_field_and_hmset_answers_ok() {
8903        let mut f = Fixture::new();
8904        assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
8905        assert_eq!(
8906            f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
8907            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
8908            "the reply is positional, so b is a nil and not a gap"
8909        );
8910        assert_eq!(
8911            f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
8912            "*2\r\n$-1\r\n$-1\r\n",
8913            "and a missing key is all nils rather than an empty array"
8914        );
8915
8916        assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
8917        assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
8918        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8919    }
8920
8921    #[test]
8922    fn a_hash_counts_up_and_says_so_when_it_cannot() {
8923        let mut f = Fixture::new();
8924        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
8925        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
8926        assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
8927        assert_eq!(
8928            f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
8929            "$4\r\n10.5\r\n",
8930            "a bulk string and not a double, on both protocols"
8931        );
8932
8933        f.run(&[b"HSET", b"h", b"s", b"words"]);
8934        let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
8935        assert!(
8936            bad.starts_with("-ERR hash value is not an integer"),
8937            "{bad}"
8938        );
8939        let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
8940        assert!(
8941            bad.starts_with("-ERR value is not an integer"),
8942            "a bad argument is not yet a hash value, {bad}"
8943        );
8944        assert_eq!(
8945            f.run(&[b"HGET", b"h", b"s"]),
8946            "$5\r\nwords\r\n",
8947            "and neither of them wrote anything"
8948        );
8949    }
8950
8951    #[test]
8952    fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
8953        // Fourteen minutes under Miri at five hundred, which was the slowest
8954        // test in this crate that was not about megabytes. What the count has
8955        // to be is more than one page of the cursor, and the count below is
8956        // thirty two, so ninety six is three pages and asks the same question.
8957        let fields = if cfg!(miri) { 96 } else { 500 };
8958        let mut f = Fixture::new();
8959        for i in 0..fields {
8960            let field = format!("field-{i}");
8961            let value = format!("value-{i}");
8962            f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
8963        }
8964
8965        let mut seen: Vec<String> = Vec::new();
8966        let mut cursor = "0".to_owned();
8967        loop {
8968            let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
8969            let (next, items) = scan_reply(&reply);
8970            assert_eq!(items.len() % 2, 0, "a pair went out half written");
8971            for pair in items.chunks(2) {
8972                assert_eq!(
8973                    pair[0].strip_prefix("field-"),
8974                    pair[1].strip_prefix("value-"),
8975                    "a field came back with someone else's value"
8976                );
8977                seen.push(pair[0].clone());
8978            }
8979            cursor = next;
8980            if cursor == "0" {
8981                break;
8982            }
8983        }
8984        seen.sort();
8985        seen.dedup();
8986        assert_eq!(seen.len(), fields, "every field once and only once");
8987
8988        let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
8989        assert!(
8990            items.iter().all(|s| s.starts_with("field-")),
8991            "NOVALUES still sent the values"
8992        );
8993
8994        let last = fields - 1;
8995        let (_, one) = scan_reply(&f.run(&[
8996            b"HSCAN",
8997            b"h",
8998            b"0",
8999            b"MATCH",
9000            format!("field-{last}").as_bytes(),
9001            b"COUNT",
9002            b"1000",
9003        ]));
9004        assert_eq!(
9005            one,
9006            [format!("field-{last}"), format!("value-{last}")],
9007            "MATCH is on the field"
9008        );
9009    }
9010
9011    #[test]
9012    fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
9013        let mut f = Fixture::new();
9014        f.run(&[b"HSET", b"h", b"a", b"1"]);
9015        assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
9016        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
9017        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
9018        assert_eq!(
9019            f.run(&[b"HRANDFIELD", b"h", b"3"]),
9020            "*1\r\n$1\r\na\r\n",
9021            "a positive count is capped at the size of the hash"
9022        );
9023        assert_eq!(
9024            f.run(&[b"HRANDFIELD", b"h", b"-3"]),
9025            "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
9026            "and a negative one repeats itself"
9027        );
9028        assert_eq!(
9029            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
9030            "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
9031            "flat on RESP2"
9032        );
9033
9034        f.run(&[b"HELLO", b"3"]);
9035        assert_eq!(
9036            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
9037            "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
9038            "and nested on RESP3, but still an array and never a map"
9039        );
9040    }
9041
9042    #[test]
9043    fn every_hash_command_says_wrongtype_and_writes_nothing() {
9044        let mut f = Fixture::new();
9045        f.run(&[b"SET", b"str", b"v"]);
9046        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9047
9048        for cmd in [
9049            &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
9050            &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
9051            &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
9052            &[b"HGET".as_slice(), b"str", b"f"][..],
9053            &[b"HMGET".as_slice(), b"str", b"f"][..],
9054            &[b"HDEL".as_slice(), b"str", b"f"][..],
9055            &[b"HLEN".as_slice(), b"str"][..],
9056            &[b"HEXISTS".as_slice(), b"str", b"f"][..],
9057            &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
9058            &[b"HGETALL".as_slice(), b"str"][..],
9059            &[b"HKEYS".as_slice(), b"str"][..],
9060            &[b"HVALS".as_slice(), b"str"][..],
9061            &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
9062            &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
9063            &[b"HRANDFIELD".as_slice(), b"str"][..],
9064            &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
9065            &[b"HSCAN".as_slice(), b"str", b"0"][..],
9066        ] {
9067            let reply = f.run(cmd);
9068            assert_eq!(reply, wrong, "{:?}", cmd[0]);
9069        }
9070        assert_eq!(
9071            f.run(&[b"GET", b"str"]),
9072            "$1\r\nv\r\n",
9073            "and none of them touched the value"
9074        );
9075    }
9076
9077    #[test]
9078    fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
9079        let mut f = Fixture::new();
9080        f.run(&[b"HSET", b"h", b"f", b"v"]);
9081        for bad in [
9082            &[b"HSCAN".as_slice(), b"h", b"abc"][..],
9083            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
9084            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
9085            &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
9086        ] {
9087            let reply = f.run(bad);
9088            assert!(reply.starts_with("-ERR"), "got {reply}");
9089            assert!(!reply.contains('*'), "an array header went out in front");
9090        }
9091    }
9092
9093    #[test]
9094    fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
9095        let mut f = Fixture::new();
9096        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9097        assert_eq!(
9098            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
9099            "*1\r\n:1\r\n"
9100        );
9101        assert_eq!(
9102            f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
9103            "*3\r\n:100\r\n:-1\r\n:-2\r\n",
9104            "one answer per field, and the two sentinels are TTL's own"
9105        );
9106
9107        // The same deadline in the other three units, all of them derived from
9108        // the one number the store kept.
9109        let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
9110        assert!((99_000..=100_000).contains(&ms), "got {ms}");
9111        let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
9112        let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
9113        assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
9114        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
9115
9116        assert_eq!(
9117            f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
9118            "*3\r\n:1\r\n:-1\r\n:-2\r\n",
9119            "one for the deadline taken off, and it does not say what it was"
9120        );
9121        assert_eq!(
9122            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9123            "*1\r\n:-1\r\n"
9124        );
9125        assert_eq!(
9126            f.run(&[b"HGET", b"h", b"a"]),
9127            "$1\r\n1\r\n",
9128            "and the field is still there with the value it had"
9129        );
9130    }
9131
9132    #[test]
9133    fn a_deadline_that_has_already_gone_deletes_the_field_now() {
9134        let mut f = Fixture::new();
9135        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9136        assert_eq!(
9137            f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
9138            "*1\r\n:2\r\n",
9139            "two, and not one, because nothing was stored"
9140        );
9141        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
9142        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
9143
9144        assert_eq!(
9145            f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
9146            "*1\r\n:2\r\n"
9147        );
9148        assert_eq!(
9149            f.run(&[b"EXISTS", b"h"]),
9150            ":0\r\n",
9151            "and the last field going took the key with it"
9152        );
9153
9154        // Zero is a delete and not an error, where minus one is an error. That
9155        // is Redis's split and it is easy to get backwards.
9156        f.run(&[b"HSET", b"h", b"a", b"1"]);
9157        assert_eq!(
9158            f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
9159            "*1\r\n:2\r\n"
9160        );
9161    }
9162
9163    #[test]
9164    fn a_field_is_gone_once_its_moment_passes() {
9165        let mut f = Fixture::new();
9166        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9167        assert_eq!(
9168            f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
9169            "*1\r\n:1\r\n"
9170        );
9171        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
9172
9173        // Time moves once per turn of the event loop and nowhere else, so a
9174        // test moves it by hand rather than by sleeping. There is nothing to
9175        // sleep for: the deadline is a number and so is the clock.
9176        f.server.advance_clock_ms(60);
9177        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
9178        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
9179        assert_eq!(
9180            f.run(&[b"HGETALL", b"h"]),
9181            "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
9182            "and the walks do not hand back a field that has expired"
9183        );
9184    }
9185
9186    #[test]
9187    fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
9188        let mut f = Fixture::new();
9189        for cmd in [
9190            &[
9191                b"HEXPIRE".as_slice(),
9192                b"nokey",
9193                b"100",
9194                b"FIELDS",
9195                b"2",
9196                b"a",
9197                b"b",
9198            ][..],
9199            &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
9200            &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
9201            &[
9202                b"HEXPIRETIME".as_slice(),
9203                b"nokey",
9204                b"FIELDS",
9205                b"2",
9206                b"a",
9207                b"b",
9208            ][..],
9209            &[
9210                b"HPERSIST".as_slice(),
9211                b"nokey",
9212                b"FIELDS",
9213                b"2",
9214                b"a",
9215                b"b",
9216            ][..],
9217        ] {
9218            assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
9219        }
9220    }
9221
9222    #[test]
9223    fn writing_a_field_clears_the_deadline_that_was_on_it() {
9224        let mut f = Fixture::new();
9225        f.run(&[b"HSET", b"h", b"a", b"1"]);
9226        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
9227        f.run(&[b"HSET", b"h", b"a", b"2"]);
9228        assert_eq!(
9229            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9230            "*1\r\n:-1\r\n",
9231            "Redis has done this since 7.4, and it is why HGETEX exists"
9232        );
9233    }
9234
9235    #[test]
9236    fn the_four_conditions_reach_the_store_the_way_they_were_written() {
9237        let mut f = Fixture::new();
9238        f.run(&[b"HSET", b"h", b"a", b"1"]);
9239        assert_eq!(
9240            f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
9241            "*1\r\n:0\r\n",
9242            "XX on a field with no deadline changes nothing"
9243        );
9244        assert_eq!(
9245            f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
9246            "*1\r\n:1\r\n"
9247        );
9248        assert_eq!(
9249            f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
9250            "*1\r\n:0\r\n",
9251            "and NX will not move one that is already there"
9252        );
9253        assert_eq!(
9254            f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
9255            "*1\r\n:0\r\n"
9256        );
9257        assert_eq!(
9258            f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
9259            "*1\r\n:1\r\n"
9260        );
9261        assert_eq!(
9262            f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
9263            "*1\r\n:1\r\n"
9264        );
9265        assert_eq!(
9266            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9267            "*1\r\n:50\r\n"
9268        );
9269    }
9270
9271    #[test]
9272    fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
9273        let mut f = Fixture::new();
9274        f.run(&[b"HSET", b"h", b"a", b"1"]);
9275        for (bad, want) in [
9276            (
9277                &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
9278                "-ERR invalid expire time, must be >= 0",
9279            ),
9280            (
9281                &[
9282                    b"HEXPIRE".as_slice(),
9283                    b"h",
9284                    b"9999999999999999",
9285                    b"FIELDS",
9286                    b"1",
9287                    b"a",
9288                ][..],
9289                "-ERR invalid expire time in 'hexpire' command",
9290            ),
9291            (
9292                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
9293                "-ERR wrong number of arguments for 'hexpire' command",
9294            ),
9295            (
9296                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
9297                "-ERR Parameter `numFields` should be greater than 0",
9298            ),
9299            (
9300                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
9301                "-ERR wrong number of arguments",
9302            ),
9303            (
9304                &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
9305                "-ERR wrong number of arguments",
9306            ),
9307        ] {
9308            let reply = f.run(bad);
9309            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9310            assert!(!reply.contains('*'), "an array header went out in front");
9311        }
9312        assert_eq!(
9313            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9314            "*1\r\n:-1\r\n",
9315            "and not one of them put a deadline on anything"
9316        );
9317    }
9318
9319    #[test]
9320    fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
9321        let mut f = Fixture::new();
9322        f.run(&[b"SET", b"str", b"v"]);
9323        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9324
9325        for cmd in [
9326            &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
9327            &[
9328                b"HPEXPIRE".as_slice(),
9329                b"str",
9330                b"100",
9331                b"FIELDS",
9332                b"1",
9333                b"f",
9334            ][..],
9335            &[
9336                b"HEXPIREAT".as_slice(),
9337                b"str",
9338                b"9999999999",
9339                b"FIELDS",
9340                b"1",
9341                b"f",
9342            ][..],
9343            &[
9344                b"HPEXPIREAT".as_slice(),
9345                b"str",
9346                b"9999999999999",
9347                b"FIELDS",
9348                b"1",
9349                b"f",
9350            ][..],
9351            &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9352            &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9353            &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9354            &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9355            &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9356        ] {
9357            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9358        }
9359        assert_eq!(
9360            f.run(&[b"GET", b"str"]),
9361            "$1\r\nv\r\n",
9362            "and none of them touched the value"
9363        );
9364    }
9365
9366    #[test]
9367    fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
9368        let mut f = Fixture::new();
9369        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
9370        assert_eq!(
9371            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
9372            "*2\r\n$1\r\n1\r\n$-1\r\n",
9373            "positional, so the field that was not there is a nil in its place"
9374        );
9375        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
9376        assert_eq!(
9377            f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
9378            "*1\r\n$-1\r\n"
9379        );
9380        assert_eq!(
9381            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
9382            "*1\r\n$1\r\n2\r\n"
9383        );
9384        assert_eq!(
9385            f.run(&[b"EXISTS", b"h"]),
9386            ":0\r\n",
9387            "and the last field took the key"
9388        );
9389    }
9390
9391    #[test]
9392    fn hgetex_reads_and_moves_the_deadline_in_one_command() {
9393        let mut f = Fixture::new();
9394        f.run(&[b"HSET", b"h", b"a", b"1"]);
9395        assert_eq!(
9396            f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
9397            "*1\r\n$1\r\n1\r\n"
9398        );
9399        assert_eq!(
9400            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9401            "*1\r\n:-1\r\n",
9402            "no option means leave it alone, which is the one place this is not GETEX"
9403        );
9404
9405        f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
9406        assert_eq!(
9407            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9408            "*1\r\n:100\r\n"
9409        );
9410        f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
9411        assert_eq!(
9412            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9413            "*1\r\n:100\r\n",
9414            "and a plain read really does leave it alone"
9415        );
9416        assert_eq!(
9417            f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
9418            "*1\r\n$1\r\n1\r\n"
9419        );
9420        assert_eq!(
9421            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9422            "*1\r\n:-1\r\n"
9423        );
9424
9425        assert_eq!(
9426            f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
9427            "*1\r\n$1\r\n1\r\n",
9428            "the value goes out before the deadline that has already gone is applied"
9429        );
9430        assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
9431        assert_eq!(
9432            f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
9433            "*1\r\n$-1\r\n"
9434        );
9435    }
9436
9437    #[test]
9438    fn hsetex_writes_all_of_it_or_none_of_it() {
9439        let mut f = Fixture::new();
9440        assert_eq!(
9441            f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
9442            ":1\r\n"
9443        );
9444        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
9445        assert_eq!(
9446            f.run(&[
9447                b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
9448            ]),
9449            ":0\r\n",
9450            "FNX wants every field named to be missing"
9451        );
9452        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
9453        assert_eq!(
9454            f.run(&[b"HEXISTS", b"h", b"new"]),
9455            ":0\r\n",
9456            "and none of the list was written"
9457        );
9458        assert_eq!(
9459            f.run(&[
9460                b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
9461            ]),
9462            ":0\r\n",
9463            "and FXX wants every one of them to be there"
9464        );
9465        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
9466        assert_eq!(
9467            f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
9468            ":1\r\n"
9469        );
9470        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
9471
9472        assert_eq!(
9473            f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
9474            ":0\r\n"
9475        );
9476        assert_eq!(
9477            f.run(&[b"EXISTS", b"gone"]),
9478            ":0\r\n",
9479            "a key with no fields cannot meet FXX and is not created trying"
9480        );
9481    }
9482
9483    #[test]
9484    fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
9485        let mut f = Fixture::new();
9486        f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
9487        assert_eq!(
9488            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9489            "*1\r\n:100\r\n"
9490        );
9491
9492        f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
9493        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
9494        assert_eq!(
9495            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9496            "*1\r\n:100\r\n",
9497            "KEEPTTL put back what the write cleared"
9498        );
9499
9500        f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
9501        assert_eq!(
9502            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9503            "*1\r\n:-1\r\n",
9504            "and without it a write clears the deadline the way HSET does"
9505        );
9506
9507        // Any order, because Redis reads these in a loop and not in a fixed
9508        // sequence.
9509        assert_eq!(
9510            f.run(&[
9511                b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
9512            ]),
9513            ":1\r\n"
9514        );
9515        assert_eq!(
9516            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9517            "*1\r\n:100\r\n"
9518        );
9519
9520        assert_eq!(
9521            f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
9522            ":1\r\n",
9523            "written, and not the separate code the HEXPIRE family has for this"
9524        );
9525        assert_eq!(
9526            f.run(&[b"EXISTS", b"h"]),
9527            ":0\r\n",
9528            "and storing it and then removing it emptied the hash"
9529        );
9530    }
9531
9532    #[test]
9533    fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
9534        let mut f = Fixture::new();
9535        f.run(&[b"HSET", b"h", b"a", b"1"]);
9536        for (bad, want) in [
9537            // HGETDEL has three sentences of its own for these three mistakes.
9538            (
9539                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9540                "-ERR Number of fields must be a positive integer",
9541            ),
9542            (
9543                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9544                "-ERR The `numfields` parameter must match the number of arguments",
9545            ),
9546            (
9547                &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9548                "-ERR Mandatory argument FIELDS is missing or not at the right position",
9549            ),
9550            // And HGETEX and HSETEX have three different ones between them.
9551            (
9552                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9553                "-ERR invalid number of fields",
9554            ),
9555            (
9556                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9557                "-ERR wrong number of arguments",
9558            ),
9559            (
9560                &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9561                "-ERR unknown argument: FIELD",
9562            ),
9563            (
9564                &[
9565                    b"HGETEX".as_slice(),
9566                    b"h",
9567                    b"KEEPTTL",
9568                    b"FIELDS",
9569                    b"1",
9570                    b"a",
9571                ][..],
9572                "-ERR unknown argument: KEEPTTL",
9573            ),
9574            (
9575                &[
9576                    b"HGETEX".as_slice(),
9577                    b"h",
9578                    b"EX",
9579                    b"100",
9580                    b"PERSIST",
9581                    b"FIELDS",
9582                    b"1",
9583                    b"a",
9584                ][..],
9585                "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
9586            ),
9587            (
9588                &[
9589                    b"HSETEX".as_slice(),
9590                    b"h",
9591                    b"EX",
9592                    b"1",
9593                    b"KEEPTTL",
9594                    b"FIELDS",
9595                    b"1",
9596                    b"a",
9597                    b"1",
9598                ][..],
9599                "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
9600            ),
9601            (
9602                &[
9603                    b"HSETEX".as_slice(),
9604                    b"h",
9605                    b"FNX",
9606                    b"FXX",
9607                    b"FIELDS",
9608                    b"1",
9609                    b"a",
9610                    b"1",
9611                ][..],
9612                "-ERR Only one of FXX or FNX arguments can be specified",
9613            ),
9614            (
9615                &[
9616                    b"HSETEX".as_slice(),
9617                    b"h",
9618                    b"FIELDS",
9619                    b"2",
9620                    b"a",
9621                    b"1",
9622                    b"b",
9623                ][..],
9624                "-ERR wrong number of arguments",
9625            ),
9626            (
9627                &[
9628                    b"HGETEX".as_slice(),
9629                    b"h",
9630                    b"EX",
9631                    b"-1",
9632                    b"FIELDS",
9633                    b"1",
9634                    b"a",
9635                ][..],
9636                "-ERR invalid expire time, must be >= 0",
9637            ),
9638            (
9639                &[
9640                    b"HGETEX".as_slice(),
9641                    b"h",
9642                    b"PXAT",
9643                    b"99999999999999",
9644                    b"FIELDS",
9645                    b"1",
9646                    b"a",
9647                ][..],
9648                "-ERR invalid expire time in 'hgetex' command",
9649            ),
9650            (
9651                &[
9652                    b"HSETEX".as_slice(),
9653                    b"h",
9654                    b"EX",
9655                    b"abc",
9656                    b"FIELDS",
9657                    b"1",
9658                    b"a",
9659                    b"1",
9660                ][..],
9661                "-ERR value is not an integer or out of range",
9662            ),
9663        ] {
9664            let reply = f.run(bad);
9665            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9666            assert!(!reply.contains('*'), "an array header went out in front");
9667        }
9668        assert_eq!(
9669            f.run(&[b"HGET", b"h", b"a"]),
9670            "$1\r\n1\r\n",
9671            "and not one of them wrote anything"
9672        );
9673        assert_eq!(
9674            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9675            "*1\r\n:-1\r\n"
9676        );
9677    }
9678
9679    #[test]
9680    fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
9681        let mut f = Fixture::new();
9682        f.run(&[b"SET", b"str", b"v"]);
9683        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9684        for cmd in [
9685            &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9686            &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9687            &[
9688                b"HGETEX".as_slice(),
9689                b"str",
9690                b"EX",
9691                b"100",
9692                b"FIELDS",
9693                b"1",
9694                b"f",
9695            ][..],
9696            &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
9697        ] {
9698            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9699        }
9700        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
9701    }
9702
9703    /// The two orders `HIMPORT` juggles, which are not the same order.
9704    ///
9705    /// Values arrive in the order the fields were declared in and the hash is
9706    /// built in sorted order, so the first value is not generally the first
9707    /// field. And the sort is by length before bytes, which nothing else here
9708    /// sorts names with: `b` comes before `aa` where a plain byte comparison
9709    /// would put `aa` first. Both read off 8.10.1.
9710    #[test]
9711    fn himport_writes_declared_values_into_sorted_fields() {
9712        let mut f = Fixture::new();
9713        assert_eq!(
9714            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
9715            "+OK\r\n"
9716        );
9717        assert_eq!(
9718            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
9719            "+OK\r\n"
9720        );
9721        assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
9722        assert_eq!(
9723            f.run(&[b"HGETALL", b"k"]),
9724            bulks(&["a", "3", "b", "1", "aa", "2"])
9725        );
9726    }
9727
9728    /// It replaces the key rather than writing over it, so a field the fieldset
9729    /// does not name is gone afterwards and so is the deadline.
9730    #[test]
9731    fn himport_set_replaces_the_whole_key() {
9732        let mut f = Fixture::new();
9733        f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
9734        f.run(&[b"EXPIRE", b"k", b"100"]);
9735        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9736        assert_eq!(
9737            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9738            "+OK\r\n"
9739        );
9740        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9741        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
9742    }
9743
9744    /// A fieldset is connection state. `SELECT` leaves them alone and `RESET`
9745    /// throws them away, and a key built from one outlives it.
9746    #[test]
9747    fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
9748        let mut f = Fixture::new();
9749        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
9750        f.run(&[b"SELECT", b"1"]);
9751        assert_eq!(
9752            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9753            "+OK\r\n"
9754        );
9755        f.run(&[b"SELECT", b"0"]);
9756        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
9757        assert_eq!(
9758            f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
9759            "-ERR no such fieldset\r\n"
9760        );
9761    }
9762
9763    /// Which complaint wins when a line is wrong in more than one place.
9764    ///
9765    /// The type of the key beats both of the others, so a `HIMPORT SET` against
9766    /// a string is a WRONGTYPE even when the fieldset is missing too, which is
9767    /// the ordering a real server has and not the one the argument order
9768    /// suggests.
9769    #[test]
9770    fn himport_complains_in_the_order_a_real_server_does() {
9771        let mut f = Fixture::new();
9772        f.run(&[b"SET", b"str", b"v"]);
9773        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9774        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9775        assert_eq!(
9776            f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
9777            wrong,
9778            "the type beats a missing fieldset"
9779        );
9780        assert_eq!(
9781            f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
9782            wrong,
9783            "and it beats a value count that does not fit"
9784        );
9785        assert_eq!(
9786            f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
9787            "-ERR no such fieldset\r\n"
9788        );
9789        // One sentence for too few and for too many alike.
9790        for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
9791            let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
9792            line.extend_from_slice(values);
9793            assert_eq!(
9794                f.run(&line),
9795                "-ERR value count does not match fieldset field count\r\n",
9796                "{} values into two fields",
9797                values.len()
9798            );
9799        }
9800        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9801    }
9802
9803    /// The arity of each subcommand, and the unknown one.
9804    #[test]
9805    fn himport_checks_each_subcommand_count_under_its_own_name() {
9806        let mut f = Fixture::new();
9807        assert_eq!(
9808            f.run(&[b"HIMPORT"]),
9809            "-ERR wrong number of arguments for 'himport' command\r\n"
9810        );
9811        for (rest, name) in [
9812            (&["PREPARE"][..], "prepare"),
9813            (&["PREPARE", "fs"][..], "prepare"),
9814            (&["SET"][..], "set"),
9815            (&["SET", "k"][..], "set"),
9816            (&["SET", "k", "fs"][..], "set"),
9817            (&["DISCARD"][..], "discard"),
9818            (&["DISCARD", "a", "b"][..], "discard"),
9819            (&["DISCARDALL", "x"][..], "discardall"),
9820        ] {
9821            let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
9822            line.extend(rest.iter().map(|a| a.as_bytes()));
9823            assert_eq!(
9824                f.run(&line),
9825                format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
9826                "HIMPORT {}",
9827                rest.join(" ")
9828            );
9829        }
9830        assert_eq!(
9831            f.run(&[b"HIMPORT", b"NOPE", b"x"]),
9832            "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
9833        );
9834    }
9835
9836    /// A `PREPARE` that fails leaves the name pointing where it pointed, which
9837    /// is the answer of the two that could not be guessed from outside.
9838    #[test]
9839    fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
9840        let mut f = Fixture::new();
9841        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9842        assert_eq!(
9843            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
9844            "-ERR duplicate field name in fieldset\r\n"
9845        );
9846        assert_eq!(
9847            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9848            "+OK\r\n"
9849        );
9850        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9851    }
9852
9853    /// Preparing the same name twice replaces it, and the two discards count
9854    /// what they took rather than answering OK.
9855    #[test]
9856    fn himport_prepare_replaces_and_the_discards_count() {
9857        let mut f = Fixture::new();
9858        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9859        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
9860        assert_eq!(
9861            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9862            "+OK\r\n"
9863        );
9864        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
9865
9866        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
9867        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
9868        f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
9869        f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
9870        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
9871        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
9872    }
9873
9874    /// The one integer of a single element array reply.
9875    /// The number out of a plain integer reply.
9876    ///
9877    /// [`int_reply`] is the same thing wrapped in a one element array, which is
9878    /// the shape every hash field command answers in.
9879    fn int(reply: &str) -> i64 {
9880        let body = reply
9881            .strip_prefix(':')
9882            .and_then(|s| s.strip_suffix("\r\n"))
9883            .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
9884        body.parse().expect("an integer")
9885    }
9886
9887    fn int_reply(reply: &str) -> i64 {
9888        let body = reply
9889            .strip_prefix("*1\r\n:")
9890            .and_then(|s| s.strip_suffix("\r\n"))
9891            .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
9892        body.parse().expect("an integer")
9893    }
9894
9895    /// The cursor and the flat items of a scan reply.
9896    fn scan_reply(reply: &str) -> (String, Vec<String>) {
9897        let mut lines = reply.split("\r\n");
9898        assert_eq!(lines.next(), Some("*2"), "got {reply}");
9899        lines.next().expect("the cursor header");
9900        let cursor = lines.next().expect("a cursor").to_owned();
9901        let header = lines.next().expect("an item count");
9902        let n: usize = header[1..].parse().expect("a count");
9903        let mut items = Vec::with_capacity(n);
9904        for _ in 0..n {
9905            lines.next().expect("an item header");
9906            items.push(lines.next().expect("an item").to_owned());
9907        }
9908        (cursor, items)
9909    }
9910
9911    /// The members of a set reply, sorted, since none of these promise an
9912    /// order and a test that asserted one would be asserting an accident.
9913    fn sorted(reply: &str) -> Vec<String> {
9914        let mut lines = reply.split("\r\n");
9915        let header = lines.next().expect("a header");
9916        assert!(
9917            header.starts_with('*') || header.starts_with('~'),
9918            "got {reply}"
9919        );
9920        let n: usize = header[1..].parse().expect("a member count");
9921        let mut got = Vec::with_capacity(n);
9922        for _ in 0..n {
9923            lines.next().expect("a member header");
9924            got.push(lines.next().expect("a member").to_owned());
9925        }
9926        got.sort();
9927        got
9928    }
9929
9930    #[test]
9931    fn the_algebra_answers_what_the_sets_share_and_do_not() {
9932        let mut f = Fixture::new();
9933        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9934        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9935        f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
9936
9937        assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
9938        assert_eq!(
9939            sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
9940            ["1", "2", "3", "4", "5"]
9941        );
9942        assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
9943        assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
9944
9945        // A key that is not there is an empty set, which empties an
9946        // intersection and does nothing at all to a union.
9947        assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
9948        assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
9949        assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
9950        assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
9951    }
9952
9953    #[test]
9954    fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
9955        let mut f = Fixture::new();
9956        f.run(&[b"SADD", b"a", b"x"]);
9957        assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
9958        assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
9959        assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
9960
9961        f.run(&[b"HELLO", b"3"]);
9962        assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
9963        assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
9964        assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
9965        assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
9966    }
9967
9968    #[test]
9969    fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
9970        let mut f = Fixture::new();
9971        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9972        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9973
9974        assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
9975        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
9976        assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
9977        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
9978        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
9979        assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
9980
9981        // An empty answer deletes the destination rather than leaving an empty
9982        // set behind, and the destination may be one of the sources.
9983        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
9984        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
9985        assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
9986        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
9987
9988        // And a destination holding something else is overwritten, the same way
9989        // SET overwrites, rather than refused.
9990        f.run(&[b"SET", b"str", b"v"]);
9991        assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
9992        assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
9993    }
9994
9995    #[test]
9996    fn sintercard_counts_without_building_and_stops_at_a_limit() {
9997        let mut f = Fixture::new();
9998        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9999        f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
10000
10001        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
10002        assert_eq!(
10003            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
10004            ":2\r\n"
10005        );
10006        assert_eq!(
10007            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
10008            ":3\r\n",
10009            "a limit of zero is no limit"
10010        );
10011        assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
10012        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
10013
10014        // The counted keys are what make its three error messages its own.
10015        assert_eq!(
10016            f.run(&[b"SINTERCARD", b"0", b"a"]),
10017            "-ERR numkeys should be greater than 0\r\n"
10018        );
10019        assert_eq!(
10020            f.run(&[b"SINTERCARD", b"abc", b"a"]),
10021            "-ERR numkeys should be greater than 0\r\n"
10022        );
10023        assert_eq!(
10024            f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
10025            "-ERR Number of keys can't be greater than number of args\r\n"
10026        );
10027        assert_eq!(
10028            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
10029            "-ERR LIMIT can't be negative\r\n"
10030        );
10031        assert_eq!(
10032            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
10033            "-ERR syntax error\r\n"
10034        );
10035        // A key really can be called LIMIT, which is why the count exists.
10036        f.run(&[b"SADD", b"LIMIT", b"2"]);
10037        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
10038    }
10039
10040    /// The two Redis 8.10 added, which are SINTERCARD's shape over a union and
10041    /// over a difference. Every number here was read off 8.10.1 first.
10042    #[test]
10043    fn sunioncard_and_sdiffcard_count_without_building() {
10044        let mut f = Fixture::new();
10045        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
10046        f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
10047
10048        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
10049        assert_eq!(
10050            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
10051            ":2\r\n"
10052        );
10053        assert_eq!(
10054            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
10055            ":6\r\n",
10056            "a limit of zero is no limit"
10057        );
10058        assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
10059        assert_eq!(
10060            f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
10061            ":4\r\n",
10062            "a missing key adds nothing to a union"
10063        );
10064
10065        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
10066        assert_eq!(
10067            f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
10068            ":1\r\n"
10069        );
10070        assert_eq!(
10071            f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
10072            ":2\r\n",
10073            "a difference is not symmetric"
10074        );
10075        assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
10076        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
10077        assert_eq!(
10078            f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
10079            ":0\r\n",
10080            "nothing taken away from nothing"
10081        );
10082
10083        // The same three messages SINTERCARD has, because the line is the same
10084        // line and is parsed once for all three.
10085        for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
10086            assert_eq!(
10087                f.run(&[name, b"0", b"a"]),
10088                "-ERR numkeys should be greater than 0\r\n"
10089            );
10090            assert_eq!(
10091                f.run(&[name, b"abc", b"a"]),
10092                "-ERR numkeys should be greater than 0\r\n"
10093            );
10094            assert_eq!(
10095                f.run(&[name, b"-1", b"a"]),
10096                "-ERR numkeys should be greater than 0\r\n"
10097            );
10098            assert_eq!(
10099                f.run(&[name, b"3", b"a", b"b"]),
10100                "-ERR Number of keys can't be greater than number of args\r\n"
10101            );
10102            assert_eq!(
10103                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
10104                "-ERR LIMIT can't be negative\r\n"
10105            );
10106            assert_eq!(
10107                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
10108                "-ERR LIMIT can't be negative\r\n",
10109                "a LIMIT that is not a number gets the negative message too"
10110            );
10111            assert_eq!(
10112                f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
10113                "-ERR syntax error\r\n"
10114            );
10115            assert_eq!(
10116                f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
10117                "-ERR syntax error\r\n"
10118            );
10119            assert_eq!(
10120                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
10121                "-ERR syntax error\r\n"
10122            );
10123        }
10124
10125        // And a key called LIMIT is a key, here as much as on SINTERCARD.
10126        f.run(&[b"SADD", b"LIMIT", b"2"]);
10127        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
10128        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
10129    }
10130
10131    #[test]
10132    fn the_algebra_answers_wrongtype_before_it_writes_anything() {
10133        let mut f = Fixture::new();
10134        f.run(&[b"SADD", b"a", b"1"]);
10135        f.run(&[b"SADD", b"d", b"old"]);
10136        f.run(&[b"SET", b"str", b"v"]);
10137
10138        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10139        for bad in [
10140            &[b"SINTER".as_slice(), b"a", b"str"][..],
10141            &[b"SUNION".as_slice(), b"str"][..],
10142            &[b"SDIFF".as_slice(), b"a", b"str"][..],
10143            &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
10144            &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
10145            &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
10146            &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
10147        ] {
10148            let reply = f.run(bad);
10149            assert_eq!(reply, wrong, "for {:?}", bad[0]);
10150        }
10151        assert_eq!(
10152            f.run(&[b"SMEMBERS", b"d"]),
10153            "*1\r\n$3\r\nold\r\n",
10154            "and the destination was left alone every time"
10155        );
10156    }
10157
10158    /// The leak a set can spring that nothing on the wire would ever show: the
10159    /// key goes, the body does not, and `DBSIZE` looks right the whole time.
10160    /// Not under Miri. What this claims is that memory does not grow over two
10161    /// hundred passes, so the passes are the claim rather than the way it
10162    /// happens to be written, and two hundred passes of a two hundred member
10163    /// collection is forty thousand trips through dispatch, which is what an
10164    /// interpreter charges for. A count small enough to run there would leave a
10165    /// server that reclaims nothing inside the bound and the test would pass on
10166    /// a leak. Nothing about memory safety goes uninterpreted either way: this
10167    /// is an accounting claim, and the same commands are run a few at a time by
10168    /// the tests around it.
10169    #[cfg_attr(miri, ignore = "the volume is the claim")]
10170    #[test]
10171    fn churning_sets_does_not_grow_the_server() {
10172        let mut f = Fixture::new();
10173        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
10174        let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
10175            .chain(std::iter::once(&b"s"[..]))
10176            .chain(members.iter().map(Vec::as_slice))
10177            .collect();
10178
10179        f.run(&args);
10180        f.run(&[b"DEL", b"s"]);
10181        f.server.compact_step();
10182        let after_first = f.server.memory_bytes();
10183
10184        for _ in 0..200 {
10185            f.run(&args);
10186            f.run(&[b"DEL", b"s"]);
10187            f.server.compact_step();
10188        }
10189        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
10190        assert!(
10191            f.server.memory_bytes() <= after_first * 2,
10192            "held {} after two hundred passes against {after_first} after one",
10193            f.server.memory_bytes()
10194        );
10195    }
10196
10197    // --------------------------------------------------------------- bitmaps
10198
10199    /// The two single bit commands, and the encoding rule underneath them.
10200    ///
10201    /// A write always leaves the value `raw` and a read never re-encodes, which
10202    /// is why the `int` key here is still `int` after a `GETBIT` and is `raw`
10203    /// with its first digit changed after a `SETBIT`.
10204    #[test]
10205    fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
10206        let mut f = Fixture::new();
10207        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
10208        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
10209        assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
10210        assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
10211        assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
10212        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
10213
10214        // Writing a nought past the end still creates the key and still pads.
10215        assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
10216        assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
10217        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
10218
10219        f.run(&[b"SET", b"num", b"12345"]);
10220        assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
10221        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
10222        assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
10223        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
10224        assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
10225    }
10226
10227    /// Counting, in bytes and in bits.
10228    ///
10229    /// The `0 -5 BIT` row is 25 on a real 8.10.1 and Redis's own documentation
10230    /// says 22 for it. The server is the thing being copied here.
10231    #[test]
10232    fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
10233        let mut f = Fixture::new();
10234        f.run(&[b"SET", b"mykey", b"foobar"]);
10235        assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
10236        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
10237        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
10238        assert_eq!(
10239            f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
10240            ":6\r\n"
10241        );
10242        assert_eq!(
10243            f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
10244            ":25\r\n"
10245        );
10246        assert_eq!(
10247            f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
10248            ":17\r\n"
10249        );
10250        assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
10251
10252        // A start past the end is left where it is and the end is pulled back,
10253        // so the range comes out backwards and counts nothing.
10254        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
10255
10256        // A lone start is a syntax error here, where BITPOS allows it.
10257        assert_eq!(
10258            f.run(&[b"BITCOUNT", b"mykey", b"0"]),
10259            "-ERR syntax error\r\n"
10260        );
10261        assert_eq!(
10262            f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
10263            "-ERR syntax error\r\n"
10264        );
10265    }
10266
10267    /// Searching, and the one place a miss is not minus one.
10268    ///
10269    /// A search for a nought that runs to the end of the string answers the
10270    /// length in bits, because the string is treated as if it had noughts after
10271    /// it forever. Give it an explicit end and it answers minus one instead.
10272    #[test]
10273    fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
10274        let mut f = Fixture::new();
10275        f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
10276        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
10277        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
10278        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
10279        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
10280        assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
10281
10282        f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
10283        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
10284        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
10285        assert_eq!(
10286            f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
10287            ":8\r\n"
10288        );
10289
10290        // A missing key is all noughts, so a one is never found and a nought is
10291        // at position zero.
10292        assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
10293        assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
10294    }
10295
10296    /// The eight operations, with the answers a real server gives for them.
10297    #[test]
10298    fn the_eight_combinations_write_what_a_real_server_writes() {
10299        let mut f = Fixture::new();
10300        f.run(&[b"SET", b"a", b"abc"]);
10301        f.run(&[b"SET", b"b", b"abd"]);
10302        let cases: &[(&[u8], &str)] = &[
10303            (b"AND", "ab`"),
10304            (b"OR", "abg"),
10305            (b"XOR", "\u{0}\u{0}\u{7}"),
10306            (b"DIFF", "\u{0}\u{0}\u{3}"),
10307            (b"DIFF1", "\u{0}\u{0}\u{4}"),
10308            (b"ANDOR", "ab`"),
10309            (b"ONE", "\u{0}\u{0}\u{7}"),
10310        ];
10311        for (op, want) in cases {
10312            assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
10313            assert_eq!(
10314                f.run(&[b"GET", b"d"]),
10315                format!("$3\r\n{want}\r\n"),
10316                "{op:?}"
10317            );
10318        }
10319        // The one whose answer is not text, so it is compared as bytes.
10320        assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
10321        assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
10322
10323        // A missing source is a string of noughts as long as it needs to be, so
10324        // an AND against one writes three zero bytes rather than nothing.
10325        assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
10326        assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
10327
10328        // Every source missing is an empty result, and an empty result takes
10329        // the destination with it.
10330        f.run(&[b"SET", b"dest", b"x"]);
10331        assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
10332        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10333    }
10334
10335    /// What `BITOP` says when it is asked for something it cannot do.
10336    #[test]
10337    fn bitop_names_the_operation_in_its_own_complaints() {
10338        let mut f = Fixture::new();
10339        f.run(&[b"SET", b"a", b"abc"]);
10340        assert_eq!(
10341            f.run(&[b"BITOP", b"nope", b"d", b"a"]),
10342            "-ERR syntax error\r\n"
10343        );
10344        assert_eq!(
10345            f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
10346            "-ERR BITOP NOT must be called with a single source key.\r\n"
10347        );
10348        for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
10349            assert_eq!(
10350                f.run(&[b"BITOP", op, b"d", b"a"]),
10351                format!(
10352                    "-ERR BITOP {} must be called with at least two source keys.\r\n",
10353                    String::from_utf8_lossy(op)
10354                )
10355            );
10356        }
10357        f.run(&[b"LPUSH", b"l", b"x"]);
10358        assert_eq!(
10359            f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
10360            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10361        );
10362    }
10363
10364    /// Packed fields, the three overflow policies and the `#` offset.
10365    #[test]
10366    fn bitfield_reads_and_writes_packed_fields() {
10367        let mut f = Fixture::new();
10368        assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
10369        assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
10370
10371        assert_eq!(
10372            f.run(&[
10373                b"BITFIELD",
10374                b"bf",
10375                b"INCRBY",
10376                b"u2",
10377                b"100",
10378                b"1",
10379                b"GET",
10380                b"u4",
10381                b"0"
10382            ]),
10383            "*2\r\n:1\r\n:0\r\n"
10384        );
10385        // The field at bit 100 is two bits wide, so it ends in the thirteenth
10386        // byte and the value grew to thirteen bytes to hold it.
10387        assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
10388
10389        // A `#` offset counts in fields rather than in bits.
10390        assert_eq!(
10391            f.run(&[
10392                b"BITFIELD",
10393                b"bf",
10394                b"SET",
10395                b"u8",
10396                b"#0",
10397                b"255",
10398                b"GET",
10399                b"u8",
10400                b"#0"
10401            ]),
10402            "*2\r\n:0\r\n:255\r\n"
10403        );
10404
10405        assert_eq!(
10406            f.run(&[
10407                b"BITFIELD",
10408                b"bf",
10409                b"OVERFLOW",
10410                b"SAT",
10411                b"INCRBY",
10412                b"i8",
10413                b"0",
10414                b"120",
10415                b"INCRBY",
10416                b"i8",
10417                b"0",
10418                b"120"
10419            ]),
10420            "*2\r\n:119\r\n:127\r\n"
10421        );
10422        assert_eq!(
10423            f.run(&[
10424                b"BITFIELD",
10425                b"bf2",
10426                b"OVERFLOW",
10427                b"FAIL",
10428                b"INCRBY",
10429                b"u2",
10430                b"0",
10431                b"5"
10432            ]),
10433            "*1\r\n$-1\r\n"
10434        );
10435        assert_eq!(
10436            f.run(&[
10437                b"BITFIELD",
10438                b"bf3",
10439                b"OVERFLOW",
10440                b"WRAP",
10441                b"INCRBY",
10442                b"u2",
10443                b"0",
10444                b"5"
10445            ]),
10446            "*1\r\n:1\r\n"
10447        );
10448        assert_eq!(
10449            f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
10450            "*1\r\n:4611686018427387904\r\n"
10451        );
10452    }
10453
10454    /// A bad subcommand anywhere in the line stops all of it.
10455    ///
10456    /// Redis checks the whole argument list before it runs any of it, so the
10457    /// `SET` in front of the bad type here never happens and the key it would
10458    /// have created is not there afterwards.
10459    #[test]
10460    fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
10461        let mut f = Fixture::new();
10462        let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
10463        assert_eq!(
10464            f.run(&[
10465                b"BITFIELD",
10466                b"bad",
10467                b"SET",
10468                b"u8",
10469                b"0",
10470                b"1",
10471                b"GET",
10472                b"u99",
10473                b"0"
10474            ]),
10475            bad_type
10476        );
10477        assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
10478        assert_eq!(
10479            f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
10480            bad_type
10481        );
10482        assert_eq!(
10483            f.run(&[b"BITFIELD", b"bad", b"GET"]),
10484            "-ERR syntax error\r\n"
10485        );
10486        assert_eq!(
10487            f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
10488            "-ERR syntax error\r\n"
10489        );
10490        assert_eq!(
10491            f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
10492            "-ERR syntax error\r\n"
10493        );
10494        assert_eq!(
10495            f.run(&[
10496                b"BITFIELD",
10497                b"bad",
10498                b"OVERFLOW",
10499                b"NOPE",
10500                b"GET",
10501                b"u8",
10502                b"0"
10503            ]),
10504            "-ERR Invalid OVERFLOW type specified\r\n"
10505        );
10506        assert_eq!(
10507            f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
10508            "-ERR value is not an integer or out of range\r\n"
10509        );
10510        for at in [&b"#-1"[..], b"abc"] {
10511            assert_eq!(
10512                f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
10513                "-ERR bit offset is not an integer or out of range\r\n"
10514            );
10515        }
10516    }
10517
10518    /// The read only twin reads, refuses to write, and creates nothing.
10519    #[test]
10520    fn bitfield_ro_answers_gets_and_refuses_the_rest() {
10521        let mut f = Fixture::new();
10522        f.run(&[b"SET", b"n", b"123"]);
10523        assert_eq!(
10524            f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
10525            "*1\r\n:49\r\n"
10526        );
10527        // A read does not unpack an int the way a write does.
10528        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
10529
10530        // An OVERFLOW word is allowed even though nothing here can overflow.
10531        assert_eq!(
10532            f.run(&[
10533                b"BITFIELD_RO",
10534                b"n",
10535                b"OVERFLOW",
10536                b"SAT",
10537                b"GET",
10538                b"u8",
10539                b"0"
10540            ]),
10541            "*1\r\n:49\r\n"
10542        );
10543        for sub in [&b"SET"[..], b"INCRBY"] {
10544            assert_eq!(
10545                f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
10546                "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
10547            );
10548        }
10549
10550        assert_eq!(
10551            f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
10552            "*1\r\n:0\r\n"
10553        );
10554        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10555    }
10556
10557    /// The offsets a bitmap command will not take.
10558    #[test]
10559    fn an_offset_off_the_end_of_the_world_is_refused() {
10560        let mut f = Fixture::new();
10561        let bad = "-ERR bit offset is not an integer or out of range\r\n";
10562        for arg in [&b"abc"[..], b"-1", b"4294967296"] {
10563            assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
10564            assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
10565        }
10566        for arg in [&b"2"[..], b"-1"] {
10567            assert_eq!(
10568                f.run(&[b"BITPOS", b"k", arg]),
10569                "-ERR The bit argument must be 1 or 0.\r\n"
10570            );
10571        }
10572        assert_eq!(
10573            f.run(&[b"BITPOS", b"k", b"abc"]),
10574            "-ERR value is not an integer or out of range\r\n"
10575        );
10576        assert_eq!(
10577            f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
10578            "-ERR value is not an integer or out of range\r\n"
10579        );
10580        let bad_bit = "-ERR bit is not an integer or out of range\r\n";
10581        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
10582        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
10583    }
10584
10585    /// Every one of the seven refuses a key that is not a string.
10586    #[test]
10587    fn every_bitmap_command_says_wrongtype() {
10588        let mut f = Fixture::new();
10589        f.run(&[b"LPUSH", b"l", b"x"]);
10590        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10591        let cases: &[&[&[u8]]] = &[
10592            &[b"SETBIT", b"l", b"0", b"1"],
10593            &[b"GETBIT", b"l", b"0"],
10594            &[b"BITCOUNT", b"l"],
10595            &[b"BITPOS", b"l", b"1"],
10596            &[b"BITOP", b"AND", b"d", b"l"],
10597            &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
10598            &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
10599        ];
10600        for case in cases {
10601            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10602        }
10603    }
10604
10605    // --------------------------------------------------------- hyperloglogs
10606
10607    #[test]
10608    fn a_sketch_is_added_to_and_counted() {
10609        let mut f = Fixture::new();
10610        // Creating the key counts as a change, even with nothing to add.
10611        assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
10612        assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
10613        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
10614        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
10615        // And it is a string, which is not an implementation detail: a client
10616        // can `GET` a sketch out of one server and `SET` it into another.
10617        assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
10618        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
10619
10620        assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
10621        assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
10622        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10623    }
10624
10625    #[test]
10626    fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
10627        let mut f = Fixture::new();
10628        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10629        // Not text, so it is compared as bytes.
10630        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";
10631        let mut reply = b"$27\r\n".to_vec();
10632        reply.extend_from_slice(want);
10633        reply.extend_from_slice(b"\r\n");
10634        assert_eq!(f.raw(&[b"GET", b"h"]), reply);
10635    }
10636
10637    #[test]
10638    fn counting_several_keys_counts_their_union() {
10639        let mut f = Fixture::new();
10640        f.run(&[b"PFADD", b"a", b"x", b"y"]);
10641        f.run(&[b"PFADD", b"b", b"y", b"z"]);
10642        assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
10643        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
10644        // A key that is not there is an empty sketch, not an error and not
10645        // something that gets created by being counted.
10646        assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
10647        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
10648        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10649    }
10650
10651    #[test]
10652    fn a_merge_keeps_what_the_destination_had() {
10653        let mut f = Fixture::new();
10654        f.run(&[b"PFADD", b"a", b"x", b"y"]);
10655        f.run(&[b"PFADD", b"b", b"z"]);
10656        assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
10657        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
10658        // The destination is one of the sources, so a second merge adds to it.
10659        f.run(&[b"PFADD", b"c", b"w"]);
10660        assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
10661        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
10662        // And with no sources it is a no-op that still answers OK and still
10663        // creates a destination that was not there.
10664        assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
10665        assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
10666    }
10667
10668    /// Not under Miri, and not for the number of commands: a dense sketch is
10669    /// sixteen thousand three hundred and eighty four registers and every
10670    /// command here walks all of them, so one `PFCOUNT` is more interpreted
10671    /// work than a hundred ordinary tests. The registers and the walking are in
10672    /// `yo-kv`, where fifteen tests of their own cover both encodings and where
10673    /// the interpreter does run over them. What is left here is the dispatch
10674    /// around it, which is the same dispatch every other command in this file
10675    /// goes through.
10676    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
10677    #[test]
10678    fn the_debug_forms_answer_four_different_shapes() {
10679        let mut f = Fixture::new();
10680        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10681        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
10682        assert_eq!(
10683            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10684            "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
10685        );
10686        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
10687        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
10688        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
10689        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
10690        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10691        // A dense sketch has no opcodes left to print.
10692        assert_eq!(
10693            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10694            "-ERR HLL encoding is not sparse\r\n"
10695        );
10696
10697        // All 16384 registers, of which three are not nought.
10698        let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
10699        assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
10700        assert_eq!(reply.matches(":0\r\n").count(), 16381);
10701        assert_eq!(reply.matches(":1\r\n").count(), 2);
10702        assert_eq!(reply.matches(":2\r\n").count(), 1);
10703
10704        assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
10705    }
10706
10707    #[test]
10708    fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
10709        let mut f = Fixture::new();
10710        f.run(&[b"SET", b"plain", b"not a sketch"]);
10711        let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
10712        assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
10713        assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
10714        assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
10715        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
10716
10717        // A key that is not a string at all gets the ordinary sentence, and a
10718        // destination that would have been written is not created.
10719        f.run(&[b"RPUSH", b"l", b"x"]);
10720        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10721        assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
10722        assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
10723        assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
10724        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10725        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
10726    }
10727
10728    #[test]
10729    fn pfdebug_has_its_own_complaints() {
10730        let mut f = Fixture::new();
10731        f.run(&[b"PFADD", b"h", b"a"]);
10732        // The word is quoted exactly as the client spelled it, and this is not
10733        // the "Try X HELP." sentence every other container command uses.
10734        assert_eq!(
10735            f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
10736            "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
10737        );
10738        // Where all three of the real commands take a missing key as empty.
10739        let gone = "-ERR The specified key does not exist\r\n";
10740        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
10741        assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
10742        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
10743        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
10744        assert_eq!(
10745            f.run(&[b"PFDEBUG"]),
10746            "-ERR wrong number of arguments for 'pfdebug' command\r\n"
10747        );
10748        assert_eq!(
10749            f.run(&[b"PFSELFTEST", b"x"]),
10750            "-ERR wrong number of arguments for 'pfselftest' command\r\n"
10751        );
10752    }
10753
10754    #[test]
10755    fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
10756        let mut f = Fixture::new();
10757        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10758        // The sketch with its last byte cut off, which is still a header and a
10759        // magic and is a run length encoding that stops short of register 16384.
10760        let reply = f.raw(&[b"GET", b"h"]);
10761        let short = reply[5..reply.len() - 3].to_vec();
10762        f.run(&[b"SET", b"h", &short]);
10763        assert_eq!(
10764            f.run(&[b"PFCOUNT", b"h"]),
10765            "-INVALIDOBJ Corrupted HLL object detected\r\n"
10766        );
10767    }
10768
10769    #[test]
10770    fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
10771        let mut f = Fixture::new();
10772        // One that stays sparse and one that has gone dense, since the payload
10773        // carries the bytes and the two encodings are different lengths.
10774        f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
10775        // Ten thousand elements is what takes a sketch dense on its own, and it
10776        // is ten thousand trips through dispatch, which is what Miri charges
10777        // for. There the same sketch is taken across by hand. What this test is
10778        // about is a dense payload surviving a round trip and the encoding is
10779        // dense either way: that a sketch converts when it fills up is what
10780        // `the_debug_forms_answer_four_different_shapes` is for.
10781        if cfg!(miri) {
10782            f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
10783            f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
10784        } else {
10785            for i in 0..10_000u32 {
10786                let ele = format!("e{i}");
10787                f.run(&[b"PFADD", b"big", ele.as_bytes()]);
10788            }
10789        }
10790        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
10791        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
10792
10793        for key in [&b"small"[..], b"big"] {
10794            let mut copy = key.to_vec();
10795            copy.push(b'2');
10796            let bytes = payload(&f.raw(&[b"DUMP", key]));
10797            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
10798            // The bytes, the encoding and the estimate all come back, which is
10799            // the whole of what byte compatibility across a round trip means.
10800            assert_eq!(f.raw(&[b"GET", &copy]), f.raw(&[b"GET", key]));
10801            assert_eq!(
10802                f.run(&[b"PFDEBUG", b"ENCODING", &copy]),
10803                f.run(&[b"PFDEBUG", b"ENCODING", key])
10804            );
10805            assert_eq!(f.run(&[b"PFCOUNT", &copy]), f.run(&[b"PFCOUNT", key]));
10806        }
10807        assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
10808        assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
10809    }
10810
10811    /// One RESP2 bulk string. The JSON replies are almost all one of these and
10812    /// the text inside them has quotes in it, so writing the frame out by hand
10813    /// buries the part of the assertion that matters.
10814    fn bulk(s: &str) -> String {
10815        format!("${}\r\n{s}\r\n", s.len())
10816    }
10817
10818    /// A RESP2 array of bulk strings, which is what most of the list replies
10819    /// are and what writing them out by hand in every assertion looks like.
10820    fn bulks(parts: &[&str]) -> String {
10821        let mut s = format!("*{}\r\n", parts.len());
10822        for p in parts {
10823            s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
10824        }
10825        s
10826    }
10827
10828    #[test]
10829    fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
10830        let mut f = Fixture::new();
10831        // Each element in turn goes at the head, so the last one sent is at the
10832        // front when it is over. That reads like a bug in the client and it is
10833        // what every Redis has always done.
10834        assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
10835        assert_eq!(
10836            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10837            bulks(&["c", "b", "a"])
10838        );
10839        assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
10840        assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
10841        assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
10842        assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
10843        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
10844        assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
10845    }
10846
10847    #[test]
10848    fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
10849        let mut f = Fixture::new();
10850        assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
10851        assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
10852        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10853        f.run(&[b"RPUSH", b"k", b"a"]);
10854        assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
10855        assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
10856        assert_eq!(
10857            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10858            bulks(&["z", "a", "y"])
10859        );
10860    }
10861
10862    /// The four ways a pop can come back with nothing, which are three
10863    /// different replies and a RESP2 client can tell all of them apart.
10864    #[test]
10865    fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
10866        let mut f = Fixture::new();
10867        assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
10868        assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
10869        assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
10870        assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
10871        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10872        // A count of zero against a list that is there is an empty array and
10873        // not a null array, which is the fourth answer.
10874        assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
10875        assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
10876        // More than there is takes what there is and the key goes with it.
10877        assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
10878        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10879    }
10880
10881    #[test]
10882    fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
10883        let mut f = Fixture::new();
10884        f.run(&[b"RPUSH", b"k", b"a"]);
10885        let range = "-ERR value is out of range, must be positive\r\n";
10886        assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
10887        assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
10888        assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
10889        // Redis calls this an arity error and not a syntax error, which is a
10890        // distinction it does not always make.
10891        assert_eq!(
10892            f.run(&[b"LPOP", b"k", b"1", b"2"]),
10893            "-ERR wrong number of arguments for 'lpop' command\r\n"
10894        );
10895        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10896    }
10897
10898    #[test]
10899    fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
10900        let mut f = Fixture::new();
10901        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10902        assert_eq!(
10903            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10904            bulks(&["a", "b", "c"])
10905        );
10906        assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
10907        assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
10908        assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
10909        assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
10910        assert_eq!(
10911            f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
10912            bulks(&["a", "b", "c"])
10913        );
10914        // A key that is not there is an empty range and not a nil, which is the
10915        // one place a list disagrees with a set.
10916        assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
10917        assert_eq!(
10918            f.run(&[b"LRANGE", b"k", b"a", b"b"]),
10919            "-ERR value is not an integer or out of range\r\n"
10920        );
10921    }
10922
10923    #[test]
10924    fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
10925        let mut f = Fixture::new();
10926        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10927        assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
10928        assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
10929        assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
10930        assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
10931        assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
10932        assert_eq!(
10933            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10934            bulks(&["a", "b", "z"])
10935        );
10936        // Both ways of missing are errors here rather than a nil, because a
10937        // list is never empty and there is nothing else the reply could be.
10938        assert_eq!(
10939            f.run(&[b"LSET", b"k", b"99", b"z"]),
10940            "-ERR index out of range\r\n"
10941        );
10942        assert_eq!(
10943            f.run(&[b"LSET", b"nope", b"0", b"z"]),
10944            "-ERR no such key\r\n"
10945        );
10946    }
10947
10948    #[test]
10949    fn linsert_says_three_things_with_one_signed_number() {
10950        let mut f = Fixture::new();
10951        // Zero for a key that is not there, which is not the same as minus one
10952        // for a pivot that is not in a list that is.
10953        assert_eq!(
10954            f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
10955            ":0\r\n"
10956        );
10957        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10958        assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
10959        assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
10960        assert_eq!(
10961            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10962            bulks(&["X", "a", "b", "Y"])
10963        );
10964        assert_eq!(
10965            f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
10966            ":-1\r\n"
10967        );
10968        assert_eq!(
10969            f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
10970            "-ERR syntax error\r\n"
10971        );
10972    }
10973
10974    #[test]
10975    fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
10976        let mut f = Fixture::new();
10977        f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
10978        assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
10979        assert_eq!(
10980            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10981            bulks(&["b", "c", "a"])
10982        );
10983        assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
10984        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10985        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
10986        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
10987        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10988        assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
10989    }
10990
10991    #[test]
10992    fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
10993        let mut f = Fixture::new();
10994        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
10995        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
10996        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10997        // `LTRIM k 1 0` is the documented way to empty a list, so it has to
10998        // leave `EXISTS` answering zero rather than leaving an empty one.
10999        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
11000        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
11001        assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
11002    }
11003
11004    #[test]
11005    fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
11006        let mut f = Fixture::new();
11007        f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
11008        assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
11009        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
11010        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
11011        assert_eq!(
11012            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
11013            "*2\r\n:0\r\n:3\r\n"
11014        );
11015        assert_eq!(
11016            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
11017            "*3\r\n:6\r\n:3\r\n:0\r\n"
11018        );
11019        // MAXLEN counts elements looked at and not matches found, so three
11020        // stops after `a b c` and finds the one match in it.
11021        assert_eq!(
11022            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
11023            "*1\r\n:0\r\n"
11024        );
11025        // Nothing found is three different replies depending on how it was
11026        // asked and whether the key is there at all.
11027        assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
11028        assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
11029        assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
11030        assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
11031    }
11032
11033    #[test]
11034    fn lpos_words_its_three_mistakes_the_way_redis_does() {
11035        let mut f = Fixture::new();
11036        f.run(&[b"RPUSH", b"p", b"a"]);
11037        // The whole sentence and not a prefix, because the older wording of it
11038        // is still all over the internet and clients match on the text.
11039        assert_eq!(
11040            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
11041            "-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"
11042        );
11043        assert_eq!(
11044            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
11045            "-ERR COUNT can't be negative\r\n"
11046        );
11047        assert_eq!(
11048            f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
11049            "-ERR MAXLEN can't be negative\r\n"
11050        );
11051        assert_eq!(
11052            f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
11053            "-ERR syntax error\r\n"
11054        );
11055        assert_eq!(
11056            f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
11057            "-ERR syntax error\r\n"
11058        );
11059    }
11060
11061    #[test]
11062    fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
11063        let mut f = Fixture::new();
11064        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
11065        assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
11066        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
11067        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
11068        assert_eq!(
11069            f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
11070            "$1\r\na\r\n"
11071        );
11072        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
11073        // The same key twice is the documented way to rotate a list and falls
11074        // out of taking the element before deciding where to put it.
11075        f.run(&[b"DEL", b"r"]);
11076        f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
11077        assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
11078        assert_eq!(
11079            f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
11080            bulks(&["3", "1", "2"])
11081        );
11082        assert_eq!(
11083            f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
11084            "$-1\r\n"
11085        );
11086        assert_eq!(
11087            f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
11088            "-ERR syntax error\r\n"
11089        );
11090    }
11091
11092    #[test]
11093    fn a_move_checks_the_destination_before_it_takes_anything() {
11094        let mut f = Fixture::new();
11095        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
11096        f.run(&[b"SET", b"str", b"v"]);
11097        assert_eq!(
11098            f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
11099            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
11100        );
11101        // The element is still where it was, rather than having gone nowhere.
11102        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
11103    }
11104
11105    #[test]
11106    fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
11107        // OBO is what you get from sending LMOVE that many times, BULK keeps
11108        // the source order. The two only differ when both ends are the same,
11109        // which is the whole reason the word exists.
11110        for (from, to, order, want) in [
11111            ("LEFT", "RIGHT", "OBO", ["a", "b"]),
11112            ("LEFT", "RIGHT", "BULK", ["a", "b"]),
11113            ("LEFT", "LEFT", "OBO", ["b", "a"]),
11114            ("LEFT", "LEFT", "BULK", ["a", "b"]),
11115            ("RIGHT", "LEFT", "OBO", ["d", "e"]),
11116            ("RIGHT", "LEFT", "BULK", ["d", "e"]),
11117            ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
11118            ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
11119        ] {
11120            let mut f = Fixture::new();
11121            f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
11122            let how = format!("{from} {to} {order}");
11123            let reply = f.run(&[
11124                b"LMOVEM",
11125                b"s",
11126                b"d",
11127                from.as_bytes(),
11128                to.as_bytes(),
11129                b"COUNT",
11130                b"2",
11131                order.as_bytes(),
11132            ]);
11133            assert_eq!(reply, bulks(&want), "the reply for {how}");
11134            assert_eq!(
11135                f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
11136                bulks(&want),
11137                "the destination for {how}"
11138            );
11139        }
11140    }
11141
11142    #[test]
11143    fn a_block_move_of_one_needs_no_count_at_all() {
11144        let mut f = Fixture::new();
11145        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
11146        assert_eq!(
11147            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
11148            bulks(&["a"])
11149        );
11150        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
11151        // Six and seven arguments are neither of the two forms, so the
11152        // reference calls both of them a syntax error rather than guessing.
11153        assert_eq!(
11154            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
11155            "-ERR syntax error\r\n"
11156        );
11157        assert_eq!(
11158            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
11159            "-ERR syntax error\r\n"
11160        );
11161    }
11162
11163    #[test]
11164    fn a_block_move_with_exactly_takes_all_of_them_or_none() {
11165        let mut f = Fixture::new();
11166        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
11167        // A null array and not a null bulk string, which `redis-cli` prints as
11168        // `(nil)` either way and only the raw wire tells apart. What it would
11169        // have sent is an array, so its nothing is an array's nothing.
11170        assert_eq!(
11171            f.run(&[
11172                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
11173            ]),
11174            "*-1\r\n"
11175        );
11176        assert_eq!(
11177            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
11178            bulks(&["a", "b", "c"])
11179        );
11180        // COUNT takes what there is, and an emptied source goes away.
11181        assert_eq!(
11182            f.run(&[
11183                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
11184            ]),
11185            bulks(&["a", "b", "c"])
11186        );
11187        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
11188        assert_eq!(
11189            f.run(&[
11190                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
11191            ]),
11192            "*-1\r\n"
11193        );
11194    }
11195
11196    #[test]
11197    fn a_block_move_onto_itself_rotates_by_the_count() {
11198        let mut f = Fixture::new();
11199        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
11200        assert_eq!(
11201            f.run(&[
11202                b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
11203            ]),
11204            bulks(&["a", "b"])
11205        );
11206        assert_eq!(
11207            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
11208            bulks(&["c", "a", "b"])
11209        );
11210    }
11211
11212    #[test]
11213    fn a_block_move_reads_the_count_before_the_ordering_word() {
11214        let mut f = Fixture::new();
11215        f.run(&[b"RPUSH", b"s", b"a", b"b"]);
11216        f.run(&[b"SET", b"str", b"v"]);
11217        let count = "-ERR count should be greater than 0\r\n";
11218        assert_eq!(
11219            f.run(&[
11220                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
11221            ]),
11222            count
11223        );
11224        assert_eq!(
11225            f.run(&[
11226                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
11227            ]),
11228            count
11229        );
11230        assert_eq!(
11231            f.run(&[
11232                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
11233            ]),
11234            "-ERR syntax error\r\n"
11235        );
11236        assert_eq!(
11237            f.run(&[
11238                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
11239            ]),
11240            "-ERR syntax error\r\n"
11241        );
11242        // Every argument is read before the keys are looked at, so a bad count
11243        // beats a wrong type even when the type is wrong on the source.
11244        assert_eq!(
11245            f.run(&[
11246                b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
11247            ]),
11248            count
11249        );
11250        assert_eq!(
11251            f.run(&[
11252                b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
11253            ]),
11254            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
11255        );
11256        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
11257    }
11258
11259    #[test]
11260    fn lmpop_answers_from_the_first_key_that_has_anything() {
11261        let mut f = Fixture::new();
11262        f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
11263        // The name of the key that answered comes back with the elements,
11264        // because the client cannot work out which one it was.
11265        assert_eq!(
11266            f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
11267            "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
11268        );
11269        assert_eq!(
11270            f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
11271            "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
11272        );
11273        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
11274        // A null array and not a null, even though what it stands in for is an
11275        // array holding a key name and then another array.
11276        assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
11277    }
11278
11279    #[test]
11280    fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
11281        let mut f = Fixture::new();
11282        f.run(&[b"RPUSH", b"k", b"a"]);
11283        assert_eq!(
11284            f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
11285            "-ERR numkeys should be greater than 0\r\n"
11286        );
11287        assert_eq!(
11288            f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
11289            "-ERR numkeys should be greater than 0\r\n"
11290        );
11291        assert_eq!(
11292            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
11293            "-ERR count should be greater than 0\r\n"
11294        );
11295        // A key count that eats the direction is a syntax error and not a
11296        // sentence about key counts, because the direction is simply not there.
11297        assert_eq!(
11298            f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
11299            "-ERR syntax error\r\n"
11300        );
11301        assert_eq!(
11302            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
11303            "-ERR syntax error\r\n"
11304        );
11305        assert_eq!(
11306            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
11307            "-ERR syntax error\r\n"
11308        );
11309        assert_eq!(
11310            f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
11311            "-ERR syntax error\r\n"
11312        );
11313        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
11314    }
11315
11316    #[test]
11317    fn every_list_command_says_wrongtype_and_writes_nothing() {
11318        let mut f = Fixture::new();
11319        f.run(&[b"SET", b"str", b"v"]);
11320        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11321        for cmd in [
11322            &[b"LPUSH".as_slice(), b"str", b"a"][..],
11323            &[b"RPUSH", b"str", b"a"],
11324            &[b"LPUSHX", b"str", b"a"],
11325            &[b"RPUSHX", b"str", b"a"],
11326            &[b"LPOP", b"str"],
11327            &[b"LPOP", b"str", b"2"],
11328            &[b"RPOP", b"str"],
11329            &[b"LLEN", b"str"],
11330            &[b"LRANGE", b"str", b"0", b"-1"],
11331            &[b"LINDEX", b"str", b"0"],
11332            &[b"LSET", b"str", b"0", b"a"],
11333            &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
11334            &[b"LREM", b"str", b"0", b"a"],
11335            &[b"LTRIM", b"str", b"0", b"-1"],
11336            &[b"LPOS", b"str", b"a"],
11337            &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
11338            &[b"RPOPLPUSH", b"str", b"d"],
11339            &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
11340            &[b"LMPOP", b"1", b"str", b"LEFT"],
11341        ] {
11342            assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
11343        }
11344        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
11345        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11346    }
11347
11348    /// A timeout is not an integer and it is not an ordinary float either: the
11349    /// three sentences it can answer with are its own, and which one a given
11350    /// argument gets is not what reading the code would suggest.
11351    #[test]
11352    fn a_timeout_has_three_ways_of_being_wrong() {
11353        let mut f = Fixture::new();
11354        let not_float = "-ERR timeout is not a float or out of range\r\n";
11355        let range = "-ERR timeout is out of range\r\n";
11356        for (bad, want) in [
11357            (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
11358            (&[b"BLPOP", b"k", b"nan"], not_float),
11359            (&[b"BLPOP", b"k", b""], not_float),
11360            // Whitespace on either side, which `strtold` would take and Redis
11361            // does not.
11362            (&[b"BLPOP", b"k", b" 1"], not_float),
11363            (&[b"BLPOP", b"k", b"1 "], not_float),
11364            (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
11365            (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
11366            // These three parse, so they are not the not-a-float error, and all
11367            // three are further off than an i64 of milliseconds reaches.
11368            (&[b"BLPOP", b"k", b"1e400"], range),
11369            (&[b"BLPOP", b"k", b"inf"], range),
11370            (&[b"BLPOP", b"k", b"9999999999999999"], range),
11371            (&[b"BRPOP", b"k", b"abc"], not_float),
11372            (
11373                &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
11374                not_float,
11375            ),
11376            (
11377                &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
11378                "-ERR timeout is negative\r\n",
11379            ),
11380            (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
11381        ] {
11382            assert_eq!(f.run(bad), want, "for {bad:?}");
11383        }
11384    }
11385
11386    /// A timeout of exactly zero means no timeout, and there are two ways of
11387    /// writing exactly zero.
11388    #[test]
11389    fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
11390        let mut f = Fixture::new();
11391        for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
11392            let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
11393            assert_eq!(flow, Flow::Block, "for {timeout:?}");
11394            assert!(out.is_empty(), "for {timeout:?}");
11395        }
11396        // Positive, so it is a real deadline, and the deadline is this
11397        // millisecond. Nothing is written here either: the reply comes from the
11398        // sweep, which is the engine's and not this layer's.
11399        let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
11400        assert_eq!(flow, Flow::Block);
11401        assert!(out.is_empty());
11402    }
11403
11404    #[test]
11405    fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
11406        let mut f = Fixture::new();
11407        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
11408
11409        // The one difference from LPOP: the reply names the key that answered,
11410        // which is what makes BLPOP over several keys usable.
11411        assert_eq!(
11412            f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
11413            (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
11414        );
11415        assert_eq!(
11416            f.run(&[b"BRPOP", b"L", b"0"]),
11417            "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
11418        );
11419        assert_eq!(
11420            f.run(&[
11421                b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
11422            ]),
11423            "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11424        );
11425        assert_eq!(
11426            f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
11427            "$1\r\nd\r\n"
11428        );
11429        assert_eq!(
11430            f.run(&[b"EXISTS", b"L"]),
11431            ":0\r\n",
11432            "and the key went with it"
11433        );
11434        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
11435        // Onto itself, which is how a list is rotated and is a real thing to ask
11436        // a blocking move for.
11437        f.run(&[b"RPUSH", b"D", b"x"]);
11438        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
11439        assert_eq!(
11440            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11441            "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
11442        );
11443    }
11444
11445    #[test]
11446    fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
11447        let mut f = Fixture::new();
11448        f.run(&[b"RPUSH", b"k", b"a"]);
11449        for (bad, want) in [
11450            (
11451                &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
11452                "-ERR numkeys should be greater than 0\r\n",
11453            ),
11454            (
11455                &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
11456                "-ERR numkeys should be greater than 0\r\n",
11457            ),
11458            // Two keys named and one given, so the word that should have been
11459            // the direction is a key and there is no direction left.
11460            (
11461                &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
11462                "-ERR syntax error\r\n",
11463            ),
11464            (
11465                &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
11466                "-ERR syntax error\r\n",
11467            ),
11468            (
11469                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
11470                "-ERR syntax error\r\n",
11471            ),
11472            (
11473                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
11474                "-ERR syntax error\r\n",
11475            ),
11476            // A count that is not a number at all gets the same sentence a zero
11477            // or a negative one gets, rather than the usual one about integers.
11478            (
11479                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
11480                "-ERR count should be greater than 0\r\n",
11481            ),
11482            (
11483                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
11484                "-ERR count should be greater than 0\r\n",
11485            ),
11486        ] {
11487            assert_eq!(f.run(bad), want, "for {bad:?}");
11488        }
11489        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
11490    }
11491
11492    #[test]
11493    fn a_blocking_move_reads_its_directions_before_its_timeout() {
11494        let mut f = Fixture::new();
11495        // Both are wrong. Redis checks the directions first, so this is the
11496        // syntax error and not a complaint about the timeout.
11497        assert_eq!(
11498            f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
11499            "-ERR syntax error\r\n"
11500        );
11501        assert_eq!(
11502            f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
11503            "-ERR syntax error\r\n"
11504        );
11505    }
11506
11507    /// `BLMOVEM` answers exactly what `LMOVEM` answers when it does not have to
11508    /// wait, which is the same relationship every other command in this file has
11509    /// with the one it wraps.
11510    #[test]
11511    fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
11512        let mut f = Fixture::new();
11513        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
11514        assert_eq!(
11515            f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
11516            (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
11517        );
11518        assert_eq!(
11519            f.run(&[
11520                b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
11521            ]),
11522            bulks(&["e", "d"])
11523        );
11524        assert_eq!(
11525            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11526            bulks(&["a", "e", "d"])
11527        );
11528        // `EXACTLY` with enough there does not wait either.
11529        assert_eq!(
11530            f.run(&[
11531                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
11532            ]),
11533            bulks(&["b", "c"])
11534        );
11535        assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
11536    }
11537
11538    /// The one thing `BLMOVEM` decides differently from the other five: `COUNT`
11539    /// is ready as soon as there is anything and `EXACTLY` is not ready until the
11540    /// whole block has arrived.
11541    #[test]
11542    fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
11543        let mut f = Fixture::new();
11544        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11545        // Two there and three asked for. `COUNT` takes the two.
11546        assert_eq!(
11547            f.flow(&[
11548                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
11549            ]),
11550            (Flow::Continue, bulks(&["a", "b"]))
11551        );
11552
11553        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11554        // The same line with `EXACTLY` parks instead, and takes nothing on the
11555        // way past.
11556        assert_eq!(
11557            f.flow(&[
11558                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
11559            ])
11560            .0,
11561            Flow::Block
11562        );
11563        assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
11564    }
11565
11566    #[test]
11567    fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
11568        let mut f = Fixture::new();
11569        let syntax = "-ERR syntax error\r\n";
11570        // All three are wrong and the directions are read first.
11571        assert_eq!(
11572            f.run(&[
11573                b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
11574            ]),
11575            syntax
11576        );
11577        // Directions fine, timeout and count both wrong, so the timeout wins.
11578        assert_eq!(
11579            f.run(&[
11580                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
11581            ]),
11582            "-ERR timeout is not a float or out of range\r\n"
11583        );
11584        assert_eq!(
11585            f.run(&[
11586                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
11587            ]),
11588            "-ERR timeout is negative\r\n"
11589        );
11590        // And with the timeout fine, the count before the ordering word.
11591        assert_eq!(
11592            f.run(&[
11593                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
11594            ]),
11595            "-ERR count should be greater than 0\r\n"
11596        );
11597        assert_eq!(
11598            f.run(&[
11599                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
11600            ]),
11601            syntax
11602        );
11603        // Seven and eight arguments are neither of the two forms, the same way
11604        // six and seven are for `LMOVEM`.
11605        assert_eq!(
11606            f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
11607            syntax
11608        );
11609        assert_eq!(
11610            f.run(&[
11611                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
11612            ]),
11613            syntax
11614        );
11615    }
11616
11617    /// The four ways a blocking command sees a key of another type, and the one
11618    /// way it does not.
11619    #[test]
11620    fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
11621        let mut f = Fixture::new();
11622        f.run(&[b"SET", b"S", b"v"]);
11623        f.run(&[b"RPUSH", b"D", b"x"]);
11624        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11625
11626        assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
11627        // Every key is checked even when an earlier one would have blocked, so
11628        // an empty key in front of a string does not hide it.
11629        assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
11630        assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
11631        assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
11632        assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
11633        // The destination, which is only reached because the source has
11634        // something in it.
11635        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
11636        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
11637        assert_eq!(
11638            f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
11639            wrong
11640        );
11641        assert_eq!(
11642            f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
11643            wrong
11644        );
11645
11646        // And the one that does not: an empty source means the destination is
11647        // never looked at, so this waits rather than erroring, and on a real
11648        // server it times out.
11649        assert_eq!(
11650            f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11651                .0,
11652            Flow::Block
11653        );
11654        // `BLMOVEM` has a second way of not being ready, and it hides the
11655        // destination just as well: the source is a list with two elements in it
11656        // and `EXACTLY` wants three, so the string never gets looked at.
11657        assert_eq!(
11658            f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11659                .0,
11660            Flow::Block
11661        );
11662        f.run(&[b"RPUSH", b"E", b"1", b"2"]);
11663        assert_eq!(
11664            f.flow(&[
11665                b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
11666            ])
11667            .0,
11668            Flow::Block
11669        );
11670    }
11671
11672    /// The same churn the set and the string get, because a list that leaks a
11673    /// chunk per push looks exactly like one that does not until it has run for
11674    /// an afternoon.
11675    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
11676    #[cfg_attr(miri, ignore = "the volume is the claim")]
11677    #[test]
11678    fn churning_lists_does_not_grow_the_server() {
11679        let mut f = Fixture::new();
11680        let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
11681        let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
11682            .into_iter()
11683            .chain(vals.iter().map(Vec::as_slice))
11684            .collect();
11685
11686        f.run(&args);
11687        f.run(&[b"DEL", b"k"]);
11688        f.server.compact_step();
11689        let after_first = f.server.memory_bytes();
11690
11691        for _ in 0..200 {
11692            f.run(&args);
11693            f.run(&[b"LTRIM", b"k", b"1", b"0"]);
11694            f.server.compact_step();
11695        }
11696        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11697        assert!(
11698            f.server.memory_bytes() <= after_first * 2,
11699            "held {} after two hundred passes against {after_first} after one",
11700            f.server.memory_bytes()
11701        );
11702    }
11703
11704    // ------------------------------------------------------------ sorted set
11705
11706    #[test]
11707    fn a_sorted_set_takes_scores_and_gives_them_back() {
11708        let mut f = Fixture::new();
11709        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
11710        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
11711        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
11712        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
11713        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
11714        assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
11715        assert_eq!(
11716            f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
11717            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
11718        );
11719        assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
11720        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
11721        // The key goes when the last member does.
11722        assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
11723        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11724    }
11725
11726    #[test]
11727    fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
11728        let mut f = Fixture::new();
11729        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
11730        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
11731        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
11732        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
11733
11734        f.out = Out::new(Proto::Resp3);
11735        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
11736        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
11737        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
11738        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
11739    }
11740
11741    #[test]
11742    fn the_zadd_options_gate_what_gets_written() {
11743        let mut f = Fixture::new();
11744        f.run(&[b"ZADD", b"z", b"5", b"a"]);
11745        // NX leaves a member that is there alone, XX will not create one.
11746        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
11747        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
11748        assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
11749        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
11750        // GT and LT only move a score one way.
11751        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
11752        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
11753        assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
11754        // CH counts a moved score and plain ZADD does not.
11755        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
11756        assert_eq!(
11757            f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
11758            ":2\r\n"
11759        );
11760    }
11761
11762    #[test]
11763    fn zadd_incr_answers_a_score_or_nothing_at_all() {
11764        let mut f = Fixture::new();
11765        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
11766        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
11767        // A gate that refuses is the string nil, because the reply it stands in
11768        // for is a score.
11769        assert_eq!(
11770            f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
11771            "$-1\r\n"
11772        );
11773        assert_eq!(
11774            f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
11775            "$-1\r\n"
11776        );
11777        assert_eq!(
11778            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
11779            "$-1\r\n"
11780        );
11781        assert_eq!(
11782            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
11783            "$1\r\n8\r\n"
11784        );
11785        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
11786        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
11787    }
11788
11789    #[test]
11790    fn the_two_infinities_will_not_be_added_together() {
11791        let mut f = Fixture::new();
11792        f.run(&[b"ZADD", b"z", b"inf", b"m"]);
11793        let nan = "-ERR resulting score is not a number (NaN)\r\n";
11794        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
11795        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
11796        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
11797        // And a key made for an increment that then fails does not stay behind.
11798        assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
11799    }
11800
11801    #[test]
11802    fn zadd_says_its_mistakes_the_way_redis_says_them() {
11803        let mut f = Fixture::new();
11804        // The pairs are counted before the options are looked at, so this is a
11805        // syntax error about having none and not a complaint about NX and XX.
11806        assert_eq!(
11807            f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
11808            "-ERR syntax error\r\n"
11809        );
11810        assert_eq!(
11811            f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
11812            "-ERR XX and NX options at the same time are not compatible\r\n"
11813        );
11814        let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
11815        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
11816        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
11817        assert_eq!(
11818            f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
11819            "-ERR INCR option supports a single increment-element pair\r\n"
11820        );
11821        // An odd number of arguments after the options.
11822        assert_eq!(
11823            f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
11824            "-ERR syntax error\r\n"
11825        );
11826        // Every score is read before the first is stored.
11827        assert_eq!(
11828            f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
11829            "-ERR value is not a valid float\r\n"
11830        );
11831        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11832    }
11833
11834    #[test]
11835    fn a_rank_says_where_a_member_sits_from_either_end() {
11836        let mut f = Fixture::new();
11837        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11838        assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
11839        assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
11840        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
11841        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
11842        // WITHSCORE changes both shapes: the answer and the nothing.
11843        assert_eq!(
11844            f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
11845            "*2\r\n:1\r\n$1\r\n2\r\n"
11846        );
11847        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
11848        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
11849        assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
11850        // A bad option is a syntax error and one argument too many is an arity
11851        // error, which is Redis's split.
11852        assert_eq!(
11853            f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
11854            "-ERR syntax error\r\n"
11855        );
11856        assert_eq!(
11857            f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
11858            "-ERR wrong number of arguments for 'zrevrank' command\r\n"
11859        );
11860    }
11861
11862    #[test]
11863    fn the_two_counts_read_their_two_kinds_of_bound() {
11864        let mut f = Fixture::new();
11865        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11866        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
11867        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
11868        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
11869        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
11870        assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
11871        assert_eq!(
11872            f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
11873            "-ERR min or max is not a float\r\n"
11874        );
11875
11876        f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
11877        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
11878        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
11879        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
11880        // A bare member is not a bound, because a member can start with any
11881        // byte and there would be no way to say the bracket if it were optional.
11882        assert_eq!(
11883            f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
11884            "-ERR min or max not valid string range item\r\n"
11885        );
11886    }
11887
11888    /// The three ways `ZRANGE` can be asked for a window, forwards and back.
11889    ///
11890    /// Every byte in here was read off a real 8.10.1 rather than worked out,
11891    /// because the interesting part of this command is not what it selects, it
11892    /// is which of the two ends the client is expected to name first.
11893    #[test]
11894    fn one_range_command_selects_by_rank_or_score_or_name() {
11895        let mut f = Fixture::new();
11896        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11897        assert_eq!(
11898            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11899            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11900        );
11901        assert_eq!(
11902            f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
11903            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11904        );
11905        assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
11906        assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
11907        // REV over ranks reverses the walk and leaves the two arguments alone,
11908        // because a rank counts from the end the walk starts at.
11909        assert_eq!(
11910            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
11911            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11912        );
11913        assert_eq!(
11914            f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
11915            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11916        );
11917        // And REV over scores does swap them, since a bound does not count from
11918        // anywhere. This is the one line of the parse that tells the two apart.
11919        assert_eq!(
11920            f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
11921            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11922        );
11923        assert_eq!(
11924            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
11925            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11926        );
11927        assert_eq!(
11928            f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
11929            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11930        );
11931    }
11932
11933    /// The older spellings, which are the same six windows with the mode in the
11934    /// name and the high end named first on the three that go backwards.
11935    #[test]
11936    fn the_older_range_spellings_name_their_high_end_first() {
11937        let mut f = Fixture::new();
11938        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11939        assert_eq!(
11940            f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
11941            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11942        );
11943        assert_eq!(
11944            f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
11945            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11946        );
11947        assert_eq!(
11948            f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
11949            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11950        );
11951        assert_eq!(
11952            f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
11953            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11954        );
11955        // The two arguments the wrong way round is an empty answer and not an
11956        // error, which is what the swap being in the parse rather than in the
11957        // window buys.
11958        assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
11959        assert_eq!(
11960            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
11961            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
11962        );
11963        assert_eq!(
11964            f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
11965            "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
11966        );
11967        // BYSCORE, BYLEX and REV mean nothing to these, so they are not another
11968        // way of spelling the mode, they are a syntax error.
11969        for cmd in [
11970            &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
11971            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
11972            &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
11973        ] {
11974            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
11975        }
11976    }
11977
11978    /// `LIMIT` and `WITHSCORES`, which every one of these commands reads and
11979    /// only some of them accept.
11980    #[test]
11981    fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
11982        let mut f = Fixture::new();
11983        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11984        assert_eq!(
11985            f.run(&[
11986                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
11987            ]),
11988            "*1\r\n$1\r\nb\r\n"
11989        );
11990        // A negative offset skips past everything, a negative count is no bound.
11991        assert_eq!(
11992            f.run(&[
11993                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
11994            ]),
11995            "*0\r\n"
11996        );
11997        assert_eq!(
11998            f.run(&[
11999                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
12000            ]),
12001            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
12002        );
12003        // The two options in either order, which falls out of the parse loop.
12004        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";
12005        assert_eq!(
12006            f.run(&[
12007                b"ZRANGEBYSCORE",
12008                b"z",
12009                b"1",
12010                b"3",
12011                b"WITHSCORES",
12012                b"LIMIT",
12013                b"0",
12014                b"2"
12015            ]),
12016            both
12017        );
12018        assert_eq!(
12019            f.run(&[
12020                b"ZRANGEBYSCORE",
12021                b"z",
12022                b"1",
12023                b"3",
12024                b"LIMIT",
12025                b"0",
12026                b"2",
12027                b"WITHSCORES"
12028            ]),
12029            both
12030        );
12031        // LIMIT on a range by rank is refused after the whole option list has
12032        // been read, so this complains about LIMIT and not about WITHSCORES.
12033        let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
12034        assert_eq!(
12035            f.run(&[
12036                b"ZREVRANGE",
12037                b"z",
12038                b"0",
12039                b"-1",
12040                b"WITHSCORES",
12041                b"LIMIT",
12042                b"0",
12043                b"1"
12044            ]),
12045            needs_by
12046        );
12047        assert_eq!(
12048            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
12049            needs_by
12050        );
12051        let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
12052        assert_eq!(
12053            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
12054            not_bylex
12055        );
12056        assert_eq!(
12057            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
12058            not_bylex
12059        );
12060        // Two modes at once, an option nobody knows, a LIMIT missing its count,
12061        // and the three number errors, which are three different sentences.
12062        for cmd in [
12063            &[
12064                b"ZRANGE".as_slice(),
12065                b"z",
12066                b"0",
12067                b"-1",
12068                b"BYSCORE",
12069                b"BYLEX",
12070            ][..],
12071            &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
12072            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
12073        ] {
12074            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
12075        }
12076        assert_eq!(
12077            f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
12078            "-ERR min or max is not a float\r\n"
12079        );
12080        assert_eq!(
12081            f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
12082            "-ERR min or max not valid string range item\r\n"
12083        );
12084        assert_eq!(
12085            f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
12086            "-ERR value is not an integer or out of range\r\n"
12087        );
12088    }
12089
12090    /// `WITHSCORES` is the one place in this group where the two protocols
12091    /// disagree about the shape of the reply and not just the type of a value.
12092    #[test]
12093    fn withscores_nests_on_resp3_and_flattens_on_resp2() {
12094        let mut f = Fixture::new();
12095        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12096        assert_eq!(
12097            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
12098            "*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"
12099        );
12100        f.out = Out::new(Proto::Resp3);
12101        assert_eq!(
12102            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
12103            "*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"
12104        );
12105        assert_eq!(
12106            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
12107            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
12108        );
12109    }
12110
12111    /// The store form, which is the same parse with the destination in front.
12112    #[test]
12113    fn a_range_store_writes_the_window_into_another_key() {
12114        let mut f = Fixture::new();
12115        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12116        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
12117        // A window that selects nothing deletes the destination rather than
12118        // leaving an empty sorted set, because an empty one does not exist.
12119        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
12120        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
12121        assert_eq!(
12122            f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
12123            ":2\r\n"
12124        );
12125        assert_eq!(
12126            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
12127            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12128        );
12129        // The destination is allowed to be the source, because the result is
12130        // built whole before anything is written over.
12131        assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
12132        assert_eq!(
12133            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
12134            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12135        );
12136        // It takes every option ZRANGE takes except WITHSCORES, which is a
12137        // plain syntax error here and not the sentence about BYLEX.
12138        assert_eq!(
12139            f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
12140            "-ERR syntax error\r\n"
12141        );
12142    }
12143
12144    /// The three removals, which are the read side's window with the walk
12145    /// turned into a removal and no options at all.
12146    #[test]
12147    fn the_three_removals_share_their_window_with_the_reads() {
12148        let mut f = Fixture::new();
12149        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12150        assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
12151        assert_eq!(
12152            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
12153            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
12154        );
12155        assert_eq!(
12156            f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
12157            ":1\r\n"
12158        );
12159        assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
12160        // The last member going takes the key with it.
12161        assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
12162        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12163        assert_eq!(
12164            f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
12165            ":0\r\n"
12166        );
12167        assert_eq!(
12168            f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
12169            "-ERR value is not an integer or out of range\r\n"
12170        );
12171    }
12172
12173    /// The algebra, which is one gather and three names for it.
12174    #[test]
12175    fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
12176        let mut f = Fixture::new();
12177        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12178        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
12179        assert_eq!(
12180            f.run(&[b"ZUNION", b"2", b"z", b"y"]),
12181            "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
12182        );
12183        // The scores are added where a member is in both, and the answer comes
12184        // out in the order those combined scores put it in.
12185        assert_eq!(
12186            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
12187            "*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"
12188        );
12189        assert_eq!(
12190            f.run(&[
12191                b"ZUNION",
12192                b"2",
12193                b"z",
12194                b"y",
12195                b"WEIGHTS",
12196                b"2",
12197                b"3",
12198                b"WITHSCORES"
12199            ]),
12200            "*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"
12201        );
12202        assert_eq!(
12203            f.run(&[
12204                b"ZUNION",
12205                b"2",
12206                b"z",
12207                b"y",
12208                b"AGGREGATE",
12209                b"MIN",
12210                b"WITHSCORES"
12211            ]),
12212            "*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"
12213        );
12214        assert_eq!(
12215            f.run(&[
12216                b"ZUNION",
12217                b"2",
12218                b"z",
12219                b"y",
12220                b"AGGREGATE",
12221                b"MAX",
12222                b"WITHSCORES"
12223            ]),
12224            "*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"
12225        );
12226        assert_eq!(
12227            f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
12228            "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
12229        );
12230        assert_eq!(
12231            f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
12232            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
12233        );
12234        assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
12235        // A plain set is an input, and it behaves as a sorted set in which
12236        // every member scores one.
12237        f.run(&[b"SADD", b"p", b"a", b"d"]);
12238        assert_eq!(
12239            f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
12240            "*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"
12241        );
12242        // A difference never combines two scores, so it has nothing for either
12243        // of the two options to do and refuses both.
12244        for cmd in [
12245            &[
12246                b"ZDIFF".as_slice(),
12247                b"2",
12248                b"z",
12249                b"y",
12250                b"WEIGHTS",
12251                b"1",
12252                b"1",
12253            ][..],
12254            &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
12255        ] {
12256            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
12257        }
12258    }
12259
12260    /// The count of keys, which is what lets a key be named `WEIGHTS`.
12261    #[test]
12262    fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
12263        let mut f = Fixture::new();
12264        f.run(&[b"ZADD", b"z", b"1", b"a"]);
12265        f.run(&[b"ZADD", b"y", b"2", b"b"]);
12266        // Redis names the command in this one, so each spelling says its own.
12267        assert_eq!(
12268            f.run(&[b"ZUNION", b"0", b"z"]),
12269            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
12270        );
12271        assert_eq!(
12272            f.run(&[b"ZUNION", b"-1", b"z"]),
12273            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
12274        );
12275        assert_eq!(
12276            f.run(&[b"ZINTERCARD", b"0", b"z"]),
12277            "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
12278        );
12279        // A count bigger than the line is a plain syntax error, which reads
12280        // oddly and is what Redis says.
12281        assert_eq!(
12282            f.run(&[b"ZUNION", b"3", b"z", b"y"]),
12283            "-ERR syntax error\r\n"
12284        );
12285        assert_eq!(
12286            f.run(&[b"ZUNION", b"x", b"z"]),
12287            "-ERR value is not an integer or out of range\r\n"
12288        );
12289        // A WEIGHTS list that is not one per key is a syntax error, and a
12290        // weight that is not a number gets a sentence of its own.
12291        assert_eq!(
12292            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
12293            "-ERR syntax error\r\n"
12294        );
12295        assert_eq!(
12296            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
12297            "-ERR weight value is not a float\r\n"
12298        );
12299        assert_eq!(
12300            f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
12301            "-ERR syntax error\r\n"
12302        );
12303    }
12304
12305    /// The three store forms, which answer a count and take no WITHSCORES.
12306    #[test]
12307    fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
12308        let mut f = Fixture::new();
12309        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12310        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
12311        assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
12312        assert_eq!(
12313            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
12314            "*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"
12315        );
12316        assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
12317        assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
12318        // An empty result deletes the destination rather than leaving an empty
12319        // sorted set, because an empty one does not exist.
12320        assert_eq!(
12321            f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
12322            ":0\r\n"
12323        );
12324        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
12325        // The destination is allowed to name its own source.
12326        assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
12327        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
12328        for cmd in [
12329            &[
12330                b"ZUNIONSTORE".as_slice(),
12331                b"d",
12332                b"2",
12333                b"z",
12334                b"y",
12335                b"WITHSCORES",
12336            ][..],
12337            &[
12338                b"ZDIFFSTORE",
12339                b"d",
12340                b"2",
12341                b"z",
12342                b"y",
12343                b"WEIGHTS",
12344                b"1",
12345                b"1",
12346            ],
12347        ] {
12348            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
12349        }
12350    }
12351
12352    /// `ZINTERCARD`, which counts without building anything.
12353    #[test]
12354    fn intercard_counts_and_stops_at_its_limit() {
12355        let mut f = Fixture::new();
12356        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12357        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
12358        assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
12359        // A limit of zero is no limit, which is Redis's reading of it.
12360        assert_eq!(
12361            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
12362            ":2\r\n"
12363        );
12364        assert_eq!(
12365            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
12366            ":1\r\n"
12367        );
12368        // A negative limit and a limit that is not a number at all get the same
12369        // sentence, which looks like a mistake in Redis and is copied as one.
12370        let bad = "-ERR LIMIT can't be negative\r\n";
12371        assert_eq!(
12372            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
12373            bad
12374        );
12375        assert_eq!(
12376            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
12377            bad
12378        );
12379        for cmd in [
12380            &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
12381            &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
12382            &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
12383        ] {
12384            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
12385        }
12386    }
12387
12388    /// `ZRANDMEMBER`, which answers two different shapes out of one name.
12389    #[test]
12390    fn a_draw_answers_one_member_or_an_array_of_them() {
12391        let mut f = Fixture::new();
12392        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12393        // No count is one member or a nil, a count is an array that may be
12394        // empty, and those are two reply types the client has to tell apart.
12395        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
12396        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
12397        assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
12398        assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
12399        // A positive count draws without replacement, so a count over the size
12400        // answers the whole set and never a member twice.
12401        let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
12402        assert!(all.starts_with("*3\r\n"), "{all}");
12403        for m in ["a", "b", "c"] {
12404            assert!(all.contains(m), "{all}");
12405        }
12406        // A negative one draws with replacement and answers exactly as many as
12407        // it was asked for, whatever the size of the set.
12408        assert!(
12409            f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
12410            "five draws with replacement"
12411        );
12412        assert!(
12413            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
12414                .starts_with("*4\r\n"),
12415            "two pairs, flat on RESP2"
12416        );
12417        f.out = Out::new(Proto::Resp3);
12418        let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
12419        assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
12420        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
12421        f.out = Out::new(Proto::Resp2);
12422        assert_eq!(
12423            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
12424            "-ERR syntax error\r\n"
12425        );
12426        assert_eq!(
12427            f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
12428            "-ERR value is not an integer or out of range\r\n"
12429        );
12430    }
12431
12432    /// `ZSCAN`, and the one sorted set reply where a score is not a double.
12433    #[test]
12434    fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
12435        let mut f = Fixture::new();
12436        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12437        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";
12438        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
12439        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
12440        assert_eq!(
12441            f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
12442            "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
12443        );
12444        assert_eq!(
12445            f.run(&[b"ZSCAN", b"nokey", b"0"]),
12446            "*2\r\n$1\r\n0\r\n*0\r\n"
12447        );
12448        // A score stays a bulk string on RESP3, which is the one place the two
12449        // protocols agree about a score and everywhere else they do not.
12450        f.out = Out::new(Proto::Resp3);
12451        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
12452        f.out = Out::new(Proto::Resp2);
12453        assert_eq!(
12454            f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
12455            "-ERR NOVALUES option can only be used in HSCAN\r\n"
12456        );
12457        assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
12458        assert_eq!(
12459            f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
12460            "-ERR syntax error\r\n"
12461        );
12462    }
12463
12464    /// The count is what decides the shape, and its value is not.
12465    #[test]
12466    fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
12467        let mut f = Fixture::new();
12468        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12469        // No count, so one flat pair, and the score is a bulk string on RESP2.
12470        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
12471        assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
12472        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12473        // A count, so pairs, and on RESP2 they are flattened into one run.
12474        assert_eq!(
12475            f.run(&[b"ZPOPMIN", b"z", b"2"]),
12476            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
12477        );
12478        // An empty array rather than a null, which is where a sorted set pop and
12479        // a list pop part company, and the same answer a count of zero gives.
12480        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
12481        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
12482        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
12483        // The last member takes the key with it.
12484        assert_eq!(
12485            f.run(&[b"ZPOPMIN", b"z", b"9"]),
12486            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12487        );
12488        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12489
12490        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
12491        f.out = Out::new(Proto::Resp3);
12492        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
12493        assert_eq!(
12494            f.run(&[b"ZPOPMIN", b"z", b"1"]),
12495            "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
12496        );
12497        f.out = Out::new(Proto::Resp2);
12498        // Both of these are the range error rather than the usual sentence about
12499        // integers, which is the odd answer and so the one worth copying.
12500        let bad = "-ERR value is out of range, must be positive\r\n";
12501        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
12502        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
12503        assert_eq!(
12504            f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
12505            "-ERR syntax error\r\n"
12506        );
12507    }
12508
12509    /// `ZMPOP`, which is `LMPOP` with scores and the same parse.
12510    #[test]
12511    fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
12512        let mut f = Fixture::new();
12513        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12514        assert_eq!(
12515            f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
12516            "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
12517        );
12518        // Nested on RESP2 as well, because the key name is already in front of
12519        // the pairs and there is nothing left to flatten into.
12520        assert_eq!(
12521            f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
12522            "*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"
12523        );
12524        // A null array and not a null, the same as LMPOP.
12525        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
12526        f.out = Out::new(Proto::Resp3);
12527        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
12528        f.out = Out::new(Proto::Resp2);
12529        let numkeys = "-ERR numkeys should be greater than 0\r\n";
12530        for bad in [
12531            &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
12532            &[b"ZMPOP", b"-1", b"z", b"MIN"],
12533            &[b"ZMPOP", b"x", b"z", b"MIN"],
12534        ] {
12535            assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
12536        }
12537        let count = "-ERR count should be greater than 0\r\n";
12538        for bad in [
12539            &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
12540            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
12541            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
12542        ] {
12543            assert_eq!(f.run(bad), count, "{:?}", bad[5]);
12544        }
12545        let syntax = "-ERR syntax error\r\n";
12546        for bad in [
12547            // Two keys named and one given, so the word that should have been
12548            // the direction is a key and there is no direction left.
12549            &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
12550            &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
12551            &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
12552            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
12553        ] {
12554            assert_eq!(f.run(bad), syntax, "{bad:?}");
12555        }
12556    }
12557
12558    /// The three that wait, when there is something there and they do not have
12559    /// to. `BZPOPMIN` is the one reply in the group that is three flat elements.
12560    #[test]
12561    fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
12562        let mut f = Fixture::new();
12563        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12564        assert_eq!(
12565            f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
12566            (
12567                Flow::Continue,
12568                "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
12569            )
12570        );
12571        assert_eq!(
12572            f.run(&[b"BZPOPMAX", b"z", b"0"]),
12573            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
12574        );
12575        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12576        assert_eq!(
12577            f.run(&[
12578                b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
12579            ]),
12580            "*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"
12581        );
12582        f.out = Out::new(Proto::Resp3);
12583        assert_eq!(
12584            f.run(&[b"BZPOPMIN", b"z", b"0"]),
12585            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
12586        );
12587        f.out = Out::new(Proto::Resp2);
12588        // Nothing to take, so the client is parked and nothing was written.
12589        assert_eq!(
12590            f.flow(&[b"BZPOPMIN", b"z", b"0"]),
12591            (Flow::Block, String::new())
12592        );
12593        assert_eq!(
12594            f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
12595            (Flow::Block, String::new())
12596        );
12597        // The timeout is read before the key count, so this complains about the
12598        // timeout and not about the count.
12599        assert_eq!(
12600            f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
12601            "-ERR timeout is not a float or out of range\r\n"
12602        );
12603        assert_eq!(
12604            f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
12605            "-ERR numkeys should be greater than 0\r\n"
12606        );
12607        assert_eq!(
12608            f.run(&[b"BZPOPMIN", b"z", b"-1"]),
12609            "-ERR timeout is negative\r\n"
12610        );
12611    }
12612
12613    /// A parked sorted set client is served by whatever puts a member under one
12614    /// of its keys, and is not served by something of another type landing
12615    /// there.
12616    #[test]
12617    fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
12618        let mut f = Fixture::new();
12619        assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
12620        assert_eq!(f.server.parked(), 1);
12621        // A string under the key is not what it asked for, so it stays parked
12622        // rather than being handed a WRONGTYPE on a command that was accepted.
12623        f.run(&[b"SET", b"z", b"v"]);
12624        let mut out = Out::new(Proto::Resp2);
12625        assert!(!f.server.serve_waiter(7, 0, &mut out));
12626        assert!(out.as_slice().is_empty());
12627        f.run(&[b"DEL", b"z"]);
12628        f.run(&[b"ZADD", b"z", b"5", b"m"]);
12629        assert!(f.server.serve_waiter(7, 0, &mut out));
12630        assert_eq!(
12631            core::str::from_utf8(out.as_slice()).expect("ascii"),
12632            "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
12633        );
12634        // And the member is gone, which is what makes a queue of workers on a
12635        // sorted set work at all.
12636        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12637    }
12638
12639    #[test]
12640    fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
12641        let mut f = Fixture::new();
12642        f.run(&[b"SET", b"s", b"v"]);
12643        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12644        for cmd in [
12645            &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
12646            &[b"ZINCRBY", b"s", b"1", b"a"],
12647            &[b"ZCARD", b"s"],
12648            &[b"ZSCORE", b"s", b"a"],
12649            &[b"ZMSCORE", b"s", b"a"],
12650            &[b"ZREM", b"s", b"a"],
12651            &[b"ZRANK", b"s", b"a"],
12652            &[b"ZREVRANK", b"s", b"a"],
12653            &[b"ZCOUNT", b"s", b"1", b"2"],
12654            &[b"ZLEXCOUNT", b"s", b"-", b"+"],
12655            &[b"ZRANGE", b"s", b"0", b"-1"],
12656            &[b"ZREVRANGE", b"s", b"0", b"-1"],
12657            &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
12658            &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
12659            &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
12660            &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
12661            &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
12662            &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
12663            &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
12664            &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
12665            &[b"ZUNION", b"1", b"s"],
12666            &[b"ZINTER", b"1", b"s"],
12667            &[b"ZDIFF", b"1", b"s"],
12668            &[b"ZUNIONSTORE", b"d", b"1", b"s"],
12669            &[b"ZINTERSTORE", b"d", b"1", b"s"],
12670            &[b"ZDIFFSTORE", b"d", b"1", b"s"],
12671            &[b"ZINTERCARD", b"1", b"s"],
12672            &[b"ZRANDMEMBER", b"s"],
12673            &[b"ZSCAN", b"s", b"0"],
12674            &[b"ZPOPMIN", b"s"],
12675            &[b"ZPOPMAX", b"s", b"2"],
12676            &[b"ZMPOP", b"1", b"s", b"MIN"],
12677            &[b"BZPOPMIN", b"s", b"0"],
12678            &[b"BZPOPMAX", b"s", b"0"],
12679            &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
12680        ] {
12681            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12682        }
12683        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
12684    }
12685
12686    /// The same churn the set, the string and the list get, because a sorted
12687    /// set that leaks a tree node per add looks exactly like one that does not
12688    /// until it has run for an afternoon.
12689    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
12690    #[cfg_attr(miri, ignore = "the volume is the claim")]
12691    #[test]
12692    fn churning_sorted_sets_does_not_grow_the_server() {
12693        let mut f = Fixture::new();
12694        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
12695        let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
12696        let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
12697        for i in 0..200 {
12698            args.push(&scores[i]);
12699            args.push(&members[i]);
12700        }
12701
12702        f.run(&args);
12703        f.run(&[b"DEL", b"z"]);
12704        f.server.compact_step();
12705        let after_first = f.server.memory_bytes();
12706
12707        for _ in 0..200 {
12708            f.run(&args);
12709            f.run(&[b"DEL", b"z"]);
12710            f.server.compact_step();
12711        }
12712        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
12713        assert!(
12714            f.server.memory_bytes() <= after_first * 2,
12715            "held {} after two hundred passes against {after_first} after one",
12716            f.server.memory_bytes()
12717        );
12718    }
12719
12720    // ------------------------------------------------------------------- geo
12721
12722    /// The three places every Redis geo example uses, and one more.
12723    ///
12724    /// Every reply this section asserts on came off a running 8.10.1 with these
12725    /// three loaded, byte for byte, including the number of digits in a
12726    /// coordinate and the four places on a distance.
12727    fn sicily(f: &mut Fixture) {
12728        f.run(&[
12729            b"GEOADD",
12730            b"Sicily",
12731            b"13.361389",
12732            b"38.115556",
12733            b"Palermo",
12734            b"15.087269",
12735            b"37.502669",
12736            b"Catania",
12737        ]);
12738        f.run(&[
12739            b"GEOADD",
12740            b"Sicily",
12741            b"13.583333",
12742            b"37.316667",
12743            b"Agrigento",
12744        ]);
12745    }
12746
12747    #[test]
12748    fn places_go_in_as_scores_and_come_back_as_positions() {
12749        let mut f = Fixture::new();
12750        assert_eq!(
12751            f.run(&[
12752                b"GEOADD",
12753                b"Sicily",
12754                b"13.361389",
12755                b"38.115556",
12756                b"Palermo",
12757                b"15.087269",
12758                b"37.502669",
12759                b"Catania"
12760            ]),
12761            ":2\r\n"
12762        );
12763        // A geo key is a sorted set and says so, which is not an implementation
12764        // detail either: a client removes a place with ZREM and counts them
12765        // with ZCARD, and the score is the number a real server stores.
12766        assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
12767        assert_eq!(
12768            f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
12769            "$16\r\n3479099956230698\r\n"
12770        );
12771        assert_eq!(
12772            f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
12773            "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
12774        );
12775        assert_eq!(
12776            f.run(&[
12777                b"GEOHASH",
12778                b"Sicily",
12779                b"Palermo",
12780                b"Catania",
12781                b"NonExisting"
12782            ]),
12783            "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
12784        );
12785        // A key that is not there is an empty one, and the two nulls are not
12786        // the same null: GEOPOS answers the array one and GEOHASH the string
12787        // one, which a RESP2 client can tell apart.
12788        assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
12789        assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
12790    }
12791
12792    #[test]
12793    fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
12794        let mut f = Fixture::new();
12795        sicily(&mut f);
12796        assert_eq!(
12797            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
12798            "$11\r\n166274.1516\r\n"
12799        );
12800        assert_eq!(
12801            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
12802            "$8\r\n166.2742\r\n"
12803        );
12804        assert_eq!(
12805            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
12806            "$8\r\n103.3182\r\n"
12807        );
12808        // A member that is not there and a key that is not there are the same
12809        // nil, and the unit is read before the key is looked up, so a bad unit
12810        // on a missing key is still an error.
12811        assert_eq!(
12812            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
12813            "$-1\r\n"
12814        );
12815        assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
12816        assert_eq!(
12817            f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
12818            "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
12819        );
12820        assert_eq!(
12821            f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
12822            "-ERR syntax error\r\n"
12823        );
12824    }
12825
12826    #[test]
12827    fn a_search_finds_what_is_inside_it_nearest_first() {
12828        let mut f = Fixture::new();
12829        sicily(&mut f);
12830        let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
12831        assert_eq!(
12832            f.run(&[
12833                b"GEOSEARCH",
12834                b"Sicily",
12835                b"FROMLONLAT",
12836                b"15",
12837                b"37",
12838                b"BYRADIUS",
12839                b"200",
12840                b"km",
12841                b"ASC"
12842            ]),
12843            all
12844        );
12845        // The older spelling of the same search, which is the same nine boxes
12846        // and the same order.
12847        assert_eq!(
12848            f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
12849            all
12850        );
12851        assert_eq!(
12852            f.run(&[
12853                b"GEORADIUS_RO",
12854                b"Sicily",
12855                b"15",
12856                b"37",
12857                b"200",
12858                b"km",
12859                b"ASC"
12860            ]),
12861            all
12862        );
12863        // A count with no ordering means the nearest ones, so DESC has to be
12864        // asked for to get the far end.
12865        assert_eq!(
12866            f.run(&[
12867                b"GEORADIUS",
12868                b"Sicily",
12869                b"15",
12870                b"37",
12871                b"200",
12872                b"km",
12873                b"DESC",
12874                b"COUNT",
12875                b"1"
12876            ]),
12877            "*1\r\n$7\r\nPalermo\r\n"
12878        );
12879        assert_eq!(
12880            f.run(&[
12881                b"GEORADIUS",
12882                b"Sicily",
12883                b"15",
12884                b"37",
12885                b"200",
12886                b"km",
12887                b"COUNT",
12888                b"1"
12889            ]),
12890            "*1\r\n$7\r\nCatania\r\n"
12891        );
12892        // Nothing inside a kilometre of that point, and nothing in a key that
12893        // is not there, and both are the empty array rather than an error.
12894        let empty = "*0\r\n";
12895        assert_eq!(
12896            f.run(&[
12897                b"GEOSEARCH",
12898                b"Sicily",
12899                b"FROMLONLAT",
12900                b"15",
12901                b"37",
12902                b"BYRADIUS",
12903                b"1",
12904                b"km"
12905            ]),
12906            empty
12907        );
12908        assert_eq!(
12909            f.run(&[
12910                b"GEOSEARCH",
12911                b"nokey",
12912                b"FROMLONLAT",
12913                b"15",
12914                b"37",
12915                b"BYRADIUS",
12916                b"1",
12917                b"km"
12918            ]),
12919            empty
12920        );
12921        assert_eq!(
12922            f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
12923            empty
12924        );
12925    }
12926
12927    #[test]
12928    fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
12929        let mut f = Fixture::new();
12930        sicily(&mut f);
12931        assert_eq!(
12932            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
12933            "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12934        );
12935        // The member itself is nothing away from itself, which is where the
12936        // fixed point writer's zero shows up on the wire.
12937        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";
12938        assert_eq!(
12939            f.run(&[
12940                b"GEORADIUSBYMEMBER_RO",
12941                b"Sicily",
12942                b"Agrigento",
12943                b"100",
12944                b"km",
12945                b"WITHDIST"
12946            ]),
12947            with_dist
12948        );
12949        assert_eq!(
12950            f.run(&[
12951                b"GEOSEARCH",
12952                b"Sicily",
12953                b"FROMMEMBER",
12954                b"Agrigento",
12955                b"BYRADIUS",
12956                b"100",
12957                b"km",
12958                b"ASC",
12959                b"WITHDIST"
12960            ]),
12961            with_dist
12962        );
12963        assert_eq!(
12964            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
12965            "-ERR could not decode requested zset member\r\n"
12966        );
12967    }
12968
12969    #[test]
12970    fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
12971        let mut f = Fixture::new();
12972        sicily(&mut f);
12973        // Three options asked for, so each result is a four element array of
12974        // the member, the distance, the hash and a pair. The order of the three
12975        // is Redis's and not the order they were written in the command.
12976        assert_eq!(
12977            f.run(&[
12978                b"GEOSEARCH",
12979                b"Sicily",
12980                b"FROMLONLAT",
12981                b"15",
12982                b"37",
12983                b"BYBOX",
12984                b"400",
12985                b"400",
12986                b"km",
12987                b"ASC",
12988                b"WITHCOORD",
12989                b"WITHDIST",
12990                b"WITHHASH"
12991            ]),
12992            "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
12993             $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
12994             *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
12995             $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
12996             *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
12997             $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
12998        );
12999    }
13000
13001    #[test]
13002    fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
13003        let mut f = Fixture::new();
13004        sicily(&mut f);
13005        let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
13006                      $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
13007                      $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
13008        assert_eq!(
13009            f.run(&[
13010                b"GEOSEARCHSTORE",
13011                b"dst",
13012                b"Sicily",
13013                b"FROMLONLAT",
13014                b"15",
13015                b"37",
13016                b"BYRADIUS",
13017                b"200",
13018                b"km",
13019                b"ASC"
13020            ]),
13021            ":3\r\n"
13022        );
13023        assert_eq!(
13024            f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
13025            hashes
13026        );
13027        // The same again through the older spelling, which stores the same
13028        // scores, so a key written by either is a geo key.
13029        assert_eq!(
13030            f.run(&[
13031                b"GEORADIUS",
13032                b"Sicily",
13033                b"15",
13034                b"37",
13035                b"200",
13036                b"km",
13037                b"STORE",
13038                b"dst3"
13039            ]),
13040            ":3\r\n"
13041        );
13042        assert_eq!(
13043            f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
13044            hashes
13045        );
13046        // STOREDIST stores the distance in the search unit instead, and those
13047        // are full doubles rather than the four places WITHDIST writes. The
13048        // numbers on the right are what 8.10.1 stored for this search, and they
13049        // are compared with a tolerance rather than byte for byte because the
13050        // last bit of a haversine is the platform's sin, cos and asin: this
13051        // machine and that one disagree in the sixteenth digit, and so do two
13052        // Redis builds. Everything a client actually reads back is four places
13053        // and is asserted exactly above.
13054        assert_eq!(
13055            f.run(&[
13056                b"GEOSEARCHSTORE",
13057                b"dst2",
13058                b"Sicily",
13059                b"FROMLONLAT",
13060                b"15",
13061                b"37",
13062                b"BYRADIUS",
13063                b"200",
13064                b"km",
13065                b"ASC",
13066                b"STOREDIST"
13067            ]),
13068            ":3\r\n"
13069        );
13070        for (member, want) in [
13071            ("Catania", 56.441_257_870_158_19),
13072            ("Agrigento", 130.423_487_067_147_14),
13073            ("Palermo", 190.442_429_847_757_92),
13074        ] {
13075            let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
13076            let got: f64 = reply
13077                .trim_start_matches(|c: char| c != '\n')
13078                .trim()
13079                .parse()
13080                .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
13081            assert!(
13082                (got - want).abs() < 1e-9,
13083                "{member} scored {got} not {want}"
13084            );
13085        }
13086        // The order they went in is the order the scores put them in, which is
13087        // the point of storing the distance rather than the hash.
13088        assert_eq!(
13089            f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
13090            "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
13091        );
13092        // A search that finds nothing takes the destination with it rather than
13093        // leaving what was there, and a source key that is not there is a
13094        // search that finds nothing.
13095        assert_eq!(
13096            f.run(&[
13097                b"GEOSEARCHSTORE",
13098                b"dst",
13099                b"nokey",
13100                b"FROMLONLAT",
13101                b"15",
13102                b"37",
13103                b"BYRADIUS",
13104                b"200",
13105                b"km"
13106            ]),
13107            ":0\r\n"
13108        );
13109        assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
13110    }
13111
13112    #[test]
13113    fn the_gates_on_geoadd_are_the_ones_zadd_has() {
13114        let mut f = Fixture::new();
13115        sicily(&mut f);
13116        // XX on a member that is already where it is changes nothing, and NX on
13117        // one that is there refuses to move it.
13118        assert_eq!(
13119            f.run(&[
13120                b"GEOADD",
13121                b"Sicily",
13122                b"XX",
13123                b"CH",
13124                b"13.361389",
13125                b"38.115556",
13126                b"Palermo"
13127            ]),
13128            ":0\r\n"
13129        );
13130        assert_eq!(
13131            f.run(&[
13132                b"GEOADD",
13133                b"Sicily",
13134                b"NX",
13135                b"13.361389",
13136                b"38.9",
13137                b"Palermo"
13138            ]),
13139            ":0\r\n"
13140        );
13141        assert_eq!(
13142            f.run(&[
13143                b"GEOADD",
13144                b"Sicily",
13145                b"CH",
13146                b"13.361389",
13147                b"38.9",
13148                b"Palermo"
13149            ]),
13150            ":1\r\n"
13151        );
13152        // Out of range, and nothing is stored: the whole call is refused rather
13153        // than the good pairs going in and the bad one stopping it.
13154        assert_eq!(
13155            f.run(&[
13156                b"GEOADD",
13157                b"new",
13158                b"13.361389",
13159                b"38.115556",
13160                b"here",
13161                b"181",
13162                b"38",
13163                b"there"
13164            ]),
13165            "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
13166        );
13167        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
13168        assert_eq!(
13169            f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
13170            "-ERR value is not a valid float\r\n"
13171        );
13172        // The count of triples is checked before the two gates are, and a call
13173        // with no triples at all reaches the same sentence.
13174        assert_eq!(
13175            f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
13176            "-ERR syntax error\r\n"
13177        );
13178        assert_eq!(
13179            f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
13180            "-ERR syntax error\r\n"
13181        );
13182        assert_eq!(
13183            f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
13184            "-ERR syntax error\r\n"
13185        );
13186        assert_eq!(
13187            f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
13188            "-ERR wrong number of arguments for 'geoadd' command\r\n"
13189        );
13190    }
13191
13192    /// The sentences a search answers, which are its contract as much as the
13193    /// results are.
13194    #[test]
13195    fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
13196        let mut f = Fixture::new();
13197        sicily(&mut f);
13198        let cases: &[(&[&[u8]], &str)] = &[
13199            (
13200                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
13201                "-ERR need numeric radius\r\n",
13202            ),
13203            (
13204                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
13205                "-ERR radius cannot be negative\r\n",
13206            ),
13207            (
13208                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
13209                "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
13210            ),
13211            (
13212                &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
13213                "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
13214            ),
13215            (
13216                &[
13217                    b"GEOSEARCH",
13218                    b"Sicily",
13219                    b"FROMLONLAT",
13220                    b"15",
13221                    b"37",
13222                    b"BYBOX",
13223                    b"x",
13224                    b"1",
13225                    b"km",
13226                ],
13227                "-ERR need numeric width\r\n",
13228            ),
13229            (
13230                &[
13231                    b"GEOSEARCH",
13232                    b"Sicily",
13233                    b"FROMLONLAT",
13234                    b"15",
13235                    b"37",
13236                    b"BYBOX",
13237                    b"1",
13238                    b"y",
13239                    b"km",
13240                ],
13241                "-ERR need numeric height\r\n",
13242            ),
13243            (
13244                &[
13245                    b"GEOSEARCH",
13246                    b"Sicily",
13247                    b"FROMLONLAT",
13248                    b"15",
13249                    b"37",
13250                    b"BYBOX",
13251                    b"-1",
13252                    b"1",
13253                    b"km",
13254                ],
13255                "-ERR height or width cannot be negative\r\n",
13256            ),
13257            (
13258                &[
13259                    b"GEOSEARCH",
13260                    b"Sicily",
13261                    b"FROMLONLAT",
13262                    b"15",
13263                    b"37",
13264                    b"BYRADIUS",
13265                    b"1",
13266                    b"km",
13267                    b"ANY",
13268                ],
13269                "-ERR the ANY argument requires COUNT argument\r\n",
13270            ),
13271            (
13272                &[
13273                    b"GEOSEARCH",
13274                    b"Sicily",
13275                    b"FROMLONLAT",
13276                    b"15",
13277                    b"37",
13278                    b"BYRADIUS",
13279                    b"1",
13280                    b"km",
13281                    b"COUNT",
13282                    b"0",
13283                ],
13284                "-ERR COUNT must be > 0\r\n",
13285            ),
13286            (
13287                &[
13288                    b"GEOSEARCH",
13289                    b"Sicily",
13290                    b"BYRADIUS",
13291                    b"1",
13292                    b"km",
13293                    b"BYBOX",
13294                    b"1",
13295                    b"1",
13296                    b"km",
13297                ],
13298                "-ERR syntax error\r\n",
13299            ),
13300            (
13301                &[
13302                    b"GEOSEARCH",
13303                    b"Sicily",
13304                    b"FROMMEMBER",
13305                    b"Palermo",
13306                    b"FROMLONLAT",
13307                    b"1",
13308                    b"2",
13309                    b"BYRADIUS",
13310                    b"1",
13311                    b"km",
13312                ],
13313                "-ERR syntax error\r\n",
13314            ),
13315            // The two options a GEOSEARCH cannot leave out, each with its own
13316            // sentence, and the command quoted the way the client spelled it.
13317            (
13318                &[
13319                    b"geosearch",
13320                    b"Sicily",
13321                    b"BYRADIUS",
13322                    b"1",
13323                    b"km",
13324                    b"ASC",
13325                    b"WITHDIST",
13326                ],
13327                "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
13328            ),
13329            (
13330                &[
13331                    b"GEOSEARCH",
13332                    b"Sicily",
13333                    b"FROMLONLAT",
13334                    b"15",
13335                    b"37",
13336                    b"ASC",
13337                    b"WITHDIST",
13338                ],
13339                "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
13340            ),
13341            // A store cannot also be asked for the distance, and the two
13342            // families name themselves differently in the same sentence.
13343            (
13344                &[
13345                    b"GEOSEARCHSTORE",
13346                    b"d",
13347                    b"Sicily",
13348                    b"FROMLONLAT",
13349                    b"15",
13350                    b"37",
13351                    b"BYRADIUS",
13352                    b"1",
13353                    b"km",
13354                    b"WITHCOORD",
13355                ],
13356                "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
13357            ),
13358            (
13359                &[
13360                    b"GEORADIUS",
13361                    b"Sicily",
13362                    b"15",
13363                    b"37",
13364                    b"1",
13365                    b"km",
13366                    b"WITHDIST",
13367                    b"STORE",
13368                    b"d",
13369                ],
13370                "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
13371            ),
13372            // The read only forms have no store at all, so the word is a stray
13373            // one, and GEOSEARCH's STOREDIST is only a GEOSEARCHSTORE option.
13374            (
13375                &[
13376                    b"GEORADIUS_RO",
13377                    b"Sicily",
13378                    b"15",
13379                    b"37",
13380                    b"1",
13381                    b"km",
13382                    b"STORE",
13383                    b"d",
13384                ],
13385                "-ERR syntax error\r\n",
13386            ),
13387            (
13388                &[
13389                    b"GEOSEARCH",
13390                    b"Sicily",
13391                    b"FROMLONLAT",
13392                    b"15",
13393                    b"37",
13394                    b"BYRADIUS",
13395                    b"1",
13396                    b"km",
13397                    b"STOREDIST",
13398                ],
13399                "-ERR syntax error\r\n",
13400            ),
13401        ];
13402        for (parts, want) in cases {
13403            assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
13404        }
13405    }
13406
13407    /// A wrong type wins over a bad argument, because the key is looked up
13408    /// first, and every one of the ten says the same thing about it.
13409    #[test]
13410    fn every_geo_command_says_wrongtype() {
13411        let mut f = Fixture::new();
13412        f.run(&[b"SET", b"s", b"v"]);
13413        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13414        let cases: &[&[&[u8]]] = &[
13415            &[b"GEOADD", b"s", b"13", b"38", b"m"],
13416            &[b"GEOPOS", b"s", b"m"],
13417            &[b"GEOHASH", b"s", b"m"],
13418            &[b"GEODIST", b"s", b"a", b"b"],
13419            &[
13420                b"GEOSEARCH",
13421                b"s",
13422                b"FROMLONLAT",
13423                b"15",
13424                b"37",
13425                b"BYRADIUS",
13426                b"1",
13427                b"km",
13428            ],
13429            &[
13430                b"GEOSEARCHSTORE",
13431                b"d",
13432                b"s",
13433                b"FROMLONLAT",
13434                b"15",
13435                b"37",
13436                b"BYRADIUS",
13437                b"1",
13438                b"km",
13439            ],
13440            &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
13441            &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
13442            &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
13443            &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
13444        ];
13445        for case in cases {
13446            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
13447        }
13448        // And it wins over an argument that will not parse, which is the whole
13449        // reason the lookup comes first.
13450        assert_eq!(
13451            f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
13452            wrong
13453        );
13454    }
13455
13456    // ----------------------------------------------------------------- array
13457
13458    #[test]
13459    fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
13460        let mut f = Fixture::new();
13461        // Three consecutive positions from a high index, and the reply is how
13462        // many of them were empty before rather than how many were written.
13463        assert_eq!(
13464            f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
13465            ":3\r\n"
13466        );
13467        assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
13468        assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
13469        assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
13470        // A hole and a key that is not there are the same answer.
13471        assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
13472        assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
13473        assert_eq!(
13474            f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
13475            "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
13476        );
13477        // Scattered pairs in one command, last write wins within it.
13478        assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
13479        assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
13480    }
13481
13482    /// The two numbers an array reports are not the same number, and one of
13483    /// them does not fit a signed integer.
13484    #[test]
13485    fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
13486        let mut f = Fixture::new();
13487        assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
13488        assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
13489        f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
13490        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13491        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13492        // Deleting in the middle leaves the high water mark where it was.
13493        assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
13494        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13495        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
13496
13497        // The top of the space is addressable, and its length is a number with
13498        // bit sixty three set, so the reply has to be unsigned or it comes back
13499        // negative.
13500        f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
13501        assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
13502        assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
13503        // And one past it does not exist, so a write that would reach it fails
13504        // before any of it lands.
13505        assert_eq!(
13506            f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
13507            "-ERR array index overflow\r\n"
13508        );
13509        assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
13510    }
13511
13512    /// One reply per position and not one per element, which is the whole
13513    /// reason the range is capped.
13514    #[test]
13515    fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
13516        let mut f = Fixture::new();
13517        f.run(&[b"ARSET", b"a", b"1", b"x"]);
13518        assert_eq!(
13519            f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
13520            "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
13521        );
13522        // The two ends may come in either order, and the answer is reversed
13523        // rather than empty.
13524        assert_eq!(
13525            f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
13526            "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
13527        );
13528        // A key that is not there reads like an array of nothing but holes.
13529        assert_eq!(
13530            f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
13531            "*2\r\n$-1\r\n$-1\r\n"
13532        );
13533        // A range wider than a million positions is refused and not trimmed,
13534        // because against a missing key it is a request for as many nulls as
13535        // the range is wide.
13536        assert_eq!(
13537            f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
13538            "-ERR range exceeds maximum of 1000000 items\r\n"
13539        );
13540    }
13541
13542    /// Every index in the argument list is read before the key is touched, so
13543    /// a bad one at the end leaves nothing half written.
13544    #[test]
13545    fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
13546        let mut f = Fixture::new();
13547        assert_eq!(
13548            f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
13549            "-ERR invalid array index\r\n"
13550        );
13551        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13552        f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
13553        assert_eq!(
13554            f.run(&[b"ARDEL", b"a", b"0", b"01"]),
13555            "-ERR invalid array index\r\n"
13556        );
13557        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
13558        // An index is unsigned here, so the numbers a list would take are not
13559        // the last element, they are errors.
13560        assert_eq!(
13561            f.run(&[b"ARGET", b"a", b"-1"]),
13562            "-ERR invalid array index\r\n"
13563        );
13564        // And a pair list with an odd tail is an arity error rather than a
13565        // syntax one.
13566        assert_eq!(
13567            f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
13568            "-ERR wrong number of arguments for 'armset' command\r\n"
13569        );
13570        assert_eq!(
13571            f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
13572            "-ERR wrong number of arguments for 'ardelrange' command\r\n"
13573        );
13574    }
13575
13576    #[test]
13577    fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
13578        let mut f = Fixture::new();
13579        f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
13580        assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
13581        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13582        // Two ranges in one command, and the second one covers the whole space
13583        // without walking it.
13584        assert_eq!(
13585            f.run(&[
13586                b"ARDELRANGE",
13587                b"a",
13588                b"100",
13589                b"200",
13590                b"0",
13591                b"18446744073709551614"
13592            ]),
13593            ":2\r\n"
13594        );
13595        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13596        assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
13597        assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
13598    }
13599
13600    /// A value goes out as the bytes it came in as, whichever of the three ways
13601    /// the array found to store it.
13602    #[test]
13603    fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
13604        let mut f = Fixture::new();
13605        let long = vec![b'v'; 200];
13606        f.run(&[
13607            b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
13608            b"short", b"5", &long, b"6", b"-0",
13609        ]);
13610        // 42 is an integer, 007 is not one because it does not print back the
13611        // same, 3.5 survives a double and 3.14 does not, and the last two are a
13612        // word packed string and a blob.
13613        assert_eq!(
13614            f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
13615            format!(
13616                "*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",
13617                String::from_utf8_lossy(&long)
13618            )
13619        );
13620    }
13621
13622    #[test]
13623    fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
13624        let mut f = Fixture::new();
13625        f.run(&[b"ARSET", b"a", b"0", b"x"]);
13626        assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
13627        assert_eq!(
13628            f.run(&[b"OBJECT", b"ENCODING", b"a"]),
13629            "$12\r\nsliced-array\r\n"
13630        );
13631        // And it is a body like any other, so the key commands work on it.
13632        assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
13633        assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
13634        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
13635        assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
13636        assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
13637        assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
13638    }
13639
13640    #[test]
13641    fn every_array_command_refuses_a_key_holding_something_else() {
13642        let mut f = Fixture::new();
13643        f.run(&[b"SET", b"s", b"v"]);
13644        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13645        for cmd in [
13646            &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
13647            &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
13648            &[b"ARGET".as_ref(), b"s", b"0"][..],
13649            &[b"ARMGET".as_ref(), b"s", b"0"][..],
13650            &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
13651            &[b"ARLEN".as_ref(), b"s"][..],
13652            &[b"ARCOUNT".as_ref(), b"s"][..],
13653            &[b"ARDEL".as_ref(), b"s", b"0"][..],
13654            &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
13655            &[b"ARINSERT".as_ref(), b"s", b"x"][..],
13656            &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
13657            &[b"ARNEXT".as_ref(), b"s"][..],
13658            &[b"ARSEEK".as_ref(), b"s", b"1"][..],
13659            &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
13660            &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
13661            &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
13662            &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
13663            &[b"ARINFO".as_ref(), b"s"][..],
13664        ] {
13665            assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
13666        }
13667    }
13668
13669    /// Two of the array commands look the key up before they read the index and
13670    /// the rest read the index first, so the same broken argument gets two
13671    /// different errors depending on which command it went to.
13672    #[test]
13673    fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
13674        let mut f = Fixture::new();
13675        f.run(&[b"SET", b"s", b"v"]);
13676        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13677        let bad = "-ERR invalid array index\r\n";
13678        assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
13679        assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
13680        assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
13681        assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
13682        assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
13683        assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
13684        // And on a key that is an array the index is just an index.
13685        f.run(&[b"ARSET", b"a", b"0", b"x"]);
13686        assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
13687        assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
13688    }
13689
13690    #[test]
13691    fn an_append_follows_a_cursor_the_client_can_move() {
13692        let mut f = Fixture::new();
13693        assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
13694        assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
13695        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
13696        assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
13697        assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
13698
13699        // A seek says where the next one goes, and a missing key has no cursor
13700        // to move and is not created by the asking.
13701        assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
13702        assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
13703        assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
13704        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
13705        assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
13706        assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
13707        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
13708
13709        // The top of the space is the one index only ARSEEK will take, and it
13710        // leaves the cursor with nowhere to go.
13711        assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
13712        assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
13713        assert_eq!(
13714            f.run(&[b"ARINSERT", b"a", b"x"]),
13715            "-ERR insert index overflow\r\n"
13716        );
13717        assert_eq!(
13718            f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
13719            "-ERR invalid array index\r\n"
13720        );
13721    }
13722
13723    #[test]
13724    fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
13725        let mut f = Fixture::new();
13726        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
13727        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
13728        assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
13729        assert_eq!(
13730            f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
13731            "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
13732        );
13733        // Growing it after it has wrapped puts the survivors back in the order
13734        // they arrived, which is the whole point of paying for the rebuild.
13735        assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
13736        assert_eq!(
13737            f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
13738            "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
13739        );
13740        // The size is read before the key, so a bad one is a bad size wherever
13741        // it is sent.
13742        assert_eq!(
13743            f.run(&[b"ARRING", b"r", b"0", b"x"]),
13744            "-ERR size must be positive\r\n"
13745        );
13746        assert_eq!(
13747            f.run(&[b"ARRING", b"r", b"big", b"x"]),
13748            "-ERR invalid size\r\n"
13749        );
13750    }
13751
13752    #[test]
13753    fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
13754        let mut f = Fixture::new();
13755        assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
13756        f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
13757        assert_eq!(
13758            f.run(&[b"ARLASTITEMS", b"r", b"3"]),
13759            "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
13760        );
13761        assert_eq!(
13762            f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
13763            "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
13764        );
13765        assert_eq!(
13766            f.run(&[b"ARLASTITEMS", b"r", b"99"]),
13767            "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
13768            "more than there is gets what there is"
13769        );
13770        // Nothing asked for is an empty reply, and Redis answers that before it
13771        // has read the option or looked at the key.
13772        assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
13773        assert_eq!(
13774            f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
13775            "-ERR syntax error\r\n"
13776        );
13777        assert_eq!(
13778            f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
13779            "-ERR invalid COUNT\r\n"
13780        );
13781
13782        // With no cursor the tail of the array is the anchor, and a hole inside
13783        // the window is reported as one.
13784        f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
13785        assert_eq!(
13786            f.run(&[b"ARLASTITEMS", b"h", b"5"]),
13787            "*2\r\n$-1\r\n$1\r\nz\r\n"
13788        );
13789    }
13790
13791    #[test]
13792    fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
13793        let mut f = Fixture::new();
13794        assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
13795        f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
13796        // The whole index space, which ARGETRANGE refuses and this one answers
13797        // in three visits because holes cost nothing.
13798        assert_eq!(
13799            f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
13800            "*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"
13801        );
13802        assert_eq!(
13803            f.run(&[
13804                b"ARSCAN",
13805                b"a",
13806                b"18446744073709551614",
13807                b"0",
13808                b"LIMIT",
13809                b"1"
13810            ]),
13811            "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
13812        );
13813        assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
13814        assert_eq!(
13815            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
13816            "-ERR LIMIT must be positive\r\n"
13817        );
13818        assert_eq!(
13819            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
13820            "-ERR syntax error\r\n"
13821        );
13822        assert_eq!(
13823            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
13824            "-ERR wrong number of arguments for 'arscan' command\r\n"
13825        );
13826    }
13827
13828    #[test]
13829    fn a_grep_answers_the_indexes_whose_elements_match() {
13830        let mut f = Fixture::new();
13831        assert_eq!(
13832            f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
13833            "*0\r\n"
13834        );
13835        f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
13836
13837        // The two bounds take the ends of the array as well as an index, and a
13838        // reversed range is walked backwards the way ARSCAN walks one.
13839        assert_eq!(
13840            f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
13841            "*3\r\n:0\r\n:1\r\n:2\r\n"
13842        );
13843        assert_eq!(
13844            f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
13845            "*3\r\n:2\r\n:1\r\n:0\r\n"
13846        );
13847        assert_eq!(
13848            f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
13849            "*2\r\n:1\r\n:2\r\n"
13850        );
13851
13852        // One test each. NOCASE reaches all four of them and it may be written
13853        // after the pattern it applies to.
13854        assert_eq!(
13855            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
13856            "*1\r\n:0\r\n"
13857        );
13858        assert_eq!(
13859            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
13860            "*2\r\n:0\r\n:3\r\n"
13861        );
13862        assert_eq!(
13863            f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
13864            "*1\r\n:2\r\n"
13865        );
13866        assert_eq!(
13867            f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
13868            "*2\r\n:1\r\n:2\r\n"
13869        );
13870
13871        // OR is the default and AND has to be asked for, and either way the
13872        // last of a repeated option wins.
13873        let both: &[&[u8]] = &[
13874            b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
13875        ];
13876        assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
13877        assert_eq!(
13878            f.run(&[
13879                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
13880            ]),
13881            "*0\r\n"
13882        );
13883        assert_eq!(
13884            f.run(&[
13885                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
13886            ]),
13887            "*2\r\n:0\r\n:1\r\n"
13888        );
13889
13890        // WITHVALUES turns each hit into a pair, and LIMIT counts the hits and
13891        // not the positions it had to look at.
13892        assert_eq!(
13893            f.run(&[
13894                b"ARGREP",
13895                b"a",
13896                b"-",
13897                b"+",
13898                b"MATCH",
13899                b"a",
13900                b"WITHVALUES",
13901                b"LIMIT",
13902                b"2"
13903            ]),
13904            "*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"
13905        );
13906        assert_eq!(
13907            f.run(&[
13908                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
13909            ]),
13910            "*1\r\n:3\r\n"
13911        );
13912    }
13913
13914    /// Everything ARGREP refuses, in the order it refuses it.
13915    #[test]
13916    fn a_grep_reports_a_broken_command_the_way_redis_does() {
13917        let mut f = Fixture::new();
13918        f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
13919        let syntax = "-ERR syntax error\r\n";
13920
13921        // The bounds are read before the plan, so a bad index beats a bad
13922        // predicate whichever way round the two are written.
13923        assert_eq!(
13924            f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
13925            "-ERR invalid array index\r\n"
13926        );
13927        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
13928        // A keyword with nothing after it, and a command that asks for nothing.
13929        assert_eq!(
13930            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
13931            syntax
13932        );
13933        assert_eq!(
13934            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
13935            syntax
13936        );
13937        assert_eq!(
13938            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
13939            syntax,
13940            "a command with no predicate in it at all"
13941        );
13942        assert_eq!(
13943            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
13944            "-ERR LIMIT must be positive\r\n"
13945        );
13946        assert_eq!(
13947            f.run(&[
13948                b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
13949            ]),
13950            "-ERR value is not an integer or out of range\r\n"
13951        );
13952        assert_eq!(
13953            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
13954            "-ERR regular expression is empty\r\n"
13955        );
13956        assert_eq!(
13957            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
13958            "-ERR invalid regular expression: Missing ')'\r\n"
13959        );
13960        assert_eq!(
13961            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
13962            "-ERR regular expression backreferences are not supported\r\n"
13963        );
13964        // The arity is minus six, so a predicate keyword with no pattern after
13965        // it is short by one and never reaches the parser.
13966        let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
13967        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
13968        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
13969    }
13970
13971    #[test]
13972    fn an_op_reduces_a_range_to_one_number() {
13973        let mut f = Fixture::new();
13974        f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
13975        assert_eq!(
13976            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
13977            "$4\r\n-0.5\r\n"
13978        );
13979        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
13980        assert_eq!(
13981            f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
13982            "$3\r\n2.5\r\n"
13983        );
13984        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
13985        assert_eq!(
13986            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
13987            ":1\r\n"
13988        );
13989        // An aggregate is written with seventeen significant digits, which is
13990        // Redis's own choice and not what a score comes back as.
13991        f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
13992        assert_eq!(
13993            f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
13994            "$19\r\n0.30000000000000004\r\n"
13995        );
13996        assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
13997        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
13998
13999        // Nothing to work with is a null, and a missing key is a null for the
14000        // aggregates and a zero for the two that count.
14001        f.run(&[b"ARSET", b"w", b"0", b"word"]);
14002        assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
14003        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
14004        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
14005
14006        assert_eq!(
14007            f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
14008            "-ERR unknown operation\r\n"
14009        );
14010        assert_eq!(
14011            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
14012            "-ERR MATCH requires a value argument\r\n"
14013        );
14014        assert_eq!(
14015            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
14016            "-ERR wrong number of arguments for 'arop' command\r\n"
14017        );
14018    }
14019
14020    #[test]
14021    fn the_info_is_a_map_and_a_missing_key_is_an_error() {
14022        let mut f = Fixture::new();
14023        assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
14024        f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
14025        let short = f.run(&[b"ARINFO", b"a"]);
14026        assert!(
14027            short.starts_with("*14\r\n"),
14028            "seven pairs on RESP2: {short}"
14029        );
14030        assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
14031        assert!(
14032            short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
14033            "{short}"
14034        );
14035        assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
14036        let full = f.run(&[b"ARINFO", b"a", b"full"]);
14037        assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
14038        // Two values one apart are held sparsely, so the dense count is zero and
14039        // the two dense averages have nothing to average.
14040        assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
14041        assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
14042        assert!(
14043            full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
14044            "{full}"
14045        );
14046        assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
14047
14048        // On RESP3 the same reply is a map and the averages are doubles.
14049        let mut g = Fixture::new();
14050        g.run(&[b"HELLO", b"3"]);
14051        g.run(&[b"ARINSERT", b"a", b"x"]);
14052        let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
14053        assert!(map.starts_with("%12\r\n"), "{map}");
14054        assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
14055        assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
14056    }
14057
14058    #[test]
14059    fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
14060        let mut f = Fixture::new();
14061        // Whole numbers up to two to the sixty second come back as integers,
14062        // and past that the digit generator takes over and uses an exponent.
14063        for (score, want) in [
14064            ("3", "3"),
14065            ("3.5", "3.5"),
14066            ("0.3", "0.3"),
14067            ("1e30", "1e+30"),
14068            ("1e19", "1e+19"),
14069            ("1e-7", "1e-7"),
14070            ("0.000001", "0.000001"),
14071            ("4611686018427387904", "4611686018427387904"),
14072            ("-0", "-0"),
14073        ] {
14074            f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
14075            assert_eq!(
14076                f.run(&[b"ZSCORE", b"z", b"m"]),
14077                format!("${}\r\n{want}\r\n", want.len()),
14078                "score {score}"
14079            );
14080        }
14081
14082        // The same bytes on RESP3, where the reply is a double rather than a
14083        // bulk string.
14084        let mut g = Fixture::new();
14085        g.run(&[b"HELLO", b"3"]);
14086        g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
14087        assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
14088        // The two float increments are not this printer. They go through
14089        // ld2string in its human mode, which is a fixed point conversion with
14090        // the trailing zeros taken off, so they never write an exponent, and
14091        // they reply with a bulk string on both protocols.
14092        assert_eq!(
14093            g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
14094            "$31\r\n1000000000000000000000000000000\r\n"
14095        );
14096        assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
14097        assert_eq!(
14098            g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
14099            "$20\r\n10000000000000000000\r\n"
14100        );
14101    }
14102
14103    // ----------------------------------------------------------------- graph
14104
14105    #[test]
14106    fn a_node_comes_back_with_the_fields_it_went_in_with() {
14107        let mut f = Fixture::new();
14108        assert_eq!(
14109            f.run(&[
14110                b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
14111            ]),
14112            ":1\r\n"
14113        );
14114        // The year comes back as the four bytes that were sent and not as a
14115        // number, because every property is text and there is nothing on the
14116        // wire that says which of `1815` and `"1815"` the client meant. The
14117        // fields are in the document's order, which is sorted by name, because
14118        // that is what makes a field lookup a binary search.
14119        assert_eq!(
14120            f.run(&[b"G.NGET", b"social", b"ada"]),
14121            "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
14122        );
14123        // A second write to the same id replaces the document and says so with
14124        // a zero, so an ingest can count what it created.
14125        assert_eq!(
14126            f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
14127            ":0\r\n"
14128        );
14129        assert_eq!(
14130            f.run(&[b"G.NGET", b"social", b"ada"]),
14131            "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
14132        );
14133        // A node with no properties is an empty map and not a null, which is
14134        // how a client tells an isolated node from one that is not there.
14135        assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
14136        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
14137        assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
14138        assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
14139
14140        // A field with no value creates nothing, because the pairs are checked
14141        // before the key is touched.
14142        assert_eq!(
14143            f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
14144            "-ERR syntax error\r\n"
14145        );
14146        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
14147
14148        // On RESP3 the same reply is a map.
14149        let mut g = Fixture::new();
14150        g.run(&[b"HELLO", b"3"]);
14151        g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
14152        assert_eq!(
14153            g.run(&[b"G.NGET", b"social", b"ada"]),
14154            "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
14155        );
14156    }
14157
14158    #[test]
14159    fn an_edge_creates_the_ends_it_needs() {
14160        let mut f = Fixture::new();
14161        assert_eq!(
14162            f.run(&[
14163                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
14164            ]),
14165            ":1\r\n"
14166        );
14167        // Neither end was written first and both are there, as empty nodes.
14168        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
14169        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
14170        assert_eq!(
14171            f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
14172            "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
14173        );
14174        assert_eq!(
14175            f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
14176            "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
14177        );
14178        // The same pair under the same label again updates the edge rather than
14179        // making a second one.
14180        assert_eq!(
14181            f.run(&[
14182                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
14183            ]),
14184            ":0\r\n"
14185        );
14186        assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
14187        // A different label between the same pair is a different edge.
14188        assert_eq!(
14189            f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
14190            ":1\r\n"
14191        );
14192        assert_eq!(
14193            f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
14194            ":1\r\n"
14195        );
14196
14197        assert_eq!(
14198            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
14199            ":1\r\n"
14200        );
14201        assert_eq!(
14202            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
14203            ":0\r\n"
14204        );
14205        // A label nothing has used, an end that is not there, and a key that is
14206        // not there are all a zero rather than an error.
14207        assert_eq!(
14208            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
14209            ":0\r\n"
14210        );
14211        assert_eq!(
14212            f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
14213            ":0\r\n"
14214        );
14215        assert_eq!(
14216            f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
14217            ":0\r\n"
14218        );
14219    }
14220
14221    /// A run is paged the way `SCAN` is paged, so a client that can walk one
14222    /// can walk the other.
14223    #[test]
14224    fn a_hop_answers_a_cursor_and_a_page() {
14225        let mut f = Fixture::new();
14226        for i in 0..25u32 {
14227            let dst = format!("n{i}");
14228            f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
14229        }
14230        // Ten without being asked, and the cursor is where to carry on from.
14231        let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
14232        assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
14233
14234        let mut seen = 0;
14235        let mut cursor = String::from("0");
14236        loop {
14237            let page = f.run(&[
14238                b"G.OUT",
14239                b"social",
14240                b"hub",
14241                b"FOLLOWS",
14242                b"COUNT",
14243                b"7",
14244                b"CURSOR",
14245                cursor.as_bytes(),
14246            ]);
14247            let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
14248            cursor = head
14249                .rsplit("\r\n")
14250                .next()
14251                .expect("the cursor line")
14252                .to_string();
14253            seen += rest
14254                .split_once("\r\n")
14255                .expect("the page length")
14256                .0
14257                .parse::<usize>()
14258                .expect("a length");
14259            if cursor == "0" {
14260                break;
14261            }
14262        }
14263        assert_eq!(seen, 25, "every neighbour once across the pages");
14264
14265        // A cursor past the end is an empty page and not an error, and so is a
14266        // key or a label that is not there.
14267        assert_eq!(
14268            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
14269            "*2\r\n$1\r\n0\r\n*0\r\n"
14270        );
14271        assert_eq!(
14272            f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
14273            "*2\r\n$1\r\n0\r\n*0\r\n"
14274        );
14275        assert_eq!(
14276            f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
14277            "*2\r\n$1\r\n0\r\n*0\r\n"
14278        );
14279        assert_eq!(
14280            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
14281            "-ERR COUNT must be a positive integer\r\n"
14282        );
14283        assert_eq!(
14284            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
14285            "-ERR syntax error\r\n"
14286        );
14287    }
14288
14289    #[test]
14290    fn a_degree_counts_one_way_or_both() {
14291        let mut f = Fixture::new();
14292        f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
14293        f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
14294        f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
14295        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
14296        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
14297        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
14298        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
14299        assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
14300        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
14301        assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
14302        assert_eq!(
14303            f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
14304            "-ERR syntax error\r\n"
14305        );
14306    }
14307
14308    /// A walk answers which nodes it can reach and not by how many routes, so a
14309    /// node two ways out is in the frontier once.
14310    #[test]
14311    fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
14312        let mut f = Fixture::new();
14313        for (src, dst) in [
14314            ("ada", "grace"),
14315            ("ada", "alan"),
14316            ("grace", "edsger"),
14317            ("alan", "edsger"),
14318            ("edsger", "barbara"),
14319        ] {
14320            f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
14321        }
14322        // Two hops without being asked, the start left out, and edsger once
14323        // even though both of the first hop's nodes point at it.
14324        assert_eq!(
14325            f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
14326            "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
14327        );
14328        assert_eq!(
14329            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
14330            "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
14331        );
14332        let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
14333        assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
14334        assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
14335        // COUNT stops the walk rather than trimming what it found.
14336        assert_eq!(
14337            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
14338            "*1\r\n$5\r\ngrace\r\n"
14339        );
14340        // A node nothing leaves is an empty array and not an error.
14341        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
14342        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
14343        assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
14344        assert_eq!(
14345            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
14346            "-ERR DEPTH must be a positive integer\r\n"
14347        );
14348        assert_eq!(
14349            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
14350            "-ERR syntax error\r\n"
14351        );
14352    }
14353
14354    /// The two sided search, which is the whole reason `G.PATH` is a command
14355    /// and not something a client builds out of `G.OUT`.
14356    #[test]
14357    fn a_path_is_the_shortest_one_and_goes_over_any_label() {
14358        let mut f = Fixture::new();
14359        // A chain of six, and a shortcut that makes a shorter way round under a
14360        // second label so the search has to take either kind of hop.
14361        for i in 0..6u32 {
14362            let src = format!("n{i}");
14363            let dst = format!("n{}", i + 1);
14364            f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
14365        }
14366        assert_eq!(
14367            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
14368            "*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"
14369        );
14370        f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
14371        assert_eq!(
14372            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
14373            "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
14374        );
14375        // A node to itself is a path of one, and a depth too short to reach is
14376        // no path at all.
14377        assert_eq!(
14378            f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
14379            "*1\r\n$2\r\nn2\r\n"
14380        );
14381        assert_eq!(
14382            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
14383            "*0\r\n"
14384        );
14385        // Direction counts: the chain only goes one way.
14386        assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
14387        // An unreachable node, a node that is not there, and a key that is not
14388        // there are the same empty answer.
14389        f.run(&[b"G.NADD", b"road", b"island"]);
14390        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
14391        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
14392        assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
14393        assert_eq!(
14394            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
14395            "-ERR syntax error\r\n"
14396        );
14397    }
14398
14399    /// The point of the escape in the record tag: the keyspace owns a graph key
14400    /// the way it owns every other key, and none of these commands know a graph
14401    /// exists.
14402    #[test]
14403    fn the_keyspace_sees_a_graph_key_like_any_other() {
14404        let mut f = Fixture::new();
14405        f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
14406        assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
14407        assert_eq!(
14408            f.run(&[b"OBJECT", b"ENCODING", b"social"]),
14409            "$9\r\nadjacency\r\n"
14410        );
14411        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
14412        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
14413        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
14414        // A graph is counted against the server the way every other body is,
14415        // which is what `maxmemory` will read when this key is a million nodes.
14416        // There is no `MEMORY USAGE` command yet, so this asks the server.
14417        let held = f.server.memory_bytes();
14418        for i in 0..200u32 {
14419            let dst = format!("n{i}");
14420            f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
14421        }
14422        assert!(
14423            f.server.memory_bytes() > held,
14424            "two hundred edges cost something: {held} then {}",
14425            f.server.memory_bytes()
14426        );
14427        f.run(&[b"DEL", b"big"]);
14428
14429        // An expiry, then a rename, then a move to another database, all of
14430        // which are the keyspace moving a record it cannot look inside.
14431        assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
14432        assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
14433        assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
14434        assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
14435        assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
14436        f.run(&[b"SELECT", b"1"]);
14437        assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
14438
14439        assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
14440        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14441        f.run(&[b"G.NADD", b"g", b"n"]);
14442        assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
14443        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14444    }
14445
14446    /// Neither `COPY` nor `DUMP` has a byte shape for a graph, so both say so
14447    /// rather than answering the way they answer for a key that is not there.
14448    #[test]
14449    fn a_graph_cannot_be_copied_or_dumped() {
14450        let mut f = Fixture::new();
14451        f.run(&[b"G.NADD", b"social", b"ada"]);
14452        assert_eq!(
14453            f.run(&[b"COPY", b"social", b"other"]),
14454            "-ERR COPY is not supported for a graph\r\n"
14455        );
14456        assert_eq!(
14457            f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
14458            "-ERR COPY is not supported for a graph\r\n"
14459        );
14460        assert_eq!(
14461            f.run(&[b"DUMP", b"social"]),
14462            "-ERR DUMP is not supported for a graph\r\n"
14463        );
14464        // A refused copy leaves both keys exactly as they were.
14465        assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
14466    }
14467
14468    /// A graph key is a key, so the commands for the other types refuse it and
14469    /// the graph commands refuse theirs.
14470    #[test]
14471    fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
14472        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14473        let mut f = Fixture::new();
14474        f.run(&[b"G.NADD", b"social", b"ada"]);
14475        assert_eq!(f.run(&[b"GET", b"social"]), wrong);
14476        assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
14477        assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
14478
14479        f.run(&[b"SET", b"str", b"v"]);
14480        for cmd in [
14481            vec![b"G.NADD".as_ref(), b"str", b"n"],
14482            vec![b"G.NGET".as_ref(), b"str", b"n"],
14483            vec![b"G.NDEL".as_ref(), b"str", b"n"],
14484            vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
14485            vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
14486            vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
14487            vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
14488            vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
14489            vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
14490            vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
14491        ] {
14492            assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
14493        }
14494    }
14495
14496    /// Every other collection here takes its key with it when its last member
14497    /// goes, and a graph is no different.
14498    #[test]
14499    fn a_graph_goes_when_its_last_node_does() {
14500        let mut f = Fixture::new();
14501        f.run(&[
14502            b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
14503        ]);
14504        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
14505        // The node and the edges that hung off it are both gone.
14506        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
14507        assert_eq!(
14508            f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
14509            ":0\r\n"
14510        );
14511        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
14512        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
14513
14514        assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
14515        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
14516        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14517        assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
14518
14519        // The id the removed node had is not handed out again, so a client
14520        // holding an id from an earlier reply cannot have it mean another node.
14521        f.run(&[b"G.NADD", b"social", b"first"]);
14522        f.run(&[b"G.NADD", b"social", b"second"]);
14523        f.run(&[b"G.NDEL", b"social", b"first"]);
14524        f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
14525        assert_eq!(
14526            f.run(&[b"G.OUT", b"social", b"third", b"F"]),
14527            "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
14528        );
14529    }
14530
14531    // ------------------------------------------------------------------ json
14532
14533    /// The two path syntaxes answer different shapes, which is the thing a
14534    /// client is most likely to be broken by and so the thing to pin first.
14535    #[test]
14536    fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
14537        let mut f = Fixture::new();
14538        let doc = br#"{"a":1,"b":{"c":true}}"#;
14539        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
14540        // No path at all is the legacy root and not `$`, so the document comes
14541        // back as itself rather than wrapped.
14542        assert_eq!(
14543            f.run(&[b"JSON.GET", b"doc"]),
14544            bulk(r#"{"a":1,"b":{"c":true}}"#)
14545        );
14546        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
14547        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
14548        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
14549        // A path that matched nothing is an empty set on one syntax and an
14550        // error on the other, and the error does not quote the path.
14551        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
14552        assert_eq!(
14553            f.run(&[b"JSON.GET", b"doc", b".nope"]),
14554            "-ERR Path does not exist\r\n"
14555        );
14556        assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
14557        // The key is a document to the rest of the keyspace, under the name
14558        // RedisJSON registers, and every generic command works on it.
14559        assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
14560        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
14561        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
14562        assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
14563        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14564    }
14565
14566    /// The two error lines RedisJSON sends without a prefix in front of them.
14567    ///
14568    /// Every other error this server writes starts `ERR` or `WRONGTYPE`. These
14569    /// two do not, on a real server, and a differential harness compares the
14570    /// whole line.
14571    #[test]
14572    fn the_two_json_errors_that_carry_no_prefix() {
14573        let mut f = Fixture::new();
14574        f.run(&[b"SET", b"plain", b"x"]);
14575        let wrong = "-Existing key has wrong Redis type\r\n";
14576        assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
14577        assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
14578        assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
14579        assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
14580        assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
14581
14582        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
14583        // A wildcard that matched something writes to all of it. A wildcard
14584        // that matched nothing would have to invent a place, and that is the
14585        // other unprefixed line.
14586        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
14587        assert_eq!(
14588            f.run(&[b"JSON.GET", b"doc"]),
14589            bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
14590        );
14591        assert_eq!(
14592            f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
14593            "-Err wrong static path\r\n"
14594        );
14595    }
14596
14597    /// What `JSON.SET` does with a path that named nowhere.
14598    #[test]
14599    fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
14600        let mut f = Fixture::new();
14601        // A key that is not there can only be written whole.
14602        assert_eq!(
14603            f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
14604            "-ERR new objects must be created at the root\r\n"
14605        );
14606        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14607        // The root check comes before NX and XX, which is the order a real
14608        // server checks them in.
14609        assert_eq!(
14610            f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
14611            "-ERR new objects must be created at the root\r\n"
14612        );
14613        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
14614        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
14615
14616        f.run(&[
14617            b"JSON.SET",
14618            b"doc",
14619            b"$",
14620            br#"{"o":{},"arr":[1,2],"s":"x"}"#,
14621        ]);
14622        // One step past a container that is there is a place to write.
14623        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
14624        // One step past something that is not, or past something that is not an
14625        // object, is not an error and is not a write either.
14626        assert_eq!(
14627            f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
14628            "$-1\r\n"
14629        );
14630        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
14631        // An index past the end does not append. JSON.ARRAPPEND appends.
14632        assert_eq!(
14633            f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
14634            "-ERR array index out of range\r\n"
14635        );
14636        assert_eq!(
14637            f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
14638            "-ERR array index out of range\r\n"
14639        );
14640        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
14641        // NX on a path that is there and XX on a path that is not are both a
14642        // nil and neither changes anything.
14643        assert_eq!(
14644            f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
14645            "$-1\r\n"
14646        );
14647        assert_eq!(
14648            f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
14649            "$-1\r\n"
14650        );
14651        assert_eq!(
14652            f.run(&[b"JSON.GET", b"doc"]),
14653            bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
14654        );
14655        // Text that is not JSON is refused before the key is touched. The
14656        // line has no `ERR` in front of it, which is this command's and not
14657        // every command's, and is in D-37.
14658        assert!(
14659            f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
14660                .starts_with("-this is not the start of a value")
14661        );
14662        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
14663    }
14664
14665    /// `JSON.DEL`, `JSON.TYPE`, `JSON.TOGGLE` and `JSON.CLEAR`, each of which
14666    /// answers a count or a word rather than text.
14667    #[test]
14668    fn the_json_commands_that_do_not_answer_text() {
14669        let mut f = Fixture::new();
14670        let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
14671        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14672
14673        assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
14674        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
14675        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
14676        assert_eq!(
14677            f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
14678            format!("*1\r\n{}", bulk("integer"))
14679        );
14680        // The one place a legacy path that matched nothing is a nil rather than
14681        // an error, which lines up with a key that is not there.
14682        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
14683        assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
14684
14685        // A boolean flips and answers the value it now has, as an integer on
14686        // one syntax and as the word on the other.
14687        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
14688        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
14689        // Something that is not a boolean is a hole on one syntax and one
14690        // sentence covering both cases on the other.
14691        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
14692        assert_eq!(
14693            f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
14694            "-ERR Path does not exist or not a bool\r\n"
14695        );
14696        assert_eq!(
14697            f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
14698            "-ERR Path does not exist or not a bool\r\n"
14699        );
14700        assert_eq!(
14701            f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
14702            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14703        );
14704
14705        // Clearing empties containers and zeroes numbers and leaves everything
14706        // else alone, and counts only what it changed.
14707        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
14708        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
14709        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
14710        assert_eq!(
14711            f.run(&[b"JSON.GET", b"doc"]),
14712            bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
14713        );
14714
14715        // Deleting counts what it removed, and deleting the root is deleting
14716        // the key.
14717        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
14718        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
14719        // Deleting the last member of the root container deletes the key, the
14720        // same way popping the last element off a list does. It is a rule about
14721        // deleting and not about shape: a document written as an empty object
14722        // by JSON.SET stays, because nothing was removed from it.
14723        assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
14724        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
14725        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14726        assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
14727        assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
14728        assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
14729        assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
14730        assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
14731    }
14732
14733    /// `JSON.GET` with more than one path, and with a layout.
14734    ///
14735    /// The wrapper the reply is built in is laid out too, so what a path
14736    /// matched starts one level in for a single JSONPath and two for one of
14737    /// several, and getting that wrong is the kind of thing only a byte for
14738    /// byte comparison catches.
14739    #[test]
14740    fn json_get_lays_out_the_wrapper_it_builds() {
14741        let mut f = Fixture::new();
14742        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
14743
14744        assert_eq!(
14745            f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
14746            bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
14747        );
14748        // Legacy paths are not wrapped, even when there are several of them.
14749        assert_eq!(
14750            f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
14751            bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
14752        );
14753        let fmt: &[&[u8]] = &[b"INDENT", b"  ", b"NEWLINE", b"\n", b"SPACE", b" "];
14754        let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
14755        one.extend_from_slice(fmt);
14756        one.push(b"$.b");
14757        assert_eq!(
14758            f.run(&one),
14759            bulk("[\n  [\n    1,\n    {\n      \"c\": 2\n    }\n  ]\n]")
14760        );
14761        let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
14762        two.extend_from_slice(fmt);
14763        two.push(b"$.a");
14764        two.push(b"$.nope");
14765        assert_eq!(
14766            f.run(&two),
14767            bulk("{\n  \"$.a\": [\n    1\n  ],\n  \"$.nope\": []\n}")
14768        );
14769        // The options are read before the paths and in any order, and a
14770        // document with nothing to lay out is the same either way.
14771        let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
14772        root.push(b".a");
14773        assert_eq!(f.run(&root), bulk("1"));
14774    }
14775
14776    /// `JSON.MGET`, which is the only command here that reads more than one key
14777    /// and so the only one whose answer has holes in it.
14778    #[test]
14779    fn json_mget_answers_once_per_key_whatever_is_under_them() {
14780        let mut f = Fixture::new();
14781        f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
14782        f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
14783        f.run(&[b"SET", b"plain", b"x"]);
14784        assert_eq!(
14785            f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
14786            format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
14787        );
14788        // A key that is not there and a key holding something else are both a
14789        // hole rather than an error, the way MGET treats a hash.
14790        assert_eq!(
14791            f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
14792            format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
14793        );
14794        // A legacy path that matched nothing is a hole too, because one bad
14795        // answer should not lose the others.
14796        assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
14797    }
14798
14799    /// The four commands that ask how big something is, and the four different
14800    /// sets of answers they give for the same three failures.
14801    ///
14802    /// There is no pattern in this and there is no reading it off the
14803    /// documentation either. It was read off a running RedisJSON one line at a
14804    /// time, and it is written down here because the error text is what a client
14805    /// library branches on.
14806    #[test]
14807    fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
14808        let mut f = Fixture::new();
14809        let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
14810        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14811
14812        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
14813        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
14814        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
14815        assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
14816        assert_eq!(
14817            f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
14818            format!("*2\r\n{}{}", bulk("x"), bulk("y"))
14819        );
14820        // A JSONPath answers one entry per match and a hole for a match of the
14821        // wrong kind, which is the one shape all four agree on.
14822        assert_eq!(
14823            f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
14824            "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
14825        );
14826
14827        // A legacy path that matched nothing. Two of them are an error and two
14828        // of them are a nil, and the two errors do not use the same sentence.
14829        assert_eq!(
14830            f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
14831            "-ERR Path does not exist\r\n"
14832        );
14833        assert_eq!(
14834            f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
14835            "-ERR Path does not exist\r\n"
14836        );
14837        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
14838        // A nil bulk and not an empty array, even though the answer would have
14839        // been an array, which is what RedisJSON sends here too.
14840        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
14841        // The JSONPath spelling of the same question is an empty array, since
14842        // no match is not a failure on that syntax.
14843        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
14844
14845        // A legacy path that matched the wrong kind of value. Now two of them
14846        // are an ERR and two of them are a WRONGTYPE, and it is not the same
14847        // two.
14848        assert_eq!(
14849            f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
14850            "-ERR Path does not exist or not an array\r\n"
14851        );
14852        assert_eq!(
14853            f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
14854            "-ERR Path does not exist or not an object\r\n"
14855        );
14856        assert_eq!(
14857            f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
14858            "-WRONGTYPE wrong type of path value - expected object\r\n"
14859        );
14860        assert_eq!(
14861            f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
14862            "-WRONGTYPE wrong type of path value - expected string\r\n"
14863        );
14864
14865        // A key that is not there, where the two syntaxes swap over: the legacy
14866        // path is the quiet answer and the JSONPath is the error.
14867        assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
14868        assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
14869        assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
14870        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
14871        assert_eq!(
14872            f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
14873            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14874        );
14875        // Except this one, which answers about the path instead.
14876        assert_eq!(
14877            f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
14878            "-ERR Path does not exist or not an object\r\n"
14879        );
14880    }
14881
14882    /// `JSON.ARRAPPEND`, `JSON.ARRINSERT`, `JSON.ARRTRIM` and `JSON.ARRPOP`.
14883    ///
14884    /// The four of them share one error line for a path that named something
14885    /// that is not an array, and they disagree about what an index outside the
14886    /// array means: insert refuses it and the other two clamp.
14887    #[test]
14888    fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
14889        let mut f = Fixture::new();
14890        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
14891
14892        assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
14893        assert_eq!(
14894            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
14895            "*1\r\n:6\r\n"
14896        );
14897        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
14898
14899        // A negative index counts back from the end, and the end itself is a
14900        // place to insert at, so an insert at the length is an append.
14901        assert_eq!(
14902            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
14903            ":7\r\n"
14904        );
14905        assert_eq!(
14906            f.run(&[b"JSON.GET", b"doc", b".a"]),
14907            bulk("[1,2,3,4,5,0,6]")
14908        );
14909        assert_eq!(
14910            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
14911            ":8\r\n"
14912        );
14913        // One past the end is not, and neither is one before the front.
14914        assert_eq!(
14915            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
14916            "-ERR index out of bounds\r\n"
14917        );
14918        assert_eq!(
14919            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
14920            "-ERR index out of bounds\r\n"
14921        );
14922
14923        // Trim takes both ends inclusive and clamps both of them, so a start
14924        // past the end leaves an empty array rather than an error.
14925        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
14926        assert_eq!(
14927            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
14928            ":3\r\n"
14929        );
14930        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
14931        assert_eq!(
14932            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
14933            ":2\r\n"
14934        );
14935        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
14936        assert_eq!(
14937            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
14938            ":0\r\n"
14939        );
14940        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14941
14942        // Pop clamps as well, its default is the last element, and an empty
14943        // array pops a nil rather than failing.
14944        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
14945        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
14946        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
14947        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
14948        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
14949
14950        // One sentence covers a path that matched nothing and a path that
14951        // matched the wrong kind of value, for all four of them.
14952        for call in [
14953            &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
14954            &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
14955            &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
14956            &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
14957        ] {
14958            for path in [&b".n"[..], &b".nope"[..]] {
14959                let args: Vec<&[u8]> = call
14960                    .iter()
14961                    .map(|a| if *a == b"PATH" { path } else { *a })
14962                    .collect();
14963                assert_eq!(
14964                    f.run(&args),
14965                    "-ERR Path does not exist or not an array\r\n",
14966                    "{} {}",
14967                    String::from_utf8_lossy(call[0]),
14968                    String::from_utf8_lossy(path)
14969                );
14970            }
14971        }
14972
14973        // A key that is not there is the same sentence for all four, on either
14974        // syntax, and it is about the key and not about the path.
14975        assert_eq!(
14976            f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
14977            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14978        );
14979        assert_eq!(
14980            f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
14981            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14982        );
14983
14984        // The values are parsed before the key is touched, so text that is not
14985        // JSON leaves the document alone.
14986        // Text that is not JSON is refused before the key is touched, and
14987        // the line has no `ERR` in front of it, which is D-37.
14988        assert!(
14989            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
14990                .starts_with("-this is not the start of a value")
14991        );
14992        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14993    }
14994
14995    /// `JSON.ARRINSERT` refuses the whole command when any one of the arrays a
14996    /// path matched cannot take the index, which is D-36.
14997    ///
14998    /// RedisJSON walks the matches, inserts into each one it can, and returns
14999    /// the error on the first one it cannot, leaving the earlier inserts in the
15000    /// document. A write here is one list of edits applied together, so either
15001    /// all of them happen or none of them do.
15002    #[test]
15003    fn json_arrinsert_is_all_or_nothing_across_the_matches() {
15004        let mut f = Fixture::new();
15005        let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
15006        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15007        assert_eq!(
15008            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
15009            "-ERR index out of bounds\r\n"
15010        );
15011        assert_eq!(
15012            f.run(&[b"JSON.GET", b"doc"]),
15013            bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
15014        );
15015        // Every match can take the index, so every match gets it.
15016        assert_eq!(
15017            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
15018            "*3\r\n:4\r\n:3\r\n:2\r\n"
15019        );
15020        assert_eq!(
15021            f.run(&[b"JSON.GET", b"doc"]),
15022            bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
15023        );
15024    }
15025
15026    /// `JSON.ARRINDEX`, whose stop is exclusive and whose start clamps to the
15027    /// last element rather than to one past it.
15028    ///
15029    /// Both of those read like mistakes and both are what RedisJSON does. The
15030    /// start is the one that bites: a start of five into an array of four still
15031    /// looks at the fourth, so a search that should have run out of array comes
15032    /// back with an answer.
15033    #[test]
15034    fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
15035        let mut f = Fixture::new();
15036        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
15037
15038        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
15039        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
15040        assert_eq!(
15041            f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
15042            "*1\r\n:1\r\n"
15043        );
15044
15045        // Zero as the stop means the end rather than the front, so leaving it
15046        // off and passing it are the same thing.
15047        assert_eq!(
15048            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
15049            ":3\r\n"
15050        );
15051        // The stop is exclusive, so a stop of three does not look at index
15052        // three.
15053        assert_eq!(
15054            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
15055            ":-1\r\n"
15056        );
15057
15058        // The start clamps to the last element in both directions, which is why
15059        // a start of four, five or minus one all find the 1 at index three.
15060        for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
15061            assert_eq!(
15062                f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
15063                ":3\r\n",
15064                "{}",
15065                String::from_utf8_lossy(start)
15066            );
15067        }
15068        assert_eq!(
15069            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
15070            ":0\r\n"
15071        );
15072        // An empty array is the one case that comes back with nothing, since
15073        // the stop is zero and the loop never starts.
15074        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
15075        assert_eq!(
15076            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
15077            ":-1\r\n"
15078        );
15079
15080        // The comparison is structural rather than one of the encoded bytes,
15081        // because an object in a stored document holds its keys as intern table
15082        // ids where one parsed off the wire holds them as bytes.
15083        f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
15084        assert_eq!(
15085            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
15086            ":0\r\n"
15087        );
15088        assert_eq!(
15089            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
15090            ":1\r\n"
15091        );
15092        assert_eq!(
15093            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
15094            ":-1\r\n"
15095        );
15096
15097        // Its errors are a third set again: a missing legacy path is the short
15098        // sentence, the wrong kind of value is a WRONGTYPE, and a key that is
15099        // not there is about the path on either syntax.
15100        assert_eq!(
15101            f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
15102            "-ERR Path does not exist\r\n"
15103        );
15104        assert_eq!(
15105            f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
15106            "-WRONGTYPE wrong type of path value - expected array\r\n"
15107        );
15108        assert_eq!(
15109            f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
15110            "-ERR Path does not exist\r\n"
15111        );
15112        assert_eq!(
15113            f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
15114            "-ERR Path does not exist\r\n"
15115        );
15116    }
15117
15118    /// The number family answers text and keeps an integer an integer until
15119    /// something in the sum is not one.
15120    #[test]
15121    fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
15122        let mut f = Fixture::new();
15123        let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
15124        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15125
15126        // A legacy path answers the new value as JSON text in a bulk string,
15127        // not as a number, which is the shape all three of them use.
15128        assert_eq!(
15129            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
15130            bulk("9").as_str()
15131        );
15132        // A JSONPath answers a bulk string holding a JSON array.
15133        assert_eq!(
15134            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
15135            bulk("[11]").as_str()
15136        );
15137        // Two integers stay an integer and a double anywhere in it makes the
15138        // answer a double, which the document then holds.
15139        assert_eq!(
15140            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
15141            bulk("13.0").as_str()
15142        );
15143        assert_eq!(
15144            f.run(&[b"JSON.TYPE", b"doc", b".i"]),
15145            bulk("number").as_str()
15146        );
15147        assert_eq!(
15148            f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
15149            bulk("3.0").as_str()
15150        );
15151        assert_eq!(
15152            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
15153            bulk("-8").as_str()
15154        );
15155        // A power of a half is a square root, and the square root of a negative
15156        // number is the error that says the answer is not a number.
15157        f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
15158        assert_eq!(
15159            f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
15160            bulk("1.224744871391589").as_str()
15161        );
15162        assert_eq!(
15163            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
15164            "-ERR result is not a number\r\n"
15165        );
15166        // An integer answer that does not fit is refused rather than promoted,
15167        // and a negative exponent lands in the same error because there is no
15168        // integer answer to two to the minus one.
15169        f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
15170        assert_eq!(
15171            f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
15172            "-ERR numeric overflow\r\n"
15173        );
15174        f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
15175        assert_eq!(
15176            f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
15177            "-ERR numeric overflow\r\n"
15178        );
15179        // A double that leaves the finite numbers is the other error.
15180        f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
15181        assert_eq!(
15182            f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
15183            "-ERR result is not a number\r\n"
15184        );
15185
15186        // A match that is not a number is a null inside the array on a
15187        // JSONPath, and a legacy path that found no number at all is the error
15188        // with the module's own typo in it.
15189        assert_eq!(
15190            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
15191            bulk("[null]").as_str()
15192        );
15193        assert_eq!(
15194            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
15195            bulk("[]").as_str()
15196        );
15197        assert_eq!(
15198            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
15199            "-ERR Path does not exist or does not contains a number\r\n"
15200        );
15201        assert_eq!(
15202            f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
15203            "-ERR Path does not exist or does not contains a number\r\n"
15204        );
15205        // The operand is JSON and has to be a number. Valid JSON that is not
15206        // one is a line of its own, and it goes out without a prefix.
15207        assert_eq!(
15208            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
15209            "-bad input number\r\n"
15210        );
15211        assert_eq!(
15212            f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
15213            "-ERR could not perform this operation on a key that doesn't exist\r\n"
15214        );
15215        assert_eq!(
15216            f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
15217            "-ERR could not perform this operation on a key that doesn't exist\r\n"
15218        );
15219    }
15220
15221    /// `JSON.STRAPPEND` puts its path in the middle and makes it optional,
15222    /// which nothing else in the group does.
15223    #[test]
15224    fn json_strappend_reads_its_shape_off_the_argument_count() {
15225        let mut f = Fixture::new();
15226        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
15227
15228        assert_eq!(
15229            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
15230            ":3\r\n"
15231        );
15232        assert_eq!(
15233            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
15234            "*1\r\n:4\r\n"
15235        );
15236        // The length is in bytes and not in characters, so one two byte letter
15237        // takes it up by two.
15238        assert_eq!(
15239            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
15240            ":6\r\n"
15241        );
15242        // Three arguments means the value is the last one and the path is the
15243        // root, so this appends to a document that is a string on its own.
15244        f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
15245        assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
15246        assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
15247
15248        // The value is JSON and has to be a JSON string. A number is a
15249        // WRONGTYPE about a path value even though it was the value that was
15250        // wrong, which is the module's wording and not a slip here.
15251        assert_eq!(
15252            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
15253            "-WRONGTYPE wrong type of path value - expected string\r\n"
15254        );
15255        assert_eq!(
15256            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
15257            "*1\r\n$-1\r\n"
15258        );
15259        assert_eq!(
15260            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
15261            "-ERR Path does not exist or not a string\r\n"
15262        );
15263        assert_eq!(
15264            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
15265            "*0\r\n"
15266        );
15267        assert_eq!(
15268            f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
15269            "-ERR could not perform this operation on a key that doesn't exist\r\n"
15270        );
15271    }
15272
15273    /// A legacy path can match more than one value, and which of them the one
15274    /// answer comes from is not the same choice twice.
15275    #[test]
15276    fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
15277        let mut f = Fixture::new();
15278        // Three arrays of one, two and three elements, which tells the first
15279        // match and the last match apart in a single command.
15280        let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
15281
15282        f.run(&[b"JSON.SET", b"doc", b"$", three]);
15283        assert_eq!(
15284            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
15285            ":4\r\n"
15286        );
15287        f.run(&[b"JSON.SET", b"doc", b"$", three]);
15288        assert_eq!(
15289            f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
15290            ":2\r\n"
15291        );
15292        f.run(&[b"JSON.SET", b"doc", b"$", three]);
15293        assert_eq!(
15294            f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
15295            ":1\r\n"
15296        );
15297        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
15298        assert_eq!(
15299            f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
15300            bulk("1").as_str()
15301        );
15302        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
15303        assert_eq!(
15304            f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
15305            bulk("13").as_str()
15306        );
15307        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
15308        assert_eq!(
15309            f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
15310            ":4\r\n"
15311        );
15312        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
15313        assert_eq!(
15314            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
15315            bulk("false").as_str()
15316        );
15317        // Every one of them wrote to all three matches, whichever one it chose
15318        // to answer about.
15319        assert_eq!(
15320            f.run(&[b"JSON.GET", b"doc", b".a"]),
15321            bulk("[false,true,false]").as_str()
15322        );
15323
15324        // A match of the wrong kind is skipped rather than being the answer, so
15325        // a path that found a string and then two arrays still answers.
15326        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
15327        assert_eq!(
15328            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
15329            ":3\r\n"
15330        );
15331        assert_eq!(
15332            f.run(&[b"JSON.GET", b"doc", b".a"]),
15333            bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
15334        );
15335        // Nothing of the right kind anywhere is the error, and that is the only
15336        // case that is.
15337        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
15338        assert_eq!(
15339            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
15340            "-ERR Path does not exist or not an array\r\n"
15341        );
15342        assert_eq!(
15343            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
15344            "-ERR Path does not exist or not a bool\r\n"
15345        );
15346        // The one array that was there and had nothing in it is an answer and
15347        // not a skip, so the pop answers about it rather than about the array
15348        // after it.
15349        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
15350        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
15351        assert_eq!(
15352            f.run(&[b"JSON.GET", b"doc", b".a"]),
15353            bulk("[[],[2]]").as_str()
15354        );
15355    }
15356
15357    /// A path that matched a value and something inside that value writes to
15358    /// both, which is what `$..` and a nested wildcard are for.
15359    #[test]
15360    fn a_write_reaches_a_match_that_sits_inside_another_match() {
15361        let mut f = Fixture::new();
15362        let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
15363
15364        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
15365        assert_eq!(
15366            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
15367            "*3\r\n:3\r\n:2\r\n:3\r\n"
15368        );
15369        assert_eq!(
15370            f.run(&[b"JSON.GET", b"doc", b"$"]),
15371            bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
15372        );
15373
15374        // The same for a trim, where the outer array keeps the two elements the
15375        // inner writes landed in.
15376        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
15377        assert_eq!(
15378            f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
15379            "*3\r\n:1\r\n:1\r\n:1\r\n"
15380        );
15381        assert_eq!(
15382            f.run(&[b"JSON.GET", b"doc", b"$"]),
15383            bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
15384        );
15385
15386        // And for a number, where the first match is the object the outer array
15387        // holds and only the two inside it are numbers.
15388        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
15389        assert_eq!(
15390            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
15391            bulk("[null,8,8]").as_str()
15392        );
15393    }
15394
15395    /// The value a write is given is looked at only once the path has found
15396    /// something of the right kind to use it on.
15397    #[test]
15398    fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
15399        let mut f = Fixture::new();
15400        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
15401
15402        // A string is not a number, so the path answers first and the `"x"` is
15403        // never looked at. Same for the value that is not JSON at all.
15404        assert_eq!(
15405            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
15406            bulk("[null]").as_str()
15407        );
15408        assert_eq!(
15409            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
15410            bulk("[null]").as_str()
15411        );
15412        assert_eq!(
15413            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
15414            bulk("[]").as_str()
15415        );
15416        assert_eq!(
15417            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
15418            "-ERR Path does not exist or does not contains a number\r\n"
15419        );
15420        // A number match anywhere and the value is looked at after all.
15421        assert_eq!(
15422            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
15423            "-bad input number\r\n"
15424        );
15425
15426        // JSON.STRAPPEND follows the same order with its own two answers.
15427        assert_eq!(
15428            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
15429            "*1\r\n$-1\r\n"
15430        );
15431        assert_eq!(
15432            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
15433            "-ERR Path does not exist or not a string\r\n"
15434        );
15435        assert_eq!(
15436            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
15437            "-WRONGTYPE wrong type of path value - expected string\r\n"
15438        );
15439
15440        // A key that is not there still comes before either of them.
15441        assert_eq!(
15442            f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
15443            "-ERR could not perform this operation on a key that doesn't exist\r\n"
15444        );
15445        assert_eq!(
15446            f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
15447            "-ERR could not perform this operation on a key that doesn't exist\r\n"
15448        );
15449    }
15450
15451    /// RFC 7386 in one test: a null deletes, everything else merges, and a
15452    /// patch that is not an object replaces what it lands on.
15453    #[test]
15454    fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
15455        let mut f = Fixture::new();
15456
15457        // A key that is not there is created at the root, nulls and all,
15458        // because a deletion with nothing to delete is still what the client
15459        // sent.
15460        assert_eq!(
15461            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
15462            "+OK\r\n"
15463        );
15464        assert_eq!(
15465            f.run(&[b"JSON.GET", b"doc", b"$"]),
15466            bulk(r#"[{"x":null,"y":1}]"#).as_str()
15467        );
15468
15469        // Onto something that is there, a null deletes the member of that name
15470        // and the rest is merged one level at a time.
15471        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
15472        assert_eq!(
15473            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
15474            "+OK\r\n"
15475        );
15476        assert_eq!(
15477            f.run(&[b"JSON.GET", b"doc", b"$"]),
15478            bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
15479        );
15480
15481        // A patch that is not an object replaces what it is merged onto.
15482        assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
15483        assert_eq!(
15484            f.run(&[b"JSON.GET", b"doc", b"$"]),
15485            bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
15486        );
15487
15488        // A patch object onto a value that is not an object starts from an
15489        // empty object, so this time the null has nothing to delete and is
15490        // dropped rather than stored.
15491        assert_eq!(
15492            f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
15493            "+OK\r\n"
15494        );
15495        assert_eq!(
15496            f.run(&[b"JSON.GET", b"doc", b"$"]),
15497            bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
15498        );
15499
15500        // A member one level past the end of the document is created and keeps
15501        // its nulls, two levels past it is a write that did not happen, and a
15502        // path that would have to invent where it goes is the unprefixed line.
15503        assert_eq!(
15504            f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
15505            "+OK\r\n"
15506        );
15507        assert_eq!(
15508            f.run(&[b"JSON.GET", b"doc", b"$.new"]),
15509            bulk(r#"[{"z":null}]"#).as_str()
15510        );
15511        assert_eq!(
15512            f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
15513            "$-1\r\n"
15514        );
15515        assert_eq!(
15516            f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
15517            "-Err wrong static path\r\n"
15518        );
15519
15520        // A wildcard merges every match.
15521        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
15522        assert_eq!(
15523            f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
15524            "+OK\r\n"
15525        );
15526        assert_eq!(
15527            f.run(&[b"JSON.GET", b"doc", b"$"]),
15528            bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
15529        );
15530
15531        // The three ways to get it wrong.
15532        assert_eq!(
15533            f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
15534            "-ERR syntax error\r\n"
15535        );
15536        assert_eq!(
15537            f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
15538            "-ERR new objects must be created at the root\r\n"
15539        );
15540        f.run(&[b"SET", b"str", b"x"]);
15541        assert_eq!(
15542            f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
15543            "-Existing key has wrong Redis type\r\n"
15544        );
15545    }
15546
15547    /// A descent is the one path that matches a value and something inside that
15548    /// same value, and the inner merge has to survive the outer one.
15549    #[test]
15550    fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
15551        let mut f = Fixture::new();
15552        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15553        assert_eq!(
15554            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
15555            "+OK\r\n"
15556        );
15557        // `a`, `a.b`, `c` and `c[0]` all match. `a.b` is merged first and `a` is
15558        // merged onto the result, so the `{"m":1}` written into `a.b` is still
15559        // there. Doing it the other way round would leave `{"a":{"b":1,"m":1}}`.
15560        assert_eq!(
15561            f.run(&[b"JSON.GET", b"doc", b"$"]),
15562            bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
15563        );
15564
15565        // A deletion down the same path, which is the case where the inner
15566        // merge empties the object the outer one then copies.
15567        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15568        assert_eq!(
15569            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
15570            "+OK\r\n"
15571        );
15572        assert_eq!(
15573            f.run(&[b"JSON.GET", b"doc", b"$"]),
15574            bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
15575        );
15576    }
15577
15578    /// A filter is a selector like any other, so every command that takes a path
15579    /// takes one, reads and writes alike.
15580    #[test]
15581    fn a_filter_path_reads_and_writes_the_members_it_keeps() {
15582        let mut f = Fixture::new();
15583        let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
15584        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15585
15586        assert_eq!(
15587            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
15588            bulk(r#"["a","c"]"#).as_str()
15589        );
15590        // `$` inside the expression is the document, so a member can be measured
15591        // against something that is not inside it.
15592        assert_eq!(
15593            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
15594            bulk(r#"["a","c"]"#).as_str()
15595        );
15596        // The legacy syntax takes one too, and answers the first match.
15597        assert_eq!(
15598            f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
15599            bulk(r#""a""#).as_str()
15600        );
15601        assert_eq!(
15602            f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
15603            "*1\r\n$6\r\nobject\r\n"
15604        );
15605
15606        // A write goes through it as far as a value that is already there. A
15607        // field that is not there yet has nowhere definite to go, which is the
15608        // same refusal a wildcard gets.
15609        assert_eq!(
15610            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
15611            bulk("[9,10]").as_str()
15612        );
15613        assert_eq!(
15614            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
15615            "+OK\r\n"
15616        );
15617        assert_eq!(
15618            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
15619            "-Err wrong static path\r\n"
15620        );
15621        assert_eq!(
15622            f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
15623            ":2\r\n"
15624        );
15625        assert_eq!(
15626            f.run(&[b"JSON.GET", b"doc", b"$"]),
15627            bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
15628        );
15629
15630        // A path that does not parse is refused before the document is read, so
15631        // a key that is not there answers the same way.
15632        assert!(
15633            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
15634                .starts_with("-ERR")
15635        );
15636        assert!(
15637            f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
15638                .starts_with("-ERR")
15639        );
15640    }
15641
15642    /// The operators past the comparisons, over the wire rather than in the
15643    /// parser's own tests, so that a client can reach all of them.
15644    #[test]
15645    fn a_filter_takes_the_membership_operators_and_the_methods_too() {
15646        let mut f = Fixture::new();
15647        let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
15648        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15649
15650        for (path, want) in [
15651            (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
15652            (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
15653            (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
15654            (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
15655            (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
15656            (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
15657            (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
15658            (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
15659            (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
15660            (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
15661            (b"$.box[?(@.n~)].t", "[]"),
15662            (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
15663            (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
15664            (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
15665            (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
15666        ] {
15667            assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
15668        }
15669
15670        // A write goes through one of these the same way it goes through a
15671        // comparison.
15672        assert_eq!(
15673            f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
15674            "+OK\r\n"
15675        );
15676        assert_eq!(
15677            f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
15678            bulk(r#"["b"]"#).as_str()
15679        );
15680    }
15681
15682    /// D-41. RedisJSON refuses this one, and which document it refuses is
15683    /// decided by how it happens to hold an array of numbers.
15684    #[test]
15685    fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
15686        let mut f = Fixture::new();
15687        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
15688        assert_eq!(
15689            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15690            "+OK\r\n"
15691        );
15692        assert_eq!(
15693            f.run(&[b"JSON.GET", b"doc", b"$"]),
15694            bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
15695        );
15696        // The same document with one element that is not an integer is the one
15697        // RedisJSON is happy with, and it goes the same way here.
15698        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
15699        assert_eq!(
15700            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15701            "+OK\r\n"
15702        );
15703        assert_eq!(
15704            f.run(&[b"JSON.GET", b"doc", b"$"]),
15705            bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
15706        );
15707    }
15708
15709    /// `JSON.MSET` checks what it can before it writes anything and skips the
15710    /// one thing it cannot, which is a path with nowhere to put its value.
15711    #[test]
15712    fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
15713        let mut f = Fixture::new();
15714        assert_eq!(
15715            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
15716            "+OK\r\n"
15717        );
15718        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
15719        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
15720
15721        // A repeated key takes the last write.
15722        assert_eq!(
15723            f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
15724            "+OK\r\n"
15725        );
15726        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
15727
15728        // A triple whose path names nowhere is skipped, the others are still
15729        // written and the reply turns into a nil. Both ways round, because a
15730        // loop that gave up at the first skip would agree with this on one
15731        // order and not on the other.
15732        f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
15733        assert_eq!(
15734            f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
15735            "$-1\r\n"
15736        );
15737        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
15738        assert_eq!(
15739            f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
15740            "$-1\r\n"
15741        );
15742        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15743
15744        // A value that is not JSON, a key holding something else and a path
15745        // that would have to create a document below its own root are all
15746        // checked before anything is written, so the good triple next to them
15747        // does not happen either.
15748        f.run(&[b"SET", b"str", b"x"]);
15749        assert_eq!(
15750            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
15751            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
15752        );
15753        assert_eq!(
15754            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
15755            "-Existing key has wrong Redis type\r\n"
15756        );
15757        assert_eq!(
15758            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
15759            "-ERR new objects must be created at the root\r\n"
15760        );
15761        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
15762
15763        // The two errors a path can be are checked up front as well, so the
15764        // triple before them is not written either. A wildcard that matched
15765        // nothing has nowhere to invent, and an index that is not in the array
15766        // is out of range, and both of them stop the whole command.
15767        assert_eq!(
15768            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
15769            "-Err wrong static path\r\n"
15770        );
15771        assert_eq!(
15772            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
15773            "-ERR array index out of range\r\n"
15774        );
15775        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15776
15777        // Every triple is worked out against the keyspace as the command found
15778        // it, so a second triple on the same key does not see the first one and
15779        // the last write is the one that stays.
15780        f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
15781        assert_eq!(
15782            f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
15783            "+OK\r\n"
15784        );
15785        assert_eq!(
15786            f.run(&[b"JSON.GET", b"c", b"$"]),
15787            bulk(r#"[{"n":3}]"#).as_str()
15788        );
15789
15790        // An argument count that is not a run of key, path and value is the
15791        // arity error rather than a syntax one.
15792        assert_eq!(
15793            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
15794            "-ERR wrong number of arguments for 'json.mset' command\r\n"
15795        );
15796    }
15797
15798    /// `JSON.RESP` hands back RESP types, and the marker element is what tells
15799    /// an empty array and an empty object apart.
15800    #[test]
15801    fn json_resp_answers_the_document_as_resp_types() {
15802        let mut f = Fixture::new();
15803        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
15804        assert_eq!(
15805            f.run(&[b"JSON.RESP", b"doc"]),
15806            "*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"
15807        );
15808        // A JSONPath wraps the same answer in one more array.
15809        assert_eq!(
15810            f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
15811            "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
15812        );
15813
15814        f.run(&[
15815            b"JSON.SET",
15816            b"doc",
15817            b"$",
15818            br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
15819        ]);
15820        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
15821        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
15822        // A double goes out as its text, so a client reads the same digits
15823        // `JSON.GET` would have given it.
15824        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
15825        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
15826        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
15827
15828        // A missing legacy path is an error, a missing JSONPath is an empty
15829        // array, and a key that is not there is a nil on either.
15830        assert_eq!(
15831            f.run(&[b"JSON.RESP", b"doc", b".nope"]),
15832            "-ERR Path does not exist\r\n"
15833        );
15834        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
15835        assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
15836        assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
15837    }
15838
15839    /// `JSON.DEBUG` answers a byte count that is this encoding's, so the test
15840    /// pins the shapes and that the two syntaxes agree rather than a number
15841    /// read off another server. That is D-42.
15842    #[test]
15843    fn json_debug_answers_a_byte_count_and_its_own_help() {
15844        let mut f = Fixture::new();
15845        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
15846        let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
15847        assert!(one.starts_with(':'), "{one}");
15848        assert_eq!(
15849            f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
15850            format!("*1\r\n{one}")
15851        );
15852        let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
15853        assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
15854
15855        // A key that is not there is a zero on a legacy path and an empty set
15856        // on a JSONPath, which is the one reader here that does not answer nil
15857        // for it.
15858        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
15859        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
15860        assert_eq!(
15861            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
15862            "-ERR Path does not exist\r\n"
15863        );
15864        assert_eq!(
15865            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
15866            "*0\r\n"
15867        );
15868
15869        assert_eq!(
15870            f.run(&[b"JSON.DEBUG", b"HELP"]),
15871            "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
15872             $34\r\nHELP                - this message\r\n"
15873        );
15874        assert_eq!(
15875            f.run(&[b"JSON.DEBUG", b"NOPE"]),
15876            "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
15877        );
15878        assert_eq!(
15879            f.run(&[b"JSON.DEBUG", b"MEMORY"]),
15880            "-ERR wrong number of arguments for 'json.debug' command\r\n"
15881        );
15882    }
15883
15884    // ---------------------------------------------------------------- vector
15885
15886    /// The first `VADD` fixes the dimension and every one after it has to
15887    /// agree, because there is no create command to say it earlier.
15888    #[test]
15889    fn the_first_vadd_decides_how_wide_the_set_is() {
15890        let mut f = Fixture::new();
15891        assert_eq!(
15892            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
15893            ":1\r\n"
15894        );
15895        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15896        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15897        // A second vector under the same name replaces it and says so with a
15898        // zero, so an ingest can count what it created.
15899        assert_eq!(
15900            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
15901            ":0\r\n"
15902        );
15903        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15904        // Three dimensions into a two dimensional set names both numbers, since
15905        // a client that gets this wrong needs to know which end is which.
15906        assert_eq!(
15907            f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
15908            "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
15909        );
15910        // A vector of zeros has no direction, and it is taken anyway and comes
15911        // back as the origin, because that is what a real server does with it.
15912        assert_eq!(
15913            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
15914            ":1\r\n"
15915        );
15916        assert_eq!(
15917            f.run(&[b"VEMB", b"v", b"nowhere"]),
15918            "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
15919        );
15920        // A set is made with one quantisation and keeps it, and a `VADD` that
15921        // names another is refused. Naming none names `Q8`, which is why this
15922        // set is a `Q8` one.
15923        assert_eq!(
15924            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
15925            "-ERR asked quantization mismatch with existing vector set\r\n"
15926        );
15927        // Nothing above created a key, and a set that never took a vector has
15928        // no dimension to report.
15929        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
15930        assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
15931        assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
15932    }
15933
15934    /// What a client sent comes back out, and what a client asked for is a
15935    /// similarity and not the distance underneath it.
15936    #[test]
15937    fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
15938        let mut f = Fixture::new();
15939        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
15940        // The set stored the direction and the length is multiplied back on the
15941        // way out, so this is `3 4` and not `0.6 0.8`. It is not quite `3 4`
15942        // either, because nobody named a quantisation and that means `Q8`: the
15943        // wider coordinate lands on a code exactly and the other one does not.
15944        // Both numbers are a real server's answers for the same input.
15945        assert_eq!(
15946            f.run(&[b"VEMB", b"v", b"a"]),
15947            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15948        );
15949        // NOQUANT is the way to ask for what went in to come back out.
15950        f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
15951        assert_eq!(
15952            f.run(&[b"VEMB", b"n", b"a"]),
15953            "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
15954        );
15955        // BIN keeps the signs and nothing else, and does not multiply the
15956        // length back on, since a sign has no length in it to scale.
15957        f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
15958        assert_eq!(
15959            f.run(&[b"VEMB", b"b", b"a"]),
15960            "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
15961        );
15962        assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
15963        assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
15964
15965        // On the axes, where the unit vector is exact and so is the dot
15966        // product, both ends of the scale come out exact: the same direction is
15967        // 1 and the opposite one is 0, with a right angle at a half.
15968        let mut f = Fixture::new();
15969        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
15970        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
15971        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
15972        assert_eq!(
15973            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
15974            "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
15975             $8\r\nopposite\r\n$1\r\n0\r\n"
15976        );
15977        // A search from an element leaves that element out, since it is always
15978        // its own nearest neighbour.
15979        assert_eq!(
15980            f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
15981            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15982        );
15983        // An element that is not there is an empty answer and not an error,
15984        // which is what a missing key gives too.
15985        assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
15986        assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
15987        // COUNT bounds it and TRUTH reads every vector rather than the codes,
15988        // which has to agree with the index on a set this small.
15989        assert_eq!(
15990            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
15991            "*1\r\n$6\r\nacross\r\n"
15992        );
15993        assert_eq!(
15994            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
15995            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15996        );
15997        // EF widens how much of the index is read and does not change how many
15998        // answers come back, so a wide search still returns what COUNT asked
15999        // for.
16000        assert_eq!(
16001            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
16002            "*1\r\n$6\r\nacross\r\n"
16003        );
16004
16005        // On RESP3 a scored search is a map, which is what the vector set
16006        // module replies and is not what ZRANGE does here.
16007        let mut g = Fixture::new();
16008        g.run(&[b"HELLO", b"3"]);
16009        g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16010        assert_eq!(
16011            g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
16012            "%1\r\n$4\r\neast\r\n,1\r\n"
16013        );
16014    }
16015
16016    /// The attribute pair, and the one reply that means two things.
16017    #[test]
16018    fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
16019        let mut f = Fixture::new();
16020        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16021        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
16022        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
16023        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
16024        // Not parsed as JSON, because nothing reads into it yet and refusing a
16025        // write for a rule nothing enforces would be the wrong trade.
16026        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
16027        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
16028        // An empty string clears it, which is Redis's spelling of the removal.
16029        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
16030        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
16031        // An element that is not there answers zero rather than being created,
16032        // since an attribute with no vector under it is not a thing this holds.
16033        assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
16034        assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
16035        assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
16036        // A null for an element with no attribute and a null for one that is
16037        // not there. VISMEMBER is how a client tells the two apart.
16038        assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
16039        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
16040        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
16041        assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
16042
16043        // WITHATTRIBS carries it alongside the answers.
16044        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
16045        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16046        assert_eq!(
16047            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
16048            "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
16049        );
16050    }
16051
16052    /// The slot a removed element had is reused, and nothing that was beside it
16053    /// comes back with the next element to get it.
16054    #[test]
16055    fn vrem_takes_the_attribute_with_it() {
16056        let mut f = Fixture::new();
16057        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16058        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
16059        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
16060        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
16061        assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
16062        // The key went with the last element, the way every other collection
16063        // here works.
16064        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
16065
16066        // The next element is given the slot the removed one had, and it comes
16067        // with no attribute on it.
16068        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16069        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
16070        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16071        f.run(&[b"VREM", b"v", b"east"]);
16072        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
16073        assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
16074    }
16075
16076    /// `VINFO` says what the index is before it says anything a client could
16077    /// mistake for a graph.
16078    #[test]
16079    fn vinfo_says_partition_first() {
16080        let mut f = Fixture::new();
16081        f.run(&[
16082            b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
16083        ]);
16084        f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
16085        let info = f.run(&[b"VINFO", b"v"]);
16086        assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
16087        // What the client asked for and not what happened to the tuning, which
16088        // is `10` section 7: M is recorded and changes nothing.
16089        assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
16090        assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
16091        assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
16092        // Nobody named a quantisation, so this set is a `Q8` one and every
16093        // element in it is stored that way.
16094        assert!(
16095            info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
16096            "{info}"
16097        );
16098        let mut f = Fixture::new();
16099        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
16100        assert!(
16101            f.run(&[b"VINFO", b"v"])
16102                .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
16103        );
16104        assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
16105    }
16106
16107    /// A set to read ranges of names out of.
16108    fn named() -> Fixture {
16109        let mut f = Fixture::new();
16110        for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
16111            .iter()
16112            .enumerate()
16113        {
16114            let x = (i + 1).to_string();
16115            f.run(&[
16116                b"VADD",
16117                b"r",
16118                b"VALUES",
16119                b"2",
16120                x.as_bytes(),
16121                b"1",
16122                name.as_bytes(),
16123            ]);
16124        }
16125        f
16126    }
16127
16128    /// `VRANGE` reads the names in the order bytes come in and pays no
16129    /// attention to where the vectors point.
16130    #[test]
16131    fn vrange_walks_the_names_and_not_the_vectors() {
16132        let mut f = named();
16133        assert_eq!(
16134            f.run(&[b"VRANGE", b"r", b"-", b"+"]),
16135            "*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"
16136        );
16137        assert_eq!(
16138            f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
16139            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
16140            "the high end is a name and not a prefix, so delta is past it"
16141        );
16142        assert_eq!(
16143            f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
16144            "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
16145        );
16146        assert_eq!(
16147            f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
16148            "*1\r\n$4\r\nbeta\r\n"
16149        );
16150        assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
16151        // Bytes and not letters, so an upper case name sorts before every lower
16152        // case one rather than beside its own spelling.
16153        f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
16154        assert_eq!(
16155            f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
16156            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
16157        );
16158        assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
16159    }
16160
16161    /// The count cuts the answer after the range is decided, and zero is not
16162    /// the same as leaving it out.
16163    #[test]
16164    fn a_vrange_count_of_zero_asks_for_nothing() {
16165        let mut f = named();
16166        assert_eq!(
16167            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
16168            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
16169        );
16170        assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
16171        assert!(
16172            f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
16173                .starts_with("*5\r\n"),
16174            "a negative count is no limit at all"
16175        );
16176    }
16177
16178    /// Both ends are read before either is placed, and the count is read before
16179    /// either end.
16180    #[test]
16181    fn vrange_says_which_end_it_could_not_read() {
16182        let mut f = named();
16183        assert_eq!(
16184            f.run(&[b"VRANGE", b"r", b"x", b"y"]),
16185            "-ERR invalid start range format\r\n"
16186        );
16187        assert_eq!(
16188            f.run(&[b"VRANGE", b"r", b"+", b"x"]),
16189            "-ERR invalid end range format\r\n",
16190            "the high end is spelled wrong, which is worth saying before the \
16191             low end being on the wrong side"
16192        );
16193        assert_eq!(
16194            f.run(&[b"VRANGE", b"r", b"+", b"-"]),
16195            "-ERR '-' can only be used as first argument, '+' only as second\r\n"
16196        );
16197        // A bracket with nothing after it is not the empty name here, though an
16198        // element really can be called that.
16199        assert_eq!(
16200            f.run(&[b"VRANGE", b"r", b"[", b"+"]),
16201            "-ERR invalid start range format\r\n"
16202        );
16203        assert_eq!(
16204            f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
16205            "-ERR invalid COUNT value\r\n"
16206        );
16207        assert_eq!(
16208            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
16209            "-ERR wrong number of arguments for 'VRANGE' command\r\n"
16210        );
16211        f.run(&[b"SET", b"s", b"x"]);
16212        assert!(
16213            f.run(&[b"VRANGE", b"s", b"-", b"+"])
16214                .starts_with("-WRONGTYPE")
16215        );
16216    }
16217
16218    /// The option that asks for something this index does not have says so
16219    /// rather than doing something else quietly.
16220    #[test]
16221    fn reduce_is_refused_and_not_ignored() {
16222        let mut f = Fixture::new();
16223        let reduce = f.run(&[
16224            b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
16225        ]);
16226        assert!(
16227            reduce.starts_with("-ERR REDUCE is not supported."),
16228            "{reduce}"
16229        );
16230        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
16231    }
16232
16233    /// A filtered search answers with the nearest elements that match, and an
16234    /// expression that is not one is an error before the key is looked at.
16235    #[test]
16236    fn vsim_filter_reads_the_attributes() {
16237        let mut f = Fixture::new();
16238        for (name, x, y, attr) in [
16239            ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
16240            ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
16241            ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
16242            ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
16243        ] {
16244            f.run(&[
16245                b"VADD",
16246                b"v",
16247                b"VALUES",
16248                b"2",
16249                x.as_bytes(),
16250                y.as_bytes(),
16251                name.as_bytes(),
16252                b"SETATTR",
16253                attr.as_bytes(),
16254            ]);
16255        }
16256        // `b` is the nearest to the query and is the one the filter drops, so
16257        // this is the answer a filter applied afterwards would have got wrong.
16258        assert_eq!(
16259            f.run(&[
16260                b"VSIM",
16261                b"v",
16262                b"VALUES",
16263                b"2",
16264                b"9",
16265                b"1",
16266                b"COUNT",
16267                b"2",
16268                b"FILTER",
16269                b".lang == \"en\"",
16270            ]),
16271            "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
16272        );
16273        // A number is compared as a number, and the two halves of an `and` both
16274        // have to hold.
16275        assert_eq!(
16276            f.run(&[
16277                b"VSIM",
16278                b"v",
16279                b"VALUES",
16280                b"2",
16281                b"9",
16282                b"1",
16283                b"FILTER",
16284                b".lang == 'en' and .year > 1980",
16285            ]),
16286            "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
16287        );
16288        // A list, and a field an element does not have.
16289        assert_eq!(
16290            f.run(&[
16291                b"VSIM",
16292                b"v",
16293                b"VALUES",
16294                b"2",
16295                b"9",
16296                b"1",
16297                b"FILTER",
16298                b".lang in ['fr', 'de']",
16299            ]),
16300            "*1\r\n$1\r\nb\r\n"
16301        );
16302        assert_eq!(
16303            f.run(&[
16304                b"VSIM",
16305                b"v",
16306                b"VALUES",
16307                b"2",
16308                b"9",
16309                b"1",
16310                b"FILTER",
16311                b".rating > 3"
16312            ]),
16313            "*0\r\n"
16314        );
16315        // TRUTH measures every vector, and the filter still decides which ones
16316        // are measured.
16317        assert_eq!(
16318            f.run(&[
16319                b"VSIM",
16320                b"v",
16321                b"VALUES",
16322                b"2",
16323                b"9",
16324                b"1",
16325                b"TRUTH",
16326                b"FILTER",
16327                b".year < 1980",
16328            ]),
16329            "*1\r\n$1\r\nc\r\n"
16330        );
16331        // VSETATTR moves an element in and out of a filter, which means the tag
16332        // beside its code was rewritten and not just the string.
16333        f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
16334        assert_eq!(
16335            f.run(&[
16336                b"VSIM",
16337                b"v",
16338                b"VALUES",
16339                b"2",
16340                b"9",
16341                b"1",
16342                b"COUNT",
16343                b"1",
16344                b"FILTER",
16345                b".lang == \"en\"",
16346            ]),
16347            "*1\r\n$1\r\nb\r\n"
16348        );
16349        // And a VADD that replaces the vector keeps the attribute and the tag,
16350        // which is the same rewrite from the other end.
16351        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
16352        assert_eq!(
16353            f.run(&[
16354                b"VSIM",
16355                b"v",
16356                b"VALUES",
16357                b"2",
16358                b"9",
16359                b"1",
16360                b"COUNT",
16361                b"1",
16362                b"FILTER",
16363                b".lang == \"en\"",
16364            ]),
16365            "*1\r\n$1\r\nb\r\n"
16366        );
16367
16368        // The expression is parsed before the key is read, so a bad one is an
16369        // error whether or not the key is there.
16370        let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
16371        assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
16372        assert_eq!(
16373            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
16374            "-ERR invalid FILTER expression\r\n"
16375        );
16376        // FILTER-EF raises the effort rather than capping it, and zero is
16377        // Redis's word for no limit, so neither is an error.
16378        assert_eq!(
16379            f.run(&[
16380                b"VSIM",
16381                b"v",
16382                b"VALUES",
16383                b"2",
16384                b"9",
16385                b"1",
16386                b"COUNT",
16387                b"1",
16388                b"FILTER-EF",
16389                b"500",
16390                b"FILTER",
16391                b".lang == 'en'",
16392            ]),
16393            "*1\r\n$1\r\nb\r\n"
16394        );
16395        assert_eq!(
16396            f.run(&[
16397                b"VSIM",
16398                b"v",
16399                b"VALUES",
16400                b"2",
16401                b"9",
16402                b"1",
16403                b"COUNT",
16404                b"1",
16405                b"FILTER-EF",
16406                b"0"
16407            ]),
16408            "*1\r\n$1\r\nb\r\n"
16409        );
16410        assert_eq!(
16411            f.run(&[
16412                b"VSIM",
16413                b"v",
16414                b"VALUES",
16415                b"2",
16416                b"9",
16417                b"1",
16418                b"FILTER-EF",
16419                b"lots"
16420            ]),
16421            "-ERR EF must be a positive integer\r\n"
16422        );
16423    }
16424
16425    /// A vector set key is a key, so the keyspace owns it the way it owns every
16426    /// other one and none of those commands know what is inside it.
16427    #[test]
16428    fn the_keyspace_sees_a_vector_set_key_like_any_other() {
16429        let mut f = Fixture::new();
16430        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16431        assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
16432        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
16433        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
16434        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
16435        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
16436        assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
16437        assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
16438        assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
16439        assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
16440        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
16441
16442        // And the wrong type is the wrong type in both directions.
16443        f.run(&[b"SET", b"s", b"1"]);
16444        assert_eq!(
16445            f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
16446            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16447        );
16448        assert_eq!(
16449            f.run(&[b"VCARD", b"s"]),
16450            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16451        );
16452        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16453        assert_eq!(
16454            f.run(&[b"GET", b"v"]),
16455            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16456        );
16457        // A graph and a vector set share the escape in the record tag and are
16458        // still two different types, which is the case the tag alone cannot
16459        // decide.
16460        f.run(&[b"G.NADD", b"social", b"ada"]);
16461        assert_eq!(
16462            f.run(&[b"VCARD", b"social"]),
16463            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16464        );
16465        assert_eq!(
16466            f.run(&[b"G.NGET", b"v", b"ada"]),
16467            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16468        );
16469    }
16470
16471    /// `VRANDMEMBER` is `SRANDMEMBER` over the element names, in both of its
16472    /// shapes, off the database's own generator.
16473    #[test]
16474    fn vrandmember_has_the_two_shapes_srandmember_has() {
16475        let mut f = Fixture::new();
16476        for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
16477            let x = (i + 1).to_string();
16478            f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
16479        }
16480        // One element is a bulk string and not an array of one.
16481        let one = f.run(&[b"VRANDMEMBER", b"v"]);
16482        assert!(one.starts_with("$1\r\n"), "{one}");
16483        // A positive count is distinct and stops at the size of the set.
16484        let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
16485        assert!(all.starts_with("*3\r\n"), "{all}");
16486        for name in ["a", "b", "c"] {
16487            assert!(all.contains(name), "{all} is missing {name}");
16488        }
16489        all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
16490        assert!(all.starts_with("*2\r\n"), "{all}");
16491        // A negative one draws that many and allows repeats.
16492        let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
16493        assert!(many.starts_with("*5\r\n"), "{many}");
16494        // A key that is not there answers the shape that was asked for.
16495        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
16496        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
16497    }
16498
16499    /// `VLINKS` answers about the index that is here rather than the graph that
16500    /// is not, which is D-2.
16501    #[test]
16502    fn vlinks_reports_one_layer_of_partition_neighbours() {
16503        let mut f = Fixture::new();
16504        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16505        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16506        // One layer deep, because the index is one layer deep, so a client
16507        // walking layers gets a short list and not a shape it cannot parse.
16508        assert_eq!(
16509            f.run(&[b"VLINKS", b"v", b"east"]),
16510            "*1\r\n*1\r\n$5\r\nnorth\r\n"
16511        );
16512        assert_eq!(
16513            f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
16514            "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
16515        );
16516        assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
16517        assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
16518    }
16519
16520    /// A vector arrives either as digits or as bytes, and the two have to mean
16521    /// the same thing.
16522    #[test]
16523    fn fp32_and_values_are_the_same_vector() {
16524        let mut f = Fixture::new();
16525        let mut blob = Vec::new();
16526        for x in [3.0f32, 4.0] {
16527            blob.extend_from_slice(&x.to_le_bytes());
16528        }
16529        assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
16530        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
16531        assert_eq!(
16532            f.run(&[b"VEMB", b"v", b"a"]),
16533            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
16534        );
16535        // RAW is the stored bytes and the numbers that turn them back into the
16536        // client's vector, which for `Q8` is a code a coordinate, the length the
16537        // vector arrived with and the scale the codes are measured against. The
16538        // name of the form is a simple string, which is a real server's shape,
16539        // and all four of these are a real server's answers.
16540        assert_eq!(
16541            f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
16542            "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
16543        );
16544        // A blob that is not a whole number of floats is not a vector.
16545        assert_eq!(
16546            f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
16547            "-ERR invalid vector specification\r\n"
16548        );
16549        // Neither is a count that promises more than arrived.
16550        assert_eq!(
16551            f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
16552            "-ERR syntax error\r\n"
16553        );
16554        assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
16555    }
16556
16557    // ----------------------------------------------------------------- bloom
16558
16559    /// The filter a client gets when it does not describe one, and the two
16560    /// answers an add can give.
16561    #[test]
16562    fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
16563        let mut f = Fixture::new();
16564        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
16565        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
16566        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
16567        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
16568        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
16569        // The defaults are the module's configs and not anything the command
16570        // said, which is 100 entries at a hundredth and a growth of 2.
16571        assert_eq!(
16572            f.run(&[b"BF.INFO", b"b"]),
16573            "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16574             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16575             +Expansion rate\r\n:2\r\n"
16576        );
16577        assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
16578        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
16579        // A key that is not there has no filter to report on, and answers two
16580        // different ways about it depending on which command asked.
16581        assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
16582        assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
16583    }
16584
16585    /// `BF.EXISTS` on a key holding something else answers a miss, and
16586    /// everything else in the family answers `WRONGTYPE`.
16587    ///
16588    /// The two halves of a check and set disagree about what that key is, which
16589    /// is the module's behaviour and not a decision taken here.
16590    #[test]
16591    fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
16592        let mut f = Fixture::new();
16593        f.run(&[b"SET", b"s", b"text"]);
16594        assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
16595        assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16596        for cmd in [
16597            vec![&b"BF.ADD"[..], b"s", b"x"],
16598            vec![&b"BF.MADD"[..], b"s", b"x"],
16599            vec![&b"BF.CARD"[..], b"s"],
16600            vec![&b"BF.INFO"[..], b"s"],
16601            vec![&b"BF.DEBUG"[..], b"s"],
16602            vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
16603        ] {
16604            let name = String::from_utf8_lossy(cmd[0]).into_owned();
16605            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16606        }
16607        // The arguments are read before the key is, so a reserve with a bad
16608        // error rate complains about the rate and never learns about the string.
16609        assert_eq!(
16610            f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
16611            "-ERR bad error rate\r\n"
16612        );
16613        assert!(
16614            f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
16615                .starts_with("-WRONGTYPE")
16616        );
16617    }
16618
16619    /// A chain grows by its expansion factor and each link is half as wrong as
16620    /// the one before, which is what makes the whole filter hold its rate.
16621    #[test]
16622    fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
16623        let mut f = Fixture::new();
16624        assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
16625        for i in 0..10u32 {
16626            assert_eq!(
16627                f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
16628                ":1\r\n"
16629            );
16630        }
16631        assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
16632        assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
16633        assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
16634        // Capacity is the sum of every link and not the number that was asked
16635        // for, so it is 10 and then 10 plus 20.
16636        assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
16637        assert_eq!(
16638            f.run(&[b"BF.DEBUG", b"g"]),
16639            "*3\r\n$7\r\nsize:11\r\n\
16640             $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
16641             $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
16642        );
16643
16644        // The same filter told not to grow fills instead.
16645        assert_eq!(
16646            f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
16647            "+OK\r\n"
16648        );
16649        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
16650        assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
16651        assert_eq!(
16652            f.run(&[b"BF.ADD", b"n", b"c"]),
16653            "-ERR non scaling filter is full\r\n"
16654        );
16655        // And an item that is already in it still answers, because membership
16656        // is checked before fullness.
16657        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
16658        // A filter that will not grow has no expansion rate to report, in
16659        // either of the two spellings that make one.
16660        assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
16661        f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
16662        assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
16663        // Asking for both at once is refused, which is one of the module's
16664        // errors that carries no prefix at all.
16665        assert_eq!(
16666            f.run(&[
16667                b"BF.RESERVE",
16668                b"q",
16669                b"0.01",
16670                b"2",
16671                b"NONSCALING",
16672                b"EXPANSION",
16673                b"2"
16674            ]),
16675            "-Nonscaling filters cannot expand\r\n"
16676        );
16677    }
16678
16679    /// A multi add stops where the filter did, so the reply can be shorter than
16680    /// the argument list.
16681    #[test]
16682    fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
16683        let mut f = Fixture::new();
16684        f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
16685        assert_eq!(
16686            f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
16687            "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
16688        );
16689        assert_eq!(
16690            f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
16691            "*2\r\n:1\r\n:0\r\n"
16692        );
16693    }
16694
16695    /// `BF.INSERT` describes a filter and fills it in one command, with its own
16696    /// spelling of every complaint.
16697    #[test]
16698    fn insert_is_a_reserve_and_a_madd_with_different_errors() {
16699        let mut f = Fixture::new();
16700        assert_eq!(
16701            f.run(&[
16702                b"BF.INSERT",
16703                b"i",
16704                b"CAPACITY",
16705                b"50",
16706                b"ERROR",
16707                b"0.001",
16708                b"ITEMS",
16709                b"a",
16710                b"b"
16711            ]),
16712            "*2\r\n:1\r\n:1\r\n"
16713        );
16714        assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
16715        // NOCREATE is the only way to add without making the key.
16716        assert_eq!(
16717            f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16718            "-ERR not found\r\n"
16719        );
16720        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16721        // The same mistakes as BF.RESERVE, in the sentences this command uses
16722        // for them, and one sentence where BF.RESERVE has two.
16723        assert_eq!(
16724            f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16725            "-Bad capacity\r\n"
16726        );
16727        assert_eq!(
16728            f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
16729            "-Bad error rate\r\n"
16730        );
16731        assert_eq!(
16732            f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
16733            "-Bad expansion\r\n"
16734        );
16735        // An option is matched on its first letter and not on the word, so a
16736        // token nobody meant as an option is one anyway if it starts with the
16737        // right letter. NOSUCHTHING is NONSCALING here, and the filter it
16738        // builds says so.
16739        assert_eq!(
16740            f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
16741            "*1\r\n:1\r\n"
16742        );
16743        assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
16744        // Only E and N need a second look, one for ERROR against EXPANSION and
16745        // the other for NOCREATE against NONSCALING, and both stop as soon as
16746        // they can tell the two apart.
16747        assert_eq!(
16748            f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
16749            "*1\r\n:1\r\n"
16750        );
16751        assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
16752        assert_eq!(
16753            f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
16754            "*1\r\n:1\r\n"
16755        );
16756        assert_eq!(
16757            f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
16758            "-ERR not found\r\n"
16759        );
16760        // A letter that starts nothing is the one case that is refused.
16761        assert_eq!(
16762            f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16763            "-Unknown argument received\r\n"
16764        );
16765        // Everything after ITEMS is an item, even when it spells an option.
16766        assert_eq!(
16767            f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16768            "*1\r\n:1\r\n"
16769        );
16770        // And ITEMS with nothing after it is the same as leaving it out.
16771        assert!(
16772            f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
16773                .contains("wrong number of arguments")
16774        );
16775    }
16776
16777    /// A filter dumped a chunk at a time and put back into another key is the
16778    /// same filter.
16779    #[test]
16780    fn a_dump_replays_into_a_filter_that_answers_the_same() {
16781        let mut f = Fixture::new();
16782        f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
16783        for i in 0..25u32 {
16784            f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
16785        }
16786        assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
16787
16788        // Iterator zero asks for the header and every one after it is a running
16789        // byte offset, and a chunk never spans two links.
16790        let mut iter = b"0".to_vec();
16791        let mut chunks = 0;
16792        loop {
16793            let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
16794            let text = String::from_utf8_lossy(&raw).into_owned();
16795            let next = text
16796                .split("\r\n")
16797                .nth(1)
16798                .and_then(|n| n.strip_prefix(':'))
16799                .expect("a two element reply of an iterator and a chunk")
16800                .to_owned();
16801            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16802            let data = &body[body
16803                .windows(2)
16804                .position(|w| w == b"\r\n")
16805                .expect("a length line")
16806                + 2..body.len() - 2];
16807            if next == "0" {
16808                assert!(data.is_empty(), "the last chunk is empty");
16809                break;
16810            }
16811            let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
16812            assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
16813            iter = next.into_bytes();
16814            chunks += 1;
16815        }
16816        assert_eq!(chunks, 3, "a header and one chunk per link");
16817
16818        assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
16819        assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
16820        for i in 0..25u32 {
16821            assert_eq!(
16822                f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
16823                ":1\r\n"
16824            );
16825        }
16826
16827        // A header on top of a filter is refused rather than merged, and so is
16828        // one that no filter wrote.
16829        assert_eq!(
16830            f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
16831            "-ERR received bad data\r\n"
16832        );
16833        assert_eq!(
16834            f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
16835            "-ERR received bad data\r\n"
16836        );
16837        // An offset past the end of the filter names itself.
16838        assert_eq!(
16839            f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
16840            "-ERR invalid offset - no link found\r\n"
16841        );
16842        assert_eq!(
16843            f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
16844            "-ERR Second argument must be numeric\r\n"
16845        );
16846        // The same complaint without the prefix on the way out, which is the
16847        // module's inconsistency and not a slip here.
16848        assert_eq!(
16849            f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
16850            "-Second argument must be numeric\r\n"
16851        );
16852    }
16853
16854    /// The argument checks, which have a sentence each and read numbers the way
16855    /// Redis reads them everywhere else.
16856    #[test]
16857    fn reserve_reads_its_numbers_the_way_string2ll_does() {
16858        let mut f = Fixture::new();
16859        for (args, want) in [
16860            (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
16861            (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
16862            (
16863                vec![&b"0"[..], b"10"],
16864                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16865            ),
16866            (
16867                vec![&b"1"[..], b"10"],
16868                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16869            ),
16870            (
16871                vec![&b"inf"[..], b"10"],
16872                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16873            ),
16874            (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
16875            (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
16876            (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
16877            (
16878                vec![&b"0.01"[..], b"0"],
16879                "-ERR capacity must be in the range [1, 1073741824]\r\n",
16880            ),
16881            (
16882                vec![&b"0.01"[..], b"1073741825"],
16883                "-ERR capacity must be in the range [1, 1073741824]\r\n",
16884            ),
16885        ] {
16886            let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
16887            cmd.extend(args.iter().copied());
16888            assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
16889        }
16890        assert_eq!(
16891            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
16892            "-ERR no expansion\r\n"
16893        );
16894        assert_eq!(
16895            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
16896            "-ERR bad expansion\r\n"
16897        );
16898        assert_eq!(
16899            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
16900            "-ERR expansion must be in the range [0, 32768]\r\n"
16901        );
16902        // Trailing rubbish after the capacity is ignored rather than refused.
16903        assert_eq!(
16904            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
16905            "+OK\r\n"
16906        );
16907        assert_eq!(
16908            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
16909            "-ERR item exists\r\n"
16910        );
16911        assert_eq!(
16912            f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
16913            "-Invalid information value\r\n"
16914        );
16915        assert!(
16916            f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
16917                .contains("wrong number of arguments")
16918        );
16919    }
16920
16921    /// The RESP3 shapes, which are where this family differs most from RESP2.
16922    #[test]
16923    fn the_bloom_family_answers_in_resp3_spelling_too() {
16924        let mut f = Fixture::new();
16925        f.out.set_proto(Proto::Resp3);
16926        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
16927        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
16928        assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
16929        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
16930        assert_eq!(
16931            f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
16932            "*2\r\n#t\r\n#f\r\n"
16933        );
16934        // The count stays an integer, because it counts rather than answers.
16935        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
16936        assert_eq!(
16937            f.run(&[b"BF.INFO", b"b"]),
16938            "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16939             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
16940             +Expansion rate\r\n:2\r\n"
16941        );
16942        // One field is a map of one here and a bare array of one on RESP2, so
16943        // this is the reply where the two protocols carry different facts.
16944        assert_eq!(
16945            f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
16946            "%1\r\n+Capacity\r\n:100\r\n"
16947        );
16948    }
16949
16950    // ---------------------------------------------------------------- cuckoo
16951
16952    /// A dump header, which is the four counts and the three widths a filter
16953    /// writes in front of its fingerprints.
16954    ///
16955    /// Written by hand rather than taken from a `CF.SCANDUMP`, because what the
16956    /// tests below want out of it is the states a filter cannot be put into
16957    /// from the wire.
16958    fn cf_header(
16959        items: u64,
16960        buckets: u64,
16961        deletes: u64,
16962        filters: u64,
16963        geometry: [u16; 3],
16964    ) -> Vec<u8> {
16965        let mut out = Vec::with_capacity(38);
16966        for n in [items, buckets, deletes, filters] {
16967            out.extend_from_slice(&n.to_le_bytes());
16968        }
16969        for n in geometry {
16970            out.extend_from_slice(&n.to_le_bytes());
16971        }
16972        out
16973    }
16974
16975    /// The filter a client gets when it does not describe one, and the thing a
16976    /// cuckoo filter does that a Bloom filter cannot, which is count copies and
16977    /// take them out again.
16978    #[test]
16979    fn cf_add_makes_the_filter_and_counts_the_copies() {
16980        let mut f = Fixture::new();
16981        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16982        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16983        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
16984        // The NX form is the one that looks first, which is why it is a command
16985        // of its own rather than an option.
16986        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
16987        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
16988        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
16989        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
16990        assert_eq!(
16991            f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
16992            "*2\r\n:1\r\n:0\r\n"
16993        );
16994        // The defaults are the module's configs: 1024 entries over buckets of
16995        // two, twenty kicks and a chain that grows by one.
16996        assert_eq!(
16997            f.run(&[b"CF.INFO", b"d"]),
16998            "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16999             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
17000             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
17001             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
17002        );
17003        assert_eq!(
17004            f.run(&[b"CF.DEBUG", b"d"]),
17005            "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
17006             max_iterations:20 expansion:1\r\n"
17007        );
17008        assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
17009        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17010
17011        // A delete takes one copy, so the same item goes twice and then stops.
17012        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
17013        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
17014        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
17015        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
17016        assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
17017
17018        // A key with no filter under it gets three different sentences and one
17019        // plain miss, depending on which command asked.
17020        assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
17021        assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
17022        assert_eq!(
17023            f.run(&[b"CF.COMPACT", b"gone"]),
17024            "-Cuckoo filter was not found\r\n"
17025        );
17026        assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
17027        // And `CF.COMPACT` is declared as taking any number of keys and takes
17028        // exactly one, which is the module's own arity being wrong rather than
17029        // this table's.
17030        assert!(
17031            f.run(&[b"CF.COMPACT", b"a", b"b"])
17032                .contains("wrong number of arguments")
17033        );
17034    }
17035
17036    /// The four that only read fingerprints treat a key holding something else
17037    /// as a key with no filter, and everything else answers `WRONGTYPE`.
17038    #[test]
17039    fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
17040        let mut f = Fixture::new();
17041        f.run(&[b"SET", b"s", b"text"]);
17042        assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
17043        assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
17044        assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
17045        // `CF.DEL` writes and is still in that group, and `CF.COMPACT` writes
17046        // and is declared read only, so neither of the two halves of the family
17047        // is the same set as the flags say.
17048        assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
17049        assert_eq!(
17050            f.run(&[b"CF.COMPACT", b"s"]),
17051            "-Cuckoo filter was not found\r\n"
17052        );
17053        for cmd in [
17054            vec![&b"CF.ADD"[..], b"s", b"x"],
17055            vec![&b"CF.ADDNX"[..], b"s", b"x"],
17056            vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
17057            vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
17058            vec![&b"CF.INFO"[..], b"s"],
17059            vec![&b"CF.DEBUG"[..], b"s"],
17060            vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
17061            vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
17062            vec![&b"CF.RESERVE"[..], b"s", b"64"],
17063        ] {
17064            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17065            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
17066        }
17067    }
17068
17069    /// `CF.RESERVE` reads its options by name in an order of its own, and the
17070    /// first pair with a given name is the only one it looks at.
17071    #[test]
17072    fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
17073        let mut f = Fixture::new();
17074        assert_eq!(
17075            f.run(&[
17076                b"CF.RESERVE",
17077                b"r",
17078                b"64",
17079                b"BUCKETSIZE",
17080                b"1",
17081                b"MAXITERATIONS",
17082                b"7",
17083                b"EXPANSION",
17084                b"4"
17085            ]),
17086            "+OK\r\n"
17087        );
17088        assert_eq!(
17089            f.run(&[b"CF.DEBUG", b"r"]),
17090            "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
17091             max_iterations:7 expansion:4\r\n"
17092        );
17093        assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
17094
17095        assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
17096        assert_eq!(
17097            f.run(&[b"CF.RESERVE", b"q", b"1"]),
17098            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
17099        );
17100        // The range is the bucket size's and not a constant, so a capacity that
17101        // was fine at two slots a bucket is not at four.
17102        assert_eq!(
17103            f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
17104            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
17105        );
17106        assert_eq!(
17107            f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
17108            "+OK\r\n"
17109        );
17110
17111        // The capacity is checked last, so a command that is wrong twice
17112        // answers about the option. Which option it answers about is the order
17113        // the module looks for them in and not the order they were written, so
17114        // a bad kick budget wins over a bad bucket size wherever the two sit.
17115        assert_eq!(
17116            f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
17117            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
17118        );
17119        assert_eq!(
17120            f.run(&[
17121                b"CF.RESERVE",
17122                b"q2",
17123                b"64",
17124                b"EXPANSION",
17125                b"xx",
17126                b"BUCKETSIZE",
17127                b"0"
17128            ]),
17129            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
17130        );
17131        assert_eq!(
17132            f.run(&[
17133                b"CF.RESERVE",
17134                b"q2",
17135                b"64",
17136                b"MAXITERATIONS",
17137                b"0",
17138                b"BUCKETSIZE",
17139                b"0"
17140            ]),
17141            "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
17142        );
17143        // A second pair with a name that has already been read is not looked at
17144        // at all, so this one is a filter with buckets of one rather than an
17145        // error about a bucket size of zero.
17146        assert_eq!(
17147            f.run(&[
17148                b"CF.RESERVE",
17149                b"q3",
17150                b"64",
17151                b"BUCKETSIZE",
17152                b"1",
17153                b"BUCKETSIZE",
17154                b"0"
17155            ]),
17156            "+OK\r\n"
17157        );
17158        // A pair nobody knows is dropped, which is the opposite of what
17159        // `CF.INSERT` does with the same mistake.
17160        assert_eq!(
17161            f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
17162            "+OK\r\n"
17163        );
17164        assert_eq!(
17165            f.run(&[b"CF.DEBUG", b"q4"]),
17166            "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
17167             max_iterations:20 expansion:1\r\n"
17168        );
17169        // And an option with nothing after it leaves an odd number of them,
17170        // which is an arity error rather than a complaint about the option.
17171        assert!(
17172            f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
17173                .contains("wrong number of arguments")
17174        );
17175    }
17176
17177    /// `CF.INSERT` is a reserve and a multi add, with a grammar that agrees
17178    /// with `CF.RESERVE` about nothing.
17179    #[test]
17180    fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
17181        let mut f = Fixture::new();
17182        assert_eq!(
17183            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
17184            "*2\r\n:1\r\n:1\r\n"
17185        );
17186        assert_eq!(
17187            f.run(&[b"CF.DEBUG", b"i"]),
17188            "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
17189             max_iterations:20 expansion:1\r\n"
17190        );
17191        // The NX form has three answers rather than two, which is why it stays
17192        // integers on both protocols.
17193        assert_eq!(
17194            f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
17195            "*2\r\n:0\r\n:1\r\n"
17196        );
17197        assert_eq!(
17198            f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
17199            "-ERR not found\r\n"
17200        );
17201        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
17202
17203        assert_eq!(
17204            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
17205            "-Bad capacity\r\n"
17206        );
17207        // The bucket size cannot be given here, so the range names the config
17208        // that holds it instead of the option `CF.RESERVE` names.
17209        assert_eq!(
17210            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
17211            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
17212        );
17213        // Every occurrence is checked, which is where this differs from
17214        // `CF.RESERVE`: the second `CAPACITY` is an error even though the first
17215        // one is the one that would have been used.
17216        assert_eq!(
17217            f.run(&[
17218                b"CF.INSERT",
17219                b"i",
17220                b"CAPACITY",
17221                b"8",
17222                b"CAPACITY",
17223                b"2",
17224                b"ITEMS",
17225                b"a"
17226            ]),
17227            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
17228        );
17229        // An option is one letter and not a word, so `NOSUCH` is `NOCREATE` and
17230        // `ITEMSXYZ` is `ITEMS`, and only a letter that starts nothing is
17231        // refused.
17232        assert_eq!(
17233            f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
17234            "*1\r\n:1\r\n"
17235        );
17236        assert_eq!(
17237            f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
17238            "*1\r\n:1\r\n"
17239        );
17240        assert_eq!(
17241            f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
17242            "-Unknown argument received\r\n"
17243        );
17244        // Everything after ITEMS is an item, even when it spells an option.
17245        assert_eq!(
17246            f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
17247            "*1\r\n:1\r\n"
17248        );
17249        // And the two ways of sending no items at all are the same complaint.
17250        assert!(
17251            f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
17252                .contains("wrong number of arguments")
17253        );
17254        assert!(
17255            f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
17256                .contains("wrong number of arguments")
17257        );
17258    }
17259
17260    /// The two walls a filter can hit, which say different things and are not
17261    /// the same wall.
17262    #[test]
17263    fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
17264        let mut f = Fixture::new();
17265        f.run(&[
17266            b"CF.RESERVE",
17267            b"s",
17268            b"4",
17269            b"BUCKETSIZE",
17270            b"1",
17271            b"EXPANSION",
17272            b"0",
17273        ]);
17274        for i in 0..4u32 {
17275            assert_eq!(
17276                f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
17277                ":1\r\n"
17278            );
17279        }
17280        assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
17281        assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
17282        // The add commands say it in a sentence and the insert commands say it
17283        // in the array, one value per item, and the array is never short.
17284        assert_eq!(
17285            f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
17286            "*2\r\n:-1\r\n:-1\r\n"
17287        );
17288        assert_eq!(
17289            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
17290            "*2\r\n:0\r\n:-1\r\n"
17291        );
17292
17293        // A chain that is allowed to grow stops for a different reason, and the
17294        // count it stops at is the filter limit rather than the room: this one
17295        // gives up with three slots free. Loading a chain that already has
17296        // every filter it is allowed shows why, since it refuses an item
17297        // straight into an empty one.
17298        let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
17299        assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
17300        assert_eq!(
17301            f.run(&[b"CF.ADD", b"g", b"q"]),
17302            "-Maximum expansions reached\r\n"
17303        );
17304        assert_eq!(
17305            f.run(&[b"CF.INFO", b"g"]),
17306            "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
17307             +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
17308             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
17309             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
17310        );
17311    }
17312
17313    /// A filter dumped a chunk at a time and put back under another key is the
17314    /// same filter, and the headers that describe one nobody could build are
17315    /// refused on the way in.
17316    #[test]
17317    fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
17318        let mut f = Fixture::new();
17319        f.run(&[
17320            b"CF.RESERVE",
17321            b"src",
17322            b"8",
17323            b"BUCKETSIZE",
17324            b"2",
17325            b"EXPANSION",
17326            b"2",
17327        ]);
17328        for i in 0..40u32 {
17329            f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
17330        }
17331        // Position zero asks for the header and every one after it is a byte
17332        // offset across every filter laid end to end, and the walk ends on a
17333        // zero and a nil rather than an empty chunk.
17334        let mut pos = b"0".to_vec();
17335        let mut chunks = 0;
17336        loop {
17337            let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
17338            let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
17339            let next = head
17340                .split("\r\n")
17341                .nth(1)
17342                .and_then(|n| n.strip_prefix(':'))
17343                .expect("a two element reply of a position and a chunk")
17344                .to_owned();
17345            if next == "0" {
17346                assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
17347                break;
17348            }
17349            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
17350            let at = body
17351                .windows(2)
17352                .position(|w| w == b"\r\n")
17353                .expect("a length line")
17354                + 2;
17355            let data = &body[at..body.len() - 2];
17356            assert_eq!(
17357                f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
17358                "+OK\r\n",
17359                "loading chunk {chunks}"
17360            );
17361            pos = next.into_bytes();
17362            chunks += 1;
17363        }
17364        assert!(chunks >= 2, "a header and at least one chunk");
17365
17366        assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
17367        assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
17368        for i in 0..40u32 {
17369            assert_eq!(
17370                f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
17371                ":1\r\n"
17372            );
17373        }
17374
17375        // A filter with nothing in it hands out no header at all, so a client
17376        // that dumps one has nothing to load back.
17377        f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
17378        assert_eq!(
17379            f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
17380            "*2\r\n:0\r\n$-1\r\n"
17381        );
17382
17383        // The positions this end will not take, which are not the same set at
17384        // both ends: a dump refuses a negative one and a load takes it as an
17385        // offset and fails to find anything there.
17386        assert_eq!(
17387            f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
17388            "-Invalid position\r\n"
17389        );
17390        assert_eq!(
17391            f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
17392            "-Invalid position\r\n"
17393        );
17394        assert_eq!(
17395            f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
17396            "-Invalid position\r\n"
17397        );
17398        assert_eq!(
17399            f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
17400            "-Couldn't load chunk!\r\n"
17401        );
17402        // A header on top of a filter is refused rather than merged.
17403        let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
17404        assert_eq!(
17405            f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
17406            "-ERR item exists\r\n"
17407        );
17408        // A chunk that is not the size of a header where a header should have
17409        // been is one sentence, and one that is the size of a header and
17410        // describes a filter nobody could build is another.
17411        assert_eq!(
17412            f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
17413            "-Invalid header\r\n"
17414        );
17415        for (why, bad) in [
17416            ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
17417            ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
17418            (
17419                "a bucket count that is not a power of two",
17420                cf_header(0, 3, 0, 1, [2, 20, 1]),
17421            ),
17422            ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
17423            ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
17424            (
17425                "a growth nobody could reach",
17426                cf_header(0, 8, 0, 1, [2, 20, 32769]),
17427            ),
17428            (
17429                "a chain that cannot grow and did",
17430                cf_header(0, 8, 0, 2, [2, 20, 0]),
17431            ),
17432            // The count is written in eight bytes and read into two, so a
17433            // number that is a multiple of the second arrives as none.
17434            (
17435                "a filter count that wraps",
17436                cf_header(0, 8, 0, 65_536, [2, 20, 1]),
17437            ),
17438        ] {
17439            assert_eq!(
17440                f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
17441                "-Couldn't create filter!\r\n",
17442                "{why}"
17443            );
17444        }
17445    }
17446
17447    /// The RESP3 shapes, which are where this family differs most from RESP2
17448    /// and where one of its answers stops being readable.
17449    #[test]
17450    fn the_cuckoo_family_answers_in_resp3_spelling_too() {
17451        let mut f = Fixture::new();
17452        f.out.set_proto(Proto::Resp3);
17453        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
17454        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
17455        assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
17456        assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
17457        assert_eq!(
17458            f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
17459            "*2\r\n#t\r\n#f\r\n"
17460        );
17461        assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
17462        assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
17463        // The count stays an integer, because it counts rather than answers.
17464        assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
17465        assert_eq!(
17466            f.run(&[b"CF.INFO", b"c"]),
17467            "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
17468             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
17469             +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
17470             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
17471        );
17472
17473        // `CF.INSERT` writes a boolean per item here and an integer per item on
17474        // RESP2, and minus one has nowhere to go in a boolean, so a RESP3
17475        // client cannot tell an item that did not fit from one that is already
17476        // there. `CF.INSERTNX` keeps its integers for exactly that reason.
17477        f.run(&[
17478            b"CF.RESERVE",
17479            b"s",
17480            b"4",
17481            b"BUCKETSIZE",
17482            b"1",
17483            b"EXPANSION",
17484            b"0",
17485        ]);
17486        assert_eq!(
17487            f.run(&[
17488                b"CF.INSERT",
17489                b"s",
17490                b"ITEMS",
17491                b"a",
17492                b"b",
17493                b"c",
17494                b"d",
17495                b"e",
17496                b"f"
17497            ]),
17498            "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
17499        );
17500        assert_eq!(
17501            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
17502            "*2\r\n:0\r\n:-1\r\n"
17503        );
17504        assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
17505        // The end of a dump is a nil and not an empty chunk, which is one
17506        // underscore here and a negative length on RESP2.
17507        assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
17508    }
17509
17510    // ------------------------------------------------------------------- cms
17511
17512    /// A sketch is made from either end, and both constructors look at the key
17513    /// before they look at their arguments.
17514    #[test]
17515    fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
17516        let mut f = Fixture::new();
17517        assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
17518        assert_eq!(
17519            f.run(&[b"CMS.INFO", b"d"]),
17520            "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
17521        );
17522        assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
17523        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17524        // Two over the error rounded up, and the log of the probability over the
17525        // log of a half rounded up, which for these two is 200 by 6.
17526        assert_eq!(
17527            f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
17528            "+OK\r\n"
17529        );
17530        assert_eq!(
17531            f.run(&[b"CMS.INFO", b"p"]),
17532            "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
17533        );
17534        // The key is checked first, so a width of zero at a key that is already
17535        // there is about the key and not about the width.
17536        assert_eq!(
17537            f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
17538            "-CMS: key already exists\r\n"
17539        );
17540        assert_eq!(
17541            f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
17542            "-CMS: invalid width\r\n"
17543        );
17544        assert_eq!(
17545            f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
17546            "-CMS: invalid depth\r\n"
17547        );
17548        assert_eq!(
17549            f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
17550            "-CMS: invalid overestimation value\r\n"
17551        );
17552        assert_eq!(
17553            f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
17554            "-CMS: invalid prob value\r\n"
17555        );
17556        // A probability whose float conversion is zero has no depth, and a width
17557        // past a signed sixty four bit integer has no width, and both are the
17558        // same sentence.
17559        assert_eq!(
17560            f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
17561            "-CMS: invalid init arguments\r\n"
17562        );
17563        // And a sketch bigger than a gibibyte of counters is refused here where
17564        // the reference reserves address space nobody has touched, which is
17565        // D-47.
17566        assert_eq!(
17567            f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
17568            "-CMS: Insufficient memory to create the key\r\n"
17569        );
17570        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17571    }
17572
17573    /// Every pair is parsed before any of them lands, the counters saturate,
17574    /// and the count is a signed total of what was asked for.
17575    #[test]
17576    fn increments_are_parsed_whole_and_the_counters_saturate() {
17577        let mut f = Fixture::new();
17578        f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
17579        assert_eq!(
17580            f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
17581            "*2\r\n:3\r\n:4\r\n"
17582        );
17583        // An item that is incremented twice in one call sees its own first
17584        // increment in the reply to the second.
17585        assert_eq!(
17586            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
17587            "*2\r\n:4\r\n:5\r\n"
17588        );
17589        // A bad number anywhere means nothing at all is applied.
17590        assert_eq!(
17591            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
17592            "-CMS: Cannot parse number\r\n"
17593        );
17594        assert_eq!(
17595            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
17596            "-CMS: Number cannot be negative\r\n"
17597        );
17598        assert_eq!(
17599            f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
17600            "*2\r\n:5\r\n:4\r\n"
17601        );
17602        // The counters stop at four billion and the item that stopped says so in
17603        // its own slot while the one beside it answers a number.
17604        f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
17605        assert_eq!(
17606            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
17607            "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
17608        );
17609        assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
17610        // The count is what was asked for rather than what landed, and it is
17611        // signed, so a big enough total comes back negative.
17612        f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
17613        f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
17614        f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
17615        assert_eq!(
17616            f.run(&[b"CMS.INFO", b"w"]),
17617            "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
17618        );
17619        // An odd number of arguments after the key is an arity error and not a
17620        // syntax one.
17621        assert!(
17622            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
17623                .contains("wrong number of arguments")
17624        );
17625        assert_eq!(
17626            f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
17627            "-CMS: key does not exist\r\n"
17628        );
17629        assert_eq!(
17630            f.run(&[b"CMS.QUERY", b"nope", b"a"]),
17631            "-CMS: key does not exist\r\n"
17632        );
17633    }
17634
17635    /// A merge overwrites its destination, and it is worked out in full before
17636    /// any of it is written.
17637    #[test]
17638    fn a_merge_lands_whole_or_not_at_all() {
17639        let mut f = Fixture::new();
17640        for name in [&b"m1"[..], b"m2", b"dst"] {
17641            f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
17642        }
17643        f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
17644        f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
17645        assert_eq!(
17646            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17647            "+OK\r\n"
17648        );
17649        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17650        // Overwritten and not added to, so the same merge twice is the same
17651        // answer twice.
17652        assert_eq!(
17653            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17654            "+OK\r\n"
17655        );
17656        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17657        assert_eq!(
17658            f.run(&[
17659                b"CMS.MERGE",
17660                b"dst",
17661                b"2",
17662                b"m1",
17663                b"m2",
17664                b"WEIGHTS",
17665                b"2",
17666                b"3"
17667            ]),
17668            "+OK\r\n"
17669        );
17670        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17671        // A cell times a weight is checked wide rather than wrapped, so this is
17672        // a refusal and the destination is left exactly as it was.
17673        assert_eq!(
17674            f.run(&[
17675                b"CMS.MERGE",
17676                b"dst",
17677                b"1",
17678                b"m1",
17679                b"WEIGHTS",
17680                b"4611686018427387904"
17681            ]),
17682            "-CMS: MERGE overflow\r\n"
17683        );
17684        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17685        // The destination comes first, then the count, then the layout, then the
17686        // weights, then the sources one at a time.
17687        f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
17688        assert_eq!(
17689            f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
17690            "-CMS: key does not exist\r\n"
17691        );
17692        assert_eq!(
17693            f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
17694            "-CMS: Number of keys must be positive\r\n"
17695        );
17696        assert_eq!(
17697            f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
17698            "-CMS: wrong number of keys\r\n"
17699        );
17700        assert_eq!(
17701            f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
17702            "-CMS: wrong number of keys/weights\r\n"
17703        );
17704        assert_eq!(
17705            f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
17706            "-CMS: width/depth is not equal\r\n"
17707        );
17708        assert_eq!(
17709            f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
17710            "-CMS: key does not exist\r\n"
17711        );
17712    }
17713
17714    /// A key holding anything else is `WRONGTYPE` to all six, and a key holding
17715    /// a sketch is refused by the two commands that would have to serialise it.
17716    #[test]
17717    fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17718        let mut f = Fixture::new();
17719        f.run(&[b"SET", b"s", b"text"]);
17720        for cmd in [
17721            vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
17722            vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
17723            vec![&b"CMS.QUERY"[..], b"s", b"a"],
17724            vec![&b"CMS.INFO"[..], b"s"],
17725            vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
17726        ] {
17727            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17728            let reply = f.run(&cmd);
17729            // The two constructors see the key before anything else and say so
17730            // in the module's own words, and the rest are `WRONGTYPE`.
17731            assert!(
17732                reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
17733                "{name}: {reply}"
17734            );
17735        }
17736        f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
17737        // Redis refuses to copy a module key that has no copy callback, and
17738        // these are its words rather than ours. `DUMP` is the other half of
17739        // D-48: the reference has a payload for one of these and we do not.
17740        assert_eq!(
17741            f.run(&[b"COPY", b"c", b"c2"]),
17742            "-ERR not supported for this module key\r\n"
17743        );
17744        assert_eq!(
17745            f.run(&[b"DUMP", b"c"]),
17746            "-ERR DUMP is not supported for this module key\r\n"
17747        );
17748        // A graph is nobody's module and keeps its own sentence.
17749        f.run(&[b"G.NADD", b"g", b"a"]);
17750        assert_eq!(
17751            f.run(&[b"COPY", b"g", b"g2"]),
17752            "-ERR COPY is not supported for a graph\r\n"
17753        );
17754        assert_eq!(
17755            f.run(&[b"DUMP", b"g"]),
17756            "-ERR DUMP is not supported for a graph\r\n"
17757        );
17758        // Everything that does not need a byte shape works on a sketch key the
17759        // way it works on any other.
17760        assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
17761        assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
17762        assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
17763        assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
17764        assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
17765    }
17766
17767    // ------------------------------------------------------------------ topk
17768
17769    /// `TOPK.RESERVE` takes three arguments or six, and looks at the key before
17770    /// it looks at any of them.
17771    #[test]
17772    fn a_reserve_takes_three_arguments_or_six() {
17773        let mut f = Fixture::new();
17774        assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
17775        assert_eq!(
17776            f.run(&[b"TOPK.INFO", b"t"]),
17777            "*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"
17778        );
17779        // Four arguments and five are an arity error rather than a defaulted
17780        // depth or decay.
17781        for cmd in [
17782            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
17783            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
17784        ] {
17785            assert!(f.run(&cmd).contains("wrong number of arguments"));
17786        }
17787        assert_eq!(
17788            f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
17789            "+OK\r\n"
17790        );
17791        // The key is checked first, so a reserve with nothing else right at a
17792        // key that is taken still says the key is taken.
17793        assert_eq!(
17794            f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
17795            "-TopK: key already exists\r\n"
17796        );
17797        assert_eq!(
17798            f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
17799            "-TopK: invalid k\r\n"
17800        );
17801        assert_eq!(
17802            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
17803            "-TopK: invalid width\r\n"
17804        );
17805        assert_eq!(
17806            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
17807            "-TopK: invalid depth\r\n"
17808        );
17809        // Zero is out and one is in, which is the module's `> 0` and `<= 1`.
17810        assert_eq!(
17811            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
17812            "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
17813        );
17814        assert_eq!(
17815            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
17816            "+OK\r\n"
17817        );
17818        // Past the cap, with the one sentence in the family that has a prefix.
17819        assert_eq!(
17820            f.run(&[
17821                b"TOPK.RESERVE",
17822                b"w",
17823                b"1",
17824                b"4294967295",
17825                b"4294967295",
17826                b"0.9"
17827            ]),
17828            "-ERR Insufficient memory to create topk data structure\r\n"
17829        );
17830    }
17831
17832    /// What the sketch keeps, and the three ways of asking about it.
17833    #[test]
17834    fn the_kept_set_is_what_query_and_list_answer_from() {
17835        let mut f = Fixture::new();
17836        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
17837        // A null an item while there is room, then the name of whatever was
17838        // pushed out.
17839        assert_eq!(
17840            f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
17841            "*2\r\n$-1\r\n$-1\r\n"
17842        );
17843        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
17844        // Two slots are full and `c` arrives with a count of one, which is not
17845        // under the smallest kept count, so it takes that slot straight away.
17846        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
17847        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
17848        assert_eq!(
17849            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
17850            "*3\r\n:1\r\n:0\r\n:1\r\n"
17851        );
17852        // The table still counts what the kept set let go of.
17853        assert_eq!(
17854            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17855            "*3\r\n:11\r\n:1\r\n:6\r\n"
17856        );
17857        assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
17858        assert_eq!(
17859            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
17860            "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
17861        );
17862        // Any prefix of the keyword turns the counts on, the empty string
17863        // included, and only a longer word or a different one is refused.
17864        assert_eq!(
17865            f.run(&[b"TOPK.LIST", b"t", b"w"]),
17866            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17867        );
17868        assert_eq!(
17869            f.run(&[b"TOPK.LIST", b"t", b""]),
17870            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17871        );
17872        assert_eq!(
17873            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
17874            "-WITHCOUNT keyword expected\r\n"
17875        );
17876        // And the keyword is looked at before the key, so a missing key with a
17877        // bad keyword complains about the keyword.
17878        assert_eq!(
17879            f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
17880            "-WITHCOUNT keyword expected\r\n"
17881        );
17882        assert_eq!(
17883            f.run(&[b"TOPK.LIST", b"missing"]),
17884            "-TopK: key does not exist\r\n"
17885        );
17886        // An item counted zero times is kept and not listed.
17887        f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
17888        assert_eq!(
17889            f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
17890            "*1\r\n$-1\r\n"
17891        );
17892        assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
17893        assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
17894    }
17895
17896    /// `TOPK.INCRBY` applies as it goes, so a bad increment leaves everything
17897    /// before it counted, and the reply counts what it wrote.
17898    #[test]
17899    fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
17900        let mut f = Fixture::new();
17901        f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
17902        // Three pairs, the middle one bad: two elements come back, one of them
17903        // the error, and the array header says two rather than three. That last
17904        // part is D-51 and it is why a client here stays in step.
17905        assert_eq!(
17906            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
17907            format!(
17908                "*2\r\n$-1\r\n-{}\r\n",
17909                "TopK: increment must be an integer greater or equal to 0                            and smaller or equal to 100,000"
17910            )
17911        );
17912        assert_eq!(
17913            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17914            "*3\r\n:3\r\n:0\r\n:0\r\n"
17915        );
17916        // A hundred thousand is in and one more is out.
17917        assert_eq!(
17918            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
17919            "*1\r\n$-1\r\n"
17920        );
17921        assert!(
17922            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
17923                .contains("smaller or equal to 100,000")
17924        );
17925        // Pairs have to be pairs.
17926        assert!(
17927            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
17928                .contains("wrong number of arguments")
17929        );
17930        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
17931    }
17932
17933    /// The RESP3 shapes, which are the two the protocols disagree about.
17934    #[test]
17935    fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
17936        let mut f = Fixture::new();
17937        f.run(&[b"HELLO", b"3"]);
17938        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
17939        f.run(&[b"TOPK.ADD", b"t", b"a"]);
17940        assert_eq!(
17941            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
17942            "*2\r\n#t\r\n#f\r\n"
17943        );
17944        // The count stays an integer on both protocols.
17945        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
17946        assert_eq!(
17947            f.run(&[b"TOPK.INFO", b"t"]),
17948            "%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"
17949        );
17950        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
17951    }
17952
17953    /// A top k key answers the module sentences the other sketch families
17954    /// answer, and its own word for its type.
17955    #[test]
17956    fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17957        let mut f = Fixture::new();
17958        f.run(&[b"SET", b"s", b"text"]);
17959        for cmd in [
17960            vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
17961            vec![&b"TOPK.ADD"[..], b"s", b"a"],
17962            vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
17963            vec![&b"TOPK.QUERY"[..], b"s", b"a"],
17964            vec![&b"TOPK.COUNT"[..], b"s", b"a"],
17965            vec![&b"TOPK.LIST"[..], b"s"],
17966            vec![&b"TOPK.INFO"[..], b"s"],
17967        ] {
17968            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17969            let reply = f.run(&cmd);
17970            assert!(
17971                reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
17972                "{name}: {reply}"
17973            );
17974        }
17975        f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
17976        assert_eq!(
17977            f.run(&[b"COPY", b"t", b"t2"]),
17978            "-ERR not supported for this module key\r\n"
17979        );
17980        assert_eq!(
17981            f.run(&[b"DUMP", b"t"]),
17982            "-ERR DUMP is not supported for this module key\r\n"
17983        );
17984        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17985        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17986        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17987        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
17988        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17989        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17990        // Every one of the six that is not the constructor says the same thing
17991        // about a key that is not there.
17992        assert_eq!(
17993            f.run(&[b"TOPK.INFO", b"t3"]),
17994            "-TopK: key does not exist\r\n"
17995        );
17996    }
17997
17998    // --------------------------------------------------------------- tdigest
17999
18000    /// `TDIGEST.CREATE` takes two arguments or four, and the keyword search is a
18001    /// search rather than a lookup.
18002    #[test]
18003    fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
18004        let mut f = Fixture::new();
18005        assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
18006        // A hundred is the default and the capacity is six times it plus ten.
18007        assert_eq!(
18008            f.run(&[b"TDIGEST.INFO", b"t"]),
18009            "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
18010             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
18011             +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
18012        );
18013        assert_eq!(
18014            f.run(&[b"TDIGEST.CREATE", b"t"]),
18015            "-ERR T-Digest: key already exists\r\n"
18016        );
18017        // Three arguments is an arity error and not a missing keyword.
18018        assert!(
18019            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
18020                .contains("wrong number of arguments")
18021        );
18022        assert_eq!(
18023            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
18024            "+OK\r\n"
18025        );
18026        assert_eq!(
18027            f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
18028            "+OK\r\n"
18029        );
18030        // The word is looked for across both trailing arguments and the number
18031        // is then read out of the last one whatever was found, so this looks for
18032        // a number inside the word `COMPRESSION` and does not find one.
18033        assert_eq!(
18034            f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
18035            "-ERR T-Digest: error parsing compression parameter\r\n"
18036        );
18037        assert_eq!(
18038            f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
18039            "-ERR T-Digest: wrong keyword\r\n"
18040        );
18041        assert_eq!(
18042            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
18043            "-ERR T-Digest: error parsing compression parameter\r\n"
18044        );
18045        assert_eq!(
18046            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
18047            "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
18048        );
18049        // The reference's own ceiling, which is where the capacity stops fitting
18050        // in an int, and one past it.
18051        assert_eq!(
18052            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
18053            "-ERR T-Digest: allocation failed\r\n"
18054        );
18055        // And ours, which is a gibibyte of centroids and is D-52.
18056        assert_eq!(
18057            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
18058            "-ERR T-Digest: allocation failed\r\n"
18059        );
18060        // The key is checked before the arguments, so a bad compression at a key
18061        // that is already a digest still says the key is taken.
18062        assert_eq!(
18063            f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
18064            "-ERR T-Digest: key already exists\r\n"
18065        );
18066    }
18067
18068    /// The four samples every note about this family is written against, and the
18069    /// answers a real 8.10.1 gives for them.
18070    #[test]
18071    fn the_quantile_family_answers_what_the_module_answers() {
18072        let mut f = Fixture::new();
18073        f.run(&[b"TDIGEST.CREATE", b"s"]);
18074        assert_eq!(
18075            f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
18076            "+OK\r\n"
18077        );
18078        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
18079        assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
18080        // The cdf of a sample is the weight below it plus half its own.
18081        assert_eq!(
18082            f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
18083            "*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"
18084        );
18085        assert_eq!(
18086            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
18087            "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
18088        );
18089        // Out of order, the walk restarts, and 0.5 answers 3 either way while
18090        // the two after it are read from the front again.
18091        assert_eq!(
18092            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
18093            "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
18094        );
18095        assert_eq!(
18096            f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
18097            "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
18098        );
18099        assert_eq!(
18100            f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
18101            "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
18102        );
18103        assert_eq!(
18104            f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
18105            "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
18106        );
18107        assert_eq!(
18108            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
18109            "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
18110        );
18111        assert_eq!(
18112            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
18113            "$3\r\n2.5\r\n"
18114        );
18115        assert_eq!(
18116            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
18117            "$3\r\n2.5\r\n"
18118        );
18119        // The ranges, which are separate sentences from the parse failures.
18120        assert_eq!(
18121            f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
18122            "-ERR T-Digest: quantile should be in [0,1]\r\n"
18123        );
18124        assert_eq!(
18125            f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
18126            "-ERR T-Digest: error parsing quantile\r\n"
18127        );
18128        assert_eq!(
18129            f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
18130            "-ERR T-Digest: error parsing cdf\r\n"
18131        );
18132        assert_eq!(
18133            f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
18134            "-ERR T-Digest: error parsing value\r\n"
18135        );
18136        assert_eq!(
18137            f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
18138            "-ERR T-Digest: rank needs to be non negative\r\n"
18139        );
18140        assert_eq!(
18141            f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
18142            "-ERR T-Digest: error parsing rank\r\n"
18143        );
18144        // Both cuts have their own parse sentence and share the range one, and
18145        // equal cuts are refused rather than answering nothing.
18146        assert_eq!(
18147            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
18148            "-ERR T-Digest: error parsing low_cut_percentile\r\n"
18149        );
18150        assert_eq!(
18151            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
18152            "-ERR T-Digest: error parsing high_cut_percentile\r\n"
18153        );
18154        assert_eq!(
18155            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
18156            "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
18157        );
18158        assert_eq!(
18159            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
18160            "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
18161        );
18162    }
18163
18164    /// An empty digest answers every question, and answers most of them with
18165    /// something that is not a number.
18166    #[test]
18167    fn an_empty_digest_has_an_answer_for_everything() {
18168        let mut f = Fixture::new();
18169        f.run(&[b"TDIGEST.CREATE", b"e"]);
18170        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
18171        assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
18172        assert_eq!(
18173            f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
18174            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
18175        );
18176        assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
18177        assert_eq!(
18178            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
18179            "$3\r\nnan\r\n"
18180        );
18181        // Minus two, which is a number no rank on a digest with samples in it
18182        // can ever be.
18183        assert_eq!(
18184            f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
18185            "*2\r\n:-2\r\n:-2\r\n"
18186        );
18187        assert_eq!(
18188            f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
18189            "*2\r\n:-2\r\n:-2\r\n"
18190        );
18191        assert_eq!(
18192            f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
18193            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
18194        );
18195        // A reset puts a digest with samples back into exactly this state.
18196        f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
18197        assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
18198        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
18199        // Down to the compression count, so a reset digest and a fresh one of
18200        // the same compression report the same nine numbers.
18201        f.run(&[b"TDIGEST.CREATE", b"e2"]);
18202        assert_eq!(
18203            f.run(&[b"TDIGEST.INFO", b"e"]),
18204            f.run(&[b"TDIGEST.INFO", b"e2"])
18205        );
18206    }
18207
18208    /// The double parser is Redis's and not this engine's, and the two disagree
18209    /// at both ends of the range.
18210    #[test]
18211    fn a_sample_is_read_the_way_redis_reads_a_double() {
18212        let mut f = Fixture::new();
18213        f.run(&[b"TDIGEST.CREATE", b"a"]);
18214        // Overflow and underflow are parse failures rather than an infinity and
18215        // a zero, which is where this parts company with the rest of the engine.
18216        for bad in [
18217            &b"nan"[..],
18218            b"1e400",
18219            b"-1e400",
18220            b"1e309",
18221            b"1e-400",
18222            b"",
18223            b" 1",
18224            b"1 ",
18225            b"1e",
18226            b"--1",
18227        ] {
18228            assert_eq!(
18229                f.run(&[b"TDIGEST.ADD", b"a", bad]),
18230                "-ERR T-Digest: error parsing val parameter\r\n",
18231                "{}",
18232                String::from_utf8_lossy(bad)
18233            );
18234        }
18235        // An infinity spelled out parses and is then refused for being one, with
18236        // a different sentence.
18237        for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
18238            assert_eq!(
18239                f.run(&[b"TDIGEST.ADD", b"a", word]),
18240                "-ERR T-Digest: val parameter needs to be a finite number\r\n",
18241                "{}",
18242                String::from_utf8_lossy(word)
18243            );
18244        }
18245        // These all parse: hex, a bare point either side, and the smallest
18246        // subnormal the reference will take.
18247        for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
18248            assert_eq!(
18249                f.run(&[b"TDIGEST.ADD", b"a", good]),
18250                "+OK\r\n",
18251                "{}",
18252                String::from_utf8_lossy(good)
18253            );
18254        }
18255        // Nothing landed from the failures, so six samples is what there is.
18256        assert!(
18257            f.run(&[b"TDIGEST.INFO", b"a"])
18258                .contains("Observations\r\n:6\r\n")
18259        );
18260        // Every value is parsed before any is added, so this whole command is a
18261        // no op.
18262        assert_eq!(
18263            f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
18264            "-ERR T-Digest: error parsing val parameter\r\n"
18265        );
18266        assert!(
18267            f.run(&[b"TDIGEST.INFO", b"a"])
18268                .contains("Observations\r\n:6\r\n")
18269        );
18270    }
18271
18272    /// What a merge does to its destination, to its inputs and to the buffer
18273    /// split `TDIGEST.INFO` reports.
18274    #[test]
18275    fn a_merge_sweeps_the_destination_between_its_inputs() {
18276        let mut f = Fixture::new();
18277        f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
18278        f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
18279        f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
18280        f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
18281        assert_eq!(
18282            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
18283            "+OK\r\n"
18284        );
18285        // The destination did not exist, so the compression is the largest of
18286        // the inputs. The three from the first input were swept in before the
18287        // three from the second arrived, which is the one visible effect of the
18288        // reference folding one input at a time.
18289        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
18290        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
18291        assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
18292        assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
18293        assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
18294        assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
18295        assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
18296        // Reading a source sweeps it too, so a merge writes to keys it only
18297        // reads from.
18298        assert!(
18299            f.run(&[b"TDIGEST.INFO", b"m1"])
18300                .contains("Merged nodes\r\n:3\r\n")
18301        );
18302        // Without OVERRIDE the destination joins its own inputs, so this takes
18303        // it to nine observations and keeps its own compression.
18304        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
18305        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
18306        assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
18307        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
18308        // With OVERRIDE the old destination is dropped and the compression goes
18309        // back to the largest of the inputs.
18310        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
18311        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
18312        assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
18313        assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
18314        // And COMPRESSION beats both.
18315        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
18316        assert!(
18317            f.run(&[b"TDIGEST.INFO", b"d"])
18318                .contains("Compression\r\n:500\r\n")
18319        );
18320        // Naming the destination as a source folds it in twice.
18321        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
18322        assert!(
18323            f.run(&[b"TDIGEST.INFO", b"d"])
18324                .contains("Observations\r\n:12\r\n")
18325        );
18326        // The arguments, in the order the reference checks them.
18327        assert_eq!(
18328            f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
18329            "-ERR T-Digest: error parsing numkeys\r\n"
18330        );
18331        assert_eq!(
18332            f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
18333            "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
18334        );
18335        assert!(
18336            f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
18337                .contains("wrong number of arguments")
18338        );
18339        assert!(
18340            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
18341                .contains("wrong number of arguments")
18342        );
18343        assert_eq!(
18344            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
18345            "-ERR T-Digest: wrong keyword\r\n"
18346        );
18347        // A source that is not there stops the whole thing, and the destination
18348        // is left as it was.
18349        assert_eq!(
18350            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
18351            "-ERR T-Digest: key does not exist\r\n"
18352        );
18353        assert!(
18354            f.run(&[b"TDIGEST.INFO", b"d"])
18355                .contains("Observations\r\n:12\r\n")
18356        );
18357        // A destination that is not there and is also named as a source is the
18358        // same sentence rather than an empty merge.
18359        assert_eq!(
18360            f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
18361            "-ERR T-Digest: key does not exist\r\n"
18362        );
18363    }
18364
18365    /// The RESP3 shapes, which are the two the protocols disagree about.
18366    #[test]
18367    fn a_digest_answers_doubles_and_a_map_on_resp3() {
18368        let mut f = Fixture::new();
18369        f.run(&[b"HELLO", b"3"]);
18370        f.run(&[b"TDIGEST.CREATE", b"s"]);
18371        f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
18372        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
18373        assert_eq!(
18374            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
18375            "*2\r\n,1\r\n,4\r\n"
18376        );
18377        assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
18378        // The two infinities and the NaN go out as the bare words.
18379        assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
18380        assert_eq!(
18381            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
18382            "*1\r\n,-inf\r\n"
18383        );
18384        f.run(&[b"TDIGEST.CREATE", b"e"]);
18385        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
18386        // The ranks stay integers on both protocols.
18387        assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
18388        // Every question above swept the buffer in, so the four samples are all
18389        // merged by now and the compression count says it happened once.
18390        assert_eq!(
18391            f.run(&[b"TDIGEST.INFO", b"s"]),
18392            "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
18393             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
18394             +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
18395        );
18396    }
18397
18398    /// A t digest key answers the module sentences the other sketch families
18399    /// answer, and its own word for its type.
18400    #[test]
18401    fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
18402        let mut f = Fixture::new();
18403        f.run(&[b"SET", b"s", b"text"]);
18404        for cmd in [
18405            vec![&b"TDIGEST.CREATE"[..], b"s"],
18406            vec![&b"TDIGEST.RESET"[..], b"s"],
18407            vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
18408            vec![&b"TDIGEST.MIN"[..], b"s"],
18409            vec![&b"TDIGEST.MAX"[..], b"s"],
18410            vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
18411            vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
18412            vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
18413            vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
18414            vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
18415            vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
18416            vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
18417            vec![&b"TDIGEST.INFO"[..], b"s"],
18418        ] {
18419            let name = String::from_utf8_lossy(cmd[0]).into_owned();
18420            let reply = f.run(&cmd);
18421            assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
18422        }
18423        // The merge checks its destination the same way, and its sources too.
18424        f.run(&[b"TDIGEST.CREATE", b"t"]);
18425        assert!(
18426            f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
18427                .starts_with("-WRONGTYPE")
18428        );
18429        assert!(
18430            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
18431                .starts_with("-WRONGTYPE")
18432        );
18433        assert_eq!(
18434            f.run(&[b"COPY", b"t", b"t2"]),
18435            "-ERR not supported for this module key\r\n"
18436        );
18437        assert_eq!(
18438            f.run(&[b"DUMP", b"t"]),
18439            "-ERR DUMP is not supported for this module key\r\n"
18440        );
18441        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
18442        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
18443        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
18444        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
18445        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
18446        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
18447        // An empty digest is still a key, so the twelve that are not the
18448        // constructor all say the same thing once it is gone.
18449        assert_eq!(
18450            f.run(&[b"TDIGEST.INFO", b"t3"]),
18451            "-ERR T-Digest: key does not exist\r\n"
18452        );
18453        // The key is looked at before the arguments, so a bad argument at a key
18454        // that is not there still says the key is not there.
18455        assert_eq!(
18456            f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
18457            "-ERR T-Digest: key does not exist\r\n"
18458        );
18459    }
18460
18461    // -------------------------------------------------------------------- ts
18462
18463    /// A `TS.INFO` reply with the memory usage taken out of it.
18464    ///
18465    /// That number is what a series costs here rather than what one costs in the
18466    /// module, which is D-53, and it moves whenever the layout of a chunk does.
18467    /// Everything either side of it is the wire contract and is worth pinning
18468    /// down exactly, so the tests below check the whole reply with the one
18469    /// number lifted out.
18470    fn without_memory(reply: &str) -> String {
18471        let head = "+memoryUsage\r\n:";
18472        let at = reply.find(head).expect("every TS.INFO reports memory");
18473        let rest = &reply[at + head.len()..];
18474        let end = rest.find("\r\n").expect("and it is a whole number");
18475        format!("{}{}", &reply[..at + head.len()], &rest[end..])
18476    }
18477
18478    /// A series is made empty and still says it has a chunk, and the options are
18479    /// read before the key is looked at.
18480    #[test]
18481    fn a_series_is_made_empty_and_reports_on_itself() {
18482        let mut f = Fixture::new();
18483        assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
18484        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18485        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
18486        // Fourteen fields, so twenty eight elements. An empty series reports one
18487        // chunk and zero at both ends, and neither the chunk type nor the
18488        // duplicate policy is ever a nil.
18489        assert_eq!(
18490            without_memory(&f.run(&[b"TS.INFO", b"t"])),
18491            "*28\r\n\
18492             +totalSamples\r\n:0\r\n\
18493             +memoryUsage\r\n:\r\n\
18494             +firstTimestamp\r\n:0\r\n\
18495             +lastTimestamp\r\n:0\r\n\
18496             +retentionTime\r\n:0\r\n\
18497             +chunkCount\r\n:1\r\n\
18498             +chunkSize\r\n:4096\r\n\
18499             +chunkType\r\n+compressed\r\n\
18500             +duplicatePolicy\r\n+block\r\n\
18501             +labels\r\n*0\r\n\
18502             +sourceKey\r\n$-1\r\n\
18503             +rules\r\n*0\r\n\
18504             +ignoreMaxTimeDiff\r\n:0\r\n\
18505             +ignoreMaxValDiff\r\n$1\r\n0\r\n"
18506        );
18507        // A key that is already there is about the key whatever it holds, and
18508        // the existence is what is checked rather than the type.
18509        assert_eq!(
18510            f.run(&[b"TS.CREATE", b"t"]),
18511            "-ERR TSDB: key already exists\r\n"
18512        );
18513        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18514        assert_eq!(
18515            f.run(&[b"TS.CREATE", b"str"]),
18516            "-ERR TSDB: key already exists\r\n"
18517        );
18518        // But the arguments are read first, so a bad one at a key that is there
18519        // answers about the argument.
18520        assert_eq!(
18521            f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
18522            "-ERR TSDB: Couldn't parse RETENTION\r\n"
18523        );
18524        // The seven that will not make a series say WRONGTYPE about a key
18525        // holding something else, where the two that would say a sentence.
18526        // The word is inside the sentence and not in front of it, because the
18527        // module writes its own error text and Redis puts ERR on the front of
18528        // anything a module writes.
18529        let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
18530        assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
18531        assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
18532        assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
18533        assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
18534        assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
18535        assert_eq!(
18536            f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
18537            "-ERR TSDB: the key is not a TSDB key\r\n"
18538        );
18539        // And the ones that will not make one say so about a key that is gone.
18540        assert_eq!(
18541            f.run(&[b"TS.INFO", b"nope"]),
18542            "-ERR TSDB: the key does not exist\r\n"
18543        );
18544        assert_eq!(
18545            f.run(&[b"TS.GET", b"nope"]),
18546            "-ERR TSDB: the key does not exist\r\n"
18547        );
18548        assert_eq!(
18549            f.run(&[b"TS.ALTER", b"nope"]),
18550            "-ERR TSDB: the key does not exist\r\n"
18551        );
18552        assert_eq!(
18553            f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
18554            "-ERR TSDB: the key does not exist\r\n"
18555        );
18556    }
18557
18558    /// Every option word, including the ones that are wrong, and the scan that
18559    /// finds them.
18560    #[test]
18561    fn the_options_are_a_keyword_scan_and_not_a_grammar() {
18562        let mut f = Fixture::new();
18563        assert_eq!(
18564            f.run(&[
18565                b"TS.CREATE",
18566                b"t",
18567                b"RETENTION",
18568                b"5000",
18569                b"ENCODING",
18570                b"UNCOMPRESSED",
18571                b"CHUNK_SIZE",
18572                b"128",
18573                b"DUPLICATE_POLICY",
18574                b"LAST",
18575                b"IGNORE",
18576                b"10",
18577                b"0.5",
18578                b"LABELS",
18579                b"room",
18580                b"kitchen"
18581            ]),
18582            "+OK\r\n"
18583        );
18584        let info = f.run(&[b"TS.INFO", b"t"]);
18585        assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
18586        assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
18587        assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
18588        assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
18589        assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
18590        // A plain double here, where a sample value out of TS.GET is the
18591        // shortest digits that read back as the same number.
18592        assert!(
18593            info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
18594            "{info}"
18595        );
18596        assert!(
18597            info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
18598            "{info}"
18599        );
18600
18601        // A word that is not an option is read past rather than refused.
18602        assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
18603        // LABELS eats everything after it in pairs, and the later scans still
18604        // look inside what it ate, so this sets a retention and stores a label
18605        // called RETENTION at the same time.
18606        assert_eq!(
18607            f.run(&[
18608                b"TS.CREATE",
18609                b"g",
18610                b"LABELS",
18611                b"a",
18612                b"b",
18613                b"RETENTION",
18614                b"5"
18615            ]),
18616            "+OK\r\n"
18617        );
18618        let greedy = f.run(&[b"TS.INFO", b"g"]);
18619        assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
18620        assert!(
18621            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"),
18622            "{greedy}"
18623        );
18624
18625        // Every way an option can be wrong, in the order the module reads them.
18626        assert_eq!(
18627            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
18628            "-ERR TSDB: Couldn't parse LABELS\r\n"
18629        );
18630        assert_eq!(
18631            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
18632            "-ERR TSDB: Couldn't parse LABELS\r\n"
18633        );
18634        assert_eq!(
18635            f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
18636            "-ERR TSDB: Couldn't parse RETENTION\r\n"
18637        );
18638        // A retention below zero is one of the two the module writes with no
18639        // ERR in front of it, where one that is not a number gets one.
18640        assert_eq!(
18641            f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
18642            "-TSDB: Couldn't parse RETENTION\r\n"
18643        );
18644        assert_eq!(
18645            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
18646            "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
18647        );
18648        assert_eq!(
18649            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
18650            "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
18651        );
18652        assert_eq!(
18653            f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
18654            "-ERR TSDB: unknown ENCODING parameter\r\n"
18655        );
18656        // And an ENCODING with nothing behind it is an arity error where every
18657        // other keyword in the same spot is a sentence.
18658        assert!(
18659            f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
18660                .contains("wrong number of arguments for 'ts.create' command")
18661        );
18662        assert_eq!(
18663            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
18664            "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
18665        );
18666        assert_eq!(
18667            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
18668            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18669        );
18670        assert_eq!(
18671            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
18672            "-ERR TSDB: Couldn't parse IGNORE\r\n"
18673        );
18674        assert_eq!(
18675            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
18676            "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
18677        );
18678        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
18679
18680        // An alter changes what was named and leaves the rest alone, and reads
18681        // an encoding only far enough to refuse a bad one.
18682        assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
18683        let after = f.run(&[b"TS.INFO", b"t"]);
18684        assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
18685        assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
18686        assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
18687        assert_eq!(
18688            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
18689            "-ERR TSDB: unknown ENCODING parameter\r\n"
18690        );
18691        // An encoding it does take is still not applied.
18692        assert_eq!(
18693            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
18694            "+OK\r\n"
18695        );
18696        assert!(
18697            f.run(&[b"TS.INFO", b"t"])
18698                .contains("+chunkType\r\n+uncompressed\r\n")
18699        );
18700    }
18701
18702    /// Samples go in, come back out and are refused for the reasons the module
18703    /// refuses them.
18704    #[test]
18705    fn samples_land_where_they_are_put_and_the_newest_comes_back() {
18706        let mut f = Fixture::new();
18707        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
18708        // The series was made on the way in.
18709        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18710        assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
18711        // A sample value goes out as a simple string of the shortest digits
18712        // that read back as the same number.
18713        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
18714        assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
18715        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
18716        // An empty series has no newest sample and answers an empty array
18717        // rather than a nil.
18718        assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
18719        assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
18720
18721        // The value is read before the key, so a bad one against a key holding
18722        // a string is about the value.
18723        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18724        assert_eq!(
18725            f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
18726            "-ERR TSDB: invalid value\r\n"
18727        );
18728        // The grammar is tighter than the one a number argument usually gets:
18729        // no leading plus, no bare fraction, no infinity and nothing that does
18730        // not fit.
18731        for bad in [
18732            &b".5"[..],
18733            b"1.",
18734            b"+1",
18735            b" 1",
18736            b"0x10",
18737            b"inf",
18738            b"1e400",
18739            b"--1",
18740            b"1e",
18741        ] {
18742            assert_eq!(
18743                f.run(&[b"TS.ADD", b"v", b"1", bad]),
18744                "-ERR TSDB: invalid value\r\n",
18745                "{}",
18746                String::from_utf8_lossy(bad)
18747            );
18748        }
18749        // And a reading that is not a number is one of three words.
18750        assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
18751
18752        // A timestamp that is not a number, and one that is and is below zero,
18753        // are two different sentences.
18754        assert_eq!(
18755            f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
18756            "-ERR TSDB: invalid timestamp\r\n"
18757        );
18758        assert_eq!(
18759            f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
18760            "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
18761        );
18762
18763        // A repeated timestamp is blocked by default, and ON_DUPLICATE on the
18764        // command beats what the series was told.
18765        assert_eq!(
18766            f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
18767            "-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"
18768        );
18769        assert_eq!(
18770            f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
18771            ":300\r\n"
18772        );
18773        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
18774        // ON_DUPLICATE is only read when the key was already there, which is
18775        // why a policy word that is not a policy passes on a fresh key.
18776        assert_eq!(
18777            f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
18778            ":1\r\n"
18779        );
18780        assert_eq!(
18781            f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
18782            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18783        );
18784
18785        // Retention is exact and it is checked before anything else happens, so
18786        // a sample landing behind the window is refused rather than trimmed.
18787        assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
18788        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
18789        assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
18790        assert_eq!(
18791            f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
18792            "-ERR TSDB: Timestamp is older than retention\r\n"
18793        );
18794        // And the window trims as it moves.
18795        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
18796        assert!(
18797            f.run(&[b"TS.INFO", b"r"])
18798                .contains("+totalSamples\r\n:1\r\n")
18799        );
18800
18801        // An ignore window drops a sample close enough to the newest one to be
18802        // uninteresting, and answers the newest timestamp so a client can tell.
18803        assert_eq!(
18804            f.run(&[
18805                b"TS.CREATE",
18806                b"i",
18807                b"DUPLICATE_POLICY",
18808                b"LAST",
18809                b"IGNORE",
18810                b"10",
18811                b"0.5"
18812            ]),
18813            "+OK\r\n"
18814        );
18815        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
18816        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
18817        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
18818    }
18819
18820    /// Every triple in a `TS.MADD` is answered on its own, and none of them
18821    /// makes a series.
18822    #[test]
18823    fn a_madd_answers_each_triple_and_creates_nothing() {
18824        let mut f = Fixture::new();
18825        assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
18826        assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
18827        assert_eq!(
18828            f.run(&[
18829                b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
18830            ]),
18831            "*3\r\n:100\r\n:100\r\n:200\r\n"
18832        );
18833        // A key that is not a series is an error in its own slot and the ones
18834        // after it still land.
18835        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18836        assert_eq!(
18837            f.run(&[
18838                b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
18839            ]),
18840            "*3\r\n\
18841             -ERR TSDB: the key is not a TSDB key\r\n\
18842             -ERR TSDB: the key is not a TSDB key\r\n\
18843             :300\r\n"
18844        );
18845        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
18846        // A bad value and a bad timestamp are answered in their slots too.
18847        assert_eq!(
18848            f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
18849            "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
18850        );
18851        // And a list that is not made of triples is an arity error.
18852        assert!(
18853            f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
18854                .contains("wrong number of arguments for 'ts.madd' command")
18855        );
18856    }
18857
18858    /// The two increments, which only ever write forwards.
18859    #[test]
18860    fn an_increment_walks_the_newest_value_up_and_down() {
18861        let mut f = Fixture::new();
18862        assert_eq!(
18863            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18864            ":100\r\n"
18865        );
18866        assert_eq!(
18867            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18868            ":100\r\n"
18869        );
18870        // Two on one timestamp add up rather than collide, because the sample
18871        // goes in under the last policy whatever the series says.
18872        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
18873        assert_eq!(
18874            f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
18875            ":200\r\n"
18876        );
18877        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
18878        // A timestamp behind the newest sample is the other of the two errors
18879        // the module writes with no ERR in front of it.
18880        assert_eq!(
18881            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
18882            "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
18883        );
18884        // The increment goes through the ordinary number reader, so it takes
18885        // what a sample value will not and refuses a NaN that a sample value
18886        // takes.
18887        assert_eq!(
18888            f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
18889            ":1\r\n"
18890        );
18891        assert_eq!(
18892            f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
18893            ":1\r\n"
18894        );
18895        assert_eq!(
18896            f.run(&[b"TS.INCRBY", b"t", b"nan"]),
18897            "-ERR TSDB: invalid increase/decrease value\r\n"
18898        );
18899        assert_eq!(
18900            f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
18901            "-ERR TSDB: invalid increase/decrease value\r\n"
18902        );
18903        // A key holding something else is WRONGTYPE and is answered before the
18904        // number is looked at.
18905        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18906        assert_eq!(
18907            f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
18908            "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18909        );
18910        // A TIMESTAMP keyword with nothing behind it is about the timestamp.
18911        // The reference reads one past the end of its own arguments here and
18912        // answers whatever was in that memory, so there is nothing to copy and
18913        // this answers the same thing every time.
18914        assert_eq!(
18915            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
18916            "-ERR TSDB: invalid timestamp\r\n"
18917        );
18918        // And one behind a LABELS is a label name rather than the keyword, so
18919        // this lands at the clock rather than at 5.
18920        assert_eq!(
18921            f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
18922            format!(":{}\r\n", f.server.now_ms())
18923        );
18924        // Adding to a series whose newest value is not a number has no answer.
18925        assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
18926        assert_eq!(
18927            f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
18928            "-ERR TSDB: cannot increment/decrement NaN value\r\n"
18929        );
18930    }
18931
18932    /// Deleting a span, both ends included.
18933    #[test]
18934    fn deleting_takes_out_a_span_and_answers_how_many_went() {
18935        let mut f = Fixture::new();
18936        for at in [b"100".as_slice(), b"200", b"300", b"400"] {
18937            f.run(&[b"TS.ADD", b"t", at, b"1"]);
18938        }
18939        assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
18940        assert!(
18941            f.run(&[b"TS.INFO", b"t"])
18942                .contains("+totalSamples\r\n:2\r\n")
18943        );
18944        // Ends the wrong way round take nothing out rather than being an error.
18945        assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
18946        // The two open ends.
18947        assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
18948        // A series everything has been deleted from keeps its chunk and reports
18949        // zero at both ends again.
18950        let empty = f.run(&[b"TS.INFO", b"t"]);
18951        assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
18952        assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
18953        assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
18954        assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
18955        assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
18956        // The two ends have their own sentences.
18957        assert_eq!(
18958            f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
18959            "-ERR TSDB: wrong fromTimestamp\r\n"
18960        );
18961        assert_eq!(
18962            f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
18963            "-ERR TSDB: wrong toTimestamp\r\n"
18964        );
18965        assert_eq!(
18966            f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
18967            "-ERR TSDB: wrong fromTimestamp\r\n"
18968        );
18969    }
18970
18971    /// What RESP3 changes, which is the two places a number is written and the
18972    /// shape of `TS.INFO`.
18973    #[test]
18974    fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
18975        let mut f = Fixture::new();
18976        f.out = Out::new(Proto::Resp3);
18977        assert_eq!(
18978            f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
18979            "+OK\r\n"
18980        );
18981        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
18982        // A double rather than the simple string RESP2 gets.
18983        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
18984        assert_eq!(
18985            without_memory(&f.run(&[b"TS.INFO", b"t"])),
18986            "%14\r\n\
18987             +totalSamples\r\n:1\r\n\
18988             +memoryUsage\r\n:\r\n\
18989             +firstTimestamp\r\n:100\r\n\
18990             +lastTimestamp\r\n:100\r\n\
18991             +retentionTime\r\n:0\r\n\
18992             +chunkCount\r\n:1\r\n\
18993             +chunkSize\r\n:4096\r\n\
18994             +chunkType\r\n+compressed\r\n\
18995             +duplicatePolicy\r\n+block\r\n\
18996             +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18997             +sourceKey\r\n_\r\n\
18998             +rules\r\n%0\r\n\
18999             +ignoreMaxTimeDiff\r\n:0\r\n\
19000             +ignoreMaxValDiff\r\n,0\r\n"
19001        );
19002    }
19003
19004    /// Reading a span back, both ways round, with the two ends and the three
19005    /// things that trim what comes out.
19006    #[test]
19007    fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
19008        let mut f = Fixture::new();
19009        for (at, v) in [
19010            (b"100".as_slice(), b"1".as_slice()),
19011            (b"200", b"2"),
19012            (b"300", b"3"),
19013            (b"400", b"4"),
19014        ] {
19015            f.run(&[b"TS.ADD", b"t", at, v]);
19016        }
19017        assert_eq!(
19018            f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
19019            "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
19020             *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
19021        );
19022        // Both ends are included.
19023        assert_eq!(
19024            f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
19025            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
19026        );
19027        // Backwards, and the count takes from the front of what comes out, so
19028        // backwards it takes the newest.
19029        assert_eq!(
19030            f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
19031            "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
19032        );
19033        // Ends the wrong way round are empty rather than an error.
19034        assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
19035        // The two filters.
19036        assert_eq!(
19037            f.run(&[
19038                b"TS.RANGE",
19039                b"t",
19040                b"-",
19041                b"+",
19042                b"FILTER_BY_VALUE",
19043                b"2",
19044                b"3"
19045            ]),
19046            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
19047        );
19048        assert_eq!(
19049            f.run(&[
19050                b"TS.RANGE",
19051                b"t",
19052                b"-",
19053                b"+",
19054                b"FILTER_BY_TS",
19055                b"100",
19056                b"400"
19057            ]),
19058            "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
19059        );
19060        // A word that is not an option is ignored wherever it sits.
19061        assert_eq!(
19062            f.run(&[
19063                b"TS.RANGE",
19064                b"t",
19065                b"-",
19066                b"+",
19067                b"ZZZ",
19068                b"FILTER_BY_TS",
19069                b"400"
19070            ]),
19071            "*1\r\n*2\r\n:400\r\n+4\r\n"
19072        );
19073        // `LATEST` means nothing until there is a compaction rule to follow.
19074        assert_eq!(
19075            f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
19076            "*1\r\n*2\r\n:100\r\n+1\r\n"
19077        );
19078    }
19079
19080    /// The bucketing, which is one column a reduction and a flat row.
19081    #[test]
19082    fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
19083        let mut f = Fixture::new();
19084        for (at, v) in [
19085            (b"100".as_slice(), b"1".as_slice()),
19086            (b"200", b"2"),
19087            (b"300", b"3"),
19088            (b"400", b"4"),
19089        ] {
19090            f.run(&[b"TS.ADD", b"t", at, v]);
19091        }
19092        assert_eq!(
19093            f.run(&[
19094                b"TS.RANGE",
19095                b"t",
19096                b"-",
19097                b"+",
19098                b"AGGREGATION",
19099                b"avg",
19100                b"200"
19101            ]),
19102            "*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"
19103        );
19104        // Three reductions is a row of four and not a row of two with a nested
19105        // three in it.
19106        assert_eq!(
19107            f.run(&[
19108                b"TS.RANGE",
19109                b"t",
19110                b"-",
19111                b"+",
19112                b"AGGREGATION",
19113                b"min,max,count",
19114                b"200"
19115            ]),
19116            "*3\r\n\
19117             *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
19118             *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
19119             *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
19120        );
19121        // The timestamp a bucket is reported under.
19122        assert_eq!(
19123            f.run(&[
19124                b"TS.RANGE",
19125                b"t",
19126                b"-",
19127                b"+",
19128                b"AGGREGATION",
19129                b"avg",
19130                b"200",
19131                b"BUCKETTIMESTAMP",
19132                b"+"
19133            ]),
19134            "*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"
19135        );
19136        // An alignment moves where the bucket edges land.
19137        assert_eq!(
19138            f.run(&[
19139                b"TS.RANGE",
19140                b"t",
19141                b"100",
19142                b"400",
19143                b"ALIGN",
19144                b"100",
19145                b"AGGREGATION",
19146                b"sum",
19147                b"200"
19148            ]),
19149            "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
19150        );
19151        // A `COUNT` sitting where the reduction name belongs is that name, and
19152        // the scan for a real one starts again two words later.
19153        assert_eq!(
19154            f.run(&[
19155                b"TS.RANGE",
19156                b"t",
19157                b"-",
19158                b"+",
19159                b"AGGREGATION",
19160                b"count",
19161                b"200"
19162            ]),
19163            "*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"
19164        );
19165        assert_eq!(
19166            f.run(&[
19167                b"TS.RANGE",
19168                b"t",
19169                b"-",
19170                b"+",
19171                b"AGGREGATION",
19172                b"count",
19173                b"200",
19174                b"COUNT",
19175                b"1"
19176            ]),
19177            "*1\r\n*2\r\n:0\r\n+1\r\n"
19178        );
19179    }
19180
19181    /// `EMPTY` fills the gaps between readings and nothing else, and `last`
19182    /// carries two different things depending on which kind of empty it is.
19183    #[test]
19184    fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
19185        let mut f = Fixture::new();
19186        for (at, v) in [
19187            (b"0".as_slice(), b"1".as_slice()),
19188            (b"100", b"2"),
19189            (b"500", b"nan"),
19190            (b"600", b"3"),
19191        ] {
19192            f.run(&[b"TS.ADD", b"g", at, v]);
19193        }
19194        // Without `EMPTY` the buckets with nothing in them are not there at all,
19195        // and neither is the one holding only a reading that is not a number.
19196        assert_eq!(
19197            f.run(&[
19198                b"TS.RANGE",
19199                b"g",
19200                b"-",
19201                b"+",
19202                b"AGGREGATION",
19203                b"avg",
19204                b"100"
19205            ]),
19206            "*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"
19207        );
19208        // The sum of nothing is zero rather than not a number.
19209        assert_eq!(
19210            f.run(&[
19211                b"TS.RANGE",
19212                b"g",
19213                b"-",
19214                b"+",
19215                b"AGGREGATION",
19216                b"sum",
19217                b"100",
19218                b"EMPTY"
19219            ]),
19220            "*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\
19221             *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\
19222             *2\r\n:600\r\n+3\r\n"
19223        );
19224        // Buckets 200 through 400 have no readings at all and carry the reading
19225        // before the gap either way round. Bucket 500 has a reading that is not
19226        // a number, so it carries whatever the bucket before it in the reading
19227        // direction answered, which is 2 forwards and 3 backwards.
19228        assert_eq!(
19229            f.run(&[
19230                b"TS.RANGE",
19231                b"g",
19232                b"-",
19233                b"+",
19234                b"AGGREGATION",
19235                b"last",
19236                b"100",
19237                b"EMPTY"
19238            ]),
19239            "*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\
19240             *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\
19241             *2\r\n:600\r\n+3\r\n"
19242        );
19243        assert_eq!(
19244            f.run(&[
19245                b"TS.REVRANGE",
19246                b"g",
19247                b"-",
19248                b"+",
19249                b"AGGREGATION",
19250                b"last",
19251                b"100",
19252                b"EMPTY"
19253            ]),
19254            "*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\
19255             *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\
19256             *2\r\n:0\r\n+1\r\n"
19257        );
19258        // And a window that opens on that bucket has nothing in range before it
19259        // to carry, so it answers not a number.
19260        assert_eq!(
19261            f.run(&[
19262                b"TS.RANGE",
19263                b"g",
19264                b"500",
19265                b"600",
19266                b"AGGREGATION",
19267                b"last",
19268                b"100",
19269                b"EMPTY"
19270            ]),
19271            "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
19272        );
19273    }
19274
19275    /// The sentences a read answers when its options do not add up, which are
19276    /// the module's own word for word.
19277    #[test]
19278    fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
19279        let mut f = Fixture::new();
19280        f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
19281        f.run(&[b"SET", b"str", b"x"]);
19282        let cases: &[(&[&[u8]], &str)] = &[
19283            (
19284                &[b"TS.RANGE", b"t"],
19285                "-ERR wrong number of arguments for 'ts.range' command\r\n",
19286            ),
19287            // The key is resolved before a single option is read.
19288            (
19289                &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
19290                "-ERR TSDB: the key does not exist\r\n",
19291            ),
19292            (
19293                &[b"TS.RANGE", b"str", b"-", b"+"],
19294                "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
19295            ),
19296            (
19297                &[b"TS.RANGE", b"t", b"abc", b"+"],
19298                "-ERR TSDB: wrong fromTimestamp\r\n",
19299            ),
19300            (
19301                &[b"TS.RANGE", b"t", b"-", b"abc"],
19302                "-ERR TSDB: wrong toTimestamp\r\n",
19303            ),
19304            (
19305                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
19306                "-ERR TSDB: COUNT argument is missing\r\n",
19307            ),
19308            (
19309                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
19310                "-ERR TSDB: Couldn't parse COUNT\r\n",
19311            ),
19312            (
19313                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
19314                "-ERR TSDB: Invalid COUNT value\r\n",
19315            ),
19316            (
19317                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
19318                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19319            ),
19320            (
19321                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
19322                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19323            ),
19324            (
19325                &[
19326                    b"TS.RANGE",
19327                    b"t",
19328                    b"-",
19329                    b"+",
19330                    b"AGGREGATION",
19331                    b"nope",
19332                    b"100",
19333                ],
19334                "-ERR TSDB: Unknown aggregation type\r\n",
19335            ),
19336            (
19337                &[
19338                    b"TS.RANGE",
19339                    b"t",
19340                    b"-",
19341                    b"+",
19342                    b"AGGREGATION",
19343                    b"avg,,min",
19344                    b"100",
19345                ],
19346                "-ERR TSDB: Empty aggregation type in list\r\n",
19347            ),
19348            // The list of names is read before the width is looked at.
19349            (
19350                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
19351                "-ERR TSDB: Unknown aggregation type\r\n",
19352            ),
19353            (
19354                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
19355                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19356            ),
19357            (
19358                &[
19359                    b"TS.RANGE",
19360                    b"t",
19361                    b"-",
19362                    b"+",
19363                    b"AGGREGATION",
19364                    b"avg",
19365                    b"100",
19366                    b"X",
19367                    b"EMPTY",
19368                ],
19369                "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
19370            ),
19371            (
19372                &[
19373                    b"TS.RANGE",
19374                    b"t",
19375                    b"-",
19376                    b"+",
19377                    b"AGGREGATION",
19378                    b"avg",
19379                    b"100",
19380                    b"BUCKETTIMESTAMP",
19381                    b"z",
19382                ],
19383                "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
19384            ),
19385            (
19386                &[
19387                    b"TS.RANGE",
19388                    b"t",
19389                    b"-",
19390                    b"+",
19391                    b"AGGREGATION",
19392                    b"avg",
19393                    b"100",
19394                    b"X",
19395                    b"Y",
19396                    b"BUCKETTIMESTAMP",
19397                    b"-",
19398                ],
19399                "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
19400                 AGGREGATION flag\r\n",
19401            ),
19402            (
19403                &[
19404                    b"TS.RANGE",
19405                    b"t",
19406                    b"-",
19407                    b"+",
19408                    b"ALIGN",
19409                    b"z",
19410                    b"AGGREGATION",
19411                    b"avg",
19412                    b"100",
19413                ],
19414                "-ERR TSDB: unknown ALIGN parameter\r\n",
19415            ),
19416            (
19417                &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
19418                "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
19419            ),
19420            (
19421                &[
19422                    b"TS.RANGE",
19423                    b"t",
19424                    b"-",
19425                    b"+",
19426                    b"ALIGN",
19427                    b"-",
19428                    b"AGGREGATION",
19429                    b"avg",
19430                    b"100",
19431                ],
19432                "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
19433            ),
19434            (
19435                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
19436                "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
19437            ),
19438            (
19439                &[
19440                    b"TS.RANGE",
19441                    b"t",
19442                    b"-",
19443                    b"+",
19444                    b"FILTER_BY_VALUE",
19445                    b"x",
19446                    b"2",
19447                ],
19448                "-ERR TSDB: Couldn't parse MIN\r\n",
19449            ),
19450            (
19451                &[
19452                    b"TS.RANGE",
19453                    b"t",
19454                    b"-",
19455                    b"+",
19456                    b"FILTER_BY_VALUE",
19457                    b"1",
19458                    b"y",
19459                ],
19460                "-ERR TSDB: Couldn't parse MAX\r\n",
19461            ),
19462            (
19463                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
19464                "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
19465            ),
19466        ];
19467        for (argv, want) in cases {
19468            let got = f.run(argv);
19469            assert_eq!(&got, want, "{:?}", argv.last());
19470        }
19471        // The one sentence here that is yo's own rather than the module's, which
19472        // is D-54. A read that would build more rows than yo will build is
19473        // refused instead of attempted.
19474        f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
19475        f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
19476        assert_eq!(
19477            f.run(&[
19478                b"TS.RANGE",
19479                b"wide",
19480                b"-",
19481                b"+",
19482                b"AGGREGATION",
19483                b"avg",
19484                b"1",
19485                b"EMPTY"
19486            ]),
19487            "-ERR TSDB: the requested range holds too many empty buckets\r\n"
19488        );
19489    }
19490
19491    /// What RESP3 changes on a read, which is only how a number is written.
19492    #[test]
19493    fn resp3_writes_a_read_value_as_a_double() {
19494        let mut f = Fixture::new();
19495        f.out = Out::new(Proto::Resp3);
19496        for (at, v) in [
19497            (b"0".as_slice(), b"1".as_slice()),
19498            (b"100", b"2"),
19499            (b"500", b"nan"),
19500            (b"600", b"3"),
19501        ] {
19502            f.run(&[b"TS.ADD", b"g", at, v]);
19503        }
19504        assert_eq!(
19505            f.run(&[
19506                b"TS.RANGE",
19507                b"g",
19508                b"0",
19509                b"100",
19510                b"AGGREGATION",
19511                b"avg,min",
19512                b"200"
19513            ]),
19514            "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
19515        );
19516        assert_eq!(
19517            f.run(&[
19518                b"TS.RANGE",
19519                b"g",
19520                b"500",
19521                b"600",
19522                b"AGGREGATION",
19523                b"last",
19524                b"100",
19525                b"EMPTY"
19526            ]),
19527            "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
19528        );
19529    }
19530
19531    /// Two series with an overlap and a gap each, plus a third holding nothing,
19532    /// which is what the joined reads are measured against.
19533    fn joined() -> Fixture {
19534        let mut f = Fixture::new();
19535        f.run(&[b"TS.CREATE", b"z"]);
19536        for (at, v) in [
19537            (b"10".as_slice(), b"1".as_slice()),
19538            (b"20", b"2"),
19539            (b"40", b"4"),
19540            (b"50", b"5"),
19541        ] {
19542            f.run(&[b"TS.ADD", b"x", at, v]);
19543        }
19544        for (at, v) in [
19545            (b"20".as_slice(), b"20".as_slice()),
19546            (b"30", b"30"),
19547            (b"50", b"50"),
19548            (b"60", b"60"),
19549        ] {
19550            f.run(&[b"TS.ADD", b"y", at, v]);
19551        }
19552        f
19553    }
19554
19555    /// The joined read lines its keys up on the timestamp and writes a row as
19556    /// the timestamp and then a nested array of the columns, which is the one
19557    /// shape in the family that is not the flat pair.
19558    #[test]
19559    fn an_nrange_joins_its_keys_on_the_timestamp() {
19560        let mut f = joined();
19561        // One key still nests, so the shape does not depend on the count.
19562        assert_eq!(
19563            f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
19564            "*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\
19565             *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
19566        );
19567        // A key with no reading where another key has one writes NaN there.
19568        assert_eq!(
19569            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
19570            "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
19571             *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19572             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19573             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19574             *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
19575             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19576        );
19577        // A series holding nothing is a column of NaN and never a row of its
19578        // own, and the same key twice answers twice.
19579        assert_eq!(
19580            f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
19581            "*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"
19582        );
19583        assert_eq!(
19584            f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
19585            "*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"
19586        );
19587        // COUNT is applied to the joined rows and not to each key, so backwards
19588        // it gives the newest joined row rather than the newest of each.
19589        assert_eq!(
19590            f.run(&[
19591                b"TS.NREVRANGE",
19592                b"2",
19593                b"x",
19594                b"y",
19595                b"-",
19596                b"+",
19597                b"COUNT",
19598                b"1"
19599            ]),
19600            "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19601        );
19602        assert_eq!(
19603            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
19604            "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
19605        );
19606        // The two sample filters are settled a key at a time, before the join.
19607        assert_eq!(
19608            f.run(&[
19609                b"TS.NRANGE",
19610                b"2",
19611                b"x",
19612                b"y",
19613                b"-",
19614                b"+",
19615                b"FILTER_BY_VALUE",
19616                b"2",
19617                b"30"
19618            ]),
19619            "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19620             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19621             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19622             *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
19623        );
19624    }
19625
19626    /// The aggregation on a joined read names one reduction a key and then the
19627    /// one bucket width, and each name may be a comma list, so a row can be
19628    /// wider than the key count.
19629    #[test]
19630    fn an_nrange_aggregation_names_one_reduction_a_key() {
19631        let mut f = joined();
19632        assert_eq!(
19633            f.run(&[
19634                b"TS.NRANGE",
19635                b"2",
19636                b"x",
19637                b"y",
19638                b"-",
19639                b"+",
19640                b"AGGREGATION",
19641                b"sum",
19642                b"sum",
19643                b"20"
19644            ]),
19645            "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
19646             *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
19647             *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
19648             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19649        );
19650        // A comma list on the first key widens the row to three columns.
19651        assert_eq!(
19652            f.run(&[
19653                b"TS.NRANGE",
19654                b"2",
19655                b"x",
19656                b"y",
19657                b"-",
19658                b"+",
19659                b"AGGREGATION",
19660                b"sum,count",
19661                b"avg",
19662                b"20"
19663            ]),
19664            "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
19665             *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
19666             *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
19667             *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
19668        );
19669        // Everything behind the width moves along with it, so BUCKETTIMESTAMP
19670        // sits one or two past the width whatever the key count is.
19671        assert_eq!(
19672            f.run(&[
19673                b"TS.NRANGE",
19674                b"2",
19675                b"x",
19676                b"y",
19677                b"-",
19678                b"+",
19679                b"AGGREGATION",
19680                b"avg",
19681                b"sum",
19682                b"100",
19683                b"EMPTY",
19684                b"BUCKETTIMESTAMP",
19685                b"end"
19686            ]),
19687            "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
19688        );
19689        // A COUNT landing in one of the name slots is a reduction name and not
19690        // the keyword, and the read then has no count at all.
19691        assert_eq!(
19692            f.run(&[
19693                b"TS.NRANGE",
19694                b"2",
19695                b"x",
19696                b"y",
19697                b"-",
19698                b"+",
19699                b"AGGREGATION",
19700                b"avg",
19701                b"COUNT",
19702                b"100"
19703            ]),
19704            "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
19705        );
19706    }
19707
19708    /// The sentences a joined read answers when it does not add up, which are
19709    /// the module's own and come out in the module's own order.
19710    #[test]
19711    fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
19712        let mut f = joined();
19713        f.run(&[b"SET", b"str", b"hi"]);
19714        let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
19715        let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
19716                       must be equal to numkeys\r\n";
19717        let cases: &[(&[&[u8]], &str)] = &[
19718            (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
19719            (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
19720            (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
19721            // Not enough words behind the count for the keys and both ends of
19722            // the span, which is an arity error however many keys were named.
19723            (
19724                &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
19725                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19726            ),
19727            (
19728                &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
19729                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19730            ),
19731            // The reduction names are read before the two ends of the span,
19732            // which no other option is.
19733            (
19734                &[
19735                    b"TS.NRANGE",
19736                    b"2",
19737                    b"x",
19738                    b"y",
19739                    b"abc",
19740                    b"+",
19741                    b"AGGREGATION",
19742                    b"nope",
19743                    b"sum",
19744                    b"100",
19745                ],
19746                "-ERR TSDB: Unknown aggregation type\r\n",
19747            ),
19748            (
19749                &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
19750                "-ERR TSDB: wrong fromTimestamp\r\n",
19751            ),
19752            (
19753                &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
19754                "-ERR TSDB: wrong toTimestamp\r\n",
19755            ),
19756            // A name slot that is missing or holds a number is the count
19757            // sentence, and a width slot that is itself a reduction name is
19758            // that sentence as well.
19759            (
19760                &[
19761                    b"TS.NRANGE",
19762                    b"2",
19763                    b"x",
19764                    b"y",
19765                    b"-",
19766                    b"+",
19767                    b"AGGREGATION",
19768                    b"avg",
19769                ],
19770                numkeys,
19771            ),
19772            (
19773                &[
19774                    b"TS.NRANGE",
19775                    b"2",
19776                    b"x",
19777                    b"y",
19778                    b"-",
19779                    b"+",
19780                    b"AGGREGATION",
19781                    b"100",
19782                    b"sum",
19783                    b"100",
19784                ],
19785                numkeys,
19786            ),
19787            (
19788                &[
19789                    b"TS.NRANGE",
19790                    b"2",
19791                    b"x",
19792                    b"y",
19793                    b"-",
19794                    b"+",
19795                    b"AGGREGATION",
19796                    b"avg",
19797                    b"sum",
19798                    b"sum",
19799                    b"100",
19800                ],
19801                numkeys,
19802            ),
19803            (
19804                &[
19805                    b"TS.NRANGE",
19806                    b"2",
19807                    b"x",
19808                    b"y",
19809                    b"-",
19810                    b"+",
19811                    b"AGGREGATION",
19812                    b"avg",
19813                    b"sum",
19814                    b"abc",
19815                ],
19816                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19817            ),
19818            (
19819                &[
19820                    b"TS.NRANGE",
19821                    b"2",
19822                    b"x",
19823                    b"y",
19824                    b"-",
19825                    b"+",
19826                    b"AGGREGATION",
19827                    b"avg",
19828                    b"sum",
19829                    b"0",
19830                ],
19831                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19832            ),
19833            // With one key none of that applies and the plain parser runs, so a
19834            // lone width is a missing width rather than a count mismatch.
19835            (
19836                &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
19837                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19838            ),
19839            (
19840                &[
19841                    b"TS.NRANGE",
19842                    b"1",
19843                    b"x",
19844                    b"-",
19845                    b"+",
19846                    b"AGGREGATION",
19847                    b"100",
19848                    b"200",
19849                ],
19850                "-ERR TSDB: Unknown aggregation type\r\n",
19851            ),
19852            // The keys come last and in the order they were named.
19853            (
19854                &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
19855                "-ERR TSDB: the key does not exist\r\n",
19856            ),
19857            (
19858                &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
19859                "-ERR WRONGTYPE Operation against a key \
19860                 holding the wrong kind of value\r\n",
19861            ),
19862        ];
19863        for (argv, want) in cases {
19864            let got = f.run(argv);
19865            assert_eq!(&got, want, "{argv:?}");
19866        }
19867    }
19868
19869    /// `TS.READ`, which is a key, one timestamp and everything from there on.
19870    #[test]
19871    fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
19872        let mut f = joined();
19873        assert_eq!(
19874            f.run(&[b"TS.READ", b"x", b"-"]),
19875            "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
19876             *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19877        );
19878        // A plus is the last sample on its own, and a timestamp between two
19879        // samples starts at the one behind it.
19880        assert_eq!(
19881            f.run(&[b"TS.READ", b"x", b"+"]),
19882            "*1\r\n*2\r\n:50\r\n+5\r\n"
19883        );
19884        assert_eq!(
19885            f.run(&[b"TS.READ", b"x", b"25"]),
19886            "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19887        );
19888        // Past the end, a series holding nothing and a key that is not there
19889        // are all the empty array rather than an error.
19890        assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
19891        assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
19892        assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
19893        assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
19894        // The timestamp refusal goes out with nothing in front of it, and a key
19895        // holding something else answers the bare WRONGTYPE rather than the
19896        // module's prefixed one, both unlike the rest of the family.
19897        assert_eq!(
19898            f.run(&[b"TS.READ", b"x", b"abc"]),
19899            "-TSDB: invalid timestamp\r\n"
19900        );
19901        assert_eq!(
19902            f.run(&[b"TS.READ", b"x", b"-1"]),
19903            "-TSDB: invalid timestamp\r\n"
19904        );
19905        f.run(&[b"SET", b"str", b"hi"]);
19906        assert_eq!(
19907            f.run(&[b"TS.READ", b"str", b"-"]),
19908            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19909        );
19910        // Anything other than exactly three words is an arity error, so there
19911        // is nowhere to put an option even though the table says minus three.
19912        assert_eq!(
19913            f.run(&[b"TS.READ", b"x"]),
19914            "-ERR wrong number of arguments for 'ts.read' command\r\n"
19915        );
19916        assert_eq!(
19917            f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
19918            "-ERR wrong number of arguments for 'ts.read' command\r\n"
19919        );
19920    }
19921
19922    /// The keys of a joined read sit behind a count, so `COMMAND GETKEYS` has
19923    /// to read the count to find them.
19924    #[test]
19925    fn getkeys_reads_the_count_of_a_joined_read() {
19926        let mut f = Fixture::new();
19927        assert_eq!(
19928            f.run(&[
19929                b"COMMAND",
19930                b"GETKEYS",
19931                b"TS.NRANGE",
19932                b"2",
19933                b"a",
19934                b"b",
19935                b"-",
19936                b"+"
19937            ]),
19938            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
19939        );
19940        assert_eq!(
19941            f.run(&[
19942                b"COMMAND",
19943                b"GETKEYS",
19944                b"TS.NREVRANGE",
19945                b"1",
19946                b"a",
19947                b"-",
19948                b"+"
19949            ]),
19950            "*1\r\n$1\r\na\r\n"
19951        );
19952        // A count of zero, or one too large for the words that follow it, is
19953        // the server's own refusal and not the module's.
19954        for n in [b"0".as_slice(), b"9", b"abc"] {
19955            assert_eq!(
19956                f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
19957                "-ERR Invalid arguments specified for command\r\n"
19958            );
19959        }
19960    }
19961
19962    /// The five series every test of the label surface works against.
19963    fn labelled() -> Fixture {
19964        let mut f = Fixture::new();
19965        f.run(&[
19966            b"TS.CREATE",
19967            b"a",
19968            b"LABELS",
19969            b"room",
19970            b"kitchen",
19971            b"x",
19972            b"1",
19973        ]);
19974        f.run(&[
19975            b"TS.CREATE",
19976            b"b",
19977            b"LABELS",
19978            b"room",
19979            b"bedroom",
19980            b"x",
19981            b"2",
19982        ]);
19983        f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
19984        f.run(&[b"TS.CREATE", b"d"]);
19985        f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
19986        f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
19987        f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
19988        f
19989    }
19990
19991    /// The filter grammar, which is four steps and a `strtok` rather than a
19992    /// grammar, and which every command that searches on labels shares.
19993    #[test]
19994    fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
19995        let mut f = labelled();
19996        let cases: &[(&[&[u8]], &str)] = &[
19997            // The plain forms, and the order the answer comes back in, which is
19998            // by key name and not by anything the series remembers.
19999            (
20000                &[b"TS.QUERYINDEX", b"room=kitchen"],
20001                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
20002            ),
20003            (
20004                &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
20005                "*1\r\n$1\r\na\r\n",
20006            ),
20007            (
20008                &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
20009                "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
20010            ),
20011            // An empty list still counts as something that says which series to
20012            // take, it just never takes any.
20013            (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
20014            // Absent and present, neither of which stands on its own.
20015            (
20016                &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
20017                "*1\r\n$1\r\nc\r\n",
20018            ),
20019            (
20020                &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
20021                "*1\r\n$1\r\na\r\n",
20022            ),
20023            (
20024                &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
20025                "-ERR TSDB: please provide at least one matcher\r\n",
20026            ),
20027            // A run of separators is one separator and everything past the
20028            // second field is dropped, so all three of these ask one question.
20029            (
20030                &[b"TS.QUERYINDEX", b"room==kitchen"],
20031                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
20032            ),
20033            (
20034                &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
20035                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
20036            ),
20037            (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
20038            // A bracket is only a list when it sits straight behind the
20039            // separator, and then the label in front of it has to be there.
20040            (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
20041            (
20042                &[b"TS.QUERYINDEX", b"=(1)"],
20043                "-ERR TSDB: failed parsing labels\r\n",
20044            ),
20045            (
20046                &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
20047                "-ERR TSDB: failed parsing labels\r\n",
20048            ),
20049            (
20050                &[b"TS.QUERYINDEX", b"room=(kitchen"],
20051                "-ERR TSDB: failed parsing labels\r\n",
20052            ),
20053            (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
20054            (
20055                &[b"TS.QUERYINDEX", b"nonsense"],
20056                "-ERR TSDB: failed parsing labels\r\n",
20057            ),
20058            // Nothing here says which series to take.
20059            (
20060                &[b"TS.QUERYINDEX", b"room!=kitchen"],
20061                "-ERR TSDB: please provide at least one matcher\r\n",
20062            ),
20063            // Names and values are both compared byte for byte.
20064            (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
20065            (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
20066            (
20067                &[b"TS.QUERYINDEX"],
20068                "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
20069            ),
20070        ];
20071        for (argv, want) in cases {
20072            let got = f.run(argv);
20073            assert_eq!(&got, want, "{:?}", argv.last());
20074        }
20075    }
20076
20077    /// `TS.QUERYLABELS`, whose filter is the one that is allowed to be missing.
20078    #[test]
20079    fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
20080        let mut f = labelled();
20081        let cases: &[(&[&[u8]], &str)] = &[
20082            (
20083                &[b"TS.QUERYLABELS", b"LABELS"],
20084                "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
20085            ),
20086            (
20087                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
20088                "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
20089            ),
20090            (
20091                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
20092                "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
20093            ),
20094            // The series wearing `r` twice contributes the smaller of the two
20095            // here, which is not the one it was written down as first.
20096            (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
20097            (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
20098            (
20099                &[b"TS.QUERYLABELS", b"VALUES"],
20100                "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
20101            ),
20102            (
20103                &[b"TS.QUERYLABELS", b"ZZZ"],
20104                "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
20105            ),
20106            (
20107                &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
20108                "-ERR TSDB: unknown argument, expected FILTER\r\n",
20109            ),
20110            (
20111                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
20112                "-ERR TSDB: FILTER given with no filter expressions\r\n",
20113            ),
20114            // With no filter at all every series is taken, which is why the
20115            // first case here answers about `r` as well. A filter that is there
20116            // still has to say which series to take.
20117            (
20118                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
20119                "-ERR TSDB: please provide at least one matcher\r\n",
20120            ),
20121            (
20122                &[
20123                    b"TS.QUERYLABELS",
20124                    b"LABELS",
20125                    b"FILTER",
20126                    b"room=kitchen",
20127                    b"x=",
20128                ],
20129                "*1\r\n$4\r\nroom\r\n",
20130            ),
20131        ];
20132        for (argv, want) in cases {
20133            let got = f.run(argv);
20134            assert_eq!(&got, want, "{:?}", argv.last());
20135        }
20136    }
20137
20138    /// `TS.MGET`, the newest sample of every series a filter takes, and the two
20139    /// ways of asking for the labels back alongside it.
20140    #[test]
20141    fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
20142        let mut f = labelled();
20143        let cases: &[(&[&[u8]], &str)] = &[
20144            // A series with no samples writes an empty array where the sample
20145            // goes rather than dropping out of the reply.
20146            (
20147                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
20148                "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
20149                 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
20150            ),
20151            (
20152                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
20153                "*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\
20154                 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
20155                 *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",
20156            ),
20157            // A selected label the series does not wear is a nil, not a gap.
20158            (
20159                &[
20160                    b"TS.MGET",
20161                    b"SELECTED_LABELS",
20162                    b"x",
20163                    b"FILTER",
20164                    b"room=kitchen",
20165                ],
20166                "*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\
20167                 *2\r\n:100\r\n+1.5\r\n\
20168                 *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",
20169            ),
20170            // The other half of the duplicated name rule. This one takes the
20171            // first written down where `TS.QUERYLABELS` takes the smallest.
20172            (
20173                &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
20174                "*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",
20175            ),
20176            (
20177                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
20178                "*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\
20179                 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
20180            ),
20181            // A word that is not an option is ignored, but a missing `FILTER`
20182            // is an arity error whatever else was written.
20183            (
20184                &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
20185                "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
20186            ),
20187            (
20188                &[b"TS.MGET", b"a", b"b", b"c"],
20189                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
20190            ),
20191            (
20192                &[b"TS.MGET", b"FILTER"],
20193                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
20194            ),
20195            // Both keyword checks happen before the filter is read, and the two
20196            // sentences spell the second keyword without its `ED`.
20197            (
20198                &[
20199                    b"TS.MGET",
20200                    b"WITHLABELS",
20201                    b"SELECTED_LABELS",
20202                    b"x",
20203                    b"FILTER",
20204                    b"bad",
20205                ],
20206                "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
20207            ),
20208            (
20209                &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
20210                "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
20211            ),
20212        ];
20213        for (argv, want) in cases {
20214            let got = f.run(argv);
20215            assert_eq!(&got, want, "{:?}", argv.last());
20216        }
20217    }
20218
20219    /// What RESP3 changes across the label surface, which is a set where there
20220    /// was an array and a map where there was a pair of them.
20221    #[test]
20222    fn resp3_writes_the_label_surface_as_sets_and_maps() {
20223        let mut f = labelled();
20224        f.out = Out::new(Proto::Resp3);
20225        let cases: &[(&[&[u8]], &str)] = &[
20226            (
20227                &[b"TS.QUERYINDEX", b"room=kitchen"],
20228                "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
20229            ),
20230            (
20231                &[b"TS.QUERYLABELS", b"LABELS"],
20232                "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
20233            ),
20234            (
20235                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
20236                "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
20237            ),
20238            // The key stops being the first of three and becomes the map key,
20239            // and the labels stop being pairs and become a map of their own.
20240            (
20241                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
20242                "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
20243                 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
20244            ),
20245            (
20246                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
20247                "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
20248                 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
20249                 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
20250            ),
20251            (
20252                &[
20253                    b"TS.MGET",
20254                    b"SELECTED_LABELS",
20255                    b"x",
20256                    b"FILTER",
20257                    b"room=kitchen",
20258                ],
20259                "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
20260                 *2\r\n:100\r\n,1.5\r\n\
20261                 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
20262            ),
20263            // A map with a name in it twice, which is what a series wearing one
20264            // label name twice turns into.
20265            (
20266                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
20267                "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
20268                 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
20269            ),
20270        ];
20271        for (argv, want) in cases {
20272            let got = f.run(argv);
20273            assert_eq!(&got, want, "{:?}", argv.last());
20274        }
20275    }
20276
20277    /// The same five series with enough samples in them for a group to have
20278    /// something to fold.
20279    fn spanned() -> Fixture {
20280        let mut f = labelled();
20281        f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
20282        f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
20283        f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
20284        f
20285    }
20286
20287    /// A span read out of every series a filter takes, with and without a group
20288    /// over the top of it.
20289    #[test]
20290    fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
20291        let mut f = spanned();
20292        let cases: &[(&[&[u8]], &str)] = &[
20293            (
20294                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
20295                "*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\
20296                 *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",
20297            ),
20298            // Newest first is applied to each series before anything else sees
20299            // the rows.
20300            (
20301                &[
20302                    b"TS.MREVRANGE",
20303                    b"-",
20304                    b"+",
20305                    b"WITHLABELS",
20306                    b"FILTER",
20307                    b"room=kitchen",
20308                ],
20309                "*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\
20310                 *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\
20311                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
20312                 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
20313            ),
20314            // A label a series does not wear comes back against a nil rather
20315            // than being left out.
20316            (
20317                &[
20318                    b"TS.MRANGE",
20319                    b"-",
20320                    b"+",
20321                    b"SELECTED_LABELS",
20322                    b"x",
20323                    b"FILTER",
20324                    b"room=kitchen",
20325                ],
20326                "*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\
20327                 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
20328                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
20329                 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
20330            ),
20331            // The fold: 100 is in both series and adds up, the other two are in
20332            // one each and are still rows.
20333            (
20334                &[
20335                    b"TS.MRANGE",
20336                    b"-",
20337                    b"+",
20338                    b"FILTER",
20339                    b"room=kitchen",
20340                    b"GROUPBY",
20341                    b"room",
20342                    b"REDUCE",
20343                    b"sum",
20344                ],
20345                "*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\
20346                 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
20347            ),
20348            // RESP2 has nowhere to put the reducer and the member keys, so a
20349            // group wearing labels writes them as two more labels.
20350            (
20351                &[
20352                    b"TS.MRANGE",
20353                    b"-",
20354                    b"+",
20355                    b"WITHLABELS",
20356                    b"FILTER",
20357                    b"room=kitchen",
20358                    b"GROUPBY",
20359                    b"room",
20360                    b"REDUCE",
20361                    b"max",
20362                ],
20363                "*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\
20364                 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
20365                 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
20366                 *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",
20367            ),
20368            // A count is applied to each member and then again to the fold.
20369            (
20370                &[
20371                    b"TS.MREVRANGE",
20372                    b"-",
20373                    b"+",
20374                    b"COUNT",
20375                    b"1",
20376                    b"FILTER",
20377                    b"room=kitchen",
20378                    b"GROUPBY",
20379                    b"room",
20380                    b"REDUCE",
20381                    b"count",
20382                ],
20383                "*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",
20384            ),
20385            // Nothing wears the label, so nothing is in any group.
20386            (
20387                &[
20388                    b"TS.MRANGE",
20389                    b"-",
20390                    b"+",
20391                    b"FILTER",
20392                    b"room=kitchen",
20393                    b"GROUPBY",
20394                    b"nope",
20395                    b"REDUCE",
20396                    b"sum",
20397                ],
20398                "*0\r\n",
20399            ),
20400            (
20401                &[
20402                    b"TS.MRANGE",
20403                    b"-",
20404                    b"+",
20405                    b"AGGREGATION",
20406                    b"sum,avg",
20407                    b"100",
20408                    b"FILTER",
20409                    b"room=bedroom",
20410                ],
20411                "*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",
20412            ),
20413            // The errors, in the order they are looked for.
20414            (
20415                &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
20416                "-ERR TSDB: missing FILTER argument\r\n",
20417            ),
20418            (
20419                &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
20420                "-ERR TSDB: missing labels for filter argument\r\n",
20421            ),
20422            (
20423                &[
20424                    b"TS.MRANGE",
20425                    b"-",
20426                    b"+",
20427                    b"GROUPBY",
20428                    b"room",
20429                    b"REDUCE",
20430                    b"sum",
20431                    b"FILTER",
20432                    b"room=kitchen",
20433                ],
20434                "-ERR TSDB: GROUPBY should always come after filter\r\n",
20435            ),
20436            // The group is four words from the end here, so the length is what
20437            // is wrong with it.
20438            (
20439                &[
20440                    b"TS.MRANGE",
20441                    b"-",
20442                    b"+",
20443                    b"FILTER",
20444                    b"room=kitchen",
20445                    b"GROUPBY",
20446                    b"room",
20447                    b"REDUCE",
20448                    b"sum",
20449                    b"x",
20450                ],
20451                "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
20452            ),
20453            // And here it is not, so its words are filters and answer first.
20454            (
20455                &[
20456                    b"TS.MRANGE",
20457                    b"-",
20458                    b"+",
20459                    b"FILTER",
20460                    b"nope",
20461                    b"GROUPBY",
20462                    b"room",
20463                    b"REDUCE",
20464                    b"sum",
20465                    b"x",
20466                ],
20467                "-ERR TSDB: failed parsing labels\r\n",
20468            ),
20469            (
20470                &[
20471                    b"TS.MRANGE",
20472                    b"-",
20473                    b"+",
20474                    b"FILTER",
20475                    b"room=kitchen",
20476                    b"GROUPBY",
20477                    b"room",
20478                    b"REDUCE",
20479                    b"twa",
20480                ],
20481                "-ERR TSDB: Invalid reducer type\r\n",
20482            ),
20483            (
20484                &[
20485                    b"TS.MRANGE",
20486                    b"-",
20487                    b"+",
20488                    b"AGGREGATION",
20489                    b"sum,avg",
20490                    b"100",
20491                    b"FILTER",
20492                    b"room=kitchen",
20493                    b"GROUPBY",
20494                    b"room",
20495                    b"REDUCE",
20496                    b"sum",
20497                ],
20498                "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
20499            ),
20500            // The label list ends at a keyword, so this is a `COUNT` with a
20501            // `FILTER` where its number should be.
20502            (
20503                &[
20504                    b"TS.MRANGE",
20505                    b"-",
20506                    b"+",
20507                    b"SELECTED_LABELS",
20508                    b"COUNT",
20509                    b"FILTER",
20510                    b"room=kitchen",
20511                ],
20512                "-ERR TSDB: Couldn't parse COUNT\r\n",
20513            ),
20514        ];
20515        for (argv, want) in cases {
20516            let got = f.run(argv);
20517            assert_eq!(&got, want, "{argv:?}");
20518        }
20519    }
20520
20521    /// The multi key reads on RESP3, where the key becomes a map key and the
20522    /// reducer and the member keys become fields of their own.
20523    #[test]
20524    fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
20525        let mut f = spanned();
20526        f.out = Out::new(Proto::Resp3);
20527        let cases: &[(&[&[u8]], &str)] = &[
20528            (
20529                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
20530                "%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\
20531                 *1\r\n*2\r\n:200\r\n,2\r\n",
20532            ),
20533            // The reductions a read asked for, which RESP2 has no room for at
20534            // all and which is empty on a read that asked for none.
20535            (
20536                &[
20537                    b"TS.MRANGE",
20538                    b"-",
20539                    b"+",
20540                    b"AGGREGATION",
20541                    b"sum,avg",
20542                    b"100",
20543                    b"FILTER",
20544                    b"room=bedroom",
20545                ],
20546                "%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\
20547                 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
20548            ),
20549            (
20550                &[
20551                    b"TS.MRANGE",
20552                    b"-",
20553                    b"+",
20554                    b"FILTER",
20555                    b"room=kitchen",
20556                    b"GROUPBY",
20557                    b"room",
20558                    b"REDUCE",
20559                    b"sum",
20560                ],
20561                "%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\
20562                 $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\
20563                 *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",
20564            ),
20565            // The labels hold only the pair the group was made on, because the
20566            // reducer and the sources have somewhere else to go.
20567            (
20568                &[
20569                    b"TS.MRANGE",
20570                    b"-",
20571                    b"+",
20572                    b"WITHLABELS",
20573                    b"FILTER",
20574                    b"room=kitchen",
20575                    b"GROUPBY",
20576                    b"room",
20577                    b"REDUCE",
20578                    b"max",
20579                ],
20580                "%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\
20581                 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
20582                 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
20583                 *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",
20584            ),
20585            (
20586                &[
20587                    b"TS.MRANGE",
20588                    b"-",
20589                    b"+",
20590                    b"FILTER",
20591                    b"room=kitchen",
20592                    b"GROUPBY",
20593                    b"nope",
20594                    b"REDUCE",
20595                    b"sum",
20596                ],
20597                "%0\r\n",
20598            ),
20599        ];
20600        for (argv, want) in cases {
20601            let got = f.run(argv);
20602            assert_eq!(&got, want, "{argv:?}");
20603        }
20604    }
20605
20606    /// `TS.CREATERULE`, whose refusals come in an order of their own.
20607    #[test]
20608    fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
20609        let mut f = Fixture::new();
20610        f.run(&[b"TS.CREATE", b"src"]);
20611        f.run(&[b"TS.CREATE", b"dst"]);
20612        f.run(&[b"SET", b"plain", b"v"]);
20613        let cases: &[(&[&[u8]], &str)] = &[
20614            // The width is read before the reduction, the reduction before the
20615            // width being above zero, and all three before either key is looked
20616            // at, so a command that is wrong twice complains about the first.
20617            (
20618                &[
20619                    b"TS.CREATERULE",
20620                    b"src",
20621                    b"dst",
20622                    b"AGGREGATION",
20623                    b"nope",
20624                    b"x",
20625                ],
20626                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
20627            ),
20628            (
20629                &[
20630                    b"TS.CREATERULE",
20631                    b"src",
20632                    b"dst",
20633                    b"AGGREGATION",
20634                    b"nope",
20635                    b"10",
20636                ],
20637                "-ERR TSDB: Unknown aggregation type\r\n",
20638            ),
20639            (
20640                &[
20641                    b"TS.CREATERULE",
20642                    b"src",
20643                    b"dst",
20644                    b"AGGREGATION",
20645                    b"avg",
20646                    b"0",
20647                ],
20648                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
20649            ),
20650            (
20651                &[
20652                    b"TS.CREATERULE",
20653                    b"src",
20654                    b"dst",
20655                    b"AGGREGATION",
20656                    b"avg",
20657                    b"10",
20658                    b"x",
20659                ],
20660                "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
20661            ),
20662            (
20663                &[
20664                    b"TS.CREATERULE",
20665                    b"src",
20666                    b"src",
20667                    b"AGGREGATION",
20668                    b"avg",
20669                    b"10",
20670                ],
20671                "-ERR TSDB: the source key and destination key should be different\r\n",
20672            ),
20673            // A key holding something else answers the same as a key that is not
20674            // there at all, because the source is looked up first and neither of
20675            // them is a series.
20676            (
20677                &[
20678                    b"TS.CREATERULE",
20679                    b"nope",
20680                    b"plain",
20681                    b"AGGREGATION",
20682                    b"avg",
20683                    b"10",
20684                ],
20685                "-ERR TSDB: the key does not exist\r\n",
20686            ),
20687            (
20688                &[
20689                    b"TS.CREATERULE",
20690                    b"src",
20691                    b"nope",
20692                    b"AGGREGATION",
20693                    b"avg",
20694                    b"10",
20695                ],
20696                "-ERR TSDB: the key does not exist\r\n",
20697            ),
20698            // A keyword other than AGGREGATION is an arity error rather than a
20699            // syntax one, because the arity is all that is checked.
20700            (
20701                &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
20702                "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
20703            ),
20704            (
20705                &[
20706                    b"TS.CREATERULE",
20707                    b"src",
20708                    b"dst",
20709                    b"AGGREGATION",
20710                    b"avg",
20711                    b"10",
20712                ],
20713                "+OK\r\n",
20714            ),
20715            // The link is now in place, so the same rule again is refused from
20716            // the destination's end.
20717            (
20718                &[
20719                    b"TS.CREATERULE",
20720                    b"src",
20721                    b"dst",
20722                    b"AGGREGATION",
20723                    b"avg",
20724                    b"10",
20725                ],
20726                "-ERR TSDB: the destination key already has a src rule\r\n",
20727            ),
20728            // A source that is already someone's destination, and a destination
20729            // that is already someone's source, are two different sentences.
20730            (
20731                &[
20732                    b"TS.CREATERULE",
20733                    b"dst",
20734                    b"src",
20735                    b"AGGREGATION",
20736                    b"avg",
20737                    b"10",
20738                ],
20739                "-ERR TSDB: the source key already has a source rule\r\n",
20740            ),
20741            (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
20742            (
20743                &[b"TS.DELETERULE", b"src", b"dst"],
20744                "-ERR TSDB: compaction rule does not exist\r\n",
20745            ),
20746            // The source is looked up and the destination is not, so a missing
20747            // destination is a missing rule and a missing source is a missing
20748            // key, which is the other way round from `TS.CREATERULE`.
20749            (
20750                &[b"TS.DELETERULE", b"src", b"nope"],
20751                "-ERR TSDB: compaction rule does not exist\r\n",
20752            ),
20753            (
20754                &[b"TS.DELETERULE", b"nope", b"dst"],
20755                "-ERR TSDB: the key does not exist\r\n",
20756            ),
20757        ];
20758        for (argv, want) in cases {
20759            let got = f.run(argv);
20760            assert_eq!(&got, want, "{argv:?}");
20761        }
20762    }
20763
20764    /// What a rule writes, which is every bucket but the one it is filling.
20765    #[test]
20766    fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
20767        let mut f = Fixture::new();
20768        f.run(&[b"TS.CREATE", b"src"]);
20769        f.run(&[b"TS.CREATE", b"dst"]);
20770        // The readings written before the rule was made are not folded, so the
20771        // destination is still empty after the first two.
20772        f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
20773        f.run(&[
20774            b"TS.CREATERULE",
20775            b"src",
20776            b"dst",
20777            b"AGGREGATION",
20778            b"sum",
20779            b"100",
20780        ]);
20781        f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
20782        assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
20783        // The bucket the rule is filling holds only what it was given, so it is
20784        // 2 rather than 3, and it is written when a reading lands past it.
20785        assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
20786        f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
20787        assert_eq!(
20788            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20789            "*1\r\n*2\r\n:0\r\n+2\r\n"
20790        );
20791        // A reading into a bucket that has already been written works that
20792        // bucket out again over everything the source now holds.
20793        f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
20794        assert_eq!(
20795            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20796            "*1\r\n*2\r\n:0\r\n+11\r\n"
20797        );
20798        // Deleting from the source works the buckets it touched out again and
20799        // reopens the newest one, so `LATEST` starts from the whole bucket.
20800        assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
20801        assert_eq!(
20802            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20803            "*1\r\n*2\r\n:0\r\n+8\r\n"
20804        );
20805        assert_eq!(
20806            f.run(&[b"TS.GET", b"dst", b"LATEST"]),
20807            "*2\r\n:100\r\n+4\r\n"
20808        );
20809        // The link shows on both ends, and dropping either key takes it down.
20810        assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
20811        f.run(&[b"DEL", b"dst"]);
20812        assert_eq!(
20813            f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
20814            "-ERR TSDB: compaction rule does not exist\r\n"
20815        );
20816    }
20817
20818    /// The three shapes an `XADD` id can take, and the one rule behind all of
20819    /// them.
20820    #[test]
20821    fn xadd_ids_only_ever_go_up() {
20822        let mut f = Fixture::new();
20823        // A bare millisecond is that millisecond and sequence zero.
20824        assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
20825        // And `5-*` is the next free sequence inside it.
20826        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
20827        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
20828        assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
20829        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20830
20831        assert!(
20832            f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
20833                .contains("equal or smaller")
20834        );
20835        assert!(
20836            f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
20837                .contains("must be greater than 0-0")
20838        );
20839        assert!(
20840            f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
20841                .contains("Invalid stream ID")
20842        );
20843        // The pairs have to be pairs, and Redis calls an odd one an arity error
20844        // rather than a syntax error even though the table has already passed.
20845        assert!(
20846            f.run(&[b"XADD", b"s", b"*", b"a"])
20847                .contains("wrong number of arguments")
20848        );
20849
20850        // `NOMKSTREAM` on a key that is not there is a null and not a zero, so a
20851        // producer can tell nobody is consuming this yet from the write landed.
20852        assert_eq!(
20853            f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
20854            "$-1\r\n"
20855        );
20856        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20857        assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
20858        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
20859    }
20860
20861    /// The trim options, which are three keywords that disagree about how many
20862    /// arguments they take.
20863    #[test]
20864    fn trimming_reads_its_options_the_way_redis_does() {
20865        let mut f = Fixture::new();
20866        for i in 1..=10u32 {
20867            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20868        }
20869        assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
20870        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20871        assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
20872        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20873
20874        // One argument after the keyword and the `~` is read as the threshold,
20875        // which is what a real server does and is the reason this is a number
20876        // complaint and not a syntax one.
20877        assert!(
20878            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
20879                .contains("not an integer")
20880        );
20881        assert!(
20882            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
20883                .contains("MAXLEN argument must be >= 0")
20884        );
20885        // The strategy check runs before the approximation check, so a LIMIT
20886        // with neither is told about the missing strategy.
20887        assert!(
20888            f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
20889                .contains("without specifying a trimming strategy")
20890        );
20891        assert!(
20892            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
20893                .contains("without the special ~ option")
20894        );
20895        assert!(
20896            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
20897                .contains("at the same time are not compatible")
20898        );
20899        // NOMKSTREAM is XADD's and XTRIM does not take it.
20900        assert!(
20901            f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
20902                .contains("syntax error")
20903        );
20904        assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
20905    }
20906
20907    /// `XRANGE`, whose two kinds of nothing are the thing worth pinning.
20908    #[test]
20909    fn xrange_looks_the_key_up_before_it_reads_the_count() {
20910        let mut f = Fixture::new();
20911        f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
20912        f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
20913
20914        assert_eq!(
20915            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20916            "*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\
20917             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20918        );
20919        assert_eq!(
20920            f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
20921            "*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"
20922        );
20923        // The exclusive bound is stepped after the missing sequence is filled
20924        // in, so `(6` is `6-` and the largest sequence there is, minus one, and
20925        // `6-1` is still in the range.
20926        assert_eq!(
20927            f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
20928            "*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\
20929             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20930        );
20931        assert_eq!(
20932            f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
20933            "*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"
20934        );
20935        assert!(
20936            f.run(&[b"XRANGE", b"s", b"(-", b"+"])
20937                .contains("Invalid stream ID")
20938        );
20939
20940        // The two kinds of nothing. A key that is not there is an empty array
20941        // and a key that is there with a count of zero is a null array, because
20942        // the lookup happens first.
20943        assert_eq!(
20944            f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
20945            "*0\r\n"
20946        );
20947        assert_eq!(
20948            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
20949            "*-1\r\n"
20950        );
20951        f.run(&[b"SET", b"str", b"v"]);
20952        assert!(
20953            f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
20954                .starts_with("-WRONGTYPE")
20955        );
20956        // The count is read in a loop, so the last one wins.
20957        assert_eq!(
20958            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
20959            "*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"
20960        );
20961    }
20962
20963    /// `XDEL` and `XACK` check every id before they touch any of them.
20964    #[test]
20965    fn a_bad_id_late_in_the_list_stops_the_whole_command() {
20966        let mut f = Fixture::new();
20967        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20968        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20969        assert!(
20970            f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
20971                .contains("Invalid stream ID")
20972        );
20973        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20974        assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
20975        assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
20976        assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
20977        assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
20978    }
20979
20980    /// `XGROUP`, and the two different complaints it makes about arguments.
20981    #[test]
20982    fn xgroup_has_an_arity_per_subcommand() {
20983        let mut f = Fixture::new();
20984        assert!(
20985            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20986                .contains("requires the key")
20987        );
20988        assert_eq!(
20989            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
20990            "+OK\r\n"
20991        );
20992        // A second CREATE is BUSYGROUP and not an ordinary error, because a
20993        // client racing another one to make a group branches on the prefix.
20994        assert!(
20995            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20996                .starts_with("-BUSYGROUP")
20997        );
20998        assert_eq!(
20999            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
21000            ":1\r\n"
21001        );
21002        assert_eq!(
21003            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
21004            ":0\r\n"
21005        );
21006        assert_eq!(
21007            f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
21008            ":0\r\n"
21009        );
21010
21011        // Below the subcommand's own arity is an arity error naming the pair.
21012        let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
21013        assert!(
21014            short.contains("wrong number of arguments for 'xgroup|destroy' command"),
21015            "{short}"
21016        );
21017        // At or above it in a shape the handler will not take is the other one.
21018        let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
21019        assert!(
21020            odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
21021            "{odd}"
21022        );
21023        assert!(
21024            f.run(&[b"XGROUP", b"NOSUCH", b"s"])
21025                .contains("Try XGROUP HELP")
21026        );
21027
21028        assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
21029        assert!(
21030            f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
21031                .starts_with("-NOGROUP")
21032        );
21033        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
21034        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
21035        assert!(
21036            f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
21037                .contains("requires the key")
21038        );
21039    }
21040
21041    /// A group read, an acknowledgement, and what is left in between.
21042    #[test]
21043    fn xreadgroup_hands_out_and_xack_takes_back() {
21044        let mut f = Fixture::new();
21045        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21046        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21047        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21048
21049        let first = f.run(&[
21050            b"XREADGROUP",
21051            b"GROUP",
21052            b"g",
21053            b"c1",
21054            b"COUNT",
21055            b"1",
21056            b"STREAMS",
21057            b"s",
21058            b">",
21059        ]);
21060        assert_eq!(
21061            first,
21062            "*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"
21063        );
21064        // A history read names its stream even with nothing to show, which is
21065        // the difference between it and a `>` read that found nothing.
21066        assert_eq!(
21067            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
21068            "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
21069        );
21070        assert_eq!(
21071            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
21072            "*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"
21073        );
21074
21075        assert_eq!(
21076            f.run(&[b"XPENDING", b"s", b"g"]),
21077            "*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"
21078        );
21079        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
21080        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
21081        // Empty is four nulls and not a zero with three empty things.
21082        assert_eq!(
21083            f.run(&[b"XPENDING", b"s", b"g"]),
21084            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
21085        );
21086
21087        // A history read of an entry that has since been deleted is the id with
21088        // a null beside it, so the consumer can still acknowledge it.
21089        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21090        f.run(&[b"XDEL", b"s", b"2-1"]);
21091        assert_eq!(
21092            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
21093            "*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"
21094        );
21095
21096        // The group lookup runs before the id parse, so a `+` at a stream with
21097        // no such group is told about the group and not about the id.
21098        assert!(
21099            f.run(&[
21100                b"XREADGROUP",
21101                b"GROUP",
21102                b"nope",
21103                b"c",
21104                b"STREAMS",
21105                b"s",
21106                b"+"
21107            ])
21108            .starts_with("-NOGROUP")
21109        );
21110        assert!(
21111            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
21112                .contains("meaningless in the context of XREADGROUP")
21113        );
21114        assert!(
21115            f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
21116                .contains("only supported by XREADGROUP")
21117        );
21118        assert!(
21119            f.run(&[
21120                b"XREADGROUP",
21121                b"GROUP",
21122                b"g",
21123                b"c",
21124                b"STREAMS",
21125                b"s",
21126                b"a",
21127                b"b"
21128            ])
21129            .contains("Unbalanced 'xreadgroup' list of streams")
21130        );
21131    }
21132
21133    /// `XREAD` without `BLOCK`, which answers now and takes nothing for an
21134    /// answer.
21135    #[test]
21136    fn xread_with_no_block_writes_the_null_itself() {
21137        let mut f = Fixture::new();
21138        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21139        assert_eq!(
21140            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21141            "*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"
21142        );
21143        // Nothing new is a null array and not an empty one, and a stream with
21144        // nothing new is left out rather than sent with an empty list.
21145        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
21146        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
21147        f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
21148        assert_eq!(
21149            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
21150            "*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"
21151        );
21152        // `$` is the last id, so nothing that is already there comes back.
21153        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
21154        // And `+` is the last entry, whatever COUNT says.
21155        assert_eq!(
21156            f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
21157            "*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"
21158        );
21159        // A count of zero means unlimited here, which is the opposite of what it
21160        // means to XRANGE.
21161        assert_eq!(
21162            f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
21163            "*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"
21164        );
21165        // Milliseconds as a whole number, where BLPOP takes seconds as a float.
21166        assert!(
21167            f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
21168                .contains("not an integer")
21169        );
21170        assert!(
21171            f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
21172                .contains("timeout is negative")
21173        );
21174        assert!(
21175            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
21176                .contains("Unbalanced 'xread' list of streams")
21177        );
21178    }
21179
21180    /// A blocked reader, and the two ways it stops being blocked.
21181    #[test]
21182    fn a_blocked_xread_wakes_on_the_next_entry() {
21183        let mut f = Fixture::new();
21184        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21185        let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
21186        assert_eq!(flow, Flow::Block);
21187        assert!(reply.is_empty());
21188
21189        // Everybody parked on the stream gets the entry, because a read takes
21190        // nothing away. That is the difference between this and BLPOP. Two
21191        // clients rather than one twice, since a client that is waiting is not
21192        // reading and cannot block again.
21193        f.session = Session::new(8);
21194        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
21195        assert_eq!(flow, Flow::Block);
21196        assert_eq!(f.server.parked(), 2);
21197
21198        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21199        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";
21200        for client in [7, 8] {
21201            let mut out = Out::new(Proto::Resp2);
21202            assert!(f.server.serve_waiter(client, 0, &mut out));
21203            assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
21204        }
21205
21206        // And a deadline that runs out is a null array, the same as a plain
21207        // XREAD that found nothing.
21208        f.server.forget_waiters(7);
21209        f.server.forget_waiters(8);
21210        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
21211        assert_eq!(flow, Flow::Block);
21212        let mut out = Out::new(Proto::Resp2);
21213        assert!(!f.server.serve_waiter(8, 0, &mut out));
21214        assert!(out.as_slice().is_empty());
21215        assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
21216        assert_eq!(
21217            core::str::from_utf8(out.as_slice()).expect("ascii"),
21218            "*-1\r\n"
21219        );
21220    }
21221
21222    /// A blocked group reader whose group is destroyed under it.
21223    #[test]
21224    fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
21225        let mut f = Fixture::new();
21226        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21227        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
21228        let (flow, _) = f.flow(&[
21229            b"XREADGROUP",
21230            b"GROUP",
21231            b"g",
21232            b"c",
21233            b"BLOCK",
21234            b"0",
21235            b"STREAMS",
21236            b"s",
21237            b">",
21238        ]);
21239        assert_eq!(flow, Flow::Block);
21240
21241        f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
21242        let mut out = Out::new(Proto::Resp2);
21243        assert!(f.server.serve_waiter(7, 0, &mut out));
21244        // The ordinary sentence and not a special one about having been parked,
21245        // which is what a running 8.10 sends.
21246        assert_eq!(
21247            core::str::from_utf8(out.as_slice()).expect("ascii"),
21248            "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
21249        );
21250    }
21251
21252    /// `XCLAIM`, whose argument shape is the odd one in the group.
21253    #[test]
21254    fn xclaim_reads_ids_until_one_will_not_parse() {
21255        let mut f = Fixture::new();
21256        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21257        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21258        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21259        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21260
21261        // Everything after the first argument that is not an id is an option, so
21262        // a `-` is an unrecognised option and not a bad id.
21263        assert!(
21264            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
21265                .contains("Unrecognized XCLAIM option '-'")
21266        );
21267        assert_eq!(
21268            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
21269            "*1\r\n$3\r\n1-1\r\n"
21270        );
21271        // An id that is pending but whose entry has gone is an empty answer, and
21272        // it leaves the pending list on the way past.
21273        f.run(&[b"XDEL", b"s", b"2-1"]);
21274        assert_eq!(
21275            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
21276            "*0\r\n"
21277        );
21278        assert!(
21279            f.run(&[b"XPENDING", b"s", b"g"])
21280                .starts_with("*4\r\n:1\r\n")
21281        );
21282        assert!(
21283            f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
21284                .starts_with("-NOGROUP")
21285        );
21286        assert!(
21287            f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
21288                .contains("Invalid min-idle-time argument for XCLAIM")
21289        );
21290    }
21291
21292    /// `XAUTOCLAIM`, and the third value nobody expects.
21293    #[test]
21294    fn xautoclaim_reports_what_it_dropped() {
21295        let mut f = Fixture::new();
21296        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21297        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21298        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21299        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21300        f.run(&[b"XDEL", b"s", b"1-1"]);
21301
21302        // The cursor, what was claimed, and what was dropped for no longer being
21303        // in the stream. The third one is what makes a sweep converge.
21304        assert_eq!(
21305            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
21306            "*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"
21307        );
21308        assert!(
21309            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
21310                .contains("COUNT must be > 0")
21311        );
21312        assert!(
21313            f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
21314                .starts_with("-NOGROUP")
21315        );
21316    }
21317
21318    /// `XDELEX`, which is `XDEL` with a say in what the groups keep.
21319    #[test]
21320    fn xdelex_answers_one_integer_an_id() {
21321        let mut f = Fixture::new();
21322        for i in 1..=4 {
21323            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
21324        }
21325        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21326        f.run(&[
21327            b"XREADGROUP",
21328            b"GROUP",
21329            b"g",
21330            b"c",
21331            b"COUNT",
21332            b"2",
21333            b"STREAMS",
21334            b"s",
21335            b">",
21336        ]);
21337
21338        // One means gone and minus one means it was not there to start with.
21339        assert_eq!(
21340            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
21341            "*2\r\n:1\r\n:-1\r\n"
21342        );
21343        // `KEEPREF` leaves the pending entry behind, so the group still counts
21344        // the one it was handed even though the entry has gone.
21345        assert!(
21346            f.run(&[b"XPENDING", b"s", b"g"])
21347                .starts_with("*4\r\n:2\r\n")
21348        );
21349        // `DELREF` takes it out of every pending list on the way past.
21350        assert_eq!(
21351            f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
21352            "*1\r\n:1\r\n"
21353        );
21354        // `1-1` is still in the list, because the delete before it said KEEPREF.
21355        assert_eq!(
21356            f.run(&[b"XPENDING", b"s", b"g"]),
21357            "*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"
21358        );
21359
21360        // Two means somebody still wants it, and the question is wider than the
21361        // name: the group's bookmark is at `2-1`, so `4-1` is above it and is
21362        // refused even though no consumer has ever been handed it.
21363        assert_eq!(
21364            f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
21365            "*2\r\n:2\r\n:2\r\n"
21366        );
21367
21368        // A key that is not there answers minus ones without reading the IDs.
21369        assert_eq!(
21370            f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
21371            "*2\r\n:-1\r\n:-1\r\n"
21372        );
21373        // A key that is there validates every ID before deleting any of them.
21374        assert!(
21375            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
21376                .starts_with("-ERR Invalid stream ID")
21377        );
21378        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
21379
21380        assert!(
21381            f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
21382                .contains("Number of IDs must be a positive integer")
21383        );
21384        assert!(
21385            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
21386                .contains("The `numids` parameter must match the number of arguments")
21387        );
21388        // The condition is one word, so a second one is a syntax error, and so
21389        // is one ID more than the count promised.
21390        assert!(
21391            f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
21392                .starts_with("-ERR syntax error")
21393        );
21394        assert!(
21395            f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
21396                .starts_with("-ERR syntax error")
21397        );
21398        // The key is looked up first, so the wrong type beats the syntax.
21399        f.run(&[b"SET", b"str", b"v"]);
21400        assert!(
21401            f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
21402                .starts_with("-WRONGTYPE")
21403        );
21404    }
21405
21406    /// `XACKDEL`, whose reply is about the pending list and not about the log.
21407    #[test]
21408    fn xackdel_reports_what_the_group_was_holding() {
21409        let mut f = Fixture::new();
21410        for i in 1..=3 {
21411            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
21412        }
21413        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21414        f.run(&[
21415            b"XREADGROUP",
21416            b"GROUP",
21417            b"g",
21418            b"c",
21419            b"COUNT",
21420            b"1",
21421            b"STREAMS",
21422            b"s",
21423            b">",
21424        ]);
21425
21426        // Minus one is not about the stream: `2-1` is sitting there unread and
21427        // still answers minus one, because the group was not holding it. It also
21428        // stays, since only an ID that was acknowledged is deleted.
21429        assert_eq!(
21430            f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
21431            "*2\r\n:1\r\n:-1\r\n"
21432        );
21433        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
21434
21435        // A missing group is minus one an ID and not a NOGROUP.
21436        assert_eq!(
21437            f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
21438            "*1\r\n:-1\r\n"
21439        );
21440        assert_eq!(
21441            f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
21442            "*1\r\n:-1\r\n"
21443        );
21444
21445        // The acknowledgement happens whatever the condition says, so an ACKED
21446        // that answers two has still emptied the pending list.
21447        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
21448        f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
21449        assert_eq!(
21450            f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
21451            "*1\r\n:2\r\n"
21452        );
21453        assert_eq!(
21454            f.run(&[b"XPENDING", b"s", b"g"]),
21455            "*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"
21456        );
21457        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
21458    }
21459
21460    /// `XNACK`, which hands an entry back to nobody.
21461    #[test]
21462    fn xnack_releases_an_entry_for_the_next_claim() {
21463        let mut f = Fixture::new();
21464        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21465        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21466        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21467        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21468        // Twice, so the delivery count is two and the words have something to
21469        // do with it.
21470        f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
21471
21472        assert_eq!(
21473            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
21474            ":1\r\n"
21475        );
21476        // No owner, no idle time, and the count left where it was. A released
21477        // entry reads as idle for longer than any min-idle-time, which is what
21478        // puts it at the front of the next claim.
21479        assert_eq!(
21480            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
21481            "*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"
21482        );
21483        // The consumer no longer holds it, so a filtered XPENDING skips it.
21484        assert_eq!(
21485            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21486            "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
21487        );
21488        // The bookmark did not move, so a `>` read will not hand it out again.
21489        assert_eq!(
21490            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
21491            "*-1\r\n"
21492        );
21493        // A claim at any min-idle-time takes it.
21494        assert_eq!(
21495            f.run(&[
21496                b"XAUTOCLAIM",
21497                b"s",
21498                b"g",
21499                b"c2",
21500                b"99999999",
21501                b"-",
21502                b"JUSTID"
21503            ]),
21504            "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
21505        );
21506
21507        // `SILENT` takes one off the count rather than putting it back to zero,
21508        // which only shows on an entry that has been handed out more than once.
21509        // It was delivered and then claimed, so it is on two and goes to one.
21510        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21511        assert!(
21512            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21513                .contains(":-1\r\n:1\r\n")
21514        );
21515        // And it stops at zero rather than wrapping.
21516        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21517        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21518        assert!(
21519            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21520                .contains(":-1\r\n:0\r\n")
21521        );
21522        // `FATAL` puts it at the ceiling, and `RETRYCOUNT` wins over the word.
21523        f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
21524        assert!(
21525            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21526                .contains(":9223372036854775807\r\n")
21527        );
21528        f.run(&[
21529            b"XNACK",
21530            b"s",
21531            b"g",
21532            b"FATAL",
21533            b"IDS",
21534            b"1",
21535            b"1-1",
21536            b"RETRYCOUNT",
21537            b"3",
21538        ]);
21539        assert!(
21540            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21541                .contains(":-1\r\n:3\r\n")
21542        );
21543
21544        // Releasing something the group is not holding is zero, and `FORCE`
21545        // makes the pending entry rather than answering zero. A forced entry
21546        // starts at zero, since there was no earlier count to keep.
21547        f.run(&[b"XACK", b"s", b"g", b"2-1"]);
21548        assert_eq!(
21549            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
21550            ":0\r\n"
21551        );
21552        assert_eq!(
21553            f.run(&[
21554                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
21555            ]),
21556            ":1\r\n"
21557        );
21558        assert!(
21559            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21560                .contains(":-1\r\n:0\r\n")
21561        );
21562        // `FORCE` on an ID the stream does not have is still zero.
21563        assert_eq!(
21564            f.run(&[
21565                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
21566            ]),
21567            ":0\r\n"
21568        );
21569
21570        // The group is looked up before the mode word, and it raises rather
21571        // than answering per ID the way the two delete commands do.
21572        assert_eq!(
21573            f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
21574            "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
21575        );
21576        assert!(
21577            f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
21578                .starts_with("-ERR")
21579        );
21580        // Its own sentences, which are not the ones XDELEX uses.
21581        assert!(
21582            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
21583                .contains("numids must be a positive integer")
21584        );
21585        assert!(
21586            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
21587                .contains("number of IDs doesn't match numids")
21588        );
21589        // Everything past the counted IDs is an option, so one too many is an
21590        // option nobody recognises and not a count that does not add up.
21591        assert!(
21592            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
21593                .contains("Unrecognized XNACK option '2-1'")
21594        );
21595    }
21596
21597    /// `XINFO`, which is where the shape of the storage shows through.
21598    #[test]
21599    fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
21600        let mut f = Fixture::new();
21601        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21602        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21603        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21604        f.run(&[
21605            b"XREADGROUP",
21606            b"GROUP",
21607            b"g",
21608            b"c1",
21609            b"COUNT",
21610            b"1",
21611            b"STREAMS",
21612            b"s",
21613            b">",
21614        ]);
21615
21616        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21617        // Ten pairs, since the six idempotency fields have nothing behind them
21618        // here and a zero would claim they had. That is D-27.
21619        assert!(info.starts_with("*20\r\n"), "{info}");
21620        assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
21621        assert!(
21622            info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
21623            "{info}"
21624        );
21625        assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
21626        assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
21627
21628        let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
21629        assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
21630        assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
21631        assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
21632        assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
21633
21634        // A consumer that has never been given anything reports minus one for
21635        // inactive rather than the moment it turned up, which is what tells a
21636        // worker that is stuck from one that has nothing to do.
21637        f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
21638        let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
21639        assert!(consumers.starts_with("*2\r\n"), "{consumers}");
21640        assert!(
21641            consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
21642            "{consumers}"
21643        );
21644        // And in name order, which the storage does not hold them in.
21645        let c1 = consumers.find("c1").unwrap();
21646        let c2 = consumers.find("c2").unwrap();
21647        assert!(c1 < c2, "{consumers}");
21648
21649        let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
21650        assert!(full.starts_with("*18\r\n"), "{full}");
21651        assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
21652        assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
21653
21654        assert!(
21655            f.run(&[b"XINFO", b"STREAM", b"missing"])
21656                .contains("no such key")
21657        );
21658        assert!(
21659            f.run(&[b"XINFO", b"GROUPS", b"missing"])
21660                .contains("no such key")
21661        );
21662        assert!(
21663            f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
21664                .starts_with("-NOGROUP")
21665        );
21666        assert!(
21667            f.run(&[b"XINFO", b"NOSUCH", b"s"])
21668                .contains("Try XINFO HELP")
21669        );
21670        assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
21671        assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
21672    }
21673
21674    /// `XPENDING`'s long form, which reads its arguments by counting them.
21675    #[test]
21676    fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
21677        let mut f = Fixture::new();
21678        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21679        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21680        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21681
21682        let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
21683        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");
21684        assert_eq!(
21685            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21686            "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
21687        );
21688        // A consumer nobody has heard of holds nothing rather than erroring.
21689        assert_eq!(
21690            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
21691            "*0\r\n"
21692        );
21693        assert_eq!(
21694            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
21695            list
21696        );
21697        // IDLE is only read at position three.
21698        assert!(
21699            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
21700                .contains("syntax error")
21701        );
21702        assert!(
21703            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
21704                .contains("syntax error")
21705        );
21706        assert_eq!(
21707            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
21708            "*0\r\n"
21709        );
21710        assert!(
21711            f.run(&[b"XPENDING", b"missing", b"g"])
21712                .starts_with("-NOGROUP")
21713        );
21714    }
21715
21716    /// `XSETID`, which is three counters and two refusals.
21717    #[test]
21718    fn xsetid_will_not_go_below_what_is_there() {
21719        let mut f = Fixture::new();
21720        f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
21721        assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
21722        assert_eq!(
21723            f.run(&[
21724                b"XSETID",
21725                b"s",
21726                b"10-1",
21727                b"ENTRIESADDED",
21728                b"7",
21729                b"MAXDELETEDID",
21730                b"9-1"
21731            ]),
21732            "+OK\r\n"
21733        );
21734        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21735        assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
21736        assert!(
21737            info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
21738            "{info}"
21739        );
21740
21741        assert!(
21742            f.run(&[b"XSETID", b"s", b"1-1"])
21743                .contains("smaller than the target stream top item")
21744        );
21745        assert!(
21746            f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
21747                .contains("entries_added must be positive")
21748        );
21749        assert!(
21750            f.run(&[b"XSETID", b"missing", b"1-1"])
21751                .contains("no such key")
21752        );
21753    }
21754
21755    /// RESP3, where the two reads answer a map and the entries stay an array.
21756    #[test]
21757    fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
21758        let mut f = Fixture::new();
21759        f.run(&[b"HELLO", b"3"]);
21760        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21761        // A map header and then the key and the entries side by side, with no
21762        // two element array wrapping the pair.
21763        assert_eq!(
21764            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21765            "%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"
21766        );
21767        // The fields are still one flat array and not a map, which is Redis's
21768        // shape and is what every consumer written before RESP3 expects.
21769        assert_eq!(
21770            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
21771            "*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"
21772        );
21773        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
21774    }
21775
21776    /// A store to migrate values into, so a test can watch the inversion.
21777    ///
21778    /// A vector rather than a file for the same reason the tier's own tests use
21779    /// one: the file work has not attached a real store yet, and what this is
21780    /// checking is the policy above the store rather than the store.
21781    struct Mem {
21782        blobs: Vec<Vec<u8>>,
21783    }
21784
21785    impl yo_kv::cold::Blocks for Mem {
21786        fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
21787            self.blobs.push(bytes.to_vec());
21788            Ok(yo_common::Addr::new(
21789                yo_common::Space::Log,
21790                (self.blobs.len() - 1) as u64,
21791            ))
21792        }
21793
21794        fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
21795            self.blobs
21796                .get(at.offset() as usize)
21797                .map(Vec::as_slice)
21798                .ok_or_else(|| {
21799                    yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
21800                })
21801        }
21802
21803        fn bytes(&self) -> u64 {
21804            self.blobs.iter().map(|b| b.len() as u64).sum()
21805        }
21806    }
21807
21808    /// A server holding several segments of strings, with somewhere to put them.
21809    ///
21810    /// Answers the fixture and what it was holding when it stopped filling.
21811    /// The three tests that call this are the ones Miri is not run over.
21812    ///
21813    /// What they are about is the regime a database is in once the arena has
21814    /// several segments, and a segment is two megabytes, so there is no smaller
21815    /// version of the question: twenty four thousand keys is already the least
21816    /// that gets there. Interpreted, each of them sat for over forty minutes
21817    /// and was still going. The arena's own segment handling is interpreted in
21818    /// full in its own crate, and the policy these three check is ordinary
21819    /// bookkeeping with no unsafe block anywhere in it.
21820    fn filled(attach: bool) -> (Fixture, usize) {
21821        let mut f = Fixture::new();
21822        if attach {
21823            f.server
21824                .striped(0)
21825                .hold_stripe(0)
21826                .attach(Box::new(Mem { blobs: Vec::new() }));
21827        }
21828        let val = vec![b'v'; 256];
21829        for i in 0..24000u32 {
21830            let k = format!("key:{i:08}");
21831            f.run(&[b"SET", k.as_bytes(), &val]);
21832        }
21833        let full = f.server.memory_bytes();
21834        assert!(full > 3 * 1024 * 1024, "the arena is several segments");
21835        (f, full)
21836    }
21837
21838    /// Write until the server is under `limit` or the writes run out.
21839    ///
21840    /// The same shape the eviction test uses. A memory limit is enforced in
21841    /// front of a command, so nothing happens until something is written, and
21842    /// the budget means one command does not do the whole job.
21843    fn press(f: &mut Fixture, limit: usize) {
21844        let val = vec![b'v'; 256];
21845        for i in 0..3000u32 {
21846            let k = format!("new:{i:08}");
21847            assert_eq!(
21848                f.run(&[b"SET", k.as_bytes(), &val]),
21849                "+OK\r\n",
21850                "write {i} was refused"
21851            );
21852            f.server.refresh_memory();
21853            if f.server.memory_bytes() <= limit {
21854                return;
21855            }
21856        }
21857        panic!(
21858            "it never got under: {} against {limit}",
21859            f.server.memory_bytes()
21860        );
21861    }
21862
21863    #[test]
21864    fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
21865        let mut f = Fixture::new();
21866        assert_eq!(
21867            f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21868            "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
21869            "no limit is the default"
21870        );
21871        // The same memory value parser `maxmemory` uses, and the same trap in
21872        // it, plus the one spelling that means no limit at all.
21873        for (typed, bytes) in [
21874            (&b"0"[..], "0"),
21875            (b"1024", "1024"),
21876            (b"1k", "1000"),
21877            (b"1gb", "1073741824"),
21878            (b"-1", "-1"),
21879        ] {
21880            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
21881            assert_eq!(
21882                f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21883                format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
21884                "set {}",
21885                String::from_utf8_lossy(typed)
21886            );
21887        }
21888        for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
21889            assert_eq!(
21890                f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
21891                "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
21892                "refused {}",
21893                String::from_utf8_lossy(bad)
21894            );
21895        }
21896        // Nothing is attached, so the answer to a memory limit is still Redis's.
21897        let info = f.run(&[b"INFO", b"memory"]);
21898        assert!(info.contains("maxstore:-1"), "{info}");
21899        assert!(info.contains("yo_memory_regime:evict"), "{info}");
21900        assert!(info.contains("yo_store_bytes:0"), "{info}");
21901    }
21902
21903    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21904    #[test]
21905    fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
21906        // The inversion. The same pressure that makes a Redis server throw keys
21907        // away makes this one move values to the file, and afterwards every key
21908        // is still there and still answers with what was stored in it.
21909        let (mut f, full) = filled(true);
21910        let keys = f.run(&[b"DBSIZE"]);
21911        assert!(
21912            f.run(&[b"INFO", b"memory"])
21913                .contains("yo_memory_regime:migrate"),
21914            "a database with somewhere to put values migrates"
21915        );
21916
21917        let limit = full - 2 * 1024 * 1024;
21918        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21919        f.run(&[
21920            b"CONFIG",
21921            b"SET",
21922            b"maxmemory",
21923            limit.to_string().as_bytes(),
21924        ]);
21925        press(&mut f, limit);
21926
21927        assert!(
21928            f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21929            "nothing was thrown away"
21930        );
21931        let after: usize = f.run(&[b"DBSIZE"])[1..]
21932            .trim_end()
21933            .parse()
21934            .expect("a count");
21935        let before: usize = keys[1..].trim_end().parse().expect("a count");
21936        assert!(after > before, "the keys that came in are all still here");
21937        assert!(
21938            f.server.store_bytes() > 0,
21939            "and what came out of memory went to the file"
21940        );
21941        // And the values read back, which is the part that makes it a migration
21942        // rather than a loss.
21943        let val = format!("$256\r\n{}\r\n", "v".repeat(256));
21944        assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
21945        assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
21946    }
21947
21948    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21949    #[test]
21950    fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
21951        // The documented setting for a drop in cache. A file that may hold
21952        // nothing cannot be migrated to, so eviction is all that is left, and
21953        // the server behaves exactly as it did before any of this existed.
21954        let (mut f, full) = filled(true);
21955        f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
21956        assert!(
21957            f.run(&[b"INFO", b"memory"])
21958                .contains("yo_memory_regime:evict"),
21959            "nothing may go to the file"
21960        );
21961
21962        let limit = full - 2 * 1024 * 1024;
21963        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21964        f.run(&[
21965            b"CONFIG",
21966            b"SET",
21967            b"maxmemory",
21968            limit.to_string().as_bytes(),
21969        ]);
21970        press(&mut f, limit);
21971
21972        assert!(
21973            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21974            "keys were thrown away, which is what was asked for"
21975        );
21976        assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
21977    }
21978
21979    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21980    #[test]
21981    fn a_full_file_goes_back_to_evicting() {
21982        // A storage limit reached is a storage limit, and eviction is the right
21983        // answer to one. The budget here is a few kilobytes, so the first round
21984        // of migration fills it and everything after that is evicted.
21985        let (mut f, full) = filled(true);
21986        f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
21987        let limit = full - 2 * 1024 * 1024;
21988        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21989        f.run(&[
21990            b"CONFIG",
21991            b"SET",
21992            b"maxmemory",
21993            limit.to_string().as_bytes(),
21994        ]);
21995        press(&mut f, limit);
21996
21997        assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
21998        assert!(
21999            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
22000            "and then it started evicting"
22001        );
22002        assert!(
22003            f.run(&[b"INFO", b"memory"])
22004                .contains("yo_memory_regime:evict"),
22005            "and it says so"
22006        );
22007    }
22008    // ------------------------------------------------------------- stripes
22009
22010    /// Every string command, run twice: once on a database that is one keyspace
22011    /// and once on a database that is eight, with the same commands in the same
22012    /// order and the replies compared byte for byte.
22013    ///
22014    /// This is the whole claim the striping rests on. A key belongs to one
22015    /// stripe and to no other, so the answer to a command cannot depend on how
22016    /// many stripes there are, and the way to check that is to ask the same
22017    /// question of two servers that differ in nothing else.
22018    ///
22019    /// The keys are chosen to land on different stripes rather than to look
22020    /// tidy. `MSET a 1 b 2 c 3` over eight stripes is only a test of anything if
22021    /// those three keys are not all on the same one, and at eight stripes three
22022    /// keys land together about one time in fifty.
22023    #[test]
22024    fn the_string_group_answers_the_same_however_many_stripes_there_are() {
22025        let script: &[&[&[u8]]] = &[
22026            // The single key commands, which are the ones that get handed one
22027            // stripe at the dispatch site.
22028            &[b"SET", b"k1", b"v1"],
22029            &[b"SET", b"k2", b"v2"],
22030            &[b"GET", b"k1"],
22031            &[b"GET", b"nothing"],
22032            &[b"GETSET", b"k1", b"v1b"],
22033            &[b"SETNX", b"k1", b"no"],
22034            &[b"SETNX", b"k3", b"yes"],
22035            &[b"APPEND", b"k3", b"!"],
22036            &[b"STRLEN", b"k3"],
22037            &[b"SETRANGE", b"k3", b"1", b"XY"],
22038            &[b"GETRANGE", b"k3", b"0", b"-1"],
22039            &[b"INCR", b"n1"],
22040            &[b"INCRBY", b"n1", b"41"],
22041            &[b"DECRBY", b"n1", b"2"],
22042            &[b"INCRBYFLOAT", b"f1", b"1.5"],
22043            &[b"SETEX", b"e1", b"100", b"v"],
22044            &[b"PSETEX", b"e2", b"100000", b"v"],
22045            &[b"GETEX", b"e1", b"PERSIST"],
22046            &[b"GETDEL", b"k2"],
22047            &[b"GET", b"k2"],
22048            &[b"DIGEST", b"k1"],
22049            &[b"DELEX", b"k3"],
22050            // The five that name more than one key, which are the ones that
22051            // cannot be handed one stripe at all.
22052            &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
22053            &[b"MGET", b"a", b"b", b"c", b"missing"],
22054            &[b"MSETNX", b"d", b"4", b"e", b"5"],
22055            &[b"MSETNX", b"e", b"6", b"f", b"7"],
22056            &[b"MGET", b"d", b"e", b"f"],
22057            &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
22058            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
22059            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
22060            &[b"MGET", b"g", b"h"],
22061            &[b"SET", b"s1", b"ohmytext"],
22062            &[b"SET", b"s2", b"mynewtext"],
22063            &[b"LCS", b"s1", b"s2"],
22064            &[b"LCS", b"s1", b"s2", b"LEN"],
22065            &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
22066            &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
22067            &[b"LCS", b"s1", b"gone"],
22068            // And the errors, which have to be the same errors.
22069            &[b"MSET", b"odd"],
22070            &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
22071            &[b"MGET"],
22072        ];
22073
22074        let mut one = Fixture::new();
22075        let mut many = Fixture::striped(8);
22076        for parts in script {
22077            let a = one.run(parts);
22078            let b = many.run(parts);
22079            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22080        }
22081    }
22082
22083    /// The keys of an `MSET` really do end up on different stripes.
22084    ///
22085    /// Without this the test above could pass on a server whose stripe number
22086    /// happened to be a constant, which is a striped database in name only.
22087    #[test]
22088    fn a_striped_database_spreads_the_keys_it_is_given() {
22089        let mut f = Fixture::striped(8);
22090        for i in 0..256 {
22091            let key = format!("key:{i}");
22092            f.run(&[b"SET", key.as_bytes(), b"v"]);
22093        }
22094        assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
22095    }
22096
22097    /// A wrong type stops an `MGET` no more than it does on one stripe: the key
22098    /// that is not a string comes back nil and the rest of the reply is intact.
22099    #[test]
22100    fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
22101        let mut one = Fixture::new();
22102        let mut many = Fixture::striped(8);
22103        for f in [&mut one, &mut many] {
22104            f.run(&[b"SET", b"str", b"v"]);
22105            // Planted rather than pushed. `RPUSH` belongs to the list group,
22106            // which has not been taught about stripes yet and would refuse the
22107            // wide server. What is under test is what `MGET` does when it walks
22108            // onto a key that is not a string, and that does not care how the
22109            // key got there.
22110            f.server
22111                .striped(0)
22112                .hold(b"list")
22113                .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
22114                .expect("a new list");
22115        }
22116        assert_eq!(
22117            one.run(&[b"MGET", b"str", b"list", b"gone"]),
22118            many.run(&[b"MGET", b"str", b"list", b"gone"])
22119        );
22120    }
22121
22122    /// The same claim for the keyspace group, and the same way of checking it.
22123    ///
22124    /// `SORT` is not in the script because it is the one command in that file
22125    /// that has not been taught about stripes, and `SCAN`, `KEYS` and
22126    /// `RANDOMKEY` are not in it either, because those three do not promise an
22127    /// order and comparing two replies byte for byte would be asserting one.
22128    /// They get tests of their own below.
22129    #[test]
22130    fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
22131        let script: &[&[&[u8]]] = &[
22132            &[b"SET", b"k1", b"v1"],
22133            &[b"SET", b"k2", b"v2"],
22134            &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
22135            &[b"TYPE", b"k1"],
22136            &[b"TYPE", b"gone"],
22137            &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
22138            &[b"EXPIRE", b"k1", b"100"],
22139            &[b"TTL", b"k1"],
22140            &[b"EXPIRE", b"k1", b"200", b"NX"],
22141            &[b"PERSIST", b"k1"],
22142            &[b"TTL", b"k1"],
22143            &[b"PEXPIREAT", b"k2", b"1900000000000"],
22144            &[b"EXPIRETIME", b"k2"],
22145            &[b"PEXPIRETIME", b"k2"],
22146            &[b"PERSIST", b"k2"],
22147            &[b"OBJECT", b"ENCODING", b"k1"],
22148            &[b"OBJECT", b"REFCOUNT", b"k1"],
22149            &[b"OBJECT", b"IDLETIME", b"k1"],
22150            &[b"OBJECT", b"FREQ", b"k1"],
22151            &[b"OBJECT", b"ENCODING", b"gone"],
22152            &[b"OBJECT", b"HELP"],
22153            &[b"RENAME", b"k1", b"k9"],
22154            &[b"GET", b"k9"],
22155            &[b"RENAME", b"gone", b"x"],
22156            &[b"RENAMENX", b"k9", b"k2"],
22157            &[b"RENAMENX", b"k9", b"k8"],
22158            &[b"GET", b"k8"],
22159            &[b"COPY", b"k8", b"c1"],
22160            &[b"COPY", b"k8", b"c1"],
22161            &[b"COPY", b"k8", b"c1", b"REPLACE"],
22162            &[b"COPY", b"k8", b"k8"],
22163            &[b"COPY", b"gone", b"c2"],
22164            &[b"COPY", b"k8", b"k8", b"DB", b"1"],
22165            &[b"COPY", b"k8", b"c9", b"DB", b"9"],
22166            &[b"MOVE", b"c1", b"1"],
22167            &[b"MOVE", b"c1", b"1"],
22168            &[b"MOVE", b"k8", b"0"],
22169            &[b"DEL", b"k2", b"gone"],
22170            &[b"UNLINK", b"k8", b"k8"],
22171            &[b"DBSIZE"],
22172        ];
22173
22174        let mut one = Fixture::new();
22175        let mut many = Fixture::striped(8);
22176        for parts in script {
22177            let a = one.run(parts);
22178            let b = many.run(parts);
22179            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22180        }
22181
22182        // `RESTORE` needs bytes a client would have got from a `DUMP`, so the
22183        // payload is taken from the store rather than parsed back out of a
22184        // reply that is not text. Both servers dump the same key and the bytes
22185        // are the same bytes, which is the first half of what is being checked
22186        // here.
22187        for f in [&mut one, &mut many] {
22188            f.run(&[b"SET", b"d1", b"payload"]);
22189            let payload = f
22190                .server
22191                .striped(0)
22192                .hold(b"d1")
22193                .dump(b"d1")
22194                .expect("a key that is there");
22195            assert!(
22196                f.run(&[b"DUMP", b"d1"])
22197                    .starts_with(&format!("${}", payload.len())),
22198                "a payload of the length the store gave"
22199            );
22200            assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
22201            assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
22202            assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
22203            assert_eq!(
22204                f.run(&[b"RESTORE", b"d2", b"0", &payload]),
22205                "-BUSYKEY Target key name already exists.\r\n"
22206            );
22207            assert_eq!(
22208                f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
22209                "-ERR DUMP payload version or checksum are wrong\r\n"
22210            );
22211        }
22212    }
22213
22214    /// A `SCAN` of a database of eight stripes comes back with all of it.
22215    ///
22216    /// The cursor is the thing under test. It has to carry the stripe as well
22217    /// as the place in it, so a client that stops at one stripe and comes back
22218    /// carries on in that stripe and not at the top of the database, and the
22219    /// walk has to end once rather than eight times.
22220    #[test]
22221    fn a_scan_of_a_striped_database_walks_all_of_it() {
22222        // Eight stripes and a COUNT of ten, so eighty keys is already more than
22223        // one page on every stripe and the cursor has to carry which stripe it
22224        // was on, which is the thing being checked.
22225        let n = if cfg!(miri) { 80 } else { 500 };
22226        let mut f = Fixture::striped(8);
22227        for i in 0..n {
22228            let key = format!("key:{i}");
22229            f.run(&[b"SET", key.as_bytes(), b"v"]);
22230        }
22231
22232        let mut seen = Vec::new();
22233        let mut cursor = "0".to_owned();
22234        let mut calls = 0;
22235        loop {
22236            let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
22237            let (next, keys) = scan_reply(&reply);
22238            seen.extend(keys);
22239            cursor = next;
22240            calls += 1;
22241            assert!(calls < 5_000, "a scan that will not finish");
22242            if cursor == "0" {
22243                break;
22244            }
22245        }
22246        seen.sort();
22247        assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
22248        assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
22249
22250        // And the options still work when the walk is over several stripes,
22251        // since a `MATCH` is applied to keys a stripe handed up and a `TYPE` is
22252        // applied by each stripe on the way.
22253        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
22254        let (_, keys) = scan_reply(&reply);
22255        assert_eq!(keys.len(), 10, "key:40 through key:49");
22256        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
22257        let (_, keys) = scan_reply(&reply);
22258        assert!(keys.is_empty(), "nothing here is a list");
22259    }
22260
22261    /// `RANDOMKEY` on a striped database answers a key from any of the stripes.
22262    ///
22263    /// The draw picks the stripe first, so the thing that can go wrong is that
22264    /// it always picks the same one, and two hundred draws over eight stripes
22265    /// would make that obvious.
22266    #[test]
22267    fn a_random_key_can_come_from_any_stripe() {
22268        let mut f = Fixture::striped(8);
22269        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
22270        for i in 0..200 {
22271            let key = format!("key:{i}");
22272            f.run(&[b"SET", key.as_bytes(), b"v"]);
22273        }
22274        let mut homes = std::collections::HashSet::new();
22275        for _ in 0..200 {
22276            let got = f.run(&[b"RANDOMKEY"]);
22277            let key = got.split("\r\n").nth(1).expect("a key").to_owned();
22278            assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
22279            homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
22280        }
22281        assert_eq!(homes.len(), 8, "some stripe was never drawn from");
22282    }
22283
22284    /// Two keys that are not on the same stripe, which is what `RENAME` and
22285    /// `COPY` have to cope with and what a test has to arrange rather than
22286    /// hope for.
22287    fn apart(f: &mut Fixture, src: &str) -> String {
22288        let home = f.server.striped(0).stripe_of(src.as_bytes());
22289        for i in 0..1_000 {
22290            let dst = format!("dst:{i}");
22291            if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
22292                return dst;
22293            }
22294        }
22295        panic!("eight stripes and a thousand keys all landed in one place");
22296    }
22297
22298    /// A rename whose two keys are on two stripes moves the value, the deadline
22299    /// and, for a collection, the body itself.
22300    #[test]
22301    fn a_rename_across_stripes_takes_everything_with_it() {
22302        let mut f = Fixture::striped(8);
22303        let dst = apart(&mut f, "src");
22304        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
22305
22306        f.run(&[b"SET", src, b"v"]);
22307        f.run(&[b"EXPIRE", src, b"100"]);
22308        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
22309        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
22310        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
22311        assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
22312
22313        // A list, because a string lives in its record and a collection lives
22314        // in a slab, and the second of those is the one that can be left
22315        // behind. Planted through the store, since the list group has not been
22316        // taught about stripes yet.
22317        f.server
22318            .striped(0)
22319            .hold(src)
22320            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
22321            .expect("a new list");
22322        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
22323        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
22324        assert_eq!(
22325            f.server.striped(0).hold(dst).llen(dst).expect("a list"),
22326            2,
22327            "the members are on the stripe the key moved to"
22328        );
22329
22330        // And `RENAMENX` still refuses a destination that is taken, which is
22331        // the one answer the cross stripe path has to work out for itself.
22332        f.run(&[b"SET", src, b"v"]);
22333        assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
22334        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
22335        assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
22336    }
22337
22338    /// And a copy across two stripes leaves both keys behind it.
22339    #[test]
22340    fn a_copy_across_stripes_leaves_the_source_where_it_was() {
22341        let mut f = Fixture::striped(8);
22342        let dst = apart(&mut f, "src");
22343        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
22344
22345        f.run(&[b"SET", src, b"v"]);
22346        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
22347        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
22348        assert_eq!(
22349            f.run(&[b"COPY", src, dst]),
22350            ":0\r\n",
22351            "the destination is taken"
22352        );
22353        f.run(&[b"SET", src, b"w"]);
22354        assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
22355        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
22356
22357        // A collection is cloned rather than moved, so both keys have a body of
22358        // their own afterwards and writing to one does not show up in the
22359        // other.
22360        f.run(&[b"DEL", src, dst]);
22361        f.server
22362            .striped(0)
22363            .hold(src)
22364            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
22365            .expect("a new list");
22366        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
22367        f.server
22368            .striped(0)
22369            .hold(src)
22370            .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
22371            .expect("a list that is there");
22372        assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
22373        assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
22374    }
22375
22376    /// Every bitmap command, on one stripe and on eight, replies compared byte
22377    /// for byte.
22378    ///
22379    /// `BITOP` is the one that names more than one key and it is where the work
22380    /// went. The rest are single key commands that now find their own stripe,
22381    /// and they are here because the cheapest way to be sure the routing is
22382    /// right is to ask.
22383    #[test]
22384    fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
22385        let script: &[&[&[u8]]] = &[
22386            &[b"SET", b"k1", b"foobar"],
22387            &[b"SETBIT", b"b1", b"7", b"1"],
22388            &[b"SETBIT", b"b1", b"7", b"0"],
22389            &[b"GETBIT", b"k1", b"6"],
22390            &[b"GETBIT", b"k1", b"100"],
22391            &[b"BITCOUNT", b"k1"],
22392            &[b"BITCOUNT", b"k1", b"0", b"0"],
22393            &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
22394            &[b"BITPOS", b"k1", b"1"],
22395            &[b"BITPOS", b"k1", b"0", b"2"],
22396            &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
22397            &[
22398                b"BITFIELD",
22399                b"bf",
22400                b"SET",
22401                b"u8",
22402                b"0",
22403                b"255",
22404                b"GET",
22405                b"u8",
22406                b"0",
22407            ],
22408            &[
22409                b"BITFIELD",
22410                b"bf",
22411                b"OVERFLOW",
22412                b"SAT",
22413                b"INCRBY",
22414                b"u8",
22415                b"0",
22416                b"10",
22417            ],
22418            &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
22419            // The multi key one, over sources that are not on one stripe unless
22420            // eight stripes have folded into one.
22421            &[b"SET", b"s1", b"abc"],
22422            &[b"SET", b"s2", b"abd"],
22423            &[b"SET", b"s3", b"a"],
22424            &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
22425            &[b"GET", b"d1"],
22426            &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
22427            &[b"GET", b"d2"],
22428            &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
22429            &[b"STRLEN", b"d3"],
22430            &[b"BITOP", b"NOT", b"d4", b"s1"],
22431            &[b"STRLEN", b"d4"],
22432            &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
22433            &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
22434            &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
22435            &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
22436            // A source that is not there reads as empty, and a result with
22437            // nothing in it deletes the destination rather than writing one.
22438            &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
22439            &[b"EXISTS", b"d1"],
22440            &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
22441            &[b"GET", b"d9"],
22442            // And the errors, which have to be the same errors. The key that
22443            // is not a string is planted below rather than pushed here, since
22444            // the list group has not been taught about stripes yet.
22445            &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
22446            &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
22447            &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
22448            &[b"BITOP", b"DIFF", b"d1", b"s1"],
22449            &[b"BITOP", b"NOPE", b"d1", b"s1"],
22450            &[b"BITCOUNT", b"list"],
22451            &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
22452        ];
22453
22454        let mut one = Fixture::new();
22455        let mut many = Fixture::striped(8);
22456        for f in [&mut one, &mut many] {
22457            plant_list(f, b"list");
22458        }
22459        for parts in script {
22460            let a = one.run(parts);
22461            let b = many.run(parts);
22462            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22463        }
22464    }
22465
22466    /// A list under `key`, put there through the store.
22467    ///
22468    /// What a test does when it wants a key of the wrong type on a striped
22469    /// server, because the command that would make one is in a group that has
22470    /// not been taught about stripes yet.
22471    fn plant_list(f: &mut Fixture, key: &[u8]) {
22472        f.server
22473            .striped(0)
22474            .hold(key)
22475            .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
22476            .expect("a new list");
22477    }
22478
22479    /// A `BITOP` whose keys are on two stripes reads both of them.
22480    ///
22481    /// The test above spreads its keys by hashing and would still pass if one
22482    /// stripe were doing all the work, since the answers would be the same. This
22483    /// one puts the destination and the two sources where they are known not to
22484    /// share a stripe.
22485    #[test]
22486    fn a_bitop_across_stripes_reads_every_source() {
22487        let mut f = Fixture::striped(8);
22488        let other = apart(&mut f, "src");
22489        let (src, far) = (b"src".as_slice(), other.as_bytes());
22490        assert_ne!(
22491            f.server.striped(0).stripe_of(src),
22492            f.server.striped(0).stripe_of(far),
22493            "the two keys are the point of the test"
22494        );
22495
22496        f.run(&[b"SET", src, b"abc"]);
22497        f.run(&[b"SET", far, b"abd"]);
22498        assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
22499        assert_eq!(
22500            f.run(&[b"GET", far]),
22501            "$3\r\nab`\r\n",
22502            "a destination that is also a source"
22503        );
22504        f.run(&[b"SET", far, b"abd"]);
22505        assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
22506        assert_eq!(
22507            f.run(&[b"GET", src]),
22508            "$3\r\n\0\0\x07\r\n",
22509            "and the other way round"
22510        );
22511
22512        // A result of nothing deletes a destination on whatever stripe it is
22513        // on, and a source of the wrong type is refused before anything is
22514        // written.
22515        f.run(&[b"SET", src, b"abc"]);
22516        f.run(&[b"DEL", far]);
22517        assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
22518        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
22519        f.run(&[b"SET", src, b"abc"]);
22520        f.run(&[b"DEL", far]);
22521        plant_list(&mut f, far);
22522        assert_eq!(
22523            f.run(&[b"BITOP", b"OR", b"out", src, far]),
22524            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22525        );
22526        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
22527    }
22528
22529    /// Every HyperLogLog command, on one stripe and on eight.
22530    ///
22531    /// Not under Miri, for the reason on
22532    /// `the_debug_forms_answer_four_different_shapes`, and twice over here
22533    /// because the script is run against both shapes of server.
22534    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
22535    #[test]
22536    fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
22537        let script: &[&[&[u8]]] = &[
22538            &[b"PFADD", b"h1", b"a", b"b", b"c"],
22539            &[b"PFADD", b"h1", b"a"],
22540            &[b"PFADD", b"h2"],
22541            &[b"PFADD", b"h2", b"c", b"d", b"e"],
22542            &[b"PFCOUNT", b"h1"],
22543            &[b"PFCOUNT", b"h2"],
22544            &[b"PFCOUNT", b"missing"],
22545            // The two that name more than one key.
22546            &[b"PFCOUNT", b"h1", b"h2"],
22547            &[b"PFCOUNT", b"h1", b"missing"],
22548            &[b"PFMERGE", b"m", b"h1", b"h2"],
22549            &[b"PFCOUNT", b"m"],
22550            &[b"STRLEN", b"m"],
22551            &[b"PFMERGE", b"m"],
22552            &[b"PFCOUNT", b"m"],
22553            &[b"PFMERGE", b"m2", b"missing"],
22554            &[b"PFCOUNT", b"m2"],
22555            // The debugging ones, which are single key and change what they
22556            // look at.
22557            &[b"PFDEBUG", b"ENCODING", b"h1"],
22558            &[b"PFDEBUG", b"DECODE", b"h1"],
22559            &[b"PFDEBUG", b"TODENSE", b"h1"],
22560            &[b"PFDEBUG", b"ENCODING", b"h1"],
22561            &[b"PFDEBUG", b"TODENSE", b"h1"],
22562            &[b"PFCOUNT", b"h1", b"h2"],
22563            &[b"PFSELFTEST"],
22564            // And the errors.
22565            &[b"SET", b"plain", b"not a sketch at all"],
22566            &[b"PFADD", b"plain", b"a"],
22567            &[b"PFCOUNT", b"plain"],
22568            &[b"PFCOUNT", b"h1", b"plain"],
22569            &[b"PFMERGE", b"plain", b"h1"],
22570            &[b"PFMERGE", b"m", b"plain"],
22571            &[b"PFDEBUG", b"ENCODING", b"gone"],
22572            &[b"PFDEBUG", b"NOPE", b"h1"],
22573        ];
22574
22575        let mut one = Fixture::new();
22576        let mut many = Fixture::striped(8);
22577        for parts in script {
22578            let a = one.run(parts);
22579            let b = many.run(parts);
22580            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22581        }
22582    }
22583
22584    /// Every set command, on one stripe and on eight.
22585    ///
22586    /// The commands that answer members answer them in whatever order the set
22587    /// or the table they were built in holds them, so those replies are
22588    /// compared as sets. Everything else is compared byte for byte. Two servers
22589    /// agreeing on the order would be a fact about the tables and not about the
22590    /// answer, and asserting it would make this test fail for a reason nobody
22591    /// cares about.
22592    #[test]
22593    fn the_set_group_answers_the_same_however_many_stripes_there_are() {
22594        const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
22595        let script: &[&[&[u8]]] = &[
22596            &[b"SADD", b"s1", b"a", b"b", b"c"],
22597            &[b"SADD", b"s1", b"a"],
22598            &[b"SADD", b"s2", b"b", b"c", b"d"],
22599            &[b"SADD", b"ints", b"1", b"2", b"3"],
22600            &[b"SCARD", b"s1"],
22601            &[b"SISMEMBER", b"s1", b"a"],
22602            &[b"SISMEMBER", b"s1", b"z"],
22603            &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
22604            &[b"SMEMBERS", b"s1"],
22605            &[b"SREM", b"s1", b"c"],
22606            &[b"SADD", b"s1", b"c"],
22607            &[b"SSCAN", b"s1", b"0"],
22608            &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
22609            // The two draws, on a set of one member, which is the only shape
22610            // whose answer two servers have to agree on.
22611            &[b"SADD", b"one", b"m"],
22612            &[b"SRANDMEMBER", b"one"],
22613            &[b"SRANDMEMBER", b"one", b"-3"],
22614            &[b"SRANDMEMBER", b"gone"],
22615            &[b"SPOP", b"one"],
22616            &[b"SPOP", b"one"],
22617            &[b"SPOP", b"gone", b"2"],
22618            // The one that names two keys.
22619            &[b"SMOVE", b"s1", b"s2", b"a"],
22620            &[b"SMOVE", b"s1", b"s2", b"zzz"],
22621            &[b"SMOVE", b"gone", b"s2", b"a"],
22622            &[b"SMEMBERS", b"s1"],
22623            &[b"SMEMBERS", b"s2"],
22624            // The algebra.
22625            &[b"SINTER", b"s1", b"s2"],
22626            &[b"SUNION", b"s1", b"s2"],
22627            &[b"SDIFF", b"s2", b"s1"],
22628            &[b"SINTER", b"s1", b"gone"],
22629            &[b"SUNION", b"s1", b"gone"],
22630            &[b"SDIFF", b"gone", b"s1"],
22631            &[b"SINTER", b"ints", b"s1"],
22632            &[b"SINTERCARD", b"2", b"s1", b"s2"],
22633            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
22634            &[b"SUNIONCARD", b"2", b"s1", b"s2"],
22635            &[b"SDIFFCARD", b"2", b"s2", b"s1"],
22636            &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
22637            &[b"SMEMBERS", b"d1"],
22638            &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
22639            &[b"SCARD", b"d2"],
22640            &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
22641            &[b"SCARD", b"d3"],
22642            // An empty result deletes the destination rather than storing a
22643            // set with nothing in it.
22644            &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
22645            &[b"EXISTS", b"d4"],
22646            // And a destination that is also a source.
22647            &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
22648            &[b"SCARD", b"s2"],
22649            // The errors, which have to be the same errors.
22650            &[b"SET", b"str", b"v"],
22651            &[b"SADD", b"str", b"a"],
22652            &[b"SINTER", b"s1", b"str"],
22653            &[b"SINTERSTORE", b"d5", b"s1", b"str"],
22654            &[b"EXISTS", b"d5"],
22655            &[b"SMOVE", b"str", b"s2", b"a"],
22656            &[b"SMOVE", b"s1", b"str", b"b"],
22657            &[b"SMOVE", b"gone", b"str", b"b"],
22658            &[b"SINTERCARD", b"0", b"s1"],
22659            &[b"SINTERCARD", b"3", b"s1", b"s2"],
22660            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
22661            &[b"SPOP", b"s1", b"-1"],
22662        ];
22663
22664        let mut one = Fixture::new();
22665        let mut many = Fixture::striped(8);
22666        for parts in script {
22667            let a = one.run(parts);
22668            let b = many.run(parts);
22669            let name = String::from_utf8_lossy(parts[0]).to_uppercase();
22670            if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
22671                assert_eq!(sorted(&a), sorted(&b), "{name}");
22672            } else {
22673                assert_eq!(a, b, "{name}");
22674            }
22675        }
22676    }
22677
22678    /// The algebra over sets that are known to be on different stripes.
22679    #[test]
22680    fn a_set_operation_across_stripes_reads_every_set() {
22681        let mut f = Fixture::striped(8);
22682        let second = apart(&mut f, "s1");
22683        let third = apart(&mut f, &second);
22684        let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
22685
22686        f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
22687        f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
22688        assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
22689        assert_eq!(
22690            sorted(&f.run(&[b"SUNION", s1, s2])),
22691            ["a", "b", "c", "d"],
22692            "a union of two stripes is both of them"
22693        );
22694        assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
22695        assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
22696        assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
22697        assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
22698
22699        // A destination on a third stripe, and then one that is also a source.
22700        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
22701        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
22702        assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
22703        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
22704        assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
22705        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
22706
22707        // An empty result deletes a destination wherever it is, and a key of
22708        // the wrong type stops the command before the destination is touched.
22709        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
22710        assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
22711        f.run(&[b"SET", s3, b"v"]);
22712        assert_eq!(
22713            f.run(&[b"SINTER", s1, s3]),
22714            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22715        );
22716        assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
22717    }
22718
22719    /// An `SMOVE` whose two keys are on two stripes.
22720    #[test]
22721    fn a_move_across_stripes_takes_the_member_with_it() {
22722        let mut f = Fixture::striped(8);
22723        let other = apart(&mut f, "src");
22724        let (src, dst) = (b"src".as_slice(), other.as_bytes());
22725
22726        f.run(&[b"SADD", src, b"a", b"b"]);
22727        f.run(&[b"SADD", dst, b"c"]);
22728        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
22729        assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
22730        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
22731        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
22732
22733        // A destination that is not there is created on its own stripe, and a
22734        // source that loses its last member is deleted from its own.
22735        f.run(&[b"DEL", dst]);
22736        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
22737        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
22738        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
22739
22740        // And a source that is not there answers zero without ever asking what
22741        // the destination holds, which is Redis's order and not the obvious
22742        // one.
22743        f.run(&[b"SET", dst, b"v"]);
22744        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
22745        f.run(&[b"SADD", src, b"b"]);
22746        assert_eq!(
22747            f.run(&[b"SMOVE", src, dst, b"b"]),
22748            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22749        );
22750    }
22751
22752    /// A count and a merge over sketches that are known to be on two stripes.
22753    #[test]
22754    fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
22755        let mut f = Fixture::striped(8);
22756        let other = apart(&mut f, "src");
22757        let (src, far) = (b"src".as_slice(), other.as_bytes());
22758
22759        for i in 0..150 {
22760            let ele = format!("e:{i}");
22761            f.run(&[b"PFADD", src, ele.as_bytes()]);
22762        }
22763        for i in 150..200 {
22764            let ele = format!("e:{i}");
22765            f.run(&[b"PFADD", far, ele.as_bytes()]);
22766        }
22767        // The three numbers a real server gives for these elements, which are
22768        // the numbers the single stripe tests in the keyspace crate check too.
22769        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
22770        assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
22771        assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
22772
22773        // A merge whose destination is on a third stripe, and then one that
22774        // writes into a source.
22775        let dest = apart(&mut f, &other);
22776        assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
22777        assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
22778        assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
22779        assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
22780        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
22781    }
22782
22783    /// Every sorted set command, on one stripe and on eight.
22784    ///
22785    /// Every reply here is compared byte for byte, unlike the set group, because
22786    /// a sorted set answers in rank order and members sharing a score come out
22787    /// in the order of their bytes. There is nothing left for the table the
22788    /// answer was built in to decide.
22789    #[test]
22790    fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
22791        let script: &[&[&[u8]]] = &[
22792            &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
22793            &[b"ZADD", b"z1", b"NX", b"9", b"a"],
22794            &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
22795            &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
22796            &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
22797            &[b"ZINCRBY", b"z1", b"1.5", b"b"],
22798            &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
22799            &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
22800            &[b"ZADD", b"one", b"1", b"m"],
22801            &[b"ZCARD", b"z1"],
22802            &[b"ZCARD", b"gone"],
22803            &[b"ZSCORE", b"z1", b"a"],
22804            &[b"ZSCORE", b"z1", b"zz"],
22805            &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
22806            &[b"ZRANK", b"z1", b"c"],
22807            &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
22808            &[b"ZREVRANK", b"z1", b"c"],
22809            &[b"ZRANK", b"z1", b"gone"],
22810            &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
22811            &[b"ZCOUNT", b"z1", b"(1", b"3"],
22812            &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
22813            // The range commands, which are one parse and one walk.
22814            &[b"ZRANGE", b"z1", b"0", b"-1"],
22815            &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
22816            &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
22817            &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
22818            &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
22819            &[b"ZREVRANGE", b"z1", b"0", b"-1"],
22820            &[
22821                b"ZRANGEBYSCORE",
22822                b"z1",
22823                b"-inf",
22824                b"+inf",
22825                b"LIMIT",
22826                b"1",
22827                b"1",
22828            ],
22829            &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
22830            &[b"ZSCAN", b"z1", b"0"],
22831            &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
22832            // The draw, on a sorted set of one member, which is the only shape
22833            // whose answer two servers have to agree on.
22834            &[b"ZRANDMEMBER", b"one"],
22835            &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
22836            &[b"ZRANDMEMBER", b"gone"],
22837            // The one that copies a window into another key.
22838            &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
22839            &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
22840            &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
22841            &[b"EXISTS", b"d0"],
22842            // The algebra, in both its shapes.
22843            &[b"ZUNION", b"2", b"z1", b"z2"],
22844            &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
22845            &[
22846                b"ZUNION",
22847                b"2",
22848                b"z1",
22849                b"z2",
22850                b"WEIGHTS",
22851                b"2",
22852                b"3",
22853                b"AGGREGATE",
22854                b"MAX",
22855                b"WITHSCORES",
22856            ],
22857            &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
22858            &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
22859            &[b"ZDIFF", b"2", b"gone", b"z1"],
22860            &[b"ZINTERCARD", b"2", b"z1", b"z2"],
22861            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
22862            &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
22863            &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
22864            &[
22865                b"ZINTERSTORE",
22866                b"d2",
22867                b"2",
22868                b"z1",
22869                b"z2",
22870                b"AGGREGATE",
22871                b"MIN",
22872            ],
22873            &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
22874            &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
22875            &[b"ZCARD", b"d3"],
22876            // An empty result deletes the destination rather than storing a
22877            // sorted set with nothing in it.
22878            &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
22879            &[b"EXISTS", b"d4"],
22880            // A plain set is a sorted set where every score is one, so it is a
22881            // legal input to all of these.
22882            &[b"SADD", b"plain", b"a", b"x"],
22883            &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
22884            &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
22885            // And a destination that is also a source.
22886            &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
22887            &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
22888            // The three removals and the two pops.
22889            &[b"ZREM", b"d5", b"x", b"nothere"],
22890            &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
22891            &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
22892            &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
22893            &[b"ZPOPMIN", b"z1"],
22894            &[b"ZPOPMAX", b"z1", b"2"],
22895            &[b"ZPOPMIN", b"gone"],
22896            &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
22897            &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
22898            // The errors, which have to be the same errors.
22899            &[b"SET", b"str", b"v"],
22900            &[b"ZADD", b"str", b"1", b"a"],
22901            &[b"ZSCORE", b"str", b"a"],
22902            &[b"ZADD", b"z1", b"nan", b"a"],
22903            &[b"ZUNION", b"2", b"z1", b"str"],
22904            &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
22905            &[b"EXISTS", b"d6"],
22906            &[b"ZINTERCARD", b"0", b"z1"],
22907            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
22908            &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
22909            &[b"ZMPOP", b"1", b"str", b"MIN"],
22910            &[b"ZPOPMIN", b"z1", b"-1"],
22911        ];
22912
22913        let mut one = Fixture::new();
22914        let mut many = Fixture::striped(8);
22915        for parts in script {
22916            let a = one.run(parts);
22917            let b = many.run(parts);
22918            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22919        }
22920    }
22921
22922    /// The algebra over sorted sets that are known to be on different stripes.
22923    #[test]
22924    fn a_sorted_set_operation_across_stripes_reads_every_input() {
22925        let mut f = Fixture::striped(8);
22926        let second = apart(&mut f, "z1");
22927        let third = apart(&mut f, &second);
22928        let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
22929
22930        f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
22931        f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
22932        // a is 1, c is 4, b is 2 and 3 added together, which is the order they
22933        // come out in and the answer that says both stripes were read.
22934        assert_eq!(
22935            f.run(&[b"ZUNION", b"2", z1, z2]),
22936            "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
22937        );
22938        assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
22939        assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
22940        assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
22941        assert_eq!(
22942            f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
22943            ":1\r\n"
22944        );
22945
22946        // A destination on a third stripe, and the weights and the aggregate
22947        // reaching every input.
22948        assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
22949        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
22950        assert_eq!(
22951            f.run(&[
22952                b"ZUNIONSTORE",
22953                z3,
22954                b"2",
22955                z1,
22956                z2,
22957                b"WEIGHTS",
22958                b"2",
22959                b"3",
22960                b"AGGREGATE",
22961                b"MAX"
22962            ]),
22963            ":3\r\n"
22964        );
22965        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
22966        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
22967        assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
22968        assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
22969        assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
22970
22971        // A pop over keys on several stripes takes from the first one that has
22972        // anything, which is what makes the order of the keys matter.
22973        let popped = format!(
22974            "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
22975            second.len()
22976        );
22977        assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
22978        f.run(&[b"ZADD", z2, b"3", b"b"]);
22979
22980        // An empty result deletes a destination wherever it is, and an input of
22981        // the wrong type stops the command before the destination is touched.
22982        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
22983        assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
22984        f.run(&[b"SET", z3, b"v"]);
22985        assert_eq!(
22986            f.run(&[b"ZUNION", b"2", z1, z3]),
22987            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22988        );
22989        assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
22990
22991        // And a destination that is also a source works across stripes for the
22992        // reason it works on one: the whole result is built before anything is
22993        // written.
22994        assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
22995        assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
22996        assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
22997    }
22998
22999    /// A `ZRANGESTORE` whose two keys are on two stripes.
23000    #[test]
23001    fn a_range_store_across_stripes_copies_the_window() {
23002        let mut f = Fixture::striped(8);
23003        let other = apart(&mut f, "src");
23004        let third = apart(&mut f, &other);
23005        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
23006
23007        f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
23008        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
23009        assert_eq!(
23010            f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
23011            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
23012        );
23013        assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
23014
23015        // A window walked backwards takes the other end of the sorted set and
23016        // still stores what it took in score order.
23017        assert_eq!(
23018            f.run(&[
23019                b"ZRANGESTORE",
23020                dst,
23021                src,
23022                b"+inf",
23023                b"-inf",
23024                b"BYSCORE",
23025                b"REV",
23026                b"LIMIT",
23027                b"0",
23028                b"2"
23029            ]),
23030            ":2\r\n"
23031        );
23032        assert_eq!(
23033            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23034            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
23035        );
23036
23037        // An empty window deletes the destination on its own stripe, and a
23038        // source of the wrong type is refused before the destination is touched.
23039        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
23040        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23041        f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
23042        f.run(&[b"SET", plain, b"v"]);
23043        assert_eq!(
23044            f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
23045            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23046        );
23047        assert_eq!(
23048            f.run(&[b"ZCARD", dst]),
23049            ":3\r\n",
23050            "and left the destination"
23051        );
23052    }
23053
23054    /// Every list command, on one stripe and on eight.
23055    ///
23056    /// The blocking six are in here too, both when they can be answered on the
23057    /// spot and when they cannot, since a command that parks its client writes
23058    /// nothing at all and two servers have to agree about that as much as they
23059    /// agree about a reply.
23060    #[test]
23061    fn the_list_group_answers_the_same_however_many_stripes_there_are() {
23062        let script: &[&[&[u8]]] = &[
23063            &[b"RPUSH", b"l1", b"a", b"b", b"c"],
23064            &[b"LPUSH", b"l1", b"z"],
23065            &[b"RPUSHX", b"l1", b"d"],
23066            &[b"LPUSHX", b"gone", b"x"],
23067            &[b"RPUSHX", b"gone", b"x"],
23068            &[b"LLEN", b"l1"],
23069            &[b"LLEN", b"gone"],
23070            &[b"LRANGE", b"l1", b"0", b"-1"],
23071            &[b"LRANGE", b"l1", b"1", b"2"],
23072            &[b"LRANGE", b"l1", b"5", b"9"],
23073            &[b"LINDEX", b"l1", b"0"],
23074            &[b"LINDEX", b"l1", b"-1"],
23075            &[b"LINDEX", b"l1", b"99"],
23076            &[b"LSET", b"l1", b"0", b"y"],
23077            &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
23078            &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
23079            &[b"LPOS", b"l1", b"b"],
23080            &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
23081            &[b"LPOS", b"l1", b"nothere"],
23082            &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
23083            &[b"LREM", b"l1", b"1", b"aa"],
23084            &[b"LTRIM", b"l1", b"0", b"3"],
23085            &[b"LRANGE", b"l1", b"0", b"-1"],
23086            &[b"LPOP", b"l1"],
23087            &[b"RPOP", b"l1"],
23088            &[b"LPOP", b"l1", b"2"],
23089            &[b"LPOP", b"gone"],
23090            &[b"LPOP", b"gone", b"2"],
23091            &[b"EXISTS", b"l1"],
23092            // The ones that name two keys, and the one that takes a block of
23093            // elements rather than the one on the end.
23094            &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
23095            &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
23096            &[b"RPOPLPUSH", b"src", b"dst"],
23097            &[b"LRANGE", b"dst", b"0", b"-1"],
23098            &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
23099            &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
23100            &[
23101                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
23102            ],
23103            &[
23104                b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
23105            ],
23106            &[b"LRANGE", b"dst", b"0", b"-1"],
23107            &[
23108                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
23109            ],
23110            &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
23111            &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
23112            &[b"LMPOP", b"1", b"gone", b"LEFT"],
23113            // The blocking ones, first with something there to answer them and
23114            // then with nothing, which parks the client and writes nothing.
23115            &[b"RPUSH", b"q", b"a", b"b", b"c"],
23116            &[b"BLPOP", b"gone", b"q", b"0"],
23117            &[b"BRPOP", b"q", b"0"],
23118            &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
23119            &[b"RPUSH", b"q", b"x", b"y", b"z"],
23120            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
23121            &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
23122            &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
23123            &[b"BLPOP", b"q", b"0"],
23124            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
23125            // The errors, which have to be the same errors.
23126            &[b"SET", b"plain", b"v"],
23127            &[b"LPUSH", b"plain", b"a"],
23128            &[b"LLEN", b"plain"],
23129            &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
23130            &[b"LRANGE", b"dst", b"0", b"-1"],
23131            &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
23132            &[b"LSET", b"gone", b"0", b"v"],
23133            &[b"LSET", b"dst", b"99", b"v"],
23134            &[b"LPOP", b"dst", b"-1"],
23135            &[b"LMPOP", b"0", b"dst", b"LEFT"],
23136            &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
23137        ];
23138
23139        let mut one = Fixture::new();
23140        let mut many = Fixture::striped(8);
23141        for parts in script {
23142            let a = one.run(parts);
23143            let b = many.run(parts);
23144            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23145        }
23146    }
23147
23148    /// An `LMOVE` and an `LMOVEM` whose two keys are on two stripes.
23149    #[test]
23150    fn a_list_move_across_stripes_takes_the_elements_with_it() {
23151        let mut f = Fixture::striped(8);
23152        let other = apart(&mut f, "src");
23153        let third = apart(&mut f, &other);
23154        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
23155
23156        f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
23157        assert_eq!(
23158            f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
23159            "$1\r\na\r\n"
23160        );
23161        assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
23162        assert_eq!(
23163            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
23164            "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
23165            "one went on each end of the destination"
23166        );
23167        assert_eq!(
23168            f.run(&[b"LRANGE", src, b"0", b"-1"]),
23169            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
23170        );
23171
23172        // A block of them, which under BULK arrives in the order it left.
23173        assert_eq!(
23174            f.run(&[
23175                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
23176            ]),
23177            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
23178        );
23179        assert_eq!(
23180            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
23181            "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
23182        );
23183        assert_eq!(
23184            f.run(&[b"EXISTS", src]),
23185            ":0\r\n",
23186            "and the source is gone with its last element"
23187        );
23188
23189        // An `EXACTLY` the source cannot fill moves nothing, and a source that
23190        // is not there at all is the two kinds of nothing the two commands have.
23191        f.run(&[b"RPUSH", src, b"e", b"f"]);
23192        assert_eq!(
23193            f.run(&[
23194                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
23195            ]),
23196            "*-1\r\n"
23197        );
23198        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
23199        assert_eq!(
23200            f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
23201            "$-1\r\n"
23202        );
23203        assert_eq!(
23204            f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
23205            "*-1\r\n"
23206        );
23207
23208        // A destination of the wrong type is refused before anything is taken,
23209        // which is the order that matters most here, since an element already
23210        // out of the source would have nowhere to go back to.
23211        f.run(&[b"SET", plain, b"v"]);
23212        assert_eq!(
23213            f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
23214            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23215        );
23216        assert_eq!(
23217            f.run(&[b"LLEN", src]),
23218            ":2\r\n",
23219            "and left the source alone"
23220        );
23221        assert_eq!(
23222            f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
23223            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23224        );
23225        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
23226    }
23227
23228    /// A parked client served by a push that landed on another stripe.
23229    ///
23230    /// A waiter remembers the database and not the stripe, which is the point:
23231    /// serving it runs the same attempt the command ran, and the attempt finds
23232    /// the stripe each of its keys is on for itself.
23233    #[test]
23234    fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
23235        let mut f = Fixture::striped(8);
23236        let other = apart(&mut f, "q");
23237        let (q, far) = (b"q".as_slice(), other.as_bytes());
23238
23239        assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
23240        assert_eq!(f.server.parked(), 1);
23241        f.run(&[b"RPUSH", far, b"v"]);
23242        let mut out = Out::new(Proto::Resp2);
23243        assert!(f.server.serve_waiter(7, 0, &mut out));
23244        let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
23245        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
23246        assert_eq!(
23247            f.run(&[b"EXISTS", far]),
23248            ":0\r\n",
23249            "and it took the element with it"
23250        );
23251
23252        // And a move across two stripes is served the same way, by the push
23253        // that fills its source.
23254        f.server.forget_waiters(7);
23255        assert_eq!(
23256            f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
23257            Flow::Block
23258        );
23259        f.run(&[b"RPUSH", q, b"w"]);
23260        let mut out = Out::new(Proto::Resp2);
23261        assert!(f.server.serve_waiter(7, 0, &mut out));
23262        assert_eq!(
23263            core::str::from_utf8(out.as_slice()).expect("ascii"),
23264            "$1\r\nw\r\n"
23265        );
23266        assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
23267    }
23268
23269    /// Every stream command, on one stripe and on eight.
23270    ///
23271    /// Every ID is written out rather than left to the clock, so the two servers
23272    /// are being compared on what they store and not on how long the test took
23273    /// to get from one of them to the other.
23274    #[test]
23275    fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
23276        let script: &[&[&[u8]]] = &[
23277            &[b"XADD", b"s", b"1-1", b"a", b"1"],
23278            &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
23279            &[b"XADD", b"s", b"3-1", b"d", b"4"],
23280            &[b"XADD", b"s", b"1-1", b"e", b"5"],
23281            &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
23282            &[b"XLEN", b"s"],
23283            &[b"XLEN", b"gone"],
23284            &[b"XRANGE", b"s", b"-", b"+"],
23285            &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
23286            &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
23287            &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
23288            &[b"XREVRANGE", b"s", b"+", b"-"],
23289            &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
23290            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
23291            &[b"XREAD", b"STREAMS", b"s", b"$"],
23292            // The groups, which is where most of the state is.
23293            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
23294            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
23295            &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
23296            &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
23297            &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
23298            &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
23299            &[
23300                b"XREADGROUP",
23301                b"GROUP",
23302                b"g",
23303                b"c1",
23304                b"COUNT",
23305                b"1",
23306                b"STREAMS",
23307                b"s",
23308                b"0",
23309            ],
23310            &[
23311                b"XREADGROUP",
23312                b"GROUP",
23313                b"nope",
23314                b"c1",
23315                b"STREAMS",
23316                b"s",
23317                b">",
23318            ],
23319            &[b"XPENDING", b"s", b"g"],
23320            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
23321            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
23322            &[b"XPENDING", b"s", b"nope"],
23323            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
23324            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
23325            &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
23326            &[b"XACK", b"s", b"g", b"1-1"],
23327            &[b"XACK", b"s", b"g", b"1-1"],
23328            &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
23329            &[b"XPENDING", b"s", b"g"],
23330            &[b"XINFO", b"STREAM", b"s"],
23331            &[b"XINFO", b"GROUPS", b"s"],
23332            &[b"XINFO", b"CONSUMERS", b"s", b"g"],
23333            &[b"XINFO", b"STREAM", b"gone"],
23334            // Deleting, trimming and moving the ID on.
23335            &[b"XDEL", b"s", b"3-1"],
23336            &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
23337            &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
23338            &[b"XADD", b"s", b"9-1", b"z", b"9"],
23339            &[b"XTRIM", b"s", b"MAXLEN", b"1"],
23340            &[b"XTRIM", b"s", b"MINID", b"9"],
23341            &[b"XSETID", b"s", b"99-1"],
23342            &[b"XSETID", b"s", b"1-1"],
23343            &[b"XLEN", b"s"],
23344            &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
23345            &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
23346            &[b"XGROUP", b"DESTROY", b"s", b"g"],
23347            &[b"XGROUP", b"DESTROY", b"s", b"g"],
23348            // And the errors.
23349            &[b"SET", b"plain", b"v"],
23350            &[b"XADD", b"plain", b"1-1", b"a", b"1"],
23351            &[b"XLEN", b"plain"],
23352            &[b"XREAD", b"STREAMS", b"plain", b"0"],
23353            &[b"XRANGE", b"s", b"bogus", b"+"],
23354            &[b"XADD", b"s", b"1-1", b"a"],
23355            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
23356            &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
23357        ];
23358
23359        let mut one = Fixture::new();
23360        let mut many = Fixture::striped(8);
23361        for parts in script {
23362            let a = one.run(parts);
23363            let b = many.run(parts);
23364            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23365        }
23366    }
23367
23368    /// An `XREAD` and an `XREADGROUP` naming two keys on two stripes.
23369    ///
23370    /// Nothing is shared between the two streams, so the only thing this can go
23371    /// wrong at is looking both of them up, which is exactly what a read that
23372    /// held one database and walked it would get wrong.
23373    #[test]
23374    fn a_stream_read_across_stripes_reads_every_key() {
23375        let mut f = Fixture::striped(8);
23376        let other = apart(&mut f, "s1");
23377        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
23378
23379        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
23380        f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
23381        let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
23382        assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
23383        assert!(got.contains("1-1"), "the first one is in there: {got}");
23384        assert!(got.contains("2-1"), "and so is the second: {got}");
23385
23386        // A group read looks its group up on every key before it reads any of
23387        // them, so a group that is missing on the far key stops the near one.
23388        f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
23389        let got = f.run(&[
23390            b"XREADGROUP",
23391            b"GROUP",
23392            b"g",
23393            b"c",
23394            b"STREAMS",
23395            s1,
23396            s2,
23397            b">",
23398            b">",
23399        ]);
23400        assert!(got.starts_with("-NOGROUP"), "{got}");
23401        assert_eq!(
23402            f.run(&[b"XPENDING", s1, b"g"]),
23403            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
23404            "and read nothing from the key that did have the group"
23405        );
23406
23407        f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
23408        let got = f.run(&[
23409            b"XREADGROUP",
23410            b"GROUP",
23411            b"g",
23412            b"c",
23413            b"STREAMS",
23414            s1,
23415            s2,
23416            b">",
23417            b">",
23418        ]);
23419        assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
23420    }
23421
23422    /// A client parked on an `XREAD` woken by an entry on another stripe.
23423    #[test]
23424    fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
23425        let mut f = Fixture::striped(8);
23426        let other = apart(&mut f, "s1");
23427        let (s1, far) = (b"s1".as_slice(), other.as_bytes());
23428        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
23429        f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
23430
23431        assert_eq!(
23432            f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
23433                .0,
23434            Flow::Block
23435        );
23436        f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
23437        let mut out = Out::new(Proto::Resp2);
23438        assert!(f.server.serve_waiter(7, 0, &mut out));
23439        let want = format!(
23440            "*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",
23441            other.len()
23442        );
23443        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
23444    }
23445
23446    /// Every JSON command, on one stripe and on eight.
23447    #[test]
23448    fn the_json_group_answers_the_same_however_many_stripes_there_are() {
23449        let script: &[&[&[u8]]] = &[
23450            &[
23451                b"JSON.SET",
23452                b"d",
23453                b"$",
23454                br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
23455            ],
23456            &[b"JSON.SET", b"d", b"$.a", b"2"],
23457            &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
23458            &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
23459            &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
23460            &[b"JSON.GET", b"d"],
23461            &[b"JSON.GET", b"d", b"$.b"],
23462            &[b"JSON.GET", b"gone", b"$"],
23463            &[b"JSON.TYPE", b"d", b"$.b"],
23464            &[b"JSON.TYPE", b"d", b"$.s"],
23465            &[b"JSON.TOGGLE", b"d", b"$.t"],
23466            &[b"JSON.ARRLEN", b"d", b"$.b"],
23467            &[b"JSON.OBJLEN", b"d", b"$"],
23468            &[b"JSON.OBJKEYS", b"d", b"$"],
23469            &[b"JSON.STRLEN", b"d", b"$.s"],
23470            &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
23471            &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
23472            &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
23473            &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
23474            &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
23475            &[b"JSON.ARRPOP", b"d", b"$.b"],
23476            &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
23477            &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
23478            &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
23479            &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
23480            &[b"JSON.RESP", b"d", b"$.b"],
23481            &[b"JSON.DEBUG", b"MEMORY", b"d"],
23482            &[b"JSON.CLEAR", b"d", b"$.b"],
23483            &[b"JSON.DEL", b"d", b"$.m"],
23484            &[b"JSON.FORGET", b"d", b"$.nothere"],
23485            // The two that name more than one key.
23486            &[
23487                b"JSON.MSET",
23488                b"m1",
23489                b"$",
23490                b"1",
23491                b"m2",
23492                b"$",
23493                b"2",
23494                b"m3",
23495                b"$",
23496                b"3",
23497            ],
23498            &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
23499            &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
23500            &[b"JSON.GET", b"m1", b"$"],
23501            &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
23502            &[b"JSON.GET", b"m2", b"$"],
23503            // And the errors.
23504            &[b"SET", b"plain", b"v"],
23505            &[b"JSON.GET", b"plain", b"$"],
23506            &[b"JSON.SET", b"plain", b"$", b"1"],
23507            &[b"JSON.MGET", b"m1", b"plain", b"$"],
23508            &[b"JSON.SET", b"d", b"$.b", b"["],
23509            &[b"JSON.DEL", b"plain"],
23510        ];
23511
23512        let mut one = Fixture::new();
23513        let mut many = Fixture::striped(8);
23514        for parts in script {
23515            let a = one.run(parts);
23516            let b = many.run(parts);
23517            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23518        }
23519    }
23520
23521    /// A `JSON.MSET` and a `JSON.MGET` whose keys are on several stripes.
23522    ///
23523    /// `JSON.MSET` works every triple out against the keyspace as it was before
23524    /// the command and writes nothing until all of them are known to work, so
23525    /// the thing to check is that a triple that cannot be written stops the
23526    /// ones on other stripes as well as the ones on its own.
23527    #[test]
23528    fn a_json_multi_write_across_stripes_reaches_every_key() {
23529        let mut f = Fixture::striped(8);
23530        let second = apart(&mut f, "m1");
23531        let third = apart(&mut f, &second);
23532        let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
23533
23534        assert_eq!(
23535            f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
23536            "+OK\r\n"
23537        );
23538        assert_eq!(
23539            f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
23540            "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
23541        );
23542
23543        // A value that is not JSON is refused before anything is written, and
23544        // the key on the far stripe keeps what it had.
23545        assert_eq!(
23546            f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
23547            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
23548        );
23549        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
23550
23551        // A path that names nowhere is not an error. That triple is skipped,
23552        // the ones on the other stripes are still written, and the reply is a
23553        // nil rather than OK.
23554        assert_eq!(
23555            f.run(&[
23556                b"JSON.MSET",
23557                m1,
23558                b"$",
23559                b"9",
23560                m2,
23561                b"$.deep",
23562                b"9",
23563                m3,
23564                b"$",
23565                b"7"
23566            ]),
23567            "$-1\r\n"
23568        );
23569        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
23570        assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
23571        assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
23572    }
23573
23574    /// Every geospatial command, on one stripe and on eight.
23575    #[test]
23576    fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
23577        let script: &[&[&[u8]]] = &[
23578            &[
23579                b"GEOADD",
23580                b"g",
23581                b"13.361389",
23582                b"38.115556",
23583                b"palermo",
23584                b"15.087269",
23585                b"37.502669",
23586                b"catania",
23587            ],
23588            &[
23589                b"GEOADD",
23590                b"g",
23591                b"NX",
23592                b"13.361389",
23593                b"38.115556",
23594                b"palermo",
23595            ],
23596            &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
23597            &[b"GEOPOS", b"g", b"palermo", b"nothere"],
23598            &[b"GEOHASH", b"g", b"palermo", b"catania"],
23599            &[b"GEODIST", b"g", b"palermo", b"catania"],
23600            &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
23601            &[b"GEODIST", b"g", b"palermo", b"nothere"],
23602            &[
23603                b"GEOSEARCH",
23604                b"g",
23605                b"FROMLONLAT",
23606                b"15",
23607                b"37",
23608                b"BYRADIUS",
23609                b"200",
23610                b"KM",
23611                b"ASC",
23612                b"WITHCOORD",
23613                b"WITHDIST",
23614                b"WITHHASH",
23615            ],
23616            &[
23617                b"GEOSEARCH",
23618                b"g",
23619                b"FROMMEMBER",
23620                b"palermo",
23621                b"BYBOX",
23622                b"400",
23623                b"400",
23624                b"KM",
23625                b"DESC",
23626            ],
23627            &[
23628                b"GEORADIUS",
23629                b"g",
23630                b"15",
23631                b"37",
23632                b"200",
23633                b"KM",
23634                b"COUNT",
23635                b"1",
23636            ],
23637            &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
23638            &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
23639            &[
23640                b"GEOSEARCHSTORE",
23641                b"dst",
23642                b"g",
23643                b"FROMLONLAT",
23644                b"15",
23645                b"37",
23646                b"BYRADIUS",
23647                b"200",
23648                b"KM",
23649            ],
23650            &[b"ZRANGE", b"dst", b"0", b"-1"],
23651            &[
23652                b"GEOSEARCHSTORE",
23653                b"dst",
23654                b"g",
23655                b"FROMLONLAT",
23656                b"15",
23657                b"37",
23658                b"BYRADIUS",
23659                b"1",
23660                b"M",
23661                b"STOREDIST",
23662            ],
23663            &[b"EXISTS", b"dst"],
23664            &[
23665                b"GEORADIUS",
23666                b"g",
23667                b"15",
23668                b"37",
23669                b"200",
23670                b"KM",
23671                b"STORE",
23672                b"dst",
23673            ],
23674            &[b"ZCARD", b"dst"],
23675            // And the errors.
23676            &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
23677            &[b"SET", b"plain", b"v"],
23678            &[b"GEOPOS", b"plain", b"a"],
23679            &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
23680            &[
23681                b"GEOSEARCHSTORE",
23682                b"dst",
23683                b"g",
23684                b"FROMLONLAT",
23685                b"15",
23686                b"37",
23687                b"BYRADIUS",
23688                b"200",
23689                b"KM",
23690                b"WITHCOORD",
23691            ],
23692        ];
23693
23694        let mut one = Fixture::new();
23695        let mut many = Fixture::striped(8);
23696        for parts in script {
23697            let a = one.run(parts);
23698            let b = many.run(parts);
23699            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23700        }
23701    }
23702
23703    /// A `GEOSEARCHSTORE` whose two keys are on two stripes.
23704    #[test]
23705    fn a_geo_search_store_across_stripes_writes_what_it_found() {
23706        let mut f = Fixture::striped(8);
23707        let other = apart(&mut f, "g");
23708        let third = apart(&mut f, &other);
23709        let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
23710
23711        f.run(&[
23712            b"GEOADD",
23713            g,
23714            b"13.361389",
23715            b"38.115556",
23716            b"palermo",
23717            b"15.087269",
23718            b"37.502669",
23719            b"catania",
23720        ]);
23721        assert_eq!(
23722            f.run(&[
23723                b"GEOSEARCHSTORE",
23724                dst,
23725                g,
23726                b"FROMLONLAT",
23727                b"15",
23728                b"37",
23729                b"BYRADIUS",
23730                b"200",
23731                b"KM",
23732                b"ASC",
23733            ]),
23734            ":2\r\n"
23735        );
23736        assert_eq!(
23737            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23738            "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
23739            "the geohash is the score, so the order is not the search order"
23740        );
23741        assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
23742
23743        // `STOREDIST` stores the distance in the unit the search was asked in,
23744        // which is the destination stripe's sorted set and not the source's.
23745        assert_eq!(
23746            f.run(&[
23747                b"GEOSEARCHSTORE",
23748                dst,
23749                g,
23750                b"FROMMEMBER",
23751                b"palermo",
23752                b"BYRADIUS",
23753                b"200",
23754                b"KM",
23755                b"STOREDIST",
23756            ]),
23757            ":2\r\n"
23758        );
23759        assert_eq!(
23760            f.run(&[b"ZSCORE", dst, b"palermo"]),
23761            "$1\r\n0\r\n",
23762            "the centre is nought away from itself"
23763        );
23764
23765        // A search that found nothing deletes the destination on its own
23766        // stripe, and a source of the wrong type is refused with the
23767        // destination left alone.
23768        assert_eq!(
23769            f.run(&[
23770                b"GEOSEARCHSTORE",
23771                dst,
23772                g,
23773                b"FROMLONLAT",
23774                b"0",
23775                b"0",
23776                b"BYRADIUS",
23777                b"1",
23778                b"M",
23779            ]),
23780            ":0\r\n"
23781        );
23782        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23783        f.run(&[
23784            b"GEOSEARCHSTORE",
23785            dst,
23786            g,
23787            b"FROMLONLAT",
23788            b"15",
23789            b"37",
23790            b"BYRADIUS",
23791            b"200",
23792            b"KM",
23793        ]);
23794        f.run(&[b"SET", plain, b"v"]);
23795        assert_eq!(
23796            f.run(&[
23797                b"GEOSEARCHSTORE",
23798                dst,
23799                plain,
23800                b"FROMLONLAT",
23801                b"15",
23802                b"37",
23803                b"BYRADIUS",
23804                b"200",
23805                b"KM",
23806            ]),
23807            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23808        );
23809        assert_eq!(
23810            f.run(&[b"ZCARD", dst]),
23811            ":2\r\n",
23812            "and left the destination"
23813        );
23814    }
23815
23816    /// Every time series command, on one stripe and on eight.
23817    ///
23818    /// Every timestamp is written out rather than left to the clock, so the two
23819    /// servers are compared on the samples they hold and not on how long the
23820    /// test took to get from one of them to the other.
23821    #[test]
23822    fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
23823        let script: &[&[&[u8]]] = &[
23824            &[
23825                b"TS.CREATE",
23826                b"ts:a",
23827                b"LABELS",
23828                b"sensor",
23829                b"a",
23830                b"room",
23831                b"1",
23832            ],
23833            &[b"TS.CREATE", b"ts:a"],
23834            &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
23835            &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
23836            &[
23837                b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
23838            ],
23839            &[
23840                b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
23841            ],
23842            &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
23843            &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
23844            &[b"TS.GET", b"ts:a"],
23845            &[b"TS.GET", b"gone"],
23846            &[b"TS.RANGE", b"ts:a", b"-", b"+"],
23847            &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
23848            &[
23849                b"TS.RANGE",
23850                b"ts:a",
23851                b"-",
23852                b"+",
23853                b"AGGREGATION",
23854                b"avg",
23855                b"2000",
23856            ],
23857            &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
23858            &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23859            &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23860            &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
23861            &[b"TS.READ", b"ts:a", b"0"],
23862            &[b"TS.READ", b"ts:a", b"+"],
23863            // The filters, which are the ones that have to walk every stripe.
23864            &[b"TS.QUERYINDEX", b"sensor=a"],
23865            &[b"TS.QUERYINDEX", b"room=1"],
23866            &[b"TS.QUERYINDEX", b"room=9"],
23867            &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
23868            &[
23869                b"TS.QUERYLABELS",
23870                b"VALUES",
23871                b"sensor",
23872                b"FILTER",
23873                b"room=1",
23874            ],
23875            &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
23876            &[
23877                b"TS.MGET",
23878                b"SELECTED_LABELS",
23879                b"sensor",
23880                b"FILTER",
23881                b"sensor=a",
23882            ],
23883            &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
23884            &[
23885                b"TS.MREVRANGE",
23886                b"-",
23887                b"+",
23888                b"WITHLABELS",
23889                b"FILTER",
23890                b"sensor=a",
23891            ],
23892            &[
23893                b"TS.MRANGE",
23894                b"-",
23895                b"+",
23896                b"FILTER",
23897                b"room=1",
23898                b"GROUPBY",
23899                b"room",
23900                b"REDUCE",
23901                b"max",
23902            ],
23903            &[b"TS.INFO", b"ts:a"],
23904            // And a rule, which is the one thing here that names two keys.
23905            &[
23906                b"TS.CREATERULE",
23907                b"ts:a",
23908                b"ts:down",
23909                b"AGGREGATION",
23910                b"avg",
23911                b"1000",
23912            ],
23913            &[b"TS.CREATE", b"ts:down"],
23914            &[
23915                b"TS.CREATERULE",
23916                b"ts:a",
23917                b"ts:down",
23918                b"AGGREGATION",
23919                b"avg",
23920                b"1000",
23921            ],
23922            &[b"TS.ADD", b"ts:a", b"5000", b"4"],
23923            &[b"TS.ADD", b"ts:a", b"6000", b"5"],
23924            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23925            &[b"TS.GET", b"ts:down", b"LATEST"],
23926            &[b"TS.INFO", b"ts:down"],
23927            &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
23928            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23929            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23930            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23931            &[b"TS.DEL", b"ts:a", b"0", b"1000"],
23932            // And the errors.
23933            &[b"SET", b"plain", b"v"],
23934            &[b"TS.ADD", b"plain", b"1", b"1"],
23935            &[b"TS.GET", b"plain"],
23936            &[b"TS.READ", b"plain", b"0"],
23937            &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
23938            &[b"TS.RANGE", b"gone", b"-", b"+"],
23939            &[b"TS.INFO", b"gone"],
23940        ];
23941
23942        let mut one = Fixture::new();
23943        let mut many = Fixture::striped(8);
23944        for parts in script {
23945            let a = one.run(parts);
23946            let b = many.run(parts);
23947            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23948        }
23949    }
23950
23951    /// A compaction rule whose two ends are on two stripes.
23952    ///
23953    /// This is the one thing in the family that walks from a key to another key,
23954    /// and it walks it in both directions: a sample on the source closes a
23955    /// bucket on the destination, a `LATEST` read on the destination folds the
23956    /// bucket the source is still filling, and a delete on the source rewrites
23957    /// what the destination already held. The same script is run against a
23958    /// server one stripe wide, where the two keys share a store, and against one
23959    /// eight stripes wide, where they do not.
23960    #[test]
23961    fn a_compaction_rule_across_stripes_reaches_both_ends() {
23962        let mut many = Fixture::striped(8);
23963        let other = apart(&mut many, "src");
23964        let (src, dst) = (b"src".as_slice(), other.as_bytes());
23965        let mut one = Fixture::new();
23966        let mut both = |parts: &[&[u8]]| {
23967            let a = one.run(parts);
23968            let b = many.run(parts);
23969            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23970            a
23971        };
23972
23973        both(&[b"TS.CREATE", src]);
23974        both(&[b"TS.CREATE", dst]);
23975        assert_eq!(
23976            both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
23977            "+OK\r\n"
23978        );
23979        both(&[b"TS.ADD", src, b"1000", b"1"]);
23980        both(&[b"TS.ADD", src, b"1500", b"3"]);
23981        // The bucket the source is filling is not written down yet, and asking
23982        // for it works it out off the source.
23983        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23984        let open = both(&[b"TS.GET", dst, b"LATEST"]);
23985        assert!(open.contains(":1000"), "the open bucket is folded: {open}");
23986
23987        // A sample past the bucket closes it, which is the write that has to
23988        // land on the other stripe.
23989        both(&[b"TS.ADD", src, b"2000", b"5"]);
23990        let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
23991        assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
23992        assert!(got.contains(":1000"), "{got}");
23993
23994        // And a delete on the source takes it away again.
23995        both(&[b"TS.DEL", src, b"1000", b"1999"]);
23996        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23997
23998        // Both ends still know about each other, and the link comes apart from
23999        // the source.
24000        assert!(
24001            both(&[b"TS.INFO", dst]).contains("src"),
24002            "the source is named"
24003        );
24004        assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
24005        assert_eq!(
24006            both(&[b"TS.DELETERULE", src, dst]),
24007            "-ERR TSDB: compaction rule does not exist\r\n"
24008        );
24009    }
24010
24011    /// A label filter takes the series it names wherever they landed.
24012    #[test]
24013    fn a_label_query_across_stripes_finds_every_series() {
24014        let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
24015        let mut many = Fixture::striped(8);
24016        let mut homes: Vec<usize> = names
24017            .iter()
24018            .map(|name| many.server.striped(0).stripe_of(name))
24019            .collect();
24020        homes.sort_unstable();
24021        homes.dedup();
24022        assert!(homes.len() > 1, "the six keys are not all on one stripe");
24023
24024        let mut one = Fixture::new();
24025        let mut both = |parts: &[&[u8]]| {
24026            let a = one.run(parts);
24027            let b = many.run(parts);
24028            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24029            a
24030        };
24031        for name in &names {
24032            both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
24033            both(&[b"TS.ADD", name, b"1000", b"1"]);
24034        }
24035
24036        let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
24037        assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
24038        assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
24039        assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
24040        assert_eq!(
24041            both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
24042            "*1\r\n$4\r\nroom\r\n"
24043        );
24044    }
24045
24046    /// Every hash command, and the field import beside it, on one stripe and on
24047    /// eight.
24048    ///
24049    /// `HRANDFIELD` with a count draws from the stripe's own generator and two
24050    /// stripes do not draw the same numbers, so the only draw here is off a hash
24051    /// holding one field, where every generator gives the same answer.
24052    #[test]
24053    fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
24054        let script: &[&[&[u8]]] = &[
24055            &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
24056            &[b"HMSET", b"h", b"c", b"3"],
24057            &[b"HSETNX", b"h", b"a", b"9"],
24058            &[b"HSETNX", b"h", b"d", b"4"],
24059            &[b"HGET", b"h", b"a"],
24060            &[b"HGET", b"h", b"nope"],
24061            &[b"HMGET", b"h", b"a", b"nope"],
24062            &[b"HLEN", b"h"],
24063            &[b"HEXISTS", b"h", b"a"],
24064            &[b"HSTRLEN", b"h", b"a"],
24065            &[b"HGETALL", b"h"],
24066            &[b"HKEYS", b"h"],
24067            &[b"HVALS", b"h"],
24068            &[b"HINCRBY", b"h", b"a", b"5"],
24069            &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
24070            &[b"HSCAN", b"h", b"0"],
24071            &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
24072            &[b"HSCAN", b"h", b"0", b"NOVALUES"],
24073            &[b"HDEL", b"h", b"d"],
24074            &[b"HSET", b"one", b"f", b"v"],
24075            &[b"HRANDFIELD", b"one"],
24076            &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
24077            // The field deadlines.
24078            &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
24079            &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
24080            &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
24081            &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
24082            &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
24083            &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
24084            &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
24085            &[b"HGET", b"h", b"b"],
24086            // The three that came later and word everything their own way.
24087            &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
24088            &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
24089            &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
24090            &[b"HGET", b"h", b"e"],
24091            // And the import, whose key is the third word.
24092            &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
24093            &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
24094            &[b"HGETALL", b"imp"],
24095            &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
24096            &[b"HIMPORT", b"DISCARD", b"fs"],
24097            // And the errors.
24098            &[b"SET", b"plain", b"v"],
24099            &[b"HSET", b"plain", b"a", b"1"],
24100            &[b"HGETALL", b"plain"],
24101            &[b"HGET", b"gone", b"a"],
24102            &[b"HINCRBY", b"h", b"a", b"nan"],
24103        ];
24104
24105        let mut one = Fixture::new();
24106        let mut many = Fixture::striped(8);
24107        // The field deadlines are absolute milliseconds worked out from the
24108        // clock, so both servers are put on the same one rather than left to
24109        // read the wall a moment apart.
24110        one.server.set_clock_ms(1_700_000_000_000);
24111        many.server.set_clock_ms(1_700_000_000_000);
24112        for parts in script {
24113            let a = one.run(parts);
24114            let b = many.run(parts);
24115            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24116        }
24117    }
24118
24119    /// Every array command, on one stripe and on eight.
24120    #[test]
24121    fn the_array_group_answers_the_same_however_many_stripes_there_are() {
24122        let script: &[&[&[u8]]] = &[
24123            &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
24124            &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
24125            &[b"ARGET", b"a", b"1"],
24126            &[b"ARGET", b"a", b"99"],
24127            &[b"ARMGET", b"a", b"0", b"5", b"99"],
24128            &[b"ARGETRANGE", b"a", b"0", b"7"],
24129            &[b"ARLEN", b"a"],
24130            &[b"ARCOUNT", b"a"],
24131            &[b"ARINSERT", b"a", b"m", b"n"],
24132            &[b"ARSCAN", b"a", b"0", b"20"],
24133            &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
24134            &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
24135            &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
24136            &[b"ARLASTITEMS", b"a", b"2"],
24137            &[b"ARLASTITEMS", b"a", b"2", b"REV"],
24138            &[b"ARNEXT", b"a"],
24139            &[b"ARSEEK", b"a", b"3"],
24140            &[b"AROP", b"a", b"0", b"20", b"USED"],
24141            &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
24142            &[b"ARINFO", b"a"],
24143            &[b"ARINFO", b"a", b"FULL"],
24144            &[b"ARDEL", b"a", b"0"],
24145            &[b"ARDELRANGE", b"a", b"1", b"2"],
24146            &[b"ARCOUNT", b"a"],
24147            &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
24148            &[b"ARGETRANGE", b"r", b"0", b"9"],
24149            // And the errors.
24150            &[b"SET", b"plain", b"v"],
24151            &[b"ARGET", b"plain", b"0"],
24152            &[b"ARSET", b"plain", b"0", b"v"],
24153            &[b"ARGET", b"gone", b"0"],
24154            &[b"ARSET", b"a", b"bad", b"v"],
24155        ];
24156
24157        let mut one = Fixture::new();
24158        let mut many = Fixture::striped(8);
24159        for parts in script {
24160            let a = one.run(parts);
24161            let b = many.run(parts);
24162            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24163        }
24164    }
24165
24166    /// Every graph and vector set command, on one stripe and on eight.
24167    ///
24168    /// `VRANDMEMBER` is not in here for the reason `HRANDFIELD` with a count is
24169    /// not: it draws from the stripe's generator, and the stripes do not share
24170    /// one.
24171    #[test]
24172    fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
24173        let script: &[&[&[u8]]] = &[
24174            &[b"G.NADD", b"g", b"n1", b"name", b"one"],
24175            &[b"G.NADD", b"g", b"n2", b"name", b"two"],
24176            &[b"G.NADD", b"g", b"n3"],
24177            &[b"G.NGET", b"g", b"n1"],
24178            &[b"G.NGET", b"g", b"gone"],
24179            &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
24180            &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
24181            &[b"G.OUT", b"g", b"n1", b"knows"],
24182            &[b"G.IN", b"g", b"n2", b"knows"],
24183            &[b"G.DEG", b"g", b"n1", b"knows"],
24184            &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
24185            &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
24186            &[b"G.PATH", b"g", b"n1", b"n3"],
24187            &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
24188            &[b"G.NDEL", b"g", b"n3"],
24189            &[b"G.NGET", b"g", b"n3"],
24190            // The vector set, which is one index under one key.
24191            &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
24192            &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
24193            &[b"VCARD", b"v"],
24194            &[b"VDIM", b"v"],
24195            &[b"VEMB", b"v", b"e1"],
24196            &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
24197            &[b"VSIM", b"v", b"ELE", b"e1"],
24198            &[b"VISMEMBER", b"v", b"e1"],
24199            &[b"VISMEMBER", b"v", b"gone"],
24200            &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
24201            &[b"VGETATTR", b"v", b"e1"],
24202            &[b"VRANGE", b"v", b"-", b"+"],
24203            &[b"VLINKS", b"v", b"e1"],
24204            &[b"VINFO", b"v"],
24205            &[b"VREM", b"v", b"e2"],
24206            &[b"VCARD", b"v"],
24207            // And the errors.
24208            &[b"SET", b"plain", b"v"],
24209            &[b"G.NGET", b"plain", b"n1"],
24210            &[b"VCARD", b"plain"],
24211            &[b"G.NADD", b"gone2", b"n"],
24212            &[b"VEMB", b"gone3", b"e"],
24213        ];
24214
24215        let mut one = Fixture::new();
24216        let mut many = Fixture::striped(8);
24217        for parts in script {
24218            let a = one.run(parts);
24219            let b = many.run(parts);
24220            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24221        }
24222    }
24223
24224    /// Every bloom filter, cuckoo filter, count min sketch, top k and t digest
24225    /// command, on one stripe and on eight.
24226    #[test]
24227    fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
24228        let script: &[&[&[u8]]] = &[
24229            // The bloom filter.
24230            &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
24231            &[b"BF.ADD", b"bf", b"a"],
24232            &[b"BF.ADD", b"bf", b"a"],
24233            &[b"BF.MADD", b"bf", b"b", b"c"],
24234            &[b"BF.EXISTS", b"bf", b"a"],
24235            &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
24236            &[b"BF.CARD", b"bf"],
24237            &[b"BF.INFO", b"bf"],
24238            &[b"BF.INFO", b"bf", b"CAPACITY"],
24239            &[b"BF.DEBUG", b"bf"],
24240            &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
24241            &[b"BF.EXISTS", b"made", b"x"],
24242            &[b"BF.SCANDUMP", b"bf", b"0"],
24243            // The cuckoo filter.
24244            &[b"CF.RESERVE", b"cf", b"100"],
24245            &[b"CF.ADD", b"cf", b"a"],
24246            &[b"CF.ADDNX", b"cf", b"a"],
24247            &[b"CF.COUNT", b"cf", b"a"],
24248            &[b"CF.EXISTS", b"cf", b"a"],
24249            &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
24250            &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
24251            &[b"CF.DEL", b"cf", b"a"],
24252            &[b"CF.COMPACT", b"cf"],
24253            &[b"CF.INFO", b"cf"],
24254            &[b"CF.DEBUG", b"cf"],
24255            &[b"CF.SCANDUMP", b"cf", b"0"],
24256            // The count min sketch.
24257            &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
24258            &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
24259            &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
24260            &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
24261            &[b"CMS.INFO", b"cms"],
24262            // The top k sketch.
24263            &[b"TOPK.RESERVE", b"tk", b"3"],
24264            &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
24265            &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
24266            &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
24267            &[b"TOPK.COUNT", b"tk", b"a", b"c"],
24268            &[b"TOPK.LIST", b"tk"],
24269            &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
24270            &[b"TOPK.INFO", b"tk"],
24271            // The t digest.
24272            &[b"TDIGEST.CREATE", b"td"],
24273            &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
24274            &[b"TDIGEST.MIN", b"td"],
24275            &[b"TDIGEST.MAX", b"td"],
24276            &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
24277            &[b"TDIGEST.CDF", b"td", b"3"],
24278            &[b"TDIGEST.RANK", b"td", b"3"],
24279            &[b"TDIGEST.REVRANK", b"td", b"3"],
24280            &[b"TDIGEST.BYRANK", b"td", b"0"],
24281            &[b"TDIGEST.BYREVRANK", b"td", b"0"],
24282            &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
24283            &[b"TDIGEST.INFO", b"td"],
24284            &[b"TDIGEST.RESET", b"td"],
24285            &[b"TDIGEST.MIN", b"td"],
24286            // And the errors.
24287            &[b"SET", b"plain", b"v"],
24288            &[b"BF.ADD", b"plain", b"a"],
24289            &[b"CF.ADD", b"plain", b"a"],
24290            &[b"CMS.QUERY", b"plain", b"a"],
24291            &[b"TOPK.ADD", b"plain", b"a"],
24292            &[b"TDIGEST.ADD", b"plain", b"1"],
24293            &[b"CMS.INFO", b"gone"],
24294            &[b"TOPK.INFO", b"gone"],
24295            &[b"TDIGEST.INFO", b"gone"],
24296        ];
24297
24298        let mut one = Fixture::new();
24299        let mut many = Fixture::striped(8);
24300        for parts in script {
24301            let a = one.run(parts);
24302            let b = many.run(parts);
24303            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24304        }
24305    }
24306
24307    /// The two sketch merges, with their sources on stripes of their own.
24308    ///
24309    /// These are the only two commands in the ten groups that name more than one
24310    /// key, and both read a run of sources and write a destination, so both go
24311    /// wrong in the same way if a merge holds one store and looks every source up
24312    /// in it.
24313    #[test]
24314    fn a_sketch_merge_across_stripes_reads_every_source() {
24315        let mut many = Fixture::striped(8);
24316        let other = apart(&mut many, "s1");
24317        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
24318        let mut one = Fixture::new();
24319        let mut both = |parts: &[&[u8]]| {
24320            let a = one.run(parts);
24321            let b = many.run(parts);
24322            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24323            a
24324        };
24325
24326        // The count min sketch. The destination has to be the sources' shape,
24327        // and it is named first, so all three keys are read before anything is
24328        // written.
24329        for key in [b"cd".as_slice(), s1, s2] {
24330            both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
24331        }
24332        both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
24333        both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
24334        assert_eq!(
24335            both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
24336            "+OK\r\n",
24337            "the merge took both sources"
24338        );
24339        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
24340        // And with weights, which are read against the sources in order.
24341        both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
24342        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
24343        // A source that is not a sketch is answered before anything is written.
24344        both(&[b"SET", b"plain", b"v"]);
24345        assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
24346        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
24347
24348        // The t digest, which builds its destination and then puts it in place.
24349        // The two source keys are used again here, so what they held goes first.
24350        both(&[b"FLUSHALL"]);
24351        both(&[b"TDIGEST.CREATE", b"td"]);
24352        both(&[b"TDIGEST.CREATE", s1]);
24353        both(&[b"TDIGEST.CREATE", s2]);
24354        both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
24355        both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
24356        assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
24357        assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
24358        assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
24359    }
24360
24361    /// Every shape of `SORT`, on one stripe and on eight.
24362    ///
24363    /// The key it sorts, the keys a `BY` names, the keys a `GET` names and the
24364    /// destination are four different names and nothing lines them up, so on
24365    /// eight stripes this script is reading and writing all over the database
24366    /// while on one it is doing what it always did.
24367    #[test]
24368    fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
24369        let script: &[&[&[u8]]] = &[
24370            &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
24371            &[b"SORT", b"l"],
24372            &[b"SORT", b"l", b"DESC"],
24373            &[b"SORT", b"l", b"ALPHA"],
24374            &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
24375            &[b"SORT_RO", b"l"],
24376            // A weight per element, so the order comes off keys the command
24377            // never named.
24378            &[
24379                b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
24380            ],
24381            &[b"SORT", b"l", b"BY", b"w_*"],
24382            &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
24383            &[b"DEL", b"w_2"],
24384            &[b"SORT", b"l", b"BY", b"w_*"],
24385            // And the answer off another set of keys again, with `#` mixed in
24386            // so the rows are not all lookups.
24387            &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
24388            &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
24389            // A pattern that reaches into a hash, which is another key again.
24390            &[b"HSET", b"h_1", b"f", b"9"],
24391            &[b"HSET", b"h_2", b"f", b"8"],
24392            &[b"HSET", b"h_3", b"f", b"7"],
24393            &[b"HSET", b"h_10", b"f", b"6"],
24394            &[b"SORT", b"l", b"BY", b"h_*->f"],
24395            &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
24396            // The destination, which is a fourth place to land.
24397            &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
24398            &[b"LRANGE", b"out", b"0", b"-1"],
24399            &[b"SORT", b"l", b"STORE", b"l"],
24400            &[b"LRANGE", b"l", b"0", b"-1"],
24401            // An empty result takes the destination away rather than leaving a
24402            // list of nothing behind.
24403            &[b"SORT", b"missing", b"STORE", b"out"],
24404            &[b"EXISTS", b"out"],
24405            // A set and a sorted set sort the same way a list does, and a set
24406            // written to a destination is sorted even when nothing asked.
24407            &[b"SADD", b"s", b"c", b"a", b"b"],
24408            &[b"SORT", b"s", b"ALPHA"],
24409            &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
24410            &[b"LRANGE", b"out", b"0", b"-1"],
24411            &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
24412            &[b"SORT", b"z", b"BY", b"nosort"],
24413            &[b"SORT", b"z", b"ALPHA", b"DESC"],
24414            // And the two ways it refuses: a key of the wrong type, and an
24415            // element that is not a number under a numeric sort.
24416            &[b"SET", b"str", b"v"],
24417            &[b"SORT", b"str"],
24418            &[b"RPUSH", b"words", b"one", b"two"],
24419            &[b"SORT", b"words"],
24420            &[b"SORT_RO", b"l", b"STORE", b"out"],
24421        ];
24422
24423        let mut one = Fixture::new();
24424        let mut many = Fixture::striped(8);
24425        for parts in script {
24426            let a = one.run(parts);
24427            let b = many.run(parts);
24428            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
24429        }
24430    }
24431
24432    /// One `SORT` whose four kinds of key are on stripes of their own.
24433    ///
24434    /// The script above spreads keys around by writing enough of them, and this
24435    /// one checks the spread rather than trusting it: the list, the weight key
24436    /// for one of its elements and the destination are asserted to be in three
24437    /// places before the command runs.
24438    #[test]
24439    fn a_sort_across_stripes_reads_every_pattern_key() {
24440        let mut f = Fixture::striped(8);
24441        let out = apart(&mut f, "l");
24442        let (list, dest) = (b"l".as_slice(), out.as_bytes());
24443
24444        f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
24445        f.run(&[
24446            b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
24447        ]);
24448        f.run(&[
24449            b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
24450        ]);
24451
24452        // The weights are four keys and they are not all in one place, which is
24453        // the thing that would go unnoticed if the command held a stripe.
24454        let db = f.server.striped(0);
24455        let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
24456            .iter()
24457            .map(|k| db.stripe_of(k.as_slice()))
24458            .collect();
24459        assert!(
24460            weights.iter().any(|s| *s != weights[0]),
24461            "the four weight keys all landed on one stripe, so this proves nothing"
24462        );
24463
24464        assert_eq!(
24465            f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
24466            "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
24467            "the order came off the weights and the answer off the data keys"
24468        );
24469        assert_eq!(
24470            f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
24471            ":4\r\n"
24472        );
24473        assert_eq!(
24474            f.run(&[b"LRANGE", dest, b"0", b"-1"]),
24475            "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
24476            "the destination is on a stripe of its own and got the whole answer"
24477        );
24478    }
24479
24480    /// A `CONFIG SET` reaches every stripe, so where a key landed does not
24481    /// decide what shape it is stored in.
24482    ///
24483    /// This is the setting that would go wrong quietly. A stripe that kept the
24484    /// old ladder would hold the same hash in a different encoding from the
24485    /// stripe next to it, and the only thing that would ever say so is
24486    /// `OBJECT ENCODING`, which is why the check is on that.
24487    #[test]
24488    fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
24489        let mut f = Fixture::striped(8);
24490        let other = apart(&mut f, "h");
24491        let (first, second) = (b"h".as_slice(), other.as_bytes());
24492
24493        assert_eq!(
24494            f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
24495            "+OK\r\n"
24496        );
24497        assert_eq!(
24498            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
24499            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
24500            "the read comes off one stripe and has to answer for all of them"
24501        );
24502        for key in [first, second] {
24503            f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
24504            assert_eq!(
24505                f.run(&[b"OBJECT", b"ENCODING", key]),
24506                "$8\r\nlistpack\r\n",
24507                "two fields is still under the ladder"
24508            );
24509            f.run(&[b"HSET", key, b"c", b"3"]);
24510            assert_eq!(
24511                f.run(&[b"OBJECT", b"ENCODING", key]),
24512                "$9\r\nhashtable\r\n",
24513                "three fields is over it, on whichever stripe the key is on"
24514            );
24515        }
24516
24517        // And the policy, which every stripe has to agree about for the same
24518        // reason: an eviction draws from one stripe at a time.
24519        assert_eq!(
24520            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
24521            "+OK\r\n"
24522        );
24523        let db = f.server.striped(0);
24524        assert!(
24525            (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
24526            "a stripe kept the old policy"
24527        );
24528    }
24529
24530    /// What an index holds, as the two numbers `FT.INFO` reports about it.
24531    ///
24532    /// Read off the registry rather than parsed back out of an `FT.INFO` reply,
24533    /// because the reply is thirty odd fields and these two are the ones the
24534    /// keyspace hook moves.
24535    fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
24536        let search = f.server.search.lock();
24537        let index = search.named(name).expect("the index is there");
24538        (index.held.docs.len(), index.held.docs.last())
24539    }
24540
24541    /// A hash written under an index's prefix reaches it, and one written
24542    /// outside the prefix does not.
24543    #[test]
24544    fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
24545        let mut f = Fixture::new();
24546        f.run(&[
24547            b"FT.CREATE",
24548            b"ix",
24549            b"PREFIX",
24550            b"1",
24551            b"p:",
24552            b"SCHEMA",
24553            b"t",
24554            b"TEXT",
24555        ]);
24556        f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
24557        assert_eq!(held(&f, b"ix"), (1, 1));
24558        f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
24559        assert_eq!(held(&f, b"ix"), (1, 1));
24560
24561        // Every field of the key and not the one the command named, since a
24562        // document is read from nothing every time.
24563        f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
24564        f.run(&[b"HDEL", b"p:1", b"u"]);
24565        assert_eq!(held(&f, b"ix"), (1, 3));
24566        let search = f.server.search.lock();
24567        let index = search.named(b"ix").expect("there");
24568        assert_eq!(index.held.docs.id(b"p:1"), Some(3));
24569    }
24570
24571    /// A fresh index reads the keys that were already there, and walks past a
24572    /// key of the wrong type without counting a failure.
24573    #[test]
24574    fn a_fresh_index_reads_the_keys_that_were_already_there() {
24575        let mut f = Fixture::new();
24576        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24577        f.run(&[b"SET", b"p:str", b"not a hash"]);
24578        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24579        f.run(&[
24580            b"FT.CREATE",
24581            b"ix",
24582            b"PREFIX",
24583            b"1",
24584            b"p:",
24585            b"SCHEMA",
24586            b"t",
24587            b"TEXT",
24588        ]);
24589
24590        assert_eq!(held(&f, b"ix"), (1, 1));
24591        let search = f.server.search.lock();
24592        let index = search.named(b"ix").expect("there");
24593        assert_eq!(index.trouble.whole().failures(), 0);
24594    }
24595
24596    /// `SKIPINITIALSCAN` leaves what was there alone, and a later write to one
24597    /// of those keys still lands.
24598    #[test]
24599    fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
24600        let mut f = Fixture::new();
24601        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24602        f.run(&[
24603            b"FT.CREATE",
24604            b"ix",
24605            b"PREFIX",
24606            b"1",
24607            b"p:",
24608            b"SKIPINITIALSCAN",
24609            b"SCHEMA",
24610            b"t",
24611            b"TEXT",
24612        ]);
24613        assert_eq!(held(&f, b"ix"), (0, 0));
24614        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24615        assert_eq!(held(&f, b"ix"), (1, 1));
24616    }
24617
24618    /// A command that changed nothing leaves the document where it was, which
24619    /// is not the same as a command that was not a write.
24620    ///
24621    /// All five of these were measured against 8.10.1. Writing the same value
24622    /// again moves the number and a deadline set for later does not, which is
24623    /// the pair that makes the rule "the fields are not what they were" rather
24624    /// than "this was a write".
24625    #[test]
24626    fn only_a_real_change_gives_the_document_a_new_number() {
24627        let mut f = Fixture::new();
24628        f.run(&[
24629            b"FT.CREATE",
24630            b"ix",
24631            b"PREFIX",
24632            b"1",
24633            b"p:",
24634            b"SCHEMA",
24635            b"t",
24636            b"TEXT",
24637        ]);
24638        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24639        assert_eq!(held(&f, b"ix"), (1, 1));
24640
24641        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24642        assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
24643
24644        for quiet in [
24645            vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
24646            vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
24647            vec![b"HGET".as_slice(), b"p:1", b"t"],
24648            vec![b"HGETALL".as_slice(), b"p:1"],
24649            vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
24650            vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
24651            vec![
24652                b"HGETEX".as_slice(),
24653                b"p:1",
24654                b"EX",
24655                b"100",
24656                b"FIELDS",
24657                b"1",
24658                b"t",
24659            ],
24660            vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
24661        ] {
24662            f.run(&quiet);
24663            assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
24664        }
24665
24666        // And the ones that do change something.
24667        f.run(&[b"HSET", b"p:2", b"n", b"1"]);
24668        f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
24669        assert_eq!(held(&f, b"ix"), (2, 4));
24670        // A deadline that has already passed takes the field away, and taking
24671        // the last field away takes the key and the document with it. The
24672        // number still moves on the way past, because the field going and the
24673        // key going are two separate pieces of news and the first of them
24674        // writes the document one last time.
24675        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
24676        assert_eq!(held(&f, b"ix"), (1, 5));
24677    }
24678
24679    /// The two ways of emptying a hash, which do not leave the same thing
24680    /// behind. `HDEL` of the last field spends no number and is counted as a
24681    /// refusal, and a deadline that has already passed spends one on a document
24682    /// nobody sees and is counted as nothing. Measured against 8.10.1 and not
24683    /// something anyone would guess.
24684    #[test]
24685    fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
24686        /// The index's own failure count.
24687        fn refused(f: &Fixture, name: &[u8]) -> u64 {
24688            let search = f.server.search.lock();
24689            let index = search.named(name).expect("the index is there");
24690            index.trouble.whole().failures()
24691        }
24692
24693        let mut f = Fixture::new();
24694        f.run(&[
24695            b"FT.CREATE",
24696            b"ix",
24697            b"PREFIX",
24698            b"1",
24699            b"p:",
24700            b"SCHEMA",
24701            b"t",
24702            b"TEXT",
24703        ]);
24704        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24705        assert_eq!(held(&f, b"ix"), (1, 1));
24706        f.run(&[b"HDEL", b"p:1", b"t"]);
24707        assert_eq!(
24708            held(&f, b"ix"),
24709            (0, 1),
24710            "HDEL of the last field spends none"
24711        );
24712        assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
24713
24714        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24715        assert_eq!(held(&f, b"ix"), (1, 2));
24716        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
24717        assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
24718        assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
24719
24720        f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
24721        assert_eq!(held(&f, b"ix"), (1, 4));
24722        f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
24723        assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
24724
24725        // Two fields and one command is one rewrite and not two, whichever way
24726        // the fields go.
24727        f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
24728        assert_eq!(held(&f, b"ix"), (1, 6));
24729        f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
24730        assert_eq!(held(&f, b"ix"), (0, 7));
24731        assert_eq!(refused(&f, b"ix"), 1);
24732    }
24733
24734    /// `HSETEX` with a deadline that has already passed is two pieces of news
24735    /// from one command, so the number moves twice and the value never reaches
24736    /// the index.
24737    #[test]
24738    fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
24739        let mut f = Fixture::new();
24740        f.run(&[
24741            b"FT.CREATE",
24742            b"ix",
24743            b"PREFIX",
24744            b"1",
24745            b"p:",
24746            b"SCHEMA",
24747            b"t",
24748            b"TEXT",
24749            b"u",
24750            b"TEXT",
24751        ]);
24752        f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
24753        assert_eq!(held(&f, b"ix"), (1, 1));
24754        f.run(&[
24755            b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24756        ]);
24757        assert_eq!(
24758            held(&f, b"ix"),
24759            (1, 3),
24760            "the key lived and the field did not"
24761        );
24762
24763        // And the same when the key does not survive it.
24764        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24765        assert_eq!(held(&f, b"ix"), (2, 4));
24766        f.run(&[
24767            b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24768        ]);
24769        assert_eq!(held(&f, b"ix"), (1, 6));
24770    }
24771
24772    /// The number one key is indexed under, or `None` when it holds no
24773    /// document.
24774    fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
24775        let search = f.server.search.lock();
24776        let index = search.named(name).expect("the index is there");
24777        index.held.docs.id(key)
24778    }
24779
24780    /// An index over `p:` with one document under `p:1`, which is where four of
24781    /// the tests below start.
24782    fn indexed() -> Fixture {
24783        let mut f = Fixture::new();
24784        f.run(&[
24785            b"FT.CREATE",
24786            b"ix",
24787            b"PREFIX",
24788            b"1",
24789            b"p:",
24790            b"SCHEMA",
24791            b"t",
24792            b"TEXT",
24793        ]);
24794        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24795        f
24796    }
24797
24798    /// Every way a keyspace command takes a key away leaves no document behind,
24799    /// and none of them spends a number or is counted as a refusal.
24800    #[test]
24801    fn a_key_a_keyspace_command_takes_away_loses_its_document() {
24802        for take in [
24803            vec![b"DEL".as_slice(), b"p:1"],
24804            vec![b"UNLINK".as_slice(), b"p:1"],
24805            vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
24806            vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
24807        ] {
24808            let mut f = indexed();
24809            assert_eq!(held(&f, b"ix"), (1, 1));
24810            f.run(&take);
24811            assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
24812            let search = f.server.search.lock();
24813            let index = search.named(b"ix").expect("the index is there");
24814            assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
24815        }
24816
24817        // A deadline that has not passed yet is not one of them.
24818        let mut f = indexed();
24819        f.run(&[b"EXPIRE", b"p:1", b"1000"]);
24820        assert_eq!(held(&f, b"ix"), (1, 1));
24821        f.run(&[b"PERSIST", b"p:1"]);
24822        assert_eq!(held(&f, b"ix"), (1, 1));
24823    }
24824
24825    /// A rename inside the prefix keeps the number the document had, which is
24826    /// the one write on a followed key that does not spend one. Out of the
24827    /// prefix is an erase and into it is a fresh reading, both measured.
24828    #[test]
24829    fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
24830        let mut f = indexed();
24831        f.run(&[b"RENAME", b"p:1", b"p:2"]);
24832        assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
24833        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24834        assert_eq!(number(&f, b"ix", b"p:1"), None);
24835
24836        f.run(&[b"RENAME", b"p:2", b"q:1"]);
24837        assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
24838
24839        f.run(&[b"RENAME", b"q:1", b"p:3"]);
24840        assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
24841        assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
24842
24843        // `RENAMENX` goes the same way, and the one that answers zero changes
24844        // nothing.
24845        f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
24846        assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
24847        assert_eq!(held(&f, b"ix"), (2, 3));
24848        f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
24849        assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
24850    }
24851
24852    /// A rename over a key that already had a document leaves one document and
24853    /// not two. A real server leaves both, and D-64 is that difference.
24854    #[test]
24855    fn a_rename_over_a_document_leaves_one_of_them() {
24856        let mut f = indexed();
24857        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24858        assert_eq!(held(&f, b"ix"), (2, 2));
24859        f.run(&[b"RENAME", b"p:1", b"p:2"]);
24860        assert_eq!(held(&f, b"ix"), (1, 2));
24861        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24862    }
24863
24864    /// A key that arrives under the prefix by being copied or restored is read
24865    /// as a new document, and one that is written over by something that is not
24866    /// a hash is erased without a word.
24867    #[test]
24868    fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
24869        let mut f = indexed();
24870        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24871        f.run(&[b"COPY", b"q:1", b"p:2"]);
24872        assert_eq!(held(&f, b"ix"), (2, 2));
24873        assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
24874
24875        // Out of the prefix, where the source keeps the document it had.
24876        f.run(&[b"COPY", b"p:1", b"q:2"]);
24877        assert_eq!(held(&f, b"ix"), (2, 2));
24878
24879        // Over a key that has one, which is a new reading and not a rename.
24880        f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
24881        assert_eq!(held(&f, b"ix"), (2, 3));
24882        assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
24883
24884        // And a string landing on top of a document takes it away, spending no
24885        // number and counting no failure.
24886        f.run(&[b"SET", b"s:1", b"plain"]);
24887        f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
24888        assert_eq!(held(&f, b"ix"), (1, 3));
24889        let dump = f.run(&[b"DUMP", b"q:1"]);
24890        assert!(dump.starts_with('$'), "{dump}");
24891    }
24892
24893    /// The keyspace group reads a key back on database zero whatever database
24894    /// the command ran on, which is measured and is not what the hash commands
24895    /// do. A `COPY` into another database indexes nothing and takes away
24896    /// whatever the destination had, and a `RESTORE` anywhere else is invisible.
24897    #[test]
24898    fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
24899        let mut f = indexed();
24900        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24901        assert_eq!(held(&f, b"ix"), (2, 2));
24902        // Into database one, so the indexes look for `p:2` on database zero,
24903        // find the one that is still there and read it again.
24904        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24905        assert_eq!(held(&f, b"ix"), (2, 3));
24906        // And with nothing under that name on database zero, the copy leaves
24907        // the index one document lighter than it found it.
24908        f.run(&[b"DEL", b"p:2"]);
24909        assert_eq!(held(&f, b"ix"), (1, 3));
24910        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24911        assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
24912
24913        // A restore on another database is the same story.
24914        let dump = f.run(&[b"DUMP", b"p:1"]);
24915        assert!(dump.starts_with('$'), "{dump}");
24916        f.run(&[b"SELECT", b"1"]);
24917        f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
24918        f.run(&[b"RENAME", b"q:1", b"p:3"]);
24919        assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
24920    }
24921
24922    /// `MOVE` is not a change at all, because an index follows a key by name
24923    /// and a write on any database still reaches it.
24924    #[test]
24925    fn a_move_leaves_the_document_where_it_is() {
24926        let mut f = indexed();
24927        f.run(&[b"MOVE", b"p:1", b"1"]);
24928        assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
24929        assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
24930
24931        f.run(&[b"SELECT", b"1"]);
24932        f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
24933        assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
24934        f.run(&[b"DEL", b"p:1"]);
24935        assert_eq!(held(&f, b"ix"), (0, 2));
24936    }
24937
24938    /// A flush takes every index with it, whichever database it flushed.
24939    #[test]
24940    fn a_flush_drops_the_indexes() {
24941        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24942            let mut f = indexed();
24943            f.run(&[flush]);
24944            assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
24945            assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
24946        }
24947
24948        // Even on a database no index ever read, which is what a real server
24949        // does and is not what anyone would guess.
24950        let mut f = indexed();
24951        f.run(&[b"SELECT", b"9"]);
24952        f.run(&[b"FLUSHDB"]);
24953        assert!(f.server.search.lock().is_empty());
24954    }
24955
24956    /// An index whose schema has one tag field of each kind, plus a number so
24957    /// there is something for `FT.TAGVALS` to refuse.
24958    fn tagged() -> Fixture {
24959        let mut f = Fixture::new();
24960        f.run(&[
24961            b"FT.CREATE",
24962            b"tv",
24963            b"PREFIX",
24964            b"1",
24965            b"tv:",
24966            b"SCHEMA",
24967            b"g",
24968            b"AS",
24969            b"gg",
24970            b"TAG",
24971            b"h",
24972            b"TAG",
24973            b"SEPARATOR",
24974            b"|",
24975            b"CASESENSITIVE",
24976            b"n",
24977            b"NUMERIC",
24978        ]);
24979        f.run(&[
24980            b"HSET",
24981            b"tv:1",
24982            b"g",
24983            b"Red, BLUE ",
24984            b"h",
24985            b"Aa|bB",
24986            b"n",
24987            b"1",
24988        ]);
24989        f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
24990        f
24991    }
24992
24993    /// The values come back as they are stored, so an ordinary tag field
24994    /// answers them folded and trimmed and a `CASESENSITIVE` one answers what
24995    /// it was given. Byte order either way, which puts the capital first.
24996    #[test]
24997    fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
24998        let mut f = tagged();
24999        assert_eq!(
25000            f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
25001            "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
25002        );
25003        assert_eq!(
25004            f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
25005            "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
25006        );
25007    }
25008
25009    /// The name asked about is the attribute, so the identifier of a field
25010    /// declared `AS` is not a name this knows.
25011    #[test]
25012    fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
25013        let mut f = tagged();
25014        for (name, want) in [
25015            (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
25016            (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
25017            (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
25018        ] {
25019            assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
25020        }
25021        assert_eq!(
25022            f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
25023            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
25024        );
25025    }
25026
25027    /// Looking up the index counts as a use of it on the roads that refuse the
25028    /// field as well as on the one that answers, which is measured.
25029    #[test]
25030    fn asking_for_tag_values_counts_a_use_of_the_index() {
25031        let mut f = tagged();
25032        let uses = |f: &mut Fixture| {
25033            let reply = f.run(&[b"FT.INFO", b"tv"]);
25034            let at = reply.find("number_of_uses").expect("the field is reported");
25035            let value = reply[at..].split("\r\n").nth(1).unwrap();
25036            value.trim_start_matches(':').parse::<i64>().unwrap()
25037        };
25038        let before = uses(&mut f);
25039        f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
25040        f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
25041        // Three more than before: two tag lookups and the second `FT.INFO`.
25042        assert_eq!(uses(&mut f), before + 3);
25043    }
25044
25045    /// A tag field nothing was ever written to has no list at all, which
25046    /// answers the same empty set a list that has been emptied does.
25047    #[test]
25048    fn a_tag_field_with_nothing_in_it_answers_empty() {
25049        let mut f = Fixture::new();
25050        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
25051        assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
25052    }
25053
25054    /// A dictionary is module state and not a key, so nothing in the keyspace
25055    /// can see one.
25056    #[test]
25057    fn a_dictionary_is_not_a_key() {
25058        let mut f = Fixture::new();
25059        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
25060        assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
25061        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
25062        assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
25063    }
25064
25065    /// The count is how many terms were new, an empty term is not a term, and
25066    /// the dump is sorted by bytes rather than folded.
25067    #[test]
25068    fn a_dictionary_counts_the_terms_it_had_not_seen() {
25069        let mut f = Fixture::new();
25070        assert_eq!(
25071            f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
25072            ":3\r\n"
25073        );
25074        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
25075        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
25076        assert_eq!(
25077            f.run(&[b"FT.DICTDUMP", b"d"]),
25078            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
25079        );
25080        assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
25081    }
25082
25083    /// A name nobody ever added to is not an error on either of the two
25084    /// commands that will take one, which is the only place in the group where
25085    /// a missing name is forgiven.
25086    #[test]
25087    fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
25088        let mut f = Fixture::new();
25089        assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
25090        assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
25091    }
25092
25093    /// The dictionaries go when the keyspace does, the same way the indexes do.
25094    #[test]
25095    fn a_flush_drops_the_dictionaries() {
25096        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
25097            let mut f = Fixture::new();
25098            f.run(&[b"FT.DICTADD", b"d", b"a"]);
25099            f.run(&[flush]);
25100            assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
25101        }
25102    }
25103
25104    // -------------------------------------------------------------- profile
25105
25106    /// A fixture holding one index over three documents, two of which hold the
25107    /// first word and two the second.
25108    fn profiling() -> Fixture {
25109        let mut f = Fixture::new();
25110        f.run(&[
25111            b"FT.CREATE",
25112            b"ix",
25113            b"PREFIX",
25114            b"1",
25115            b"p:",
25116            b"SCHEMA",
25117            b"t",
25118            b"TEXT",
25119            b"n",
25120            b"NUMERIC",
25121        ]);
25122        f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
25123        f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
25124        f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
25125        f
25126    }
25127
25128    /// The reply with every time taken out of it, since no two runs agree on
25129    /// those and everything else about a profile is exact.
25130    fn timeless(reply: &str) -> String {
25131        const KEYS: &[&str] = &[
25132            "+Total profile time",
25133            "+Parsing time",
25134            "+Workers queue time",
25135            "+Pipeline creation time",
25136            "+Time",
25137        ];
25138        let mut out = String::new();
25139        let mut parts = reply.split("\r\n").peekable();
25140        while let Some(part) = parts.next() {
25141            out.push_str(part);
25142            out.push_str("\r\n");
25143            if !KEYS.contains(&part) {
25144                continue;
25145            }
25146            // A double is one line on RESP3 and a bulk header and its digits on
25147            // RESP2, and both of them stand for the same one value.
25148            match parts.next() {
25149                Some(head) if head.starts_with('$') => {
25150                    parts.next();
25151                }
25152                _ => {}
25153            }
25154            out.push_str("<t>\r\n");
25155        }
25156        // The split leaves an empty piece past the last line ending.
25157        out.truncate(out.len() - 2);
25158        out
25159    }
25160
25161    /// The whole envelope on both protocols, which is a two element array on
25162    /// one and a two key map on the other.
25163    #[test]
25164    fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
25165        let mut f = profiling();
25166        assert_eq!(
25167            timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
25168            "*2\r\n\
25169             *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\
25170             $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\
25171             *4\r\n+Shards\r\n*1\r\n*14\r\n\
25172             +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
25173             +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
25174             +Warning\r\n*1\r\n+None\r\n\
25175             +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
25176             +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
25177             +Estimated number of matches\r\n:2\r\n\
25178             +Result processors profile\r\n*4\r\n\
25179             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
25180             *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
25181             *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
25182             *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
25183             +Coordinator\r\n*0\r\n"
25184        );
25185        let mut g = profiling();
25186        g.run(&[b"HELLO", b"3"]);
25187        let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
25188        assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
25189        assert!(
25190            three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
25191            "{three}"
25192        );
25193        assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
25194        assert!(
25195            three.contains(
25196                "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
25197                 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
25198                 +Estimated number of matches\r\n:2\r\n"
25199            ),
25200            "{three}"
25201        );
25202    }
25203
25204    /// Every kind of step names itself, and the three that hold other steps say
25205    /// so in the singular or the plural depending on how many they hold.
25206    #[test]
25207    fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
25208        let mut f = profiling();
25209        let tree = |f: &mut Fixture, query: &[u8]| {
25210            let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
25211            let at = reply.find("+Iterators profile").expect("a tree");
25212            let end = reply.find("+Result processors").expect("a list of steps");
25213            reply[at..end].to_string()
25214        };
25215        assert_eq!(
25216            tree(&mut f, b"alpha beta"),
25217            "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
25218             +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
25219             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
25220             +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
25221             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
25222             +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
25223        );
25224        assert!(tree(&mut f, b"alpha|beta").starts_with(
25225            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
25226             +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
25227        ));
25228        // One thing under it, named in the singular, which is a different key
25229        // and not a list holding one.
25230        assert!(tree(&mut f, b"-alpha").starts_with(
25231            "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
25232             +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
25233        ));
25234        assert!(tree(&mut f, b"~alpha").starts_with(
25235            "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
25236             +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
25237        ));
25238        // No guess at how many, which is the one leaf that leaves it off.
25239        assert_eq!(
25240            tree(&mut f, b"*"),
25241            "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
25242             +Number of reading operations\r\n:3\r\n"
25243        );
25244        assert!(tree(&mut f, b"@n:[1 2]").starts_with(
25245            "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
25246             $19\r\n1.000000 - 2.000000\r\n"
25247        ));
25248    }
25249
25250    /// A union an expansion made folds into a count of its branches and a union
25251    /// a client wrote with a bar does not.
25252    #[test]
25253    fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
25254        let mut f = profiling();
25255        f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
25256        let tree = |f: &mut Fixture, words: &[&[u8]]| {
25257            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
25258            argv.extend_from_slice(words);
25259            let reply = timeless(&f.run(&argv));
25260            let at = reply.find("+Iterators profile").expect("a tree");
25261            let end = reply.find("+Result processors").expect("a list of steps");
25262            reply[at..end].to_string()
25263        };
25264        assert_eq!(
25265            tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
25266            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
25267             +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
25268             +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
25269             +The number of iterators in the union is 2\r\n"
25270        );
25271        assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
25272        assert!(
25273            tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
25274                .contains("+Child iterators\r\n*2\r\n")
25275        );
25276        // A union that says nothing but its own name says it as a status, and
25277        // one that says what it stood for says that as a string. Measured, and
25278        // it is the one place in this reply where the two are told apart.
25279        assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
25280        assert!(
25281            tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
25282        );
25283    }
25284
25285    /// Which steps a search runs the rows through, which turns on the window,
25286    /// on whether anything asked for the fields and on what the order is.
25287    #[test]
25288    fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
25289        let mut f = profiling();
25290        let steps = |f: &mut Fixture, words: &[&[u8]]| {
25291            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
25292            argv.extend_from_slice(words);
25293            let reply = timeless(&f.run(&argv));
25294            let at = reply.find("+Result processors").expect("a list of steps");
25295            let end = reply.find("+Coordinator").expect("an end");
25296            let mut out = Vec::new();
25297            let mut parts = reply[at..end].split("\r\n").peekable();
25298            while let Some(part) = parts.next() {
25299                if part == "+Type" {
25300                    out.push(parts.next().unwrap_or_default().to_string());
25301                }
25302            }
25303            out
25304        };
25305        assert_eq!(
25306            steps(&mut f, &[]),
25307            ["+Index", "+Scorer", "+Sorter", "+Loader"]
25308        );
25309        assert_eq!(
25310            steps(&mut f, &[b"NOCONTENT"]),
25311            ["+Index", "+Scorer", "+Sorter"]
25312        );
25313        // A window of nothing is a client asking for the total and nothing
25314        // else, so nothing is scored and nothing is sorted.
25315        assert_eq!(
25316            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
25317            ["+Index", "+Counter"]
25318        );
25319        // A sort by a field does not need a score, and asking for the scores
25320        // puts the step back.
25321        assert_eq!(
25322            steps(&mut f, &[b"SORTBY", b"n"]),
25323            ["+Index", "+Sorter", "+Loader"]
25324        );
25325        assert_eq!(
25326            steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
25327            ["+Index", "+Scorer", "+Sorter", "+Loader"]
25328        );
25329        assert_eq!(
25330            steps(&mut f, &[b"HIGHLIGHT"]),
25331            ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
25332        );
25333        assert_eq!(
25334            steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
25335            ["+Index", "+Scorer", "+Sorter"]
25336        );
25337    }
25338
25339    /// A pipeline names each of its steps after the expression it runs, which
25340    /// is what a real server prints beside them.
25341    #[test]
25342    fn a_pipeline_names_every_step_after_what_it_runs() {
25343        let mut f = profiling();
25344        let steps = |f: &mut Fixture, words: &[&[u8]]| {
25345            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
25346            argv.extend_from_slice(words);
25347            let reply = timeless(&f.run(&argv));
25348            let at = reply.find("+Result processors").expect("a list of steps");
25349            let end = reply.find("+Coordinator").expect("an end");
25350            let mut out = Vec::new();
25351            let mut parts = reply[at..end].split("\r\n").peekable();
25352            while let Some(part) = parts.next() {
25353                if part == "+Type" {
25354                    out.push(parts.next().unwrap_or_default().to_string());
25355                }
25356            }
25357            out
25358        };
25359        assert_eq!(steps(&mut f, &[]), ["+Index"]);
25360        assert_eq!(
25361            steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
25362            ["+Index", "+Projector - Literal 1"]
25363        );
25364        assert_eq!(
25365            steps(
25366                &mut f,
25367                &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
25368            ),
25369            ["+Index", "+Loader", "+Projector - Operator *"]
25370        );
25371        assert_eq!(
25372            steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
25373            ["+Index", "+Loader", "+Filter - Predicate >"]
25374        );
25375        assert_eq!(
25376            steps(
25377                &mut f,
25378                &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
25379            ),
25380            ["+Index", "+Loader", "+Grouper"]
25381        );
25382        assert_eq!(
25383            steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
25384            ["+Index", "+Loader", "+Sorter"]
25385        );
25386        assert_eq!(
25387            steps(&mut f, &[b"LIMIT", b"0", b"2"]),
25388            ["+Index", "+Pager/Limiter"]
25389        );
25390        // Asking for the score by name is a step of its own, and it goes in
25391        // front of the read rather than after it.
25392        assert_eq!(
25393            steps(
25394                &mut f,
25395                &[
25396                    b"ADDSCORES",
25397                    b"LOAD",
25398                    b"1",
25399                    b"@n",
25400                    b"APPLY",
25401                    b"@__score",
25402                    b"AS",
25403                    b"s"
25404                ]
25405            ),
25406            [
25407                "+Index",
25408                "+Scorer",
25409                "+Loader",
25410                "+Projector - Property __score"
25411            ]
25412        );
25413    }
25414
25415    /// A field the schema marked sortable is held beside the document number,
25416    /// so a pipeline that only names those never opens a key and never reports
25417    /// a read.
25418    ///
25419    /// Measured: on a schema of `n NUMERIC SORTABLE g TAG`, `LOAD 1 @n` has no
25420    /// `Loader` step and `LOAD 1 @g` has one. So does `LOAD *`, because what a
25421    /// key turns out to hold is not knowable without opening it.
25422    #[test]
25423    fn a_sortable_field_is_read_without_the_key_being_opened() {
25424        let mut f = Fixture::new();
25425        f.run(&[
25426            b"FT.CREATE",
25427            b"sx",
25428            b"PREFIX",
25429            b"1",
25430            b"s:",
25431            b"SCHEMA",
25432            b"n",
25433            b"NUMERIC",
25434            b"SORTABLE",
25435            b"g",
25436            b"TAG",
25437        ]);
25438        f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
25439        f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
25440        let loads = |f: &mut Fixture, words: &[&[u8]]| {
25441            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
25442            argv.extend_from_slice(words);
25443            f.run(&argv).contains("+Loader")
25444        };
25445        assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
25446        assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
25447        assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
25448        assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
25449        assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
25450        assert!(loads(
25451            &mut f,
25452            &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
25453        ));
25454        assert!(loads(&mut f, &[b"LOAD", b"*"]));
25455    }
25456
25457    /// The four ways the words can be wrong, none of which reaches the search
25458    /// underneath.
25459    #[test]
25460    fn a_profile_checks_its_own_words_before_it_runs_anything() {
25461        let mut f = profiling();
25462        assert_eq!(
25463            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
25464            "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
25465        );
25466        assert_eq!(
25467            f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
25468            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25469        );
25470        // The word goes between the two and nowhere else, so one written in
25471        // front of them is not the word at all.
25472        assert_eq!(
25473            f.run(&[
25474                b"FT.PROFILE",
25475                b"ix",
25476                b"LIMITED",
25477                b"SEARCH",
25478                b"QUERY",
25479                b"alpha"
25480            ]),
25481            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25482        );
25483        assert_eq!(
25484            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
25485            "-The QUERY keyword is expected\r\n"
25486        );
25487        assert_eq!(
25488            f.run(&[
25489                b"FT.PROFILE",
25490                b"ix",
25491                b"AGGREGATE",
25492                b"QUERY",
25493                b"alpha",
25494                b"WITHCURSOR"
25495            ]),
25496            "-FT.PROFILE does not support cursor\r\n"
25497        );
25498        // And what the search itself complains about comes back on its own,
25499        // without an envelope around it saying the command worked.
25500        assert_eq!(
25501            f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
25502            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
25503        );
25504        assert_eq!(
25505            f.run(&[
25506                b"FT.PROFILE",
25507                b"ix",
25508                b"SEARCH",
25509                b"QUERY",
25510                b"alpha",
25511                b"extra"
25512            ]),
25513            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
25514        );
25515    }
25516
25517    /// Every word of the command's own is read without regard to case.
25518    #[test]
25519    fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
25520        let mut f = profiling();
25521        let one = f.run(&[
25522            b"FT.PROFILE",
25523            b"ix",
25524            b"search",
25525            b"limited",
25526            b"query",
25527            b"alpha",
25528        ]);
25529        let two = f.run(&[
25530            b"FT.PROFILE",
25531            b"ix",
25532            b"SEARCH",
25533            b"LIMITED",
25534            b"QUERY",
25535            b"alpha",
25536        ]);
25537        assert_eq!(timeless(&one), timeless(&two));
25538    }
25539
25540    // -------------------------------------------------------------- dropping
25541
25542    /// The two spellings take opposite defaults, which is measured and is the
25543    /// only difference between them that a client can see.
25544    #[test]
25545    fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
25546        let mut f = profiling();
25547        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
25548        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25549
25550        let mut f = profiling();
25551        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25552        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25553
25554        let mut f = profiling();
25555        assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
25556        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25557
25558        let mut f = profiling();
25559        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
25560        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25561    }
25562
25563    /// Each spelling takes its own word and refuses the other one's, which
25564    /// reads as an oversight and is what a real server answers.
25565    #[test]
25566    fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
25567        let mut f = profiling();
25568        let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
25569        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
25570        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
25571        // Refused rather than half done, so the index is still there.
25572        assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
25573    }
25574
25575    /// Only what the index read is deleted, which is not the same as
25576    /// everything under its prefix.
25577    #[test]
25578    fn dropping_the_documents_leaves_a_key_the_index_never_read() {
25579        let mut f = profiling();
25580        f.run(&[b"SET", b"p:4", b"alpha"]);
25581        f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
25582        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25583        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25584        assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
25585    }
25586
25587    /// An index still standing over the same keys hears about them going,
25588    /// rather than answering later with keys that are not there.
25589    #[test]
25590    fn another_index_over_the_same_keys_loses_the_documents_too() {
25591        let mut f = profiling();
25592        f.run(&[
25593            b"FT.CREATE",
25594            b"other",
25595            b"PREFIX",
25596            b"1",
25597            b"p:",
25598            b"SCHEMA",
25599            b"t",
25600            b"TEXT",
25601        ]);
25602        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25603        assert_eq!(
25604            f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
25605            "*1\r\n:0\r\n"
25606        );
25607    }
25608
25609    /// A drop that found nothing to drop deletes nothing either, which is the
25610    /// one case where the shortcut spelling answers `OK` without a sweep.
25611    #[test]
25612    fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
25613        let mut f = profiling();
25614        assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
25615        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25616        assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
25617        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25618    }
25619
25620    // --------------------------------------------------------------- config
25621
25622    /// The two shapes a dump comes back in, which are the one mix of simple
25623    /// strings and bulk strings the group sends.
25624    #[test]
25625    fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
25626        let mut f = Fixture::new();
25627        assert_eq!(
25628            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25629            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25630        );
25631        assert_eq!(
25632            f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25633            "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
25634        );
25635        let mut g = Fixture::new();
25636        g.run(&[b"HELLO", b"3"]);
25637        assert_eq!(
25638            g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25639            "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25640        );
25641        assert_eq!(
25642            g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25643            "%1\r\n+EXTLOAD\r\n_\r\n"
25644        );
25645    }
25646
25647    /// The help text rides along in the middle of the same row, flat on RESP2
25648    /// and as a map of its own on RESP3.
25649    #[test]
25650    fn a_help_row_carries_the_description_and_the_value_together() {
25651        let mut f = Fixture::new();
25652        assert_eq!(
25653            f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25654            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25655             +Value\r\n$3\r\n500\r\n"
25656        );
25657        let mut g = Fixture::new();
25658        g.run(&[b"HELLO", b"3"]);
25659        assert_eq!(
25660            g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25661            "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
25662             +Value\r\n$3\r\n500\r\n"
25663        );
25664    }
25665
25666    /// A name is matched whole, ignoring case, and the single word star is the
25667    /// only thing that means all of them.
25668    #[test]
25669    fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
25670        let mut f = Fixture::new();
25671        assert_eq!(
25672            f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
25673            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25674        );
25675        for name in [
25676            b"TIMEOUT*".as_slice(),
25677            b"?IMEOUT",
25678            b"*TIMEOUT*",
25679            b"TIME",
25680            b"NOSUCH",
25681            b"",
25682        ] {
25683            assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
25684        }
25685        assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
25686        assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
25687    }
25688
25689    /// Words after the name are stepped over rather than refused, on both of
25690    /// the two reads.
25691    #[test]
25692    fn a_read_ignores_whatever_follows_the_name() {
25693        let mut f = Fixture::new();
25694        assert_eq!(
25695            f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
25696            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25697        );
25698        assert_eq!(
25699            f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
25700            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25701             +Value\r\n$3\r\n500\r\n"
25702        );
25703    }
25704
25705    /// The container reports its own name and the subcommand it was given in
25706    /// the two lines the dispatcher writes.
25707    #[test]
25708    fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
25709        let mut f = Fixture::new();
25710        assert_eq!(
25711            f.run(&[b"FT.CONFIG"]),
25712            "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
25713        );
25714        for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
25715            let want = format!(
25716                "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
25717                String::from_utf8_lossy(sub)
25718            );
25719            assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
25720        }
25721        assert_eq!(
25722            f.run(&[b"ft.config", b"get"]),
25723            "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
25724        );
25725        assert_eq!(
25726            f.run(&[b"FT.CONFIG", b"bogus"]),
25727            "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
25728        );
25729    }
25730
25731    /// The name, then whether it can move, then the value, then the count of
25732    /// words, and each of the first three answers before the next is looked at.
25733    #[test]
25734    fn a_write_checks_the_name_then_the_setting_then_the_value() {
25735        let mut f = Fixture::new();
25736        for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
25737            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
25738            cmd.extend(tail);
25739            assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
25740        }
25741        for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
25742            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
25743            cmd.extend(tail);
25744            assert_eq!(
25745                f.run(&cmd),
25746                "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
25747            );
25748        }
25749        assert_eq!(
25750            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
25751            "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
25752        );
25753    }
25754
25755    /// Too many words is a status and not an error, and the value has already
25756    /// been written by the time it goes out.
25757    #[test]
25758    fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
25759        let mut f = Fixture::new();
25760        assert_eq!(
25761            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
25762            "+OK\r\n"
25763        );
25764        assert_eq!(
25765            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
25766            "+EXCESSARGS\r\n"
25767        );
25768        assert_eq!(
25769            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25770            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
25771        );
25772    }
25773
25774    /// Strictly first and loosely second, so a hexadecimal and a leading zero
25775    /// and an exponent all land and a fraction does not.
25776    #[test]
25777    fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
25778        let mut f = Fixture::new();
25779        for (given, want) in [
25780            (b"0x10".as_slice(), "16"),
25781            (b"0X1f", "31"),
25782            (b"+0x10", "16"),
25783            (b"+5", "5"),
25784            (b"010", "10"),
25785            (b"08", "8"),
25786            (b"0777", "777"),
25787            (b"1e3", "1000"),
25788            (b"0.0", "0"),
25789            (b"-0.0", "0"),
25790        ] {
25791            assert_eq!(
25792                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25793                "+OK\r\n",
25794                "{given:?}"
25795            );
25796            let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
25797            assert_eq!(
25798                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25799                want,
25800                "{given:?}"
25801            );
25802        }
25803        for given in [
25804            b" 5".as_slice(),
25805            b"5 ",
25806            b"1.5",
25807            b"1e-3",
25808            b"x",
25809            b"",
25810            b"0b11",
25811            b"0xg",
25812            b"nan",
25813            b"inf",
25814            b"1e100",
25815            b"99999999999999999999",
25816        ] {
25817            assert_eq!(
25818                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25819                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25820                "{given:?}"
25821            );
25822        }
25823    }
25824
25825    /// Which of the two readers found a negative decides what it is told, and
25826    /// on a setting with no range at all neither of them is refused.
25827    #[test]
25828    fn a_negative_is_answered_by_whichever_reader_found_it() {
25829        let mut f = Fixture::new();
25830        for given in [b"-1".as_slice(), b"-16"] {
25831            assert_eq!(
25832                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25833                "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
25834                "{given:?}"
25835            );
25836        }
25837        for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
25838            assert_eq!(
25839                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25840                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25841                "{given:?}"
25842            );
25843        }
25844        let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
25845        for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
25846            assert_eq!(
25847                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25848                "+OK\r\n",
25849                "{given:?}"
25850            );
25851            assert_eq!(
25852                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25853                unlimited,
25854                "{given:?}"
25855            );
25856        }
25857    }
25858
25859    /// The two settings with no range truncate into a signed thirty two bit
25860    /// slot and say so once the number has gone under.
25861    #[test]
25862    fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
25863        let mut f = Fixture::new();
25864        for (given, want) in [
25865            (b"2147483647".as_slice(), "2147483647"),
25866            (b"2147483648", "unlimited"),
25867            (b"4294967295", "unlimited"),
25868            (b"9223372036854775806", "unlimited"),
25869            (b"0", "0"),
25870        ] {
25871            assert_eq!(
25872                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25873                "+OK\r\n",
25874                "{given:?}"
25875            );
25876            let want = format!(
25877                "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
25878                want.len()
25879            );
25880            assert_eq!(
25881                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25882                want,
25883                "{given:?}"
25884            );
25885        }
25886    }
25887
25888    /// A number past what a setting will take says which way it went, and the
25889    /// ones with a softer roof of their own say what that roof is about.
25890    #[test]
25891    fn a_number_out_of_range_names_the_limit_it_crossed() {
25892        let mut f = Fixture::new();
25893        let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
25894        for (name, given) in [
25895            (b"MINPREFIX".as_slice(), b"0".as_slice()),
25896            (b"MAX_AGGREGATE_GROUPS", b"0"),
25897            (b"BM25STD_TANH_FACTOR", b"0"),
25898            (b"DEFAULT_DIALECT", b"0"),
25899            (b"MINSTEMLEN", b"4294967296"),
25900            (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
25901            (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
25902            (b"CONNECT_TIMEOUT", b"2147483648"),
25903        ] {
25904            assert_eq!(
25905                f.run(&[b"FT.CONFIG", b"SET", name, given]),
25906                bounds,
25907                "{name:?}"
25908            );
25909        }
25910        for (name, given, want) in [
25911            (
25912                b"MINSTEMLEN".as_slice(),
25913                b"1".as_slice(),
25914                "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
25915            ),
25916            (
25917                b"MAX_AGGREGATE_GROUPS",
25918                b"67108865",
25919                "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
25920            ),
25921            (
25922                b"WORKERS",
25923                b"17",
25924                "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
25925            ),
25926            (
25927                b"_NUMERIC_RANGES_PARENTS",
25928                b"3",
25929                "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
25930                 depth for balance\r\n",
25931            ),
25932            (
25933                b"DEFAULT_DIALECT",
25934                b"5",
25935                "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
25936            ),
25937            (
25938                b"_BG_INDEX_MEM_PCT_THR",
25939                b"101",
25940                "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
25941                 100%\r\n",
25942            ),
25943            (
25944                b"BM25STD_TANH_FACTOR",
25945                b"10001",
25946                "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
25947                 inclusive\r\n",
25948            ),
25949            (
25950                b"BG_INDEX_SLEEP_DURATION_US",
25951                b"1000000",
25952                "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
25953                 999999 (usleep POSIX limit)\r\n",
25954            ),
25955        ] {
25956            assert_eq!(
25957                f.run(&[b"FT.CONFIG", b"SET", name, given]),
25958                want,
25959                "{name:?}"
25960            );
25961        }
25962    }
25963
25964    /// The two trimming delays are measured against each other, and the answer
25965    /// names both settings and both numbers.
25966    #[test]
25967    fn the_trimming_delays_are_checked_against_one_another() {
25968        let mut f = Fixture::new();
25969        assert_eq!(
25970            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
25971            "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
25972             _MAX_TRIM_DELAY_MS (5000)\r\n"
25973        );
25974        assert_eq!(
25975            f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
25976            "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
25977             _MIN_TRIM_DELAY_MS (2000)\r\n"
25978        );
25979        assert_eq!(
25980            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
25981            "+OK\r\n"
25982        );
25983    }
25984
25985    /// Two of the word settings fold the spelling on the way in and the scorer
25986    /// does not, which is the one place in the table case counts.
25987    #[test]
25988    fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
25989        let mut f = Fixture::new();
25990        assert_eq!(
25991            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
25992            "+OK\r\n"
25993        );
25994        assert_eq!(
25995            f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
25996            "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
25997        );
25998        assert_eq!(
25999            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
26000            "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
26001        );
26002        assert_eq!(
26003            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
26004            "+OK\r\n"
26005        );
26006        assert_eq!(
26007            f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
26008            "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
26009        );
26010        assert_eq!(
26011            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
26012            "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
26013        );
26014        let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
26015        for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
26016            assert_eq!(
26017                f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
26018                bad,
26019                "{given:?}"
26020            );
26021        }
26022        assert_eq!(
26023            f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
26024            "+OK\r\n"
26025        );
26026    }
26027
26028    /// True and false, either case, and none of the other words a client might
26029    /// reach for.
26030    #[test]
26031    fn a_yes_or_no_setting_takes_those_two_words_only() {
26032        let mut f = Fixture::new();
26033        assert_eq!(
26034            f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
26035            "+OK\r\n"
26036        );
26037        assert_eq!(
26038            f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
26039            "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
26040        );
26041        for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
26042            assert_eq!(
26043                f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
26044                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
26045                "{given:?}"
26046            );
26047        }
26048    }
26049
26050    /// Two pairs of names sit over one number each, and one of that second pair
26051    /// takes no value at all.
26052    #[test]
26053    fn two_names_for_one_setting_move_together() {
26054        let mut f = Fixture::new();
26055        f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
26056        assert_eq!(
26057            f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
26058            "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
26059        );
26060        f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
26061        assert_eq!(
26062            f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
26063            "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
26064        );
26065        let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
26066        let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
26067        f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
26068        assert_eq!(
26069            f.run(&[b"FT.CONFIG", b"GET", short]),
26070            "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
26071        );
26072        assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
26073        assert_eq!(
26074            f.run(&[b"FT.CONFIG", b"GET", long]),
26075            "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
26076        );
26077    }
26078
26079    /// The one setting that takes a write and never gives it back.
26080    #[test]
26081    fn a_password_reads_back_as_stars_whatever_was_written() {
26082        let mut f = Fixture::new();
26083        assert_eq!(
26084            f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
26085            "+OK\r\n"
26086        );
26087        assert_eq!(
26088            f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
26089            "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
26090        );
26091    }
26092
26093    /// The settings are not in the keyspace, so unlike the dictionaries and the
26094    /// synonym groups beside them they live through an emptied one.
26095    #[test]
26096    fn a_flush_leaves_the_settings_alone() {
26097        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
26098            let mut f = Fixture::new();
26099            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
26100            f.run(&[flush]);
26101            assert_eq!(
26102                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
26103                "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
26104                "{flush:?}"
26105            );
26106        }
26107    }
26108
26109    // ---------------------------------------------------------------- debug
26110
26111    /// A small index with one of everything a dump can read, so the tests below
26112    /// all name the same three documents and the same four fields.
26113    fn debugging() -> Fixture {
26114        let mut f = Fixture::new();
26115        f.run(&[
26116            b"FT.CREATE",
26117            b"dx",
26118            b"PREFIX",
26119            b"1",
26120            b"d:",
26121            b"SCHEMA",
26122            b"t",
26123            b"TEXT",
26124            b"g",
26125            b"TAG",
26126            b"n",
26127            b"NUMERIC",
26128            b"s",
26129            b"TEXT",
26130            b"SORTABLE",
26131        ]);
26132        f.run(&[
26133            b"HSET",
26134            b"d:1",
26135            b"t",
26136            b"running dogs",
26137            b"g",
26138            b"red,blue",
26139            b"n",
26140            b"1",
26141            b"s",
26142            b"Alpha",
26143        ]);
26144        f.run(&[
26145            b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
26146        ]);
26147        f.run(&[
26148            b"HSET",
26149            b"d:3",
26150            b"t",
26151            b"dogs alpha",
26152            b"g",
26153            b"green",
26154            b"n",
26155            b"3",
26156        ]);
26157        f
26158    }
26159
26160    /// The whole dictionary in byte order, with the stems in it as entries of
26161    /// their own rather than hidden behind the words they came from.
26162    #[test]
26163    fn a_term_dump_lists_the_stems_beside_the_words() {
26164        let mut f = debugging();
26165        assert_eq!(
26166            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
26167            "*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\
26168             $4\r\ndogs\r\n$7\r\nrunning\r\n"
26169        );
26170    }
26171
26172    /// A posting list is looked up on the bytes given and nothing folds them, so
26173    /// the term that a query would have found is not the term a dump wants.
26174    #[test]
26175    fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
26176        let mut f = debugging();
26177        assert_eq!(
26178            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
26179            "*2\r\n:1\r\n:2\r\n"
26180        );
26181        assert_eq!(
26182            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
26183            "*2\r\n:1\r\n:2\r\n"
26184        );
26185        for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
26186            assert_eq!(
26187                f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
26188                "-Can not find the inverted index\r\n",
26189                "{term:?}"
26190            );
26191        }
26192    }
26193
26194    /// Tag values come back folded and in byte order, each with the documents
26195    /// that hold it, and a document with two values is under both of them.
26196    #[test]
26197    fn a_tag_dump_pairs_every_value_with_its_documents() {
26198        let mut f = debugging();
26199        assert_eq!(
26200            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
26201            "*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\
26202             *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
26203        );
26204    }
26205
26206    /// One list holding every document in the field, which is D-96: a range tree
26207    /// answers one list per range and this answers the one it keeps.
26208    #[test]
26209    fn a_number_dump_answers_a_single_range() {
26210        let mut f = debugging();
26211        assert_eq!(
26212            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
26213            "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
26214        );
26215    }
26216
26217    /// A point is a number underneath, so the field that holds points answers
26218    /// the subcommand that dumps numbers and not the one that dumps tags.
26219    #[test]
26220    fn a_geo_field_is_dumped_as_a_numeric_one() {
26221        let mut f = Fixture::new();
26222        f.run(&[
26223            b"FT.CREATE",
26224            b"gx",
26225            b"PREFIX",
26226            b"1",
26227            b"q:",
26228            b"SCHEMA",
26229            b"loc",
26230            b"GEO",
26231            b"gg",
26232            b"AS",
26233            b"tag",
26234            b"TAG",
26235        ]);
26236        f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
26237        f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
26238        assert_eq!(
26239            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
26240            "*1\r\n*2\r\n:1\r\n:2\r\n"
26241        );
26242        assert_eq!(
26243            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
26244            "-Could not find given field in index spec\r\n"
26245        );
26246    }
26247
26248    /// A field is named the way a query names it, so the attribute is the name
26249    /// and the identifier the value was read from is not one.
26250    #[test]
26251    fn a_dump_takes_the_attribute_and_not_the_identifier() {
26252        let mut f = Fixture::new();
26253        f.run(&[
26254            b"FT.CREATE",
26255            b"zx",
26256            b"PREFIX",
26257            b"1",
26258            b"z:",
26259            b"SCHEMA",
26260            b"gg",
26261            b"AS",
26262            b"tag",
26263            b"TAG",
26264        ]);
26265        f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
26266        assert_eq!(
26267            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
26268            "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
26269        );
26270        assert_eq!(
26271            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
26272            "-Could not find given field in index spec\r\n"
26273        );
26274    }
26275
26276    /// The seven keys, with the score as a bulk string here and a double there,
26277    /// and the whole row flat on one protocol and a map on the other.
26278    #[test]
26279    fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
26280        let mut f = debugging();
26281        assert_eq!(
26282            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
26283            "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
26284             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
26285             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
26286             +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\
26287             $5\r\nvalue\r\n$5\r\nalpha\r\n"
26288        );
26289        let mut g = debugging();
26290        g.run(&[b"HELLO", b"3"]);
26291        assert_eq!(
26292            g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
26293            "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
26294             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
26295             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
26296             +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\
26297             $5\r\nvalue\r\n$5\r\nalpha\r\n"
26298        );
26299    }
26300
26301    /// A document that wrote nothing into a sortable slot has no sortables key
26302    /// at all, so the row is a key shorter rather than carrying an empty list.
26303    #[test]
26304    fn a_document_with_no_sortable_value_drops_the_key() {
26305        let mut f = debugging();
26306        assert_eq!(
26307            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
26308            "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
26309             +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"
26310        );
26311    }
26312
26313    /// The flag word is the number and then the names it stands for, and an
26314    /// index built without offsets has none of the three set.
26315    #[test]
26316    fn the_flag_word_spells_out_the_bits_it_carries() {
26317        let mut f = Fixture::new();
26318        f.run(&[
26319            b"FT.CREATE",
26320            b"nx",
26321            b"NOOFFSETS",
26322            b"PREFIX",
26323            b"1",
26324            b"o:",
26325            b"SCHEMA",
26326            b"t",
26327            b"TEXT",
26328        ]);
26329        f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
26330        assert!(
26331            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
26332                .contains("$6\r\n(0x0):\r\n")
26333        );
26334    }
26335
26336    /// Obfuscation replaces the field name with where the field sits in the
26337    /// whole schema, which is not where its value sits among the sortables.
26338    #[test]
26339    fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
26340        let mut f = debugging();
26341        assert!(
26342            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
26343                .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
26344        );
26345    }
26346
26347    /// The keyword is read where it belongs and anything after it is stepped
26348    /// over, whatever the line that complains about it says.
26349    #[test]
26350    fn a_document_row_reads_its_keyword_at_a_fixed_place() {
26351        let mut f = debugging();
26352        let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
26353        assert_eq!(
26354            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
26355            want
26356        );
26357        assert_eq!(
26358            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
26359            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
26360        );
26361        assert_eq!(
26362            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
26363            "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
26364        );
26365    }
26366
26367    /// The key is looked up before the keyword is read, so a key nobody indexed
26368    /// beats a keyword nobody wrote.
26369    #[test]
26370    fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
26371        let mut f = debugging();
26372        assert_eq!(
26373            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
26374            "-Document not found in index\r\n"
26375        );
26376        assert_eq!(
26377            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
26378            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
26379        );
26380    }
26381
26382    /// The two directions of the document table, and the number nobody handed
26383    /// out reads as one that was given up rather than as one that never was.
26384    #[test]
26385    fn a_document_number_goes_both_ways() {
26386        let mut f = debugging();
26387        assert_eq!(
26388            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
26389            "$3\r\nd:2\r\n"
26390        );
26391        assert_eq!(
26392            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
26393            ":2\r\n"
26394        );
26395        assert_eq!(
26396            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
26397            ":0\r\n"
26398        );
26399        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
26400        for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
26401            assert_eq!(
26402                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
26403                "-document was removed\r\n",
26404                "{id:?}"
26405            );
26406        }
26407    }
26408
26409    /// A document number is read the strict way Redis reads an integer, so a
26410    /// leading zero, a leading plus and a leading space are all refused.
26411    #[test]
26412    fn a_document_number_is_read_the_strict_way() {
26413        let mut f = debugging();
26414        for id in [
26415            b"x".as_slice(),
26416            b"1.5",
26417            b" 1",
26418            b"+1",
26419            b"01",
26420            b"0x1",
26421            b"",
26422            b"9223372036854775808",
26423            b"18446744073709551615",
26424        ] {
26425            assert_eq!(
26426                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
26427                "-bad id given\r\n",
26428                "{id:?}"
26429            );
26430        }
26431    }
26432
26433    /// A number a document has given up is still in every list it was in, so a
26434    /// dump names documents that the table says are gone.
26435    #[test]
26436    fn a_dump_keeps_a_number_the_table_has_given_up() {
26437        let mut f = debugging();
26438        f.run(&[b"DEL", b"d:2"]);
26439        assert_eq!(
26440            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
26441            "*2\r\n:1\r\n:2\r\n"
26442        );
26443        assert_eq!(
26444            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
26445            "-document was removed\r\n"
26446        );
26447        assert_eq!(
26448            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
26449            ":0\r\n"
26450        );
26451    }
26452
26453    /// A rewrite hands out a new number and leaves the old one behind, so the
26454    /// counter climbs past the number of documents there are.
26455    #[test]
26456    fn a_rewrite_takes_a_number_of_its_own() {
26457        let mut f = debugging();
26458        f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
26459        assert_eq!(
26460            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
26461            ":4\r\n"
26462        );
26463        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
26464        assert_eq!(
26465            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
26466            "-document was removed\r\n"
26467        );
26468        assert_eq!(
26469            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
26470            "*2\r\n:1\r\n:2\r\n"
26471        );
26472    }
26473
26474    /// An alias reads the index it stands for, the same as a query does.
26475    #[test]
26476    fn a_dump_follows_an_alias() {
26477        let mut f = debugging();
26478        f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
26479        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
26480        assert_eq!(
26481            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
26482            "$3\r\nd:1\r\n"
26483        );
26484    }
26485
26486    /// The index name is matched as written and the subcommand name is not, and
26487    /// an index nobody made is reported as a context that could not be built.
26488    #[test]
26489    fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
26490        let mut f = debugging();
26491        assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
26492        assert_eq!(
26493            f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
26494            "-Can not create a search ctx\r\n"
26495        );
26496        assert_eq!(
26497            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
26498            "-Can not create a search ctx\r\n"
26499        );
26500    }
26501
26502    /// A field with nothing written into it answers an empty dump rather than an
26503    /// error, since the field is in the schema and only the values are missing.
26504    #[test]
26505    fn an_empty_field_dumps_as_nothing_at_all() {
26506        let mut f = Fixture::new();
26507        f.run(&[
26508            b"FT.CREATE",
26509            b"ex",
26510            b"PREFIX",
26511            b"1",
26512            b"e:",
26513            b"SCHEMA",
26514            b"t",
26515            b"TEXT",
26516            b"g",
26517            b"TAG",
26518            b"n",
26519            b"NUMERIC",
26520        ]);
26521        assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
26522        assert_eq!(
26523            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
26524            "*0\r\n"
26525        );
26526        assert_eq!(
26527            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
26528            "*0\r\n"
26529        );
26530        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
26531    }
26532
26533    /// The two lines the dispatcher owns are the two that carry a code word, and
26534    /// every subcommand but `DOCINFO` counts its arguments exactly.
26535    #[test]
26536    fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
26537        let mut f = debugging();
26538        for (sub, extra) in [
26539            (b"DUMP_TERMS".as_slice(), 1),
26540            (b"GET_MAX_DOC_ID", 1),
26541            (b"DUMP_INVIDX", 2),
26542            (b"DUMP_TAGIDX", 2),
26543            (b"DUMP_NUMIDX", 2),
26544            (b"IDTODOCID", 2),
26545            (b"DOCIDTOID", 2),
26546        ] {
26547            let want = format!(
26548                "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
26549                str::from_utf8(sub).unwrap()
26550            );
26551            for given in [extra - 1, extra + 1] {
26552                let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
26553                cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
26554                assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
26555            }
26556            let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
26557            right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
26558            assert_ne!(f.run(&right), want, "{sub:?}");
26559        }
26560        assert_eq!(
26561            f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
26562            "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
26563        );
26564    }
26565
26566    /// The eight names that answer rather than the sixty two a real server
26567    /// registers, which is D-97, and anything after the name is stepped over.
26568    #[test]
26569    fn the_help_names_the_subcommands_that_answer() {
26570        let mut f = Fixture::new();
26571        let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
26572             $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
26573             $14\r\nGET_MAX_DOC_ID\r\n";
26574        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
26575        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
26576    }
26577
26578    // ------------------------------------------------------------- synonyms
26579
26580    /// The terms are folded on the way in and the group ids are not, and one
26581    /// term can be in more than one group.
26582    #[test]
26583    fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
26584        let mut f = Fixture::new();
26585        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26586        assert_eq!(
26587            f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
26588            "+OK\r\n"
26589        );
26590        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
26591        assert_eq!(
26592            f.run(&[b"FT.SYNDUMP", b"e"]),
26593            "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
26594             $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
26595        );
26596    }
26597
26598    /// A group is not a comparison made at query time. It is a term of its
26599    /// own, so a word in a group reads as a union of the word, the groups it
26600    /// is in and its stem.
26601    #[test]
26602    fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
26603        let mut f = Fixture::new();
26604        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26605        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
26606        assert_eq!(
26607            f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
26608            "$69\r\nUNION {\n  jogging\n  ~gr(expanded)\n  +jog(expanded)\n  jog(expanded)\n}\n\r\n"
26609        );
26610    }
26611
26612    /// The lookup on the document side is on the word and never on the stem,
26613    /// and a group written after the documents were still finds them because
26614    /// the index is read again.
26615    ///
26616    /// The group holds `running` and `d2` says `runs`, so a query for another
26617    /// word of the group finds `d1` and leaves `d2` where it is. A query for
26618    /// `running` itself does find `d2`, through the stem branch of the union
26619    /// rather than through the group, which is why the two asserts differ.
26620    #[test]
26621    fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
26622        let mut f = Fixture::new();
26623        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26624        f.run(&[b"HSET", b"d1", b"t", b"boy"]);
26625        f.run(&[b"HSET", b"d2", b"t", b"runs"]);
26626        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
26627        assert_eq!(
26628            f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
26629            "*2\r\n:1\r\n$2\r\nd1\r\n"
26630        );
26631        assert_eq!(
26632            f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
26633            "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
26634        );
26635    }
26636
26637    /// Neither command makes an index and neither forgives a name that is not
26638    /// there, in the same words the rest of the group uses.
26639    #[test]
26640    fn a_synonym_command_on_a_name_that_is_not_there_fails() {
26641        let mut f = Fixture::new();
26642        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
26643        assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
26644        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
26645    }
26646
26647    /// The words after `PARAMS n` are counted before their shape is looked at,
26648    /// so a count that reaches past the end of the command and a count that is
26649    /// merely odd are two different errors.
26650    #[test]
26651    fn params_counts_the_words_before_it_pairs_them_up() {
26652        let mut f = Fixture::new();
26653        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26654        let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
26655                    Expected an argument, but none provided\r\n";
26656        let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
26657        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
26658        assert_eq!(
26659            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
26660            none
26661        );
26662        assert_eq!(
26663            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
26664            odd
26665        );
26666        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
26667        assert_eq!(
26668            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
26669            "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
26670        );
26671    }
26672
26673    // --------------------------------------------------------------- vectors
26674
26675    /// Five documents a unit apart along one axis, written in the opposite
26676    /// order to the one they sit in, so a reply in document order and a reply
26677    /// in distance order are two different replies.
26678    ///
26679    /// `d1` is furthest from the origin and `d5` is on it. The text field
26680    /// splits them so a query can narrow before it measures: `d1`, `d2` and
26681    /// `d4` say `alpha` and the other two say `beta`.
26682    fn vectored(f: &mut Fixture) {
26683        f.run(&[
26684            b"FT.CREATE",
26685            b"h",
26686            b"SCHEMA",
26687            b"t",
26688            b"TEXT",
26689            b"v",
26690            b"VECTOR",
26691            b"FLAT",
26692            b"6",
26693            b"TYPE",
26694            b"FLOAT32",
26695            b"DIM",
26696            b"2",
26697            b"DISTANCE_METRIC",
26698            b"L2",
26699        ]);
26700        let at: [&[u8]; 5] = [
26701            b"\x00\x00\x80\x40\x00\x00\x00\x00",
26702            b"\x00\x00\x40\x40\x00\x00\x00\x00",
26703            b"\x00\x00\x00\x40\x00\x00\x00\x00",
26704            b"\x00\x00\x80\x3f\x00\x00\x00\x00",
26705            b"\x00\x00\x00\x00\x00\x00\x00\x00",
26706        ];
26707        for (n, point) in at.iter().enumerate() {
26708            let key = format!("d{}", n + 1);
26709            let word: &[u8] = match n {
26710                0 | 1 | 3 => b"alpha",
26711                _ => b"beta",
26712            };
26713            f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
26714        }
26715    }
26716
26717    /// The origin, which every query below asks about.
26718    const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
26719
26720    /// A `KNN` picks the k nearest and then answers them in document order,
26721    /// which is measured: asking for three of five that were written furthest
26722    /// first answers the last three written and not the first three.
26723    #[test]
26724    fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
26725        let mut f = Fixture::new();
26726        vectored(&mut f);
26727        assert_eq!(
26728            f.run(&[
26729                b"FT.SEARCH",
26730                b"h",
26731                b"*=>[KNN 5 @v $vec]",
26732                b"PARAMS",
26733                b"2",
26734                b"vec",
26735                ORIGIN,
26736                b"DIALECT",
26737                b"2",
26738                b"NOCONTENT",
26739            ]),
26740            "*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"
26741        );
26742        assert_eq!(
26743            f.run(&[
26744                b"FT.SEARCH",
26745                b"h",
26746                b"*=>[KNN 3 @v $vec]",
26747                b"PARAMS",
26748                b"2",
26749                b"vec",
26750                ORIGIN,
26751                b"DIALECT",
26752                b"2",
26753                b"NOCONTENT",
26754            ]),
26755            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26756        );
26757    }
26758
26759    /// A range takes what is really inside it, where the distances are squared
26760    /// so the five documents sit at 16, 9, 4, 1 and 0.
26761    #[test]
26762    fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
26763        let mut f = Fixture::new();
26764        vectored(&mut f);
26765        for (radius, want) in [
26766            ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
26767            ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
26768            (
26769                "9",
26770                "*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",
26771            ),
26772        ] {
26773            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26774            assert_eq!(
26775                f.run(&[
26776                    b"FT.SEARCH",
26777                    b"h",
26778                    query.as_bytes(),
26779                    b"PARAMS",
26780                    b"2",
26781                    b"vec",
26782                    ORIGIN,
26783                    b"DIALECT",
26784                    b"2",
26785                    b"NOCONTENT",
26786                ]),
26787                want,
26788                "radius {radius}"
26789            );
26790        }
26791    }
26792
26793    /// A `KNN` behind a query is the nearest of what the query matched, so
26794    /// asking for two of the three documents that say `alpha` answers the two
26795    /// of those three that are nearest and not the two nearest overall.
26796    #[test]
26797    fn a_knn_measures_what_the_query_in_front_of_it_matched() {
26798        let mut f = Fixture::new();
26799        vectored(&mut f);
26800        assert_eq!(
26801            f.run(&[
26802                b"FT.SEARCH",
26803                b"h",
26804                b"alpha=>[KNN 2 @v $vec]",
26805                b"PARAMS",
26806                b"2",
26807                b"vec",
26808                ORIGIN,
26809                b"DIALECT",
26810                b"2",
26811                b"NOCONTENT",
26812            ]),
26813            "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
26814        );
26815    }
26816
26817    /// A `KNN` counts in whole numbers and a range measures from zero, and the
26818    /// two are refused in their own words.
26819    ///
26820    /// The count is a token of its own and is checked where it stands, ahead of
26821    /// the field and ahead of the vector. A count that arrives through `PARAMS`
26822    /// is read by looser rules than one written into the query, which is
26823    /// measured: a leading plus is fine in a parameter and a syntax error in
26824    /// the query text.
26825    #[test]
26826    fn a_count_and_a_radius_are_refused_in_their_own_words() {
26827        let mut f = Fixture::new();
26828        vectored(&mut f);
26829        let ask = |f: &mut Fixture, query: &str| {
26830            f.run(&[
26831                b"FT.SEARCH",
26832                b"h",
26833                query.as_bytes(),
26834                b"PARAMS",
26835                b"2",
26836                b"vec",
26837                ORIGIN,
26838                b"DIALECT",
26839                b"2",
26840                b"NOCONTENT",
26841            ])
26842        };
26843        for (query, at, near) in [
26844            ("*=>[KNN -1 @v $vec]", 8, "-1"),
26845            ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
26846            ("*=>[KNN +3 @v $vec]", 8, "+3"),
26847            ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
26848            ("*=>[KNN abc @v $vec]", 8, "abc"),
26849            ("*=>[KNN 3 $vec]", 10, "vec"),
26850            ("*=>[KNN 3 @v vec]", 13, "vec"),
26851            ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
26852        ] {
26853            assert_eq!(
26854                ask(&mut f, query),
26855                format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
26856                "{query}"
26857            );
26858        }
26859
26860        // Read as a double the way a real server reads it, so the bound plus
26861        // thirty two rounds back onto the bound and gets in.
26862        let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26863                     query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
26864        assert_eq!(
26865            ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
26866            "*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"
26867        );
26868        assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
26869        assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
26870
26871        for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
26872            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26873            assert_eq!(
26874                ask(&mut f, &query),
26875                format!(
26876                    "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26877                     negative radius ({printed}) given in a range query\r\n"
26878                ),
26879                "{query}"
26880            );
26881        }
26882        // A radius of minus zero is not below zero and is a radius of zero.
26883        assert_eq!(
26884            ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
26885            "*2\r\n:1\r\n$2\r\nd5\r\n"
26886        );
26887    }
26888
26889    /// A count passed with `PARAMS` is read the way a real server reads one,
26890    /// which is not the way the same digits are read in the query text.
26891    #[test]
26892    fn a_count_that_came_from_params_is_read_by_its_own_rules() {
26893        let mut f = Fixture::new();
26894        vectored(&mut f);
26895        let ask = |f: &mut Fixture, count: &[u8]| {
26896            f.run(&[
26897                b"FT.SEARCH",
26898                b"h",
26899                b"*=>[KNN $k @v $vec]",
26900                b"PARAMS",
26901                b"4",
26902                b"vec",
26903                ORIGIN,
26904                b"k",
26905                count,
26906                b"DIALECT",
26907                b"2",
26908                b"NOCONTENT",
26909            ])
26910        };
26911        let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
26912        assert_eq!(ask(&mut f, b"3"), three);
26913        assert_eq!(ask(&mut f, b"  3"), three);
26914        assert_eq!(ask(&mut f, b"+3"), three);
26915        for bad in [
26916            &b"3.0"[..],
26917            b"0x3",
26918            b"-1",
26919            b"abc",
26920            b"",
26921            b"99999999999999999999",
26922        ] {
26923            let value = String::from_utf8_lossy(bad).into_owned();
26924            assert_eq!(
26925                ask(&mut f, bad),
26926                format!(
26927                    "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
26928                     for parameter `k`\r\n"
26929                ),
26930                "{value}"
26931            );
26932        }
26933        assert_eq!(
26934            ask(&mut f, b"288230376151711777"),
26935            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26936             query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
26937        );
26938    }
26939
26940    /// A vector the wrong size is refused against the field it was passed to,
26941    /// naming both sizes in bytes.
26942    #[test]
26943    fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
26944        let mut f = Fixture::new();
26945        vectored(&mut f);
26946        assert_eq!(
26947            f.run(&[
26948                b"FT.SEARCH",
26949                b"h",
26950                b"*=>[KNN 5 @v $vec]",
26951                b"PARAMS",
26952                b"2",
26953                b"vec",
26954                b"abc",
26955                b"DIALECT",
26956                b"2",
26957                b"NOCONTENT",
26958            ]),
26959            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26960             query vector blob size (3) does not match index's expected size (8).\r\n"
26961        );
26962    }
26963
26964    /// A nearest neighbour clause puts its distance on every row it answers,
26965    /// under `__v_score` unless the query renamed it. A range clause puts
26966    /// nothing there at all unless the query named it, which is what
26967    /// `YIELD_DISTANCE_AS` is for.
26968    #[test]
26969    fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
26970        let mut f = Fixture::new();
26971        vectored(&mut f);
26972        let ask = |f: &mut Fixture, query: &str| {
26973            f.run(&[
26974                b"FT.SEARCH",
26975                b"h",
26976                query.as_bytes(),
26977                b"PARAMS",
26978                b"2",
26979                b"vec",
26980                ORIGIN,
26981                b"DIALECT",
26982                b"2",
26983                b"LIMIT",
26984                b"0",
26985                b"1",
26986            ])
26987        };
26988        assert_eq!(
26989            ask(&mut f, "*=>[KNN 3 @v $vec]"),
26990            "*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"
26991        );
26992        assert_eq!(
26993            ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
26994            "*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"
26995        );
26996        assert_eq!(
26997            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
26998            "*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"
26999        );
27000        assert_eq!(
27001            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
27002            "*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"
27003        );
27004    }
27005
27006    /// What decides whether a `RETURN` answers the distance is the name the row
27007    /// would carry it under and not the field it would have been read from,
27008    /// because it is on the row before any key is read.
27009    ///
27010    /// So naming it answers it, renaming it answers nothing at all, and giving
27011    /// its name to another field answers the distance under that name.
27012    #[test]
27013    fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
27014        let mut f = Fixture::new();
27015        vectored(&mut f);
27016        let ask = |f: &mut Fixture, ret: &[&[u8]]| {
27017            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
27018            args.extend_from_slice(ret);
27019            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
27020            f.run(&args)
27021        };
27022        assert_eq!(
27023            ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
27024            "*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"
27025        );
27026        assert_eq!(
27027            ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
27028            "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
27029        );
27030        assert_eq!(
27031            ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
27032            "*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"
27033        );
27034        assert_eq!(
27035            ask(&mut f, &[b"RETURN", b"1", b"t"]),
27036            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
27037        );
27038        // The distance goes in front of the rest whatever order they were
27039        // named in, and `NOCONTENT` takes it away with everything else.
27040        assert_eq!(
27041            ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
27042            "*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"
27043        );
27044        assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
27045    }
27046
27047    /// A `SORTBY` can name a distance the query yielded, which sorts by the
27048    /// number rather than by anything the key holds. A name the query did not
27049    /// yield is refused the way any other unknown property is.
27050    #[test]
27051    fn a_sortby_can_name_a_distance_the_query_yielded() {
27052        let mut f = Fixture::new();
27053        vectored(&mut f);
27054        let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
27055            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
27056            if desc {
27057                args.push(b"DESC");
27058            }
27059            args.extend_from_slice(&[
27060                b"PARAMS",
27061                b"2",
27062                b"vec",
27063                ORIGIN,
27064                b"DIALECT",
27065                b"2",
27066                b"NOCONTENT",
27067            ]);
27068            f.run(&args)
27069        };
27070        assert_eq!(
27071            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
27072            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
27073        );
27074        assert_eq!(
27075            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
27076            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
27077        );
27078        assert_eq!(
27079            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
27080            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
27081        );
27082        // Renaming it takes the old name away, and a query with no vector
27083        // clause in it never had the property at all.
27084        let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
27085                       not loaded nor in schema\r\n";
27086        assert_eq!(
27087            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
27088            missing
27089        );
27090        assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
27091        // The query is read before the property is looked up, which is
27092        // measured: a query that will not parse is answered first.
27093        assert_eq!(
27094            ask(&mut f, "foo(", b"zz", false),
27095            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
27096        );
27097    }
27098
27099    /// Two vector clauses in one query answer two distances, outermost first.
27100    #[test]
27101    fn two_vector_clauses_answer_two_distances() {
27102        let mut f = Fixture::new();
27103        vectored(&mut f);
27104        assert_eq!(
27105            f.run(&[
27106                b"FT.SEARCH",
27107                b"h",
27108                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
27109                b"RETURN",
27110                b"2",
27111                b"rr",
27112                b"__v_score",
27113                b"PARAMS",
27114                b"2",
27115                b"vec",
27116                ORIGIN,
27117                b"DIALECT",
27118                b"2",
27119            ]),
27120            "*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"
27121        );
27122    }
27123
27124    /// An aggregation carries the distance on every row whether or not the
27125    /// pipeline ever mentions it, and carries it in front of everything a
27126    /// `LOAD` asked for.
27127    #[test]
27128    fn an_aggregation_answers_a_distance_nothing_asked_for() {
27129        let mut f = Fixture::new();
27130        vectored(&mut f);
27131        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
27132            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
27133            args.extend_from_slice(rest);
27134            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
27135            f.run(&args)
27136        };
27137        assert_eq!(
27138            ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
27139            "*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"
27140        );
27141        assert_eq!(
27142            ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
27143            "*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"
27144        );
27145        assert_eq!(
27146            ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
27147            "*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"
27148        );
27149        // A range shows nothing until the query names it.
27150        assert_eq!(
27151            ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
27152            "*3\r\n:1\r\n*0\r\n*0\r\n"
27153        );
27154        assert_eq!(
27155            ask(
27156                &mut f,
27157                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
27158                &[]
27159            ),
27160            "*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"
27161        );
27162    }
27163
27164    /// A nearest neighbour clause hands its documents back nearest first and an
27165    /// aggregation keeps them that way, where a search sorts them into document
27166    /// order. A tie goes to the document written first.
27167    #[test]
27168    fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
27169        let mut f = Fixture::new();
27170        vectored(&mut f);
27171        // Sitting on `d3`, so `d2` and `d4` are the same distance away.
27172        const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
27173        let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
27174            f.run(&[
27175                b"FT.AGGREGATE",
27176                b"h",
27177                query.as_bytes(),
27178                b"LOAD",
27179                b"1",
27180                b"@t",
27181                b"PARAMS",
27182                b"2",
27183                b"vec",
27184                vec,
27185                b"DIALECT",
27186                b"2",
27187            ])
27188        };
27189        assert_eq!(
27190            ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
27191            "*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"
27192        );
27193        // A range does no ordering, so those rows stay in document order.
27194        assert_eq!(
27195            ask(
27196                &mut f,
27197                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
27198                MIDDLE
27199            ),
27200            "*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"
27201        );
27202    }
27203
27204    /// Every step of the pipeline can name a distance the query yielded, and a
27205    /// query with no vector clause in it is refused for the name three
27206    /// different ways depending on which step asked.
27207    #[test]
27208    fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
27209        let mut f = Fixture::new();
27210        vectored(&mut f);
27211        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
27212            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
27213            args.extend_from_slice(rest);
27214            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
27215            f.run(&args)
27216        };
27217        let knn = "*=>[KNN 2 @v $vec]";
27218        assert_eq!(
27219            ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
27220            "*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"
27221        );
27222        assert_eq!(
27223            ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
27224            "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
27225        );
27226        assert_eq!(
27227            ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
27228            "*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"
27229        );
27230        assert_eq!(
27231            ask(
27232                &mut f,
27233                knn,
27234                &[
27235                    b"GROUPBY",
27236                    b"1",
27237                    b"@t",
27238                    b"REDUCE",
27239                    b"MAX",
27240                    b"1",
27241                    b"@__v_score",
27242                    b"AS",
27243                    b"m"
27244                ]
27245            ),
27246            "*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"
27247        );
27248        assert_eq!(
27249            ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
27250            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
27251             `__v_score`\r\n"
27252        );
27253        assert_eq!(
27254            ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
27255            "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
27256        );
27257        assert_eq!(
27258            ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
27259            "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
27260             schema\r\n"
27261        );
27262    }
27263
27264    /// An aggregation reads every word before it reads the query, and reads the
27265    /// query before it ties anything on the pipeline to a place on the row.
27266    ///
27267    /// So a command with a fault in all three answers the one about the words,
27268    /// a command with a fault in the last two answers the one about the query,
27269    /// and the pipeline speaks last. That is measured, and it is the whole
27270    /// reason the arguments are read twice.
27271    #[test]
27272    fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
27273        let mut f = Fixture::new();
27274        vectored(&mut f);
27275        let ask = |f: &mut Fixture, rest: &[&[u8]]| {
27276            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
27277            args.extend_from_slice(rest);
27278            f.run(&args)
27279        };
27280        assert_eq!(
27281            ask(
27282                &mut f,
27283                &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
27284            ),
27285            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27286        );
27287        assert_eq!(
27288            ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
27289            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
27290        );
27291        assert_eq!(
27292            ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
27293            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
27294        );
27295        // An expression that will not read is the pipeline's fault too, so it
27296        // speaks after the query and after a property named before it.
27297        assert_eq!(
27298            ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
27299            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
27300        );
27301        assert_eq!(
27302            ask(
27303                &mut f,
27304                &[
27305                    b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
27306                ]
27307            ),
27308            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
27309        );
27310        assert_eq!(
27311            ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
27312            "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
27313        );
27314    }
27315
27316    /// A vector clause says which of the ways of answering one it took, and a
27317    /// range says nothing at all when there is no distance to hand back.
27318    #[test]
27319    fn a_vector_step_says_which_way_it_was_answered() {
27320        let mut f = Fixture::new();
27321        vectored(&mut f);
27322        let tree = |f: &mut Fixture, query: &[u8]| {
27323            let reply = timeless(&f.run(&[
27324                b"FT.PROFILE",
27325                b"h",
27326                b"AGGREGATE",
27327                b"QUERY",
27328                query,
27329                b"PARAMS",
27330                b"2",
27331                b"vec",
27332                ORIGIN,
27333                b"DIALECT",
27334                b"2",
27335            ]));
27336            let at = reply.find("+Iterators profile").expect("a tree");
27337            let end = reply.find("+Result processors").expect("a list of steps");
27338            reply[at..end].to_string()
27339        };
27340        assert_eq!(
27341            tree(&mut f, b"*=>[KNN 3 @v $vec]"),
27342            "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
27343             +Number of reading operations\r\n:3\r\n\
27344             +Vector search mode\r\n+STANDARD_KNN\r\n"
27345        );
27346        // Renaming the distance changes nothing about how it was answered.
27347        assert_eq!(
27348            tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
27349            tree(&mut f, b"*=>[KNN 3 @v $vec]")
27350        );
27351        // A range with nothing to yield is not a vector step at all, and one
27352        // that yields names the distance in its own type.
27353        assert_eq!(
27354            tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
27355            "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
27356             +Number of reading operations\r\n:4\r\n"
27357        );
27358        assert_eq!(
27359            tree(
27360                &mut f,
27361                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
27362            ),
27363            "+Iterators profile\r\n*8\r\n\
27364             +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
27365             +Number of reading operations\r\n:4\r\n\
27366             +Vector search mode\r\n+RANGE_QUERY\r\n"
27367        );
27368    }
27369
27370    /// What a vector clause narrowed itself down with hangs under it as a
27371    /// single child, and the step that works the distances out is behind the
27372    /// index whenever the query yields one.
27373    #[test]
27374    fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
27375        let mut f = Fixture::new();
27376        vectored(&mut f);
27377        let ask = |f: &mut Fixture, query: &[u8]| {
27378            timeless(&f.run(&[
27379                b"FT.PROFILE",
27380                b"h",
27381                b"AGGREGATE",
27382                b"QUERY",
27383                query,
27384                b"PARAMS",
27385                b"2",
27386                b"vec",
27387                ORIGIN,
27388                b"DIALECT",
27389                b"2",
27390            ]))
27391        };
27392        let cut = |reply: &str| {
27393            let at = reply.find("+Iterators profile").expect("a tree");
27394            reply[at..].to_string()
27395        };
27396        assert_eq!(
27397            cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
27398            "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
27399             +Number of reading operations\r\n:3\r\n\
27400             +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
27401             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
27402             +Number of reading operations\r\n:3\r\n\
27403             +Estimated number of matches\r\n:3\r\n\
27404             +Result processors profile\r\n*2\r\n\
27405             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
27406             *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
27407             +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
27408        );
27409        // A range nobody named yields nothing, so nothing works a distance out
27410        // and the step is not there.
27411        assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
27412            "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
27413             +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
27414        ));
27415        // A nearest neighbour clause with nothing in front of it yields all
27416        // the same, so the step is there without a child above it.
27417        assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
27418    }
27419
27420    /// A `LIMIT 0 0` on an aggregation is a client asking for the total and
27421    /// nothing else, so the step that would have paged the rows counts them
27422    /// instead, whether or not a `SORTBY` put an order in front of it.
27423    #[test]
27424    fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
27425        let mut f = profiling();
27426        let steps = |f: &mut Fixture, words: &[&[u8]]| {
27427            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
27428            argv.extend_from_slice(words);
27429            let reply = timeless(&f.run(&argv));
27430            let at = reply.find("+Result processors").expect("a list of steps");
27431            reply[at..].to_string()
27432        };
27433        assert_eq!(
27434            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
27435            "+Result processors profile\r\n*2\r\n\
27436             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
27437             *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
27438             +Coordinator\r\n*0\r\n"
27439        );
27440        assert!(
27441            steps(
27442                &mut f,
27443                &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
27444            )
27445            .contains("+Type\r\n+Counter\r\n")
27446        );
27447        // A window that keeps something is still a window.
27448        assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
27449            "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
27450             +Results processed\r\n:2\r\n"
27451        ));
27452    }
27453
27454    // ----------------------------------------------------------- spellcheck
27455
27456    /// The score is how many documents hold the suggestion over how many
27457    /// documents there are, and how close the suggestion is to the word does
27458    /// not come into it at all, so the nearer of the two words here is second.
27459    #[test]
27460    fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
27461        let mut f = Fixture::new();
27462        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27463        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27464        f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
27465        assert_eq!(
27466            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
27467            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27468             *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"
27469        );
27470    }
27471
27472    /// On RESP3 the whole thing is wrapped in a map under one name, a word
27473    /// carries a list of one pair maps, and the score is a double rather than
27474    /// a string.
27475    #[test]
27476    fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
27477        let mut f = Fixture::new();
27478        f.run(&[b"HELLO", b"3"]);
27479        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27480        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27481        assert_eq!(
27482            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
27483            "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
27484             *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
27485        );
27486    }
27487
27488    /// A word the index already holds is not a mistake and is left out of the
27489    /// answer, and that check never looks at the field the query named, while
27490    /// the search for candidates does.
27491    #[test]
27492    fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
27493        let mut f = Fixture::new();
27494        f.run(&[
27495            b"FT.CREATE",
27496            b"e",
27497            b"SCHEMA",
27498            b"a",
27499            b"TEXT",
27500            b"NOSTEM",
27501            b"b",
27502            b"TEXT",
27503            b"NOSTEM",
27504        ]);
27505        f.run(&[b"HSET", b"d1", b"b", b"world"]);
27506        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
27507        assert_eq!(
27508            f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
27509            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
27510        );
27511    }
27512
27513    /// A dictionary named by `INCLUDE` adds words the index never read, scored
27514    /// zero and reported in the spelling the dictionary was given, and one
27515    /// named by `EXCLUDE` says a word is spelled right after all.
27516    #[test]
27517    fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
27518        let mut f = Fixture::new();
27519        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27520        f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
27521        assert_eq!(
27522            f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
27523            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
27524             *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"
27525        );
27526        assert_eq!(
27527            f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
27528            "*0\r\n"
27529        );
27530        assert_eq!(
27531            f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
27532            "-Dict does not exist: nope\r\n"
27533        );
27534    }
27535
27536    /// The first `DISTANCE` counts and the rest are dropped, an argument
27537    /// nobody recognises is stepped over rather than refused, and a distance
27538    /// outside one to four is the one thing here that does fail.
27539    #[test]
27540    fn a_spellcheck_reads_its_arguments_leniently() {
27541        let mut f = Fixture::new();
27542        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27543        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27544        let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27545                   *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
27546        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
27547        let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
27548        let args: &[&[u8]] = &[
27549            b"FT.SPELLCHECK",
27550            b"e",
27551            b"helqp",
27552            b"DISTANCE",
27553            b"1",
27554            b"DISTANCE",
27555            b"4",
27556        ];
27557        assert_eq!(f.run(args), none);
27558        assert_eq!(
27559            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
27560            "-bad distance given, distance must be a natural number between 1 to 4\r\n"
27561        );
27562        assert_eq!(
27563            f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
27564            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
27565        );
27566    }
27567
27568    // -------------------------------------------------------------- suggest
27569
27570    /// The reply is the size of the dictionary afterwards, which is neither
27571    /// what was added nor whether anything changed.
27572    #[test]
27573    fn an_add_answers_how_many_suggestions_are_in_there_now() {
27574        let mut f = Fixture::new();
27575        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
27576        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
27577        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
27578        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
27579        assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
27580    }
27581
27582    /// A suggestion dictionary is the one thing the search module puts in the
27583    /// keyspace, so every keyspace command reaches it.
27584    #[test]
27585    fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
27586        let mut f = Fixture::new();
27587        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27588        assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
27589        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
27590        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
27591        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
27592        assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
27593        assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
27594        assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
27595        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
27596    }
27597
27598    /// The last suggestion out takes the key with it, which most module types
27599    /// do not do.
27600    #[test]
27601    fn deleting_the_last_suggestion_deletes_the_key() {
27602        let mut f = Fixture::new();
27603        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27604        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
27605        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
27606        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
27607        assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
27608    }
27609
27610    /// A key holding anything else is refused rather than overwritten, on all
27611    /// four of them.
27612    #[test]
27613    fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
27614        let mut f = Fixture::new();
27615        f.run(&[b"SET", b"s", b"x"]);
27616        for cmd in [
27617            vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
27618            vec![&b"FT.SUGGET"[..], b"s", b"t"],
27619            vec![&b"FT.SUGDEL"[..], b"s", b"t"],
27620            vec![&b"FT.SUGLEN"[..], b"s"],
27621        ] {
27622            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
27623        }
27624        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
27625    }
27626
27627    /// The scores in here were read off a real server, single precision and
27628    /// all. An exact match answers a sentinel so it sorts in front.
27629    #[test]
27630    fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
27631        let mut f = Fixture::new();
27632        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27633        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27634        f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
27635        assert_eq!(
27636            f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
27637            "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
27638             $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
27639             $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
27640        );
27641        assert_eq!(
27642            f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
27643            "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
27644        );
27645        assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
27646    }
27647
27648    /// `FUZZY` is one edit, and the edit is a rune rather than a byte.
27649    #[test]
27650    fn fuzzy_allows_one_edit_and_nothing_allows_two() {
27651        let mut f = Fixture::new();
27652        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27653        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
27654        assert_eq!(
27655            f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
27656            "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
27657        );
27658        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
27659    }
27660
27661    /// Five without a `MAX`, and the terms come back in score order.
27662    #[test]
27663    fn a_lookup_answers_five_unless_it_is_told_otherwise() {
27664        let mut f = Fixture::new();
27665        for (term, score) in [
27666            (&b"a1"[..], &b"1"[..]),
27667            (b"a2", b"2"),
27668            (b"a3", b"3"),
27669            (b"a4", b"4"),
27670            (b"a5", b"5"),
27671            (b"a6", b"6"),
27672        ] {
27673            f.run(&[b"FT.SUGADD", b"s", term, score]);
27674        }
27675        assert_eq!(
27676            f.run(&[b"FT.SUGGET", b"s", b"a"]),
27677            "*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"
27678        );
27679        assert_eq!(
27680            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
27681            "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
27682        );
27683        // A `MAX` larger than the dictionary answers what there is.
27684        assert!(
27685            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
27686                .starts_with("*6\r\n")
27687        );
27688    }
27689
27690    /// A payload is replaced only when one is given, and an empty one is no
27691    /// payload at all.
27692    #[test]
27693    fn a_payload_comes_back_beside_the_term_or_a_null_does() {
27694        let mut f = Fixture::new();
27695        f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
27696        assert_eq!(
27697            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27698            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27699        );
27700        f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
27701        assert_eq!(
27702            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27703            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27704        );
27705        // An empty payload is the same as not having given one at all, so it
27706        // leaves the payload where it is rather than clearing it.
27707        f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
27708        assert_eq!(
27709            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27710            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27711        );
27712        // A term that never had one answers a null.
27713        f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
27714        assert_eq!(
27715            f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
27716            "*2\r\n$5\r\nother\r\n$-1\r\n"
27717        );
27718    }
27719
27720    /// `INCR` adds to the score that is there rather than replacing it, and
27721    /// three tenths a tenth at a time is the reading that shows the score is
27722    /// held in single precision.
27723    #[test]
27724    fn incr_adds_to_the_score_that_is_already_there() {
27725        let mut f = Fixture::new();
27726        for _ in 0..3 {
27727            f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
27728        }
27729        assert_eq!(
27730            f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
27731            "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
27732        );
27733    }
27734
27735    /// The five error sentences, none of which are written the same way.
27736    #[test]
27737    fn the_suggestion_errors_are_the_lines_the_module_sends() {
27738        let mut f = Fixture::new();
27739        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27740        assert_eq!(
27741            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
27742            "-ERR invalid score\r\n"
27743        );
27744        // The unknown word is complained about before the score is converted.
27745        assert_eq!(
27746            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
27747            "-Unknown argument `NOPE`\r\n"
27748        );
27749        assert_eq!(
27750            f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
27751            "-Invalid payload: Expected an argument, but none provided\r\n"
27752        );
27753        // Too many words is an arity error and not an unknown argument.
27754        assert!(
27755            f.run(&[
27756                b"FT.SUGADD",
27757                b"s",
27758                b"t",
27759                b"1",
27760                b"PAYLOAD",
27761                b"a",
27762                b"PAYLOAD",
27763                b"b"
27764            ])
27765            .contains("wrong number of arguments")
27766        );
27767        assert_eq!(
27768            f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
27769            "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
27770        );
27771        // A count read as a whole number and then found to be out of range,
27772        // against one that had to be read as a double first, where anything
27773        // under one is a conversion that failed rather than a range that did.
27774        for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
27775            assert_eq!(
27776                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27777                "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
27778                "{}",
27779                String::from_utf8_lossy(max)
27780            );
27781        }
27782        for max in [
27783            &b"abc"[..],
27784            b"0.0",
27785            b"00",
27786            b"-0",
27787            b"+0",
27788            b"0.5",
27789            b"-1.5",
27790            b"1e400",
27791        ] {
27792            assert_eq!(
27793                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27794                "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
27795                "{}",
27796                String::from_utf8_lossy(max)
27797            );
27798        }
27799        for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
27800            assert_eq!(
27801                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27802                "*1\r\n$3\r\none\r\n",
27803                "{}",
27804                String::from_utf8_lossy(max)
27805            );
27806        }
27807        assert_eq!(
27808            f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
27809            "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
27810        );
27811        // A score too large for a double is refused where one spelled out is
27812        // taken, which is the module reading errno after the conversion.
27813        assert_eq!(
27814            f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
27815            "-ERR invalid score\r\n"
27816        );
27817        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
27818    }
27819
27820    /// An empty term is taken and not stored, so the reply is the length that
27821    /// was already there and nothing new comes back. The key is still made,
27822    /// and a delete that finds nothing is what clears it away again.
27823    #[test]
27824    fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
27825        let mut f = Fixture::new();
27826        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27827        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
27828        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
27829        assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
27830        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
27831        assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
27832        assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
27833        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
27834    }
27835
27836    /// A key that will not read is counted against the index and against the
27837    /// field, and `FT.INFO` says so.
27838    #[test]
27839    fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
27840        let mut f = Fixture::new();
27841        f.run(&[
27842            b"FT.CREATE",
27843            b"ix",
27844            b"PREFIX",
27845            b"1",
27846            b"p:",
27847            b"SCHEMA",
27848            b"n",
27849            b"NUMERIC",
27850        ]);
27851        f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
27852        assert_eq!(held(&f, b"ix"), (0, 0));
27853
27854        let reply = f.run(&[b"FT.INFO", b"ix"]);
27855        assert!(
27856            reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
27857            "{reply}"
27858        );
27859        assert!(reply.contains("hash_indexing_failures"), "{reply}");
27860    }
27861
27862    /// An index can only be made on database zero, and the check comes after
27863    /// the `IFNX` shortcut and before everything else.
27864    #[test]
27865    fn an_index_can_only_be_made_on_database_zero() {
27866        let mut f = Fixture::new();
27867        f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
27868        f.run(&[b"SELECT", b"1"]);
27869        let refused = "-Cannot create index on db != 0\r\n";
27870        assert_eq!(
27871            f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
27872            refused
27873        );
27874        // The name is taken, and it still answers about the database.
27875        assert_eq!(
27876            f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27877            refused
27878        );
27879        // And so does one whose arguments are nonsense.
27880        assert_eq!(
27881            f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
27882            refused
27883        );
27884        // `IFNX` over a name that is taken is the one that gets through.
27885        assert_eq!(
27886            f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27887            "+OK\r\n"
27888        );
27889        assert_eq!(f.server.search.lock().len(), 1);
27890    }
27891
27892    /// The scan reads the database the create was run on, and after that the
27893    /// index follows its keys in every database.
27894    ///
27895    /// The asymmetry is a real server's, measured, and it is the sort of thing
27896    /// nobody would arrive at by choosing.
27897    #[test]
27898    fn the_scan_is_one_database_and_the_following_is_all_of_them() {
27899        let mut f = Fixture::new();
27900        f.run(&[b"SELECT", b"1"]);
27901        f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
27902        f.run(&[b"SELECT", b"0"]);
27903        f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
27904        f.run(&[
27905            b"FT.CREATE",
27906            b"ix",
27907            b"PREFIX",
27908            b"1",
27909            b"p:",
27910            b"SCHEMA",
27911            b"t",
27912            b"TEXT",
27913        ]);
27914        assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
27915
27916        f.run(&[b"SELECT", b"1"]);
27917        f.run(&[b"HSET", b"p:8", b"t", b"later"]);
27918        assert_eq!(
27919            held(&f, b"ix"),
27920            (2, 2),
27921            "and then it follows every database"
27922        );
27923    }
27924
27925    /// Four documents over the two kinds of field a query can ask about, which
27926    /// is the corpus the searches below read.
27927    fn corpus(f: &mut Fixture) {
27928        f.run(&[
27929            b"FT.CREATE",
27930            b"sx",
27931            b"PREFIX",
27932            b"1",
27933            b"d:",
27934            b"SCHEMA",
27935            b"t",
27936            b"TEXT",
27937            b"g",
27938            b"TAG",
27939            b"n",
27940            b"NUMERIC",
27941        ]);
27942        for (key, text, tag, number) in [
27943            (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
27944            (b"d:2", "alpha gamma", "bb", "2"),
27945            (b"d:3", "delta", "cc", "3"),
27946            (b"d:4", "alpha beta gamma", "aa,cc", "4"),
27947        ] {
27948            f.run(&[
27949                b"HSET",
27950                key,
27951                b"t",
27952                text.as_bytes(),
27953                b"g",
27954                tag.as_bytes(),
27955                b"n",
27956                number.as_bytes(),
27957            ]);
27958        }
27959    }
27960
27961    /// A corpus with something to sort by: a text field the index keeps a copy
27962    /// of, a number, the same text field under another name, and a text field
27963    /// the index keeps nothing of.
27964    fn sortable(f: &mut Fixture) {
27965        f.run(&[
27966            b"FT.CREATE",
27967            b"sy",
27968            b"PREFIX",
27969            b"1",
27970            b"s:",
27971            b"SCHEMA",
27972            b"t",
27973            b"TEXT",
27974            b"SORTABLE",
27975            b"n",
27976            b"NUMERIC",
27977            b"SORTABLE",
27978            b"body",
27979            b"AS",
27980            b"b",
27981            b"TEXT",
27982            b"SORTABLE",
27983            b"p",
27984            b"TEXT",
27985        ]);
27986        for (key, text, number) in [
27987            (b"s:1".as_slice(), "Banana Split", "2"),
27988            (b"s:2", "apple", "10"),
27989        ] {
27990            f.run(&[
27991                b"HSET",
27992                key,
27993                b"t",
27994                text.as_bytes(),
27995                b"n",
27996                number.as_bytes(),
27997                b"body",
27998                text.as_bytes(),
27999                b"p",
28000                b"alpha",
28001            ]);
28002        }
28003        // A key with nothing under either sortable field, which is what sorts
28004        // last whichever way round the sort runs.
28005        f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
28006    }
28007
28008    /// A sort runs off the copy of the value the index keeps, and a row with no
28009    /// value at all is last both ways round.
28010    #[test]
28011    fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
28012        let mut f = Fixture::new();
28013        sortable(&mut f);
28014        assert_eq!(
28015            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
28016            "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
28017        );
28018        assert_eq!(
28019            f.run(&[
28020                b"FT.SEARCH",
28021                b"sy",
28022                b"alpha",
28023                b"SORTBY",
28024                b"n",
28025                b"DESC",
28026                b"NOCONTENT"
28027            ]),
28028            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
28029        );
28030        // The copy of a text field is folded, so `apple` sorts before
28031        // `Banana Split` where a comparison of the bytes would not.
28032        assert_eq!(
28033            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
28034            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
28035        );
28036    }
28037
28038    /// A field the index keeps no copy of is sorted by the value read off the
28039    /// key, which happens after the walk rather than during it.
28040    #[test]
28041    fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
28042        let mut f = Fixture::new();
28043        sortable(&mut f);
28044        f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
28045        assert_eq!(
28046            f.run(&[
28047                b"FT.SEARCH",
28048                b"sy",
28049                b"alpha",
28050                b"SORTBY",
28051                b"p",
28052                b"NOCONTENT",
28053                b"LIMIT",
28054                b"0",
28055                b"2"
28056            ]),
28057            "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
28058        );
28059        // Nothing is folded on this side, because the schema never asked for a
28060        // copy to fold, so the value goes into the sort as it was written.
28061        assert_eq!(
28062            f.run(&[
28063                b"FT.SEARCH",
28064                b"sy",
28065                b"alpha",
28066                b"SORTBY",
28067                b"p",
28068                b"WITHSORTKEYS",
28069                b"NOCONTENT",
28070                b"LIMIT",
28071                b"2",
28072                b"1"
28073            ]),
28074            "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
28075        );
28076    }
28077
28078    /// The value the sort compared goes beside every row, as a number after a
28079    /// hash, as text after a dollar, and as a null on a row that had none.
28080    #[test]
28081    fn a_search_can_send_the_value_it_sorted_by_back() {
28082        let mut f = Fixture::new();
28083        sortable(&mut f);
28084        assert_eq!(
28085            f.run(&[
28086                b"FT.SEARCH",
28087                b"sy",
28088                b"alpha",
28089                b"SORTBY",
28090                b"n",
28091                b"WITHSORTKEYS",
28092                b"NOCONTENT"
28093            ]),
28094            concat!(
28095                "*7\r\n:3\r\n",
28096                "$3\r\ns:1\r\n$2\r\n#2\r\n",
28097                "$3\r\ns:2\r\n$3\r\n#10\r\n",
28098                "$3\r\ns:3\r\n$-1\r\n"
28099            )
28100        );
28101        assert_eq!(
28102            f.run(&[
28103                b"FT.SEARCH",
28104                b"sy",
28105                b"alpha",
28106                b"SORTBY",
28107                b"t",
28108                b"WITHSORTKEYS",
28109                b"NOCONTENT"
28110            ]),
28111            concat!(
28112                "*7\r\n:3\r\n",
28113                "$3\r\ns:2\r\n$6\r\n$apple\r\n",
28114                "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
28115                "$3\r\ns:3\r\n$-1\r\n"
28116            )
28117        );
28118        // Asking for a sort key without sorting is taken and answers a null on
28119        // every row, which is what a real server does.
28120        assert_eq!(
28121            f.run(&[
28122                b"FT.SEARCH",
28123                b"sy",
28124                b"banana",
28125                b"WITHSORTKEYS",
28126                b"NOCONTENT"
28127            ]),
28128            "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
28129        );
28130    }
28131
28132    /// The field a search sorted by is written in front of the fields of the
28133    /// key, and the key's own value for it wins when the two share a name.
28134    #[test]
28135    fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
28136        let mut f = Fixture::new();
28137        sortable(&mut f);
28138        // `b` is what the schema calls the field the key calls `body`, so the
28139        // folded copy comes back under one name and the value as it was written
28140        // comes back under the other.
28141        assert_eq!(
28142            f.run(&[
28143                b"FT.SEARCH",
28144                b"sy",
28145                b"alpha",
28146                b"SORTBY",
28147                b"b",
28148                b"LIMIT",
28149                b"0",
28150                b"1"
28151            ]),
28152            concat!(
28153                "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
28154                "$1\r\nb\r\n$5\r\napple\r\n",
28155                "$1\r\nt\r\n$5\r\napple\r\n",
28156                "$1\r\nn\r\n$2\r\n10\r\n",
28157                "$4\r\nbody\r\n$5\r\napple\r\n",
28158                "$1\r\np\r\n$5\r\nalpha\r\n"
28159            )
28160        );
28161        // With a `RETURN` list there is nothing to put in, so the field is moved
28162        // to the front of the names that were asked for instead.
28163        assert_eq!(
28164            f.run(&[
28165                b"FT.SEARCH",
28166                b"sy",
28167                b"alpha",
28168                b"SORTBY",
28169                b"b",
28170                b"RETURN",
28171                b"2",
28172                b"p",
28173                b"b",
28174                b"LIMIT",
28175                b"0",
28176                b"1"
28177            ]),
28178            concat!(
28179                "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
28180                "$1\r\nb\r\n$5\r\napple\r\n",
28181                "$1\r\np\r\n$5\r\nalpha\r\n"
28182            )
28183        );
28184    }
28185
28186    /// The four ways a `SORTBY` on a search is refused.
28187    #[test]
28188    fn a_search_refuses_the_sorts_it_cannot_run() {
28189        let mut f = Fixture::new();
28190        sortable(&mut f);
28191        assert_eq!(
28192            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
28193            "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
28194        );
28195        assert_eq!(
28196            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
28197            "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
28198        );
28199        assert_eq!(
28200            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
28201            "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
28202        );
28203        assert_eq!(
28204            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
28205            "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
28206        );
28207        // The property is looked up once the whole list has read cleanly, so a
28208        // word after it that nobody knows is the error that comes back.
28209        assert_eq!(
28210            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
28211            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
28212        );
28213    }
28214
28215    /// An index over two text fields, a number and a tag, holding one key whose
28216    /// `a` runs long enough to be worth cutting down and whose `b` and `g` hold
28217    /// nothing the query matches.
28218    fn marking(f: &mut Fixture) {
28219        f.run(&[
28220            b"FT.CREATE",
28221            b"mk",
28222            b"ON",
28223            b"HASH",
28224            b"PREFIX",
28225            b"1",
28226            b"m:",
28227            b"SCHEMA",
28228            b"a",
28229            b"TEXT",
28230            b"b",
28231            b"TEXT",
28232            b"n",
28233            b"NUMERIC",
28234            b"g",
28235            b"TAG",
28236        ]);
28237        f.run(&[
28238            b"HSET",
28239            b"m:1",
28240            b"a",
28241            b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
28242            b"b",
28243            b"t1 t2 t3 t4 t5 t6 t7 t8",
28244            b"n",
28245            b"1",
28246            b"g",
28247            b"red",
28248        ]);
28249    }
28250
28251    /// A field the query matched comes back as fragments and a field it did not
28252    /// comes back as its own front.
28253    #[test]
28254    fn a_summarize_cuts_a_field_down_to_what_matched() {
28255        let mut f = Fixture::new();
28256        marking(&mut f);
28257        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
28258        assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
28259        // `b` holds no match, so it keeps its front and loses its last word.
28260        assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
28261        // And so does the tag, which is a value like any other to this clause.
28262        assert!(got.contains("$1\r\nr\r\n"), "{got}");
28263    }
28264
28265    /// `FRAGS` is applied before the context either side of a fragment is worked
28266    /// out, so the fragment that is left runs over the match of the one that was
28267    /// dropped rather than stopping on it.
28268    #[test]
28269    fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
28270        let mut f = Fixture::new();
28271        marking(&mut f);
28272        let got = f.run(&[
28273            b"FT.SEARCH",
28274            b"mk",
28275            b"fox",
28276            b"SUMMARIZE",
28277            b"FRAGS",
28278            b"1",
28279            b"LEN",
28280            b"20",
28281        ]);
28282        assert!(
28283            got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
28284            "{got}"
28285        );
28286        // Keep both and the first stops on the second rather than running over
28287        // it, on the same query and the same budget.
28288        let two = f.run(&[
28289            b"FT.SEARCH",
28290            b"mk",
28291            b"fox",
28292            b"SUMMARIZE",
28293            b"FRAGS",
28294            b"2",
28295            b"LEN",
28296            b"20",
28297        ]);
28298        assert!(
28299            two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
28300            "{two}"
28301        );
28302    }
28303
28304    /// A `HIGHLIGHT` wraps every match, and on a field with no match in it the
28305    /// clause also calls off the cutting down a `SUMMARIZE` would have done.
28306    #[test]
28307    fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
28308        let mut f = Fixture::new();
28309        marking(&mut f);
28310        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
28311        assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
28312        let both = f.run(&[
28313            b"FT.SEARCH",
28314            b"mk",
28315            b"fox",
28316            b"SUMMARIZE",
28317            b"LEN",
28318            b"2",
28319            b"HIGHLIGHT",
28320        ]);
28321        assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
28322        // `b` still holds no match, and this time it comes back whole.
28323        assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
28324        assert!(both.contains("$3\r\nred\r\n"), "{both}");
28325        // Naming a field one clause does not cover leaves it cut down again.
28326        let split = f.run(&[
28327            b"FT.SEARCH",
28328            b"mk",
28329            b"fox",
28330            b"SUMMARIZE",
28331            b"FIELDS",
28332            b"1",
28333            b"b",
28334            b"LEN",
28335            b"2",
28336            b"HIGHLIGHT",
28337            b"FIELDS",
28338            b"1",
28339            b"a",
28340        ]);
28341        assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
28342    }
28343
28344    /// A tag is never marked, in its own field or in a text field beside it.
28345    #[test]
28346    fn a_highlight_does_not_mark_a_tag() {
28347        let mut f = Fixture::new();
28348        marking(&mut f);
28349        f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
28350        let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
28351        assert!(!got.contains("<b>"), "{got}");
28352        assert!(got.contains("red and blue"), "{got}");
28353    }
28354
28355    /// A search answers a total and then a row for every key in the window,
28356    /// with the fields of that key after it.
28357    #[test]
28358    fn a_search_answers_a_total_and_then_the_rows() {
28359        let mut f = Fixture::new();
28360        corpus(&mut f);
28361        assert_eq!(
28362            f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
28363            "*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"
28364        );
28365        // The fields are what the key holds and not what the schema names, so
28366        // a field nobody indexed comes back too.
28367        f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
28368        assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
28369        // `NOCONTENT` leaves the keys on their own, and `LIMIT 0 0` leaves
28370        // the total on its own.
28371        assert_eq!(
28372            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28373            "*2\r\n:1\r\n$3\r\nd:3\r\n"
28374        );
28375        assert_eq!(
28376            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28377            "*1\r\n:3\r\n"
28378        );
28379    }
28380
28381    /// The window is ten rows when nobody said, and the cap is on how wide it
28382    /// is rather than on where it starts.
28383    #[test]
28384    fn the_window_is_ten_rows_and_a_million_wide_at_most() {
28385        let mut f = Fixture::new();
28386        corpus(&mut f);
28387        assert_eq!(
28388            f.run(&[
28389                b"FT.SEARCH",
28390                b"sx",
28391                b"alpha",
28392                b"NOCONTENT",
28393                b"LIMIT",
28394                b"1",
28395                b"1"
28396            ]),
28397            "*2\r\n:3\r\n$3\r\nd:2\r\n"
28398        );
28399        assert_eq!(
28400            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
28401            "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
28402        );
28403        assert_eq!(
28404            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
28405            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
28406        );
28407        assert_eq!(
28408            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
28409            "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
28410        );
28411        assert_eq!(
28412            f.run(&[
28413                b"FT.SEARCH",
28414                b"sx",
28415                b"alpha",
28416                b"NOCONTENT",
28417                b"LIMIT",
28418                b"999999",
28419                b"1000000"
28420            ]),
28421            "*1\r\n:3\r\n"
28422        );
28423    }
28424
28425    /// `RETURN 0` reads on the wire like `NOCONTENT` and is not the same
28426    /// thing, because a later `RETURN` puts the fields back and a later
28427    /// `RETURN` after a `NOCONTENT` does not.
28428    #[test]
28429    fn a_return_of_nothing_is_not_the_same_as_nocontent() {
28430        let mut f = Fixture::new();
28431        corpus(&mut f);
28432        let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
28433        assert_eq!(
28434            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
28435            bare
28436        );
28437        assert_eq!(
28438            f.run(&[
28439                b"FT.SEARCH",
28440                b"sx",
28441                b"delta",
28442                b"NOCONTENT",
28443                b"RETURN",
28444                b"1",
28445                b"t"
28446            ]),
28447            bare
28448        );
28449        assert_eq!(
28450            f.run(&[
28451                b"FT.SEARCH",
28452                b"sx",
28453                b"delta",
28454                b"RETURN",
28455                b"0",
28456                b"RETURN",
28457                b"1",
28458                b"t"
28459            ]),
28460            "*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"
28461        );
28462    }
28463
28464    /// The count after `RETURN` counts words and not fields, so the `AS` and
28465    /// the name after it are two of them.
28466    #[test]
28467    fn the_count_after_return_counts_words() {
28468        let mut f = Fixture::new();
28469        corpus(&mut f);
28470        // Two words is one renamed field, and the name is the one it comes
28471        // back under.
28472        assert_eq!(
28473            f.run(&[
28474                b"FT.SEARCH",
28475                b"sx",
28476                b"delta",
28477                b"RETURN",
28478                b"3",
28479                b"t",
28480                b"AS",
28481                b"x"
28482            ]),
28483            "*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"
28484        );
28485        // A count that stops on the `AS` has nothing to rename to, and one
28486        // that reaches past the last word is short an argument.
28487        assert_eq!(
28488            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
28489            "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
28490        );
28491        assert_eq!(
28492            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
28493            "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
28494        );
28495        // A count that stops before the `AS` asks for a field called `AS`,
28496        // which no key holds, and a field the key does not hold is left out
28497        // rather than sent empty.
28498        assert_eq!(
28499            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
28500            "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
28501        );
28502    }
28503
28504    /// A `FILTER` is a numeric range written outside the query, and it is only
28505    /// the wrong way round on a field the schema holds as a number.
28506    #[test]
28507    fn a_filter_is_a_range_written_outside_the_query() {
28508        let mut f = Fixture::new();
28509        corpus(&mut f);
28510        assert_eq!(
28511            f.run(&[
28512                b"FT.SEARCH",
28513                b"sx",
28514                b"alpha",
28515                b"NOCONTENT",
28516                b"FILTER",
28517                b"n",
28518                b"2",
28519                b"4"
28520            ]),
28521            "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
28522        );
28523        assert_eq!(
28524            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
28525            "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
28526        );
28527        assert_eq!(
28528            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
28529            "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
28530        );
28531        assert_eq!(
28532            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
28533            "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
28534        );
28535        // The same range on a field that is not a number at all, and on a
28536        // field that is not there, answers nothing rather than refusing.
28537        for field in [b"g".as_slice(), b"nope"] {
28538            assert_eq!(
28539                f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
28540                "*1\r\n:0\r\n"
28541            );
28542        }
28543    }
28544
28545    /// The index is resolved before the arguments after it are read, so a name
28546    /// that is not there answers about the name whatever else is wrong.
28547    #[test]
28548    fn the_index_is_found_before_the_arguments_are_read() {
28549        let mut f = Fixture::new();
28550        corpus(&mut f);
28551        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
28552        assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
28553        assert_eq!(
28554            f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
28555            missing
28556        );
28557        // And the arguments are read before the query is, so a query that
28558        // will not parse still answers about the argument.
28559        assert_eq!(
28560            f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
28561            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
28562        );
28563    }
28564
28565    /// `INKEYS` filters the answer before the total is taken, which is not
28566    /// where a client would guess it happens.
28567    #[test]
28568    fn inkeys_comes_off_the_total() {
28569        let mut f = Fixture::new();
28570        corpus(&mut f);
28571        assert_eq!(
28572            f.run(&[
28573                b"FT.SEARCH",
28574                b"sx",
28575                b"alpha",
28576                b"NOCONTENT",
28577                b"INKEYS",
28578                b"1",
28579                b"d:1"
28580            ]),
28581            "*2\r\n:1\r\n$3\r\nd:1\r\n"
28582        );
28583        assert_eq!(
28584            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
28585            "*1\r\n:0\r\n"
28586        );
28587    }
28588
28589    /// The fields come from the database the session is on, and a row whose
28590    /// key will not load there is dropped from the reply and taken off the
28591    /// total.
28592    ///
28593    /// Measured against a real server, which follows a key on every database
28594    /// and then loads it from one.
28595    #[test]
28596    fn the_fields_are_read_from_the_session_database() {
28597        let mut f = Fixture::new();
28598        corpus(&mut f);
28599        f.run(&[b"SELECT", b"1"]);
28600        f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
28601        // Both documents are in the index, and only one of them is in this
28602        // database.
28603        assert_eq!(
28604            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28605            "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
28606        );
28607        assert_eq!(
28608            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28609            "*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"
28610        );
28611    }
28612
28613    /// The deeper protocol answers a map of five rather than an array, with
28614    /// every row a map of its own.
28615    #[test]
28616    fn the_third_protocol_answers_a_map_of_five() {
28617        let mut f = Fixture::new();
28618        corpus(&mut f);
28619        f.out = Out::new(Proto::Resp3);
28620        assert_eq!(
28621            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28622            concat!(
28623                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28624                "%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",
28625                "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28626            )
28627        );
28628    }
28629
28630    /// A window of nothing is a client asking for the count on its own, and a
28631    /// window of nothing that starts somewhere else is a contradiction all
28632    /// three commands refuse in the same words.
28633    #[test]
28634    fn a_window_of_nothing_has_to_start_at_the_top() {
28635        let mut f = Fixture::new();
28636        corpus(&mut f);
28637        let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
28638        assert_eq!(
28639            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28640            refused
28641        );
28642        assert_eq!(
28643            f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28644            refused
28645        );
28646        assert_eq!(
28647            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28648            refused
28649        );
28650        assert_eq!(
28651            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28652            "*1\r\n:3\r\n"
28653        );
28654    }
28655
28656    /// An aggregation answers a count and then a list of properties for every
28657    /// row, which is empty until something asks for a field.
28658    #[test]
28659    fn an_aggregation_answers_a_count_and_then_the_properties() {
28660        let mut f = Fixture::new();
28661        corpus(&mut f);
28662        assert_eq!(
28663            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
28664            "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
28665        );
28666        // Every row, and not the ten a search would have cut it down to. The
28667        // count in front of them is one because that is how far the reply had
28668        // got when it was written, which is measured against a real server.
28669        assert_eq!(
28670            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
28671            concat!(
28672                "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28673                "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
28674                "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
28675            )
28676        );
28677        // Ascending document number, because nothing sorts the answer. The
28678        // second and fourth documents are the ones the window lands on and the
28679        // best scoring one is not among them.
28680        assert_eq!(
28681            f.run(&[
28682                b"FT.AGGREGATE",
28683                b"sx",
28684                b"alpha",
28685                b"LOAD",
28686                b"1",
28687                b"@n",
28688                b"LIMIT",
28689                b"1",
28690                b"2"
28691            ]),
28692            "*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"
28693        );
28694        // A query nothing answers is a count of nothing and no rows at all.
28695        assert_eq!(
28696            f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
28697            "*1\r\n:0\r\n"
28698        );
28699    }
28700
28701    /// `LOAD` counts words rather than fields, names the property after the
28702    /// path unless an `AS` renames it, and reads everything the key holds when
28703    /// it is given a star.
28704    #[test]
28705    fn a_load_counts_words_and_can_rename_what_it_reads() {
28706        let mut f = Fixture::new();
28707        corpus(&mut f);
28708        // Three words, which are the path, the `AS` and the name.
28709        assert_eq!(
28710            f.run(&[
28711                b"FT.AGGREGATE",
28712                b"sx",
28713                b"alpha",
28714                b"LOAD",
28715                b"3",
28716                b"@t",
28717                b"AS",
28718                b"text"
28719            ]),
28720            concat!(
28721                "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
28722                "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
28723                "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
28724            )
28725        );
28726        assert_eq!(
28727            f.run(&[
28728                b"FT.AGGREGATE",
28729                b"sx",
28730                b"alpha",
28731                b"LOAD",
28732                b"*",
28733                b"LIMIT",
28734                b"0",
28735                b"1"
28736            ]),
28737            concat!(
28738                "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28739                "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
28740            )
28741        );
28742        // A field the key does not hold is left out rather than sent empty.
28743        assert_eq!(
28744            f.run(&[
28745                b"FT.AGGREGATE",
28746                b"sx",
28747                b"alpha",
28748                b"LOAD",
28749                b"2",
28750                b"@n",
28751                b"@nope",
28752                b"LIMIT",
28753                b"0",
28754                b"2"
28755            ]),
28756            "*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"
28757        );
28758    }
28759
28760    /// The `LOAD` grammar, which has four ways to go wrong and one of them is
28761    /// only reported once the rest of the argument list has read cleanly.
28762    #[test]
28763    fn a_load_refuses_a_count_it_cannot_use() {
28764        let mut f = Fixture::new();
28765        corpus(&mut f);
28766        let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
28767        assert_eq!(
28768            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
28769            format!("{head}Expected number of fields or `*`\r\n")
28770        );
28771        assert_eq!(
28772            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
28773            format!("{head}Value is outside acceptable bounds\r\n")
28774        );
28775        assert_eq!(
28776            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
28777            format!("{head}Expected an argument, but none provided\r\n")
28778        );
28779        assert_eq!(
28780            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
28781            format!("{head}Expected an argument, but none provided\r\n")
28782        );
28783        // A count that runs out on the `AS` is held back, because the word
28784        // after it is read as an argument of its own and may be worth an error
28785        // of its own. Nothing follows here, so the held back line is the one.
28786        assert_eq!(
28787            f.run(&[
28788                b"FT.AGGREGATE",
28789                b"sx",
28790                b"alpha",
28791                b"LOAD",
28792                b"2",
28793                b"@t",
28794                b"AS"
28795            ]),
28796            "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
28797        );
28798        // And here the word after it is one an aggregation stops taking once a
28799        // step has been read, so that is what the client hears about.
28800        assert_eq!(
28801            f.run(&[
28802                b"FT.AGGREGATE",
28803                b"sx",
28804                b"alpha",
28805                b"LOAD",
28806                b"2",
28807                b"@t",
28808                b"AS",
28809                b"VERBATIM"
28810            ]),
28811            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
28812        );
28813        // A `LOAD 0` is a step that names nothing. It shuts the same door
28814        // without becoming a loader, so the count stays the one a query with no
28815        // `LOAD` gets.
28816        assert_eq!(
28817            f.run(&[
28818                b"FT.AGGREGATE",
28819                b"sx",
28820                b"alpha",
28821                b"LOAD",
28822                b"0",
28823                b"LIMIT",
28824                b"0",
28825                b"1"
28826            ]),
28827            "*2\r\n:1\r\n*0\r\n"
28828        );
28829    }
28830
28831    /// Reading a step of the pipeline stops the words about the search itself
28832    /// being taken, and `LIMIT` and `TIMEOUT` are not steps.
28833    #[test]
28834    fn a_pipeline_step_closes_the_door_on_the_search_words() {
28835        let mut f = Fixture::new();
28836        corpus(&mut f);
28837        assert_eq!(
28838            f.run(&[
28839                b"FT.AGGREGATE",
28840                b"sx",
28841                b"alpha",
28842                b"LOAD",
28843                b"1",
28844                b"@t",
28845                b"VERBATIM"
28846            ]),
28847            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
28848        );
28849        assert_eq!(
28850            f.run(&[
28851                b"FT.AGGREGATE",
28852                b"sx",
28853                b"alpha",
28854                b"LIMIT",
28855                b"0",
28856                b"1",
28857                b"VERBATIM"
28858            ]),
28859            "*2\r\n:1\r\n*0\r\n"
28860        );
28861        // Three words a search takes that this command names in its refusal
28862        // rather than calling them unknown.
28863        for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
28864            let name = core::str::from_utf8(word).expect("the three words are text");
28865            assert_eq!(
28866                f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
28867                format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
28868            );
28869        }
28870    }
28871
28872    /// `ADDSCORES` writes the score as a property to twelve significant digits
28873    /// where `WITHSCORES` writes it beside the row in full.
28874    #[test]
28875    fn addscores_writes_a_shorter_score_than_withscores() {
28876        let mut f = Fixture::new();
28877        corpus(&mut f);
28878        assert_eq!(
28879            f.run(&[
28880                b"FT.AGGREGATE",
28881                b"sx",
28882                b"alpha",
28883                b"ADDSCORES",
28884                b"LOAD",
28885                b"1",
28886                b"@n",
28887                b"LIMIT",
28888                b"0",
28889                b"2"
28890            ]),
28891            concat!(
28892                "*3\r\n:1\r\n",
28893                "*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",
28894                "*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"
28895            )
28896        );
28897        // `NOCONTENT` takes the properties away and leaves whatever was asked
28898        // for beside them, and a sort key is always null because nothing sorts
28899        // by one yet.
28900        assert_eq!(
28901            f.run(&[
28902                b"FT.AGGREGATE",
28903                b"sx",
28904                b"alpha",
28905                b"NOCONTENT",
28906                b"WITHSCORES",
28907                b"LIMIT",
28908                b"0",
28909                b"2"
28910            ]),
28911            "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
28912        );
28913        assert_eq!(
28914            f.run(&[
28915                b"FT.AGGREGATE",
28916                b"sx",
28917                b"alpha",
28918                b"WITHSORTKEYS",
28919                b"LOAD",
28920                b"1",
28921                b"@n",
28922                b"LIMIT",
28923                b"0",
28924                b"1"
28925            ]),
28926            "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
28927        );
28928    }
28929
28930    /// The one scorer that has to see the whole answer first turns the count
28931    /// into the real total and hands the rows back backwards.
28932    #[test]
28933    fn a_normalising_scorer_answers_the_rows_backwards() {
28934        let mut f = Fixture::new();
28935        corpus(&mut f);
28936        assert_eq!(
28937            f.run(&[
28938                b"FT.AGGREGATE",
28939                b"sx",
28940                b"alpha",
28941                b"SCORER",
28942                b"BM25STD.NORM",
28943                b"ADDSCORES",
28944                b"LOAD",
28945                b"1",
28946                b"@n",
28947                b"LIMIT",
28948                b"1",
28949                b"2"
28950            ]),
28951            concat!(
28952                "*3\r\n:3\r\n",
28953                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
28954                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
28955            )
28956        );
28957        // Without `ADDSCORES` nothing on the row needs the score, so the rows
28958        // come back the way every other query answers them.
28959        assert_eq!(
28960            f.run(&[
28961                b"FT.AGGREGATE",
28962                b"sx",
28963                b"alpha",
28964                b"SCORER",
28965                b"BM25STD.NORM",
28966                b"LOAD",
28967                b"1",
28968                b"@n",
28969                b"LIMIT",
28970                b"1",
28971                b"2"
28972            ]),
28973            "*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"
28974        );
28975    }
28976
28977    /// The deeper protocol answers the same map of five a search answers, with
28978    /// the `id` gone because an aggregation is about the properties.
28979    #[test]
28980    fn an_aggregation_answers_a_map_of_five_as_well() {
28981        let mut f = Fixture::new();
28982        corpus(&mut f);
28983        f.out = Out::new(Proto::Resp3);
28984        assert_eq!(
28985            f.run(&[
28986                b"FT.AGGREGATE",
28987                b"sx",
28988                b"alpha",
28989                b"ADDSCORES",
28990                b"WITHSCORES",
28991                b"WITHSORTKEYS",
28992                b"LOAD",
28993                b"1",
28994                b"@n",
28995                b"LIMIT",
28996                b"0",
28997                b"1"
28998            ]),
28999            concat!(
29000                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
29001                "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
29002                "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
29003                "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
29004                "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
29005            )
29006        );
29007        // The count is worked out from the rows the reply reached under this
29008        // protocol, where under RESP2 it is worked out from the first of them.
29009        assert_eq!(
29010            f.run(&[
29011                b"FT.AGGREGATE",
29012                b"sx",
29013                b"alpha",
29014                b"NOCONTENT",
29015                b"LIMIT",
29016                b"0",
29017                b"1"
29018            ]),
29019            concat!(
29020                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
29021                "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
29022            )
29023        );
29024    }
29025    // ------------------------------------------------------------- CLIENT
29026
29027    /// The field names `CLIENT INFO` reports, in the order 8.10.1 reports them.
29028    ///
29029    /// Written out rather than derived, because the whole point of the command
29030    /// is that a parser somewhere else knows this list, so a change to it is a
29031    /// change a test should have to be edited for.
29032    const INFO_FIELDS: &[&str] = &[
29033        "id",
29034        "addr",
29035        "laddr",
29036        "fd",
29037        "name",
29038        "age",
29039        "idle",
29040        "flags",
29041        "db",
29042        "sub",
29043        "psub",
29044        "ssub",
29045        "multi",
29046        "watch",
29047        "qbuf",
29048        "qbuf-free",
29049        "argv-mem",
29050        "multi-mem",
29051        "rbs",
29052        "rbp",
29053        "obl",
29054        "oll",
29055        "omem",
29056        "omem-shared",
29057        "omem-unshared",
29058        "tot-mem",
29059        "events",
29060        "cmd",
29061        "user",
29062        "redir",
29063        "resp",
29064        "lib-name",
29065        "lib-ver",
29066        "io-thread",
29067        "tot-net-in",
29068        "tot-net-out",
29069        "tot-cmds",
29070        "read-events",
29071        "avg-pipeline-len-sum",
29072        "avg-pipeline-len-cnt",
29073    ];
29074
29075    /// The report as a list of name and value pairs, taken out of the bulk
29076    /// string the reply is on RESP2.
29077    fn client_info(f: &mut Fixture) -> Vec<(String, String)> {
29078        let reply = f.run(&[b"CLIENT", b"INFO"]);
29079        let body = reply.split_once("\r\n").expect("a bulk header").1;
29080        // A verbatim string on RESP3 carries its format in front of the text,
29081        // and the same reply is a plain bulk string on RESP2.
29082        let line = body.trim_end_matches("\r\n").trim_start_matches("txt:");
29083        assert!(
29084            line.ends_with('\n'),
29085            "the report ends in a newline: {line:?}"
29086        );
29087        line.trim_end()
29088            .split(' ')
29089            .map(|pair| {
29090                let (name, value) = pair.split_once('=').expect("name=value");
29091                (name.to_string(), value.to_string())
29092            })
29093            .collect()
29094    }
29095
29096    /// One field of the report.
29097    fn client_field(f: &mut Fixture, name: &str) -> String {
29098        client_info(f)
29099            .into_iter()
29100            .find(|(n, _)| n == name)
29101            .map(|(_, v)| v)
29102            .unwrap_or_else(|| panic!("no {name} field"))
29103    }
29104
29105    #[test]
29106    fn client_info_names_every_field_a_real_server_names() {
29107        let mut f = Fixture::new();
29108        let got: Vec<String> = client_info(&mut f).into_iter().map(|(n, _)| n).collect();
29109        assert_eq!(got, INFO_FIELDS);
29110    }
29111
29112    /// A session nobody told about a socket is what an embedded caller gets, and
29113    /// it has to answer rather than pretend to have an address.
29114    #[test]
29115    fn a_connection_with_no_socket_reports_no_address_and_no_descriptor() {
29116        let mut f = Fixture::new();
29117        assert_eq!(client_field(&mut f, "addr"), "");
29118        assert_eq!(client_field(&mut f, "laddr"), "");
29119        assert_eq!(client_field(&mut f, "fd"), "-1");
29120        assert_eq!(client_field(&mut f, "id"), "7");
29121    }
29122
29123    #[test]
29124    fn client_setname_takes_a_name_back_and_refuses_one_with_a_space_in_it() {
29125        let mut f = Fixture::new();
29126        assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$-1\r\n");
29127        assert_eq!(f.run(&[b"CLIENT", b"SETNAME", b"worker"]), "+OK\r\n");
29128        assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
29129        assert_eq!(client_field(&mut f, "name"), "worker");
29130        assert_eq!(
29131            f.run(&[b"CLIENT", b"SETNAME", b"two words"]),
29132            "-ERR Client names cannot contain spaces, newlines or special characters.\r\n"
29133        );
29134        // And the name it had is still the name it has.
29135        assert_eq!(f.run(&[b"CLIENT", b"GETNAME"]), "$6\r\nworker\r\n");
29136    }
29137
29138    /// `RESET` is `clearClientConnectionState`, and the surprising half of it is
29139    /// what it keeps: the library behind the socket is the same library it was.
29140    #[test]
29141    fn reset_clears_the_name_and_the_switches_and_keeps_the_library() {
29142        let mut f = Fixture::new();
29143        f.run(&[b"CLIENT", b"SETNAME", b"worker"]);
29144        f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME", b"yo-py"]);
29145        f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"1.2.3"]);
29146        f.run(&[b"CLIENT", b"NO-EVICT", b"on"]);
29147        f.run(&[b"CLIENT", b"NO-TOUCH", b"on"]);
29148        assert_eq!(client_field(&mut f, "flags"), "eT");
29149
29150        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
29151        assert_eq!(client_field(&mut f, "name"), "");
29152        assert_eq!(client_field(&mut f, "flags"), "N");
29153        assert_eq!(client_field(&mut f, "lib-name"), "yo-py");
29154        assert_eq!(client_field(&mut f, "lib-ver"), "1.2.3");
29155    }
29156
29157    #[test]
29158    fn client_setinfo_complains_the_way_a_real_server_does() {
29159        let mut f = Fixture::new();
29160        assert_eq!(
29161            f.run(&[b"CLIENT", b"SETINFO", b"LIB-NAME"]),
29162            "-ERR wrong number of arguments for 'client|setinfo' command\r\n"
29163        );
29164        assert_eq!(
29165            f.run(&[b"CLIENT", b"SETINFO", b"NOPE", b"x"]),
29166            "-ERR Unrecognized option 'NOPE'\r\n"
29167        );
29168        assert_eq!(
29169            f.run(&[b"CLIENT", b"SETINFO", b"lib-name", b"ok x"]),
29170            "-ERR lib-name cannot contain spaces, newlines or special characters.\r\n"
29171        );
29172        assert_eq!(
29173            f.run(&[b"CLIENT", b"SETINFO", b"LIB-VER", b"has space"]),
29174            "-ERR lib-ver cannot contain spaces, newlines or special characters.\r\n"
29175        );
29176    }
29177
29178    #[test]
29179    fn client_refuses_a_subcommand_it_does_not_have_and_arguments_it_did_not_ask_for() {
29180        let mut f = Fixture::new();
29181        assert_eq!(
29182            f.run(&[b"CLIENT", b"NOPE"]),
29183            "-ERR unknown subcommand 'NOPE'. Try CLIENT HELP.\r\n"
29184        );
29185        assert_eq!(
29186            f.run(&[b"CLIENT", b"GETNAME", b"extra"]),
29187            "-ERR wrong number of arguments for 'client|getname' command\r\n"
29188        );
29189        assert_eq!(
29190            f.run(&[b"CLIENT", b"NO-EVICT", b"maybe"]),
29191            "-ERR syntax error\r\n"
29192        );
29193        assert_eq!(
29194            f.run(&[b"CLIENT", b"REPLY", b"BAD"]),
29195            "-ERR syntax error\r\n"
29196        );
29197    }
29198
29199    /// The three subscribe namespaces are counted apart, which is not the same
29200    /// count a subscribe reply carries: that one puts channels and patterns
29201    /// together.
29202    #[test]
29203    fn client_info_counts_the_three_subscribe_namespaces_apart() {
29204        let mut f = Fixture::new();
29205        // On RESP3, because a subscribed RESP2 connection may only send nine
29206        // commands and `CLIENT` is not one of them.
29207        f.run(&[b"HELLO", b"3"]);
29208        f.run(&[b"SUBSCRIBE", b"a", b"b"]);
29209        f.run(&[b"PSUBSCRIBE", b"p*"]);
29210        f.run(&[b"SSUBSCRIBE", b"s"]);
29211        let info = client_info(&mut f);
29212        let at = |name: &str| {
29213            info.iter()
29214                .find(|(n, _)| n == name)
29215                .map(|(_, v)| v.clone())
29216                .unwrap()
29217        };
29218        assert_eq!(at("sub"), "2");
29219        assert_eq!(at("psub"), "1");
29220        assert_eq!(at("ssub"), "1");
29221        assert_eq!(at("flags"), "P");
29222        forget_session(&f.server, &mut f.session);
29223    }
29224
29225    /// The `cmd` field names the subcommand, which for this command is always
29226    /// `client|info` and is the one field that reports the command asking.
29227    #[test]
29228    fn client_info_reports_itself_as_the_command_running() {
29229        let mut f = Fixture::new();
29230        assert_eq!(client_field(&mut f, "cmd"), "client|info");
29231        f.run(&[b"GET", b"nothing"]);
29232        // Still `client|info`, because the field is about the command asking
29233        // and the command asking is this one.
29234        assert_eq!(client_field(&mut f, "cmd"), "client|info");
29235    }
29236
29237    /// A container called in mixed case is still the same command underneath.
29238    #[test]
29239    fn the_command_field_is_lower_case_however_the_client_spelled_it() {
29240        let mut f = Fixture::new();
29241        let reply = f.run(&[b"CLIENT", b"Info"]);
29242        assert!(reply.contains("cmd=client|info"), "{reply}");
29243    }
29244
29245    #[test]
29246    fn client_help_lists_the_subcommands_that_are_here() {
29247        let mut f = Fixture::new();
29248        let reply = f.run(&[b"CLIENT", b"HELP"]);
29249        for sub in ["ID", "GETNAME", "SETNAME", "SETINFO", "INFO", "REPLY"] {
29250            assert!(reply.contains(sub), "no {sub} in {reply}");
29251        }
29252        // And not the ones that are not, since a client reads this to find out
29253        // what it can send.
29254        assert!(!reply.contains("TRACKING"), "{reply}");
29255    }
29256}