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 cms;
61mod cpu;
62mod cuckoo;
63mod geo;
64mod graph;
65mod hashes;
66mod himport;
67mod hll;
68mod indexing;
69mod json;
70mod keyspace;
71mod lists;
72mod lua;
73mod migrate;
74mod multi;
75mod notify;
76mod pubsub;
77mod scan;
78mod scripting;
79mod search;
80mod server;
81mod sets;
82mod streams;
83mod strings;
84mod suggest;
85pub mod table;
86mod tdigest;
87mod topk;
88mod ts;
89mod vectors;
90mod vfilter;
91mod zsets;
92
93pub use args::Args;
94pub use blocking::{Parked, Waiters};
95pub(crate) use pubsub::Envelope;
96pub use server::parse_memory;
97pub use table::{COMMANDS, Spec, arity_ok, lookup};
98
99use crate::reply::Out;
100use std::cell::Cell;
101use std::path::{Path, PathBuf};
102use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
103use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize};
104use yo_common::lock::{Held, Lock};
105use yo_common::{Code, Error};
106use yo_kv::cold::Store;
107use yo_kv::{Clock, Db, Keyspace};
108use yo_search::Registry;
109
110use multi::Watches;
111use search::cursor::Cursors;
112
113/// How many databases a server has.
114///
115/// Redis's default is sixteen and its `databases` setting can change it. Ours
116/// is sixteen and cannot, which is why `CONFIG GET databases` can answer with a
117/// constant. Nothing in the design needs the number to be fixed; nothing yet
118/// needs it not to be.
119pub const DATABASES: usize = 16;
120
121/// Every database's bit in [`Server::dirty`], which is what a fresh server
122/// starts on so that the first maintenance turn asks all of them.
123///
124/// A `u64` holds sixteen bits with room to spare, and the assertion below is
125/// what turns raising [`DATABASES`] past sixty four into a build failure rather
126/// than a shift that silently drops the databases past the end.
127const ALL_DATABASES: u64 = if DATABASES == 64 {
128    u64::MAX
129} else {
130    (1u64 << DATABASES) - 1
131};
132const _: () = assert!(DATABASES <= 64);
133
134/// How many keys one command throws away before it leaves the rest to the next.
135///
136/// A bound and not a loop to the end, because this runs in front of a client
137/// that is waiting for its reply, and a server a long way over its limit would
138/// otherwise hold that client for as long as it took to walk all the way back
139/// under. Sixty four is a batch's worth of commands, so a server that went over
140/// by what one batch allocated comes back under in one command, and a server
141/// whose limit was just cut in half works through it over the next few thousand
142/// rather than in one long stall. Redis bounds the same loop by a time slice
143/// instead of a count and hands the rest to a timer; there is no timer here, so
144/// the rest goes to the next command that runs.
145const EVICT_BUDGET: usize = 64;
146
147/// The `maxstore` a server with no storage limit carries.
148///
149/// Sixteen exabytes, which is every disk there is and then some, so a server
150/// that set a limit this high and a server that set none behave the same way and
151/// the only difference is what `CONFIG GET maxstore` says. Zero cannot be the
152/// sentinel because zero is a limit with a meaning: nothing may live on the
153/// file.
154const NO_MAXSTORE: u64 = u64::MAX;
155
156/// What a server says to a command that would allocate when it has no room.
157///
158/// Redis's `shared.oomerr`, word for word including the full stop, because
159/// clients match on the `OOM` prefix and people match on the sentence.
160const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
161
162/// What the connection should do after a command.
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum Flow {
165    /// Read the next command.
166    Continue,
167    /// Write what is buffered and then close, which is what `QUIT` asks for.
168    Close,
169    /// Nothing was written and nothing is owed yet.
170    ///
171    /// The client is on the waiter list and its reply comes when a key it named
172    /// has something in it or when its deadline passes, whichever happens first.
173    /// Until then the connection stops reading commands, because a client that
174    /// is waiting for an answer is not a client that has sent another question.
175    Block,
176}
177
178/// A number one thread adds to and any thread may read.
179///
180/// The add is a load, an add and a store rather than a fetch and add, which on
181/// x86 is three ordinary instructions instead of one locked one. That is sound
182/// because every counter here has exactly one writer, which is what the slots
183/// below are for: two threads never hold the same counter, so nothing can be
184/// lost between the load and the store. A reader can be a command or two behind,
185/// and `INFO` on a running server is behind by the time the reply reaches the
186/// client anyway.
187#[derive(Debug, Default)]
188pub struct Counter(AtomicU64);
189
190impl Counter {
191    /// One more.
192    fn bump(&self) {
193        self.0.store(self.get().wrapping_add(1), Relaxed);
194    }
195
196    /// One fewer, stopping at zero.
197    ///
198    /// The floor is for the gauge, which is the number of open connections: a
199    /// close that arrives without its open, which nothing can do now and a
200    /// misplaced call could, is a number that stays at zero rather than one
201    /// that wraps to eighteen quintillion clients.
202    fn drop_one(&self) {
203        self.0.store(self.get().saturating_sub(1), Relaxed);
204    }
205
206    /// What it says.
207    fn get(&self) -> u64 {
208        self.0.load(Relaxed)
209    }
210
211    /// Back to zero, which is `CONFIG RESETSTAT`.
212    fn zero(&self) {
213        self.0.store(0, Relaxed);
214    }
215}
216
217/// The numbers `INFO` reports that this layer cannot see for itself.
218///
219/// The reactor owns the sockets, so the reactor is what knows how many clients
220/// there are. It counts them here and nothing else does anything with them
221/// except report them.
222#[derive(Debug, Default)]
223pub struct Stats {
224    /// Connections open right now.
225    clients: Counter,
226    /// Connections accepted since the server started.
227    connections: Counter,
228    /// Commands run since the server started, which this layer counts itself.
229    commands: Counter,
230}
231
232impl Stats {
233    /// A connection arrived.
234    pub fn opened(&self) {
235        self.clients.bump();
236        self.connections.bump();
237    }
238
239    /// A connection went away.
240    pub fn closed(&self) {
241        self.clients.drop_one();
242    }
243}
244
245/// Every thread's [`Stats`] added together, which is what `INFO` answers.
246#[derive(Debug, Clone, Copy, Default)]
247pub struct Totals {
248    /// Connections open right now.
249    pub clients: u64,
250    /// Connections accepted since the server started.
251    pub connections: u64,
252    /// Commands run since the server started.
253    pub commands: u64,
254}
255
256thread_local! {
257    /// Which set of counters the running thread writes into.
258    ///
259    /// Claimed the first time a thread counts anything and kept for as long as
260    /// the thread runs. It is a number rather than a pointer, so a thread that
261    /// has counted on one server and then counts on another lands in the same
262    /// place in both, and a process with two servers in it shares the numbering
263    /// between them. That is the tests and it is not `yodb`, which has one.
264    static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
265}
266
267/// What one thread keeps to itself.
268///
269/// One of these per thread and not one per server, because a number every
270/// thread writes to is a cache line every thread has to own to write to it, and
271/// at a few million commands a second that one line is the server. So each
272/// thread writes into its own and whoever needs the whole picture, which is
273/// `INFO` and the maintenance turn, puts the pieces together when it asks.
274///
275/// A cache line apart for the same reason, so that two threads writing at once
276/// are not two threads passing one line back and forth.
277#[derive(Debug)]
278#[repr(align(64))]
279struct Local {
280    /// What the reactor counts.
281    stats: Stats,
282    /// A counter per command, for `INFO commandstats`.
283    cmdstats: CommandStats,
284    /// Which databases this thread has run a command against since the
285    /// maintenance turn last took the mask.
286    ///
287    /// One bit per database. The thread ors into it and the turn takes the whole
288    /// of it with a swap, which is what keeps a mark that lands during the swap
289    /// from being lost: the worst that can happen is a bit the turn has already
290    /// taken being set again, and that costs one more look at a database with
291    /// nothing to collect.
292    dirty: AtomicU64,
293    /// The mask this thread's maintenance turn is working from.
294    ///
295    /// Its own and not a shared one, because a turn reads it in place and then
296    /// clears bits of it, and a shared mask cleared that way would lose whatever
297    /// another thread marked in between. Every thread turns a loop and every
298    /// loop maintains, so what stops the same work being done twice is not the
299    /// mask but the stripe lock underneath it: two threads that both look at
300    /// database nine take turns, and the second one finds nothing left to move.
301    ///
302    /// Starts with every database set, so a server that has just been built
303    /// looks at all of them once rather than waiting to be told about the ones
304    /// something was loaded into before any command ran.
305    turn: AtomicU64,
306    /// How many of this thread's clients are on the waiter list.
307    ///
308    /// The waiter list is one list behind one lock, and a thread can only answer
309    /// the waiters it parked itself, so a thread with none of its own has no
310    /// reason to take that lock at all. Without this the check is the server
311    /// wide count, and one client blocked anywhere puts every thread through the
312    /// shared lock after every command it runs and again on every disconnect.
313    ///
314    /// Only the thread this belongs to writes it, because parking, answering and
315    /// forgetting a waiter all happen on the thread that read the command, so
316    /// the load and the store either side of a change cannot lose one.
317    parked: AtomicUsize,
318}
319
320impl Default for Local {
321    fn default() -> Local {
322        Local {
323            stats: Stats::default(),
324            cmdstats: CommandStats::default(),
325            dirty: AtomicU64::new(0),
326            turn: AtomicU64::new(ALL_DATABASES),
327            parked: AtomicUsize::new(0),
328        }
329    }
330}
331
332impl Local {
333    /// Note that a command has run against these databases.
334    fn mark(&self, dbs: u64) {
335        self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
336    }
337
338    /// Add `dbs` to what this thread's turn is going to look at.
339    fn note(&self, dbs: u64) {
340        self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
341    }
342
343    /// Take `at` off the list of databases this thread's turn will look at.
344    fn done(&self, at: usize) {
345        self.turn
346            .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
347    }
348
349    /// Whether this thread's turn still has database `at` to look at.
350    fn wanted(&self, at: usize) -> bool {
351        self.turn.load(Relaxed) & (1u64 << at) != 0
352    }
353
354    /// Note that `n` more of this thread's clients are parked.
355    fn blocked(&self, n: usize) {
356        self.parked
357            .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
358    }
359
360    /// Note that `n` of them are not parked any more.
361    fn woke(&self, n: usize) {
362        self.parked
363            .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
364    }
365}
366
367/// Room for one thread, which is what a server starts with.
368fn one_thread() -> Box<[Local]> {
369    slots(1)
370}
371
372/// Room for `threads` of them.
373fn slots(threads: usize) -> Box<[Local]> {
374    (0..threads.max(1)).map(|_| Local::default()).collect()
375}
376
377/// Where the process was started, which is what `dir` defaults to.
378///
379/// A dot if the working directory cannot be read, which happens when it has
380/// been deleted out from under a running process. That is not a reason to
381/// refuse to start a server, and it leaves `BACKUP` to fail with the real error
382/// from the filesystem if anybody asks for one.
383fn working_dir() -> PathBuf {
384    std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
385}
386
387/// One command's counters, for `INFO commandstats`.
388///
389/// Three of Redis's five. `usec` and `usec_per_call` are not here because
390/// nothing times a command, and timing one means two clock reads around a call
391/// that takes tens of nanoseconds to begin with. Redis pays that because Redis
392/// has room for it; this does not, and a zero under a name that says microseconds
393/// is worse than an absent field, which is the same rule the rest of `INFO`
394/// follows.
395#[derive(Debug, Clone, Copy, Default)]
396pub struct CommandStat {
397    /// Times the command ran, whatever it answered.
398    pub calls: u64,
399    /// Times it was turned away before it ran, which is the wrong number of
400    /// arguments or no room under `maxmemory`.
401    pub rejected: u64,
402    /// Times it ran and answered with an error.
403    pub failed: u64,
404}
405
406impl CommandStat {
407    /// Whether this command has ever been seen.
408    ///
409    /// A row that has not is left out of the reply, which is what Redis does and
410    /// is why the section is a handful of lines on a working server rather than
411    /// one line per command in the table.
412    const fn seen(&self) -> bool {
413        self.calls != 0 || self.rejected != 0 || self.failed != 0
414    }
415}
416
417/// One command's counters as one thread keeps them.
418///
419/// The same three numbers as [`CommandStat`], which is what they add up to when
420/// `INFO` asks. This is the written form and that is the read one.
421#[derive(Debug, Default)]
422struct Row {
423    /// Times the command ran.
424    calls: Counter,
425    /// Times it was turned away before it ran.
426    rejected: Counter,
427    /// Times it ran and answered with an error.
428    failed: Counter,
429}
430
431/// A counter per command, indexed the way [`table::index_of`] says.
432///
433/// A flat array and not a map, because the dispatcher is already holding the
434/// spec and the spec's position in the table is two addresses subtracted. That
435/// makes the counting a load, an add and a store on a row the previous command
436/// of the same name has already pulled into cache.
437#[derive(Debug)]
438struct CommandStats(Box<[Row]>);
439
440impl Default for CommandStats {
441    fn default() -> CommandStats {
442        CommandStats((0..table::count()).map(|_| Row::default()).collect())
443    }
444}
445
446impl CommandStats {
447    /// The row for one command.
448    fn at(&self, spec: &'static Spec) -> &Row {
449        &self.0[table::index_of(spec)]
450    }
451}
452
453/// Where a database gets its store from, asked by database number.
454///
455/// `None` means that database cannot have one. The caller owns whatever the
456/// stores are cut out of, which for `yodb` is one `.yo` file with a log per
457/// database, and this crate never learns what any of that is.
458pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
459
460/// Every thread that runs commands here shares this server, so it has to be
461/// `Send` and `Sync`, and the check is here so that a type added to it that is
462/// neither is a compile error where it was added rather than an error in the
463/// code that starts the threads.
464const _: () = {
465    const fn shareable<T: Send + Sync>() {}
466    shareable::<Server>();
467};
468
469/// Everything a server holds.
470///
471/// One per process, however many threads are serving out of it. What is inside
472/// is either shared outright, which is the counters and the settings, or behind
473/// a lock, which is the stripes and the few pieces of state a command can
474/// change. What makes this a server rather than a shard is that it is the whole
475/// of what a connection can address.
476pub struct Server {
477    dbs: Vec<Db>,
478    /// How many stripes each database is cut into, the same for all of them.
479    ///
480    /// Kept here as well as in each database so that the flat slot arithmetic
481    /// below is a multiply and a divide against a field on the server rather
482    /// than a walk asking each database how wide it is.
483    width: usize,
484    clock: Clock,
485    started_ms: u64,
486    /// Where the next maintenance turn starts looking, so that a database
487    /// under constant write load cannot hold the other fifteen's space.
488    ///
489    /// Shared, because compaction is asked for from two places: the maintenance
490    /// turn, which is one thread, and a command that went over the memory limit
491    /// and is trying to get back under it, which is any thread. Two threads that
492    /// read the same cursor start on the same database, and what that costs is
493    /// one of them finding the other has already moved what was there.
494    next_db: AtomicUsize,
495    /// One bit per database, set when a command ran against it.
496    ///
497    /// The maintenance turn after every batch used to ask all sixteen
498    /// databases whether they had anything to collect, and asking costs a load
499    /// and a store in each one. Fifteen of those are cold lines on a server
500    /// where every client is on database zero, which is every server, and the
501    /// answer is no every time. This is the cheap half of the question: a
502    /// database nobody has touched since it last said no cannot have started
503    /// saying yes.
504    ///
505    /// What the connections are holding, kept by the engine.
506    ///
507    /// Shared, because every thread has connections and the memory total is one
508    /// total. Each thread adds and subtracts its own change rather than storing
509    /// a figure it worked out, so two threads whose buffers grew in the same
510    /// moment both count.
511    conn_bytes: AtomicUsize,
512    /// The `maxmemory` limit in bytes, zero when there is not one.
513    ///
514    /// Zero is the default and it is the whole reason the check in front of
515    /// every write is one comparison against a field that is already warm. It
516    /// is read by every command on every thread and written by a client that
517    /// sends `CONFIG SET`, so it is a number the threads can share rather than
518    /// a field one of them owns.
519    maxmemory: AtomicU64,
520    /// Where a database gets a store from the first time it needs one.
521    ///
522    /// A closure and not a store, because there are sixteen databases and a
523    /// server that fills memory on database zero should not have opened
524    /// anything for the other fifteen. Nothing is asked of this until a memory
525    /// limit is actually reached, so a server that never fills memory never
526    /// opens a file, and a server that has no file never has one of these.
527    ///
528    /// `None` from the closure means that database cannot have one, which is
529    /// how the caller says the file it opened has no more room for logs.
530    ///
531    /// Behind a lock because it is a closure the caller gave us and there is no
532    /// saying it can be run by two threads at once. It is asked once per
533    /// database, the first time that database has to move something, so a
534    /// server that has reached its memory limit takes this lock sixteen times
535    /// in its life.
536    store: Lock<Option<Box<StoreSource>>>,
537    /// The `maxstore` limit in bytes, `None` when there is not one.
538    ///
539    /// The storage limit, and the other half of the inversion `14` section 4.1
540    /// describes. `maxmemory` is a limit on memory and the right answer to a
541    /// memory limit on a system with a file under it is to move data to the
542    /// file, not to delete it. Deleting is the right answer to a limit on the
543    /// file, and this is that limit.
544    ///
545    /// Zero is not "no limit" here, which is the one place this reads
546    /// differently from `maxmemory` and is the difference that makes a drop in
547    /// cache possible. A storage budget of zero bytes means nothing may live on
548    /// the file, so migration cannot make room and eviction is the only thing
549    /// left, which is Redis exactly. `None` is no limit and is the default,
550    /// which with `noeviction` means the database grows until the disk is full
551    /// and then writes fail, which is what a database does.
552    ///
553    /// Shared between the threads the same way `maxmemory` is, and no limit is
554    /// [`NO_MAXSTORE`] rather than a second field saying whether the first one
555    /// counts. Two fields cannot be read as one, and a limit that was on when
556    /// the bytes were read and off by the time the number was is a limit that
557    /// answers from a server that never existed.
558    maxstore: AtomicU64,
559    /// What [`Server::memory_bytes`] said at the last maintenance turn.
560    ///
561    /// The reading is a walk over every collection in every database and cannot
562    /// go on a command path, so the command path reads this instead and is at
563    /// most one batch behind. What that costs is overshoot: a server can end a
564    /// batch holding one batch's worth of allocation more than its limit before
565    /// anything notices. A batch is 64 commands, so that is bounded by what 64
566    /// commands can allocate and not by how long the server runs.
567    ///
568    /// Only kept up to date when there is a limit to judge it against. A server
569    /// with no `maxmemory` never reads it and never pays for it.
570    ///
571    /// Shared, because it is read in front of every write on every thread and
572    /// written by whichever thread last took a reading. A reader that catches it
573    /// mid write gets one of the two readings and both of them were true a
574    /// moment ago, which is all this number ever claims to be.
575    used: AtomicUsize,
576    /// Which database the next eviction draws from.
577    ///
578    /// Its own cursor and not [`Server::next_db`], because eviction and
579    /// compaction move at different rates and sharing one would make the
580    /// database that gets compacted depend on how many keys were evicted.
581    ///
582    /// Shared for the same reason [`Server::next_db`] is, and with the same
583    /// answer: two threads evicting at once may pick the same database, and one
584    /// of them finds the other got there first and moves on.
585    evict_db: AtomicUsize,
586    /// Which database the next active expiry sweep starts at.
587    ///
588    /// A third cursor for the same reason there is a second one. A sweep runs on
589    /// every turn of the loop and compaction runs when there is dead space, so
590    /// sharing a cursor would make which database gets swept depend on which one
591    /// was last collected.
592    expire_db: AtomicUsize,
593    /// The millisecond the last active expiry sweep ran on, so the next one on
594    /// the same millisecond does not bother.
595    ///
596    /// One for the server and not one per thread, so the sweeping a server does
597    /// is a function of how long it has been running and not of how many threads
598    /// it was started with. Two threads that read the same millisecond can both
599    /// decide to sweep, which costs one extra sweep of a budget that is already
600    /// small and cannot happen twice for the same millisecond more than once per
601    /// thread.
602    expire_ms: AtomicU64,
603    /// Clients parked on a blocking command.
604    ///
605    /// Behind a lock because a client parks on the thread that ran its command
606    /// and is woken by whichever thread later puts something under a key it
607    /// named, and those are not the same thread. The lock is only ever taken to
608    /// park somebody, to serve somebody or to forget a connection that has gone,
609    /// so a command that does not block never touches it.
610    waiters: Lock<Waiters>,
611    /// How many clients are parked.
612    ///
613    /// Beside the list rather than read out of it, because every command asks
614    /// whether anybody is waiting and nearly every answer is no. Taking a lock
615    /// to be told no would be a cache line every thread has to own to ask, which
616    /// is the cost the list was put behind a lock to avoid.
617    ///
618    /// Written under the lock, by whoever changed the list, so the number and
619    /// the list agree except while a change is in progress. A reader that asks
620    /// during one is told about the moment before it, and the worst that costs
621    /// is a walk of the list that serves nobody or one that has not started yet
622    /// and happens on the next command instead.
623    parked: AtomicUsize,
624    /// Sockets `MIGRATE` is holding open to the servers it has talked to.
625    ///
626    /// Empty on a server nobody has migrated a key out of, which is nearly all
627    /// of them, and it costs a vector's three words to be empty.
628    ///
629    /// Behind a lock because a socket cannot be written by two threads at once
630    /// and a cache of them cannot be searched by one while another is taking an
631    /// entry out. It is held for the whole of a migration, which is a round trip
632    /// to another server, so two threads migrating at the same time take turns.
633    /// That is the right way round: the alternative is a socket per thread per
634    /// peer, and a `MIGRATE` is not what a server spends its time on.
635    peers: Lock<migrate::Peers>,
636    /// What each thread that runs commands here keeps to itself.
637    ///
638    /// A fixed list, because a thread reading its own entry must not have the
639    /// list move under it, and how many threads there will be is known before
640    /// any of them starts. A server nobody told otherwise has one.
641    locals: Box<[Local]>,
642    /// How many entries have been handed out.
643    claimed: AtomicUsize,
644    /// The next client id, which is what `CLIENT ID` answers.
645    ///
646    /// On the server and not on a front, because CLIENT LIST and CLIENT KILL
647    /// name a client by this number across the whole server, and two threads
648    /// counting on their own would hand the same number to two clients. Starts
649    /// at one so that zero is never a client, which is what makes it usable as
650    /// the id of a command that came from nowhere.
651    next_client: AtomicU64,
652    /// Where `BACKUP` puts its files, and where `CONFIG GET dir` points.
653    ///
654    /// Absolute, and resolved once when the server is built rather than every
655    /// time somebody asks. `BACKUP LIST` answers absolute paths and a client is
656    /// entitled to hand one of them to a copy tool, so a relative path that
657    /// meant something different after a `chdir` would be a path that stops
658    /// working for reasons nobody could see.
659    dir: PathBuf,
660    /// What backup is running, if one is.
661    ///
662    /// On the server and not on a session, because a backup outlives the
663    /// connection that asked for it and any other connection can seal it.
664    ///
665    /// Behind a lock because there is one backup at a time and any thread can be
666    /// the one that starts, seals or abandons it. It is held while the base file
667    /// is written, which is what keeps two `BACKUP START` commands from writing
668    /// over each other's files.
669    backup: Lock<backup::State>,
670    /// Whether a sealed backup is sitting on disk.
671    ///
672    /// Beside the state rather than read out of it, because every batch of
673    /// commands asks whether there is a backup old enough to sweep away and on
674    /// nearly every server the answer is that there is no backup at all. A load
675    /// answers that. Written under the lock by whoever moved the phase, so a
676    /// reader that asks mid-change sees the moment before and sweeps one batch
677    /// later, which is a file staying on disk for a few microseconds longer than
678    /// it had to.
679    sealed: AtomicBool,
680    /// The search indexes and the names pointing at them.
681    ///
682    /// On the server and not on a database, which is the one collection in this
683    /// build that is. A real server keeps its indexes in the search module, the
684    /// module has one table, and `SELECT 1` followed by `FT._LIST` lists the
685    /// indexes made on database zero. `search.rs` has the rest of why.
686    ///
687    /// A server nobody has made an index on holds two empty vectors here, which
688    /// is six words and no allocation.
689    ///
690    /// Behind a lock because an index is made and dropped by whichever thread
691    /// ran the command, and the table it goes in is one table. Only the `FT`
692    /// commands take it, so nothing a working server spends its time on comes
693    /// through here.
694    search: Lock<Registry>,
695    /// The replies that came back in pieces and have pieces left.
696    ///
697    /// Beside the indexes rather than inside one, because a cursor is read
698    /// under its own number and a real server resolves the index name on a read
699    /// and then pays no attention to it, so a cursor made on one index reads
700    /// through the name of another. Behind a lock for the reason the registry is
701    /// behind one, and a server nobody has opened a cursor on holds an empty map
702    /// here.
703    cursors: Lock<Cursors>,
704    /// The script bodies `EVALSHA` runs, by their digests.
705    ///
706    /// On the server rather than on a connection, because that is the whole
707    /// point of the cache. A client loads its scripts once when it starts up,
708    /// on whichever connection it happened to open first, and then sends nothing
709    /// but digests forever after, from every connection in its pool.
710    ///
711    /// Behind a lock because loading is a write and every thread can be the one
712    /// doing it. Held only long enough to add a body or copy one out, never
713    /// across a run: a running script calls commands, and those take locks of
714    /// their own.
715    scripts: Lock<lua::Scripts>,
716    /// Every library `FUNCTION LOAD` has taken, and what each one registered.
717    ///
718    /// Data only. A callback is a Lua value and there is an interpreter per
719    /// thread, so what is here is the name, the code, the digest of the code and
720    /// one row per function, and every thread compiles the code for itself the
721    /// first time one of its clients calls into the library.
722    libraries: Lock<lua::library::Libraries>,
723    /// Set by `SHUTDOWN`, and read by whatever is turning the loop.
724    ///
725    /// A flag rather than an exit, because the command layer is not what owns
726    /// the process. It runs inside a batch that has other commands behind it
727    /// and inside a driver that has a socket file to take away and a file to
728    /// close, and a server that calls `exit` from a command handler skips all
729    /// of that. So the command says stop and the driver stops, on the same turn
730    /// and through the same door a signal uses.
731    stopping: AtomicBool,
732    /// Every key any connection is watching, with a stamp on each.
733    ///
734    /// Here and not on the connection, and that is the whole design of `WATCH`
735    /// rather than an implementation detail. A connection cannot see a write
736    /// another thread made, so what records the write has to sit beside the key.
737    /// See the `multi` module for the rest of it.
738    watches: Lock<Watches>,
739    /// How many watched keys there are, so the write path can ask without
740    /// taking the lock.
741    ///
742    /// Zero on every server nobody has sent `WATCH` to, which is very nearly all
743    /// of them, and that is what keeps the cost of watches on a server that has
744    /// none down to one relaxed load per write.
745    watched: AtomicUsize,
746    /// Who is listening on what, for pub/sub.
747    ///
748    /// Here and not on the connection for the reason the watches are: a publish
749    /// arrives on a connection that knows nothing about the subscribers, so what
750    /// finds them has to sit beside the name rather than beside the client. See
751    /// the `pubsub` module for the rest of it.
752    pubsub: Lock<pubsub::Registry>,
753    /// How many subscriptions there are, so a publish can ask without taking
754    /// the lock.
755    ///
756    /// Zero on every server nobody has subscribed on, which is what keeps
757    /// `PUBLISH` on a server with no listeners down to one relaxed load.
758    subs: AtomicUsize,
759    /// One inbox per thread, for messages published on another one.
760    ///
761    /// Its own array and not a field on [`Local`], which is a cache line per
762    /// thread precisely so that no other thread writes to it. A mailbox is a
763    /// line another thread is meant to write to, so it gets one of its own.
764    mail: Box<[pubsub::Mailbox]>,
765    /// Which classes of keyspace notification are turned on.
766    ///
767    /// Zero is off and is the default, so the read every write does costs one
768    /// relaxed load and a test. It is `notify-keyspace-events` and the bits are
769    /// Redis's own, kept in the `notify` module beside the two parsers that
770    /// turn them into the setting text and back.
771    notify: AtomicU32,
772}
773
774impl Server {
775    /// A server with [`DATABASES`] empty databases on the system clock.
776    #[must_use]
777    pub fn new() -> Server {
778        let clock = Clock::system();
779        Server {
780            dbs: (0..DATABASES)
781                .map(|_| Db::with_clock(clock.clone(), 1))
782                .collect(),
783            width: 1,
784            started_ms: clock.now_ms(),
785            clock,
786            next_db: AtomicUsize::new(0),
787            conn_bytes: AtomicUsize::new(0),
788            maxmemory: AtomicU64::new(0),
789            store: Lock::new(None),
790            maxstore: AtomicU64::new(NO_MAXSTORE),
791            used: AtomicUsize::new(0),
792            evict_db: AtomicUsize::new(0),
793            expire_db: AtomicUsize::new(0),
794            expire_ms: AtomicU64::new(0),
795            waiters: Lock::default(),
796            parked: AtomicUsize::new(0),
797            peers: Lock::default(),
798            locals: one_thread(),
799            claimed: AtomicUsize::new(0),
800            next_client: AtomicU64::new(1),
801            dir: working_dir(),
802            backup: Lock::default(),
803            sealed: AtomicBool::new(false),
804            search: Lock::new(Registry::new()),
805            cursors: Lock::default(),
806            scripts: Lock::default(),
807            libraries: Lock::default(),
808            stopping: AtomicBool::new(false),
809            watches: Lock::default(),
810            watched: AtomicUsize::new(0),
811            pubsub: Lock::default(),
812            subs: AtomicUsize::new(0),
813            notify: AtomicU32::new(0),
814            mail: pubsub::boxes(1),
815        }
816    }
817
818    /// A server whose databases are cut into `width` stripes each.
819    ///
820    /// Not reachable from the command line yet. Every command group answers on
821    /// a server of any width now and so does everything that walks a whole
822    /// database, and the tests run each group at a width of one and a width of
823    /// eight and check the two agree.
824    ///
825    /// What is left before this is what `--threads` sets is the engine. A
826    /// database being several objects is what makes more than one thread
827    /// possible, and it is not what makes more than one thread happen.
828    #[must_use]
829    pub fn with_width(width: usize) -> Server {
830        let mut server = Server::new();
831        // The server's own clock and not a fresh one, because a database
832        // reading a different clock from the server it is on is a database
833        // whose keys expire against a time nobody set.
834        let clock = server.clock.clone();
835        server.dbs = (0..DATABASES)
836            .map(|_| Db::with_clock(clock.clone(), width))
837            .collect();
838        server.width = server.dbs[0].width();
839        server
840    }
841
842    /// A server on a clock the caller moves by hand, for tests.
843    #[must_use]
844    pub fn with_clock(clock: Clock) -> Server {
845        Server {
846            dbs: (0..DATABASES)
847                .map(|_| Db::with_clock(clock.clone(), 1))
848                .collect(),
849            width: 1,
850            started_ms: clock.now_ms(),
851            clock,
852            next_db: AtomicUsize::new(0),
853            conn_bytes: AtomicUsize::new(0),
854            maxmemory: AtomicU64::new(0),
855            store: Lock::new(None),
856            maxstore: AtomicU64::new(NO_MAXSTORE),
857            used: AtomicUsize::new(0),
858            evict_db: AtomicUsize::new(0),
859            expire_db: AtomicUsize::new(0),
860            expire_ms: AtomicU64::new(0),
861            waiters: Lock::default(),
862            parked: AtomicUsize::new(0),
863            peers: Lock::default(),
864            locals: one_thread(),
865            claimed: AtomicUsize::new(0),
866            next_client: AtomicU64::new(1),
867            dir: working_dir(),
868            backup: Lock::default(),
869            sealed: AtomicBool::new(false),
870            search: Lock::new(Registry::new()),
871            cursors: Lock::default(),
872            scripts: Lock::default(),
873            libraries: Lock::default(),
874            stopping: AtomicBool::new(false),
875            watches: Lock::default(),
876            watched: AtomicUsize::new(0),
877            pubsub: Lock::default(),
878            subs: AtomicUsize::new(0),
879            notify: AtomicU32::new(0),
880            mail: pubsub::boxes(1),
881        }
882    }
883
884    /// One database, by index.
885    ///
886    /// A caller that knows which key it wants names the one stripe the key is
887    /// on rather than working over the whole thing, which is what `at` and its
888    /// neighbours on [`Db`] are for. A caller that is about a database rather
889    /// than about a key, which is the snapshot walk and a setting, works over
890    /// all of them.
891    ///
892    /// The database is marked as having had something run against it, which is
893    /// what this does that [`Server::striped_ref`] does not. Anything that only
894    /// reads asks for that one and leaves the mark alone.
895    ///
896    /// The borrow is shared, and what makes that enough is that a database is
897    /// several stripes behind a lock each. A caller that wants to change
898    /// something holds the stripe it is changing, so two threads working on two
899    /// keys work at once and two working on one key take turns, which is the
900    /// whole point of cutting a database up.
901    ///
902    /// # Panics
903    ///
904    /// If `i` is not a database. `SELECT` is the only way a client changes the
905    /// index and it checks, so an index that is out of range here is a bug in
906    /// the caller and not something a client can ask for.
907    pub fn striped(&self, i: usize) -> &Db {
908        self.mine().mark(1u64 << i);
909        &self.dbs[i]
910    }
911
912    /// Every keyspace on the server, which is every stripe of every database.
913    ///
914    /// What the aggregates walk. A total over the whole server is a total over
915    /// all of these and the stripe boundaries do not appear in it, which is
916    /// what makes the numbers `INFO` reports the same numbers whatever the
917    /// server was cut into.
918    fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
919        self.dbs
920            .iter()
921            .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
922    }
923
924    /// How many keyspaces there are, counting every stripe of every database.
925    ///
926    /// The maintenance turns walk these rather than the databases, because a
927    /// stripe is the thing that holds an arena and a deadline heap and so it is
928    /// the thing that has anything to collect.
929    const fn slots(&self) -> usize {
930        DATABASES * self.width
931    }
932
933    /// Which database slot `i` belongs to.
934    const fn slot_db(&self, i: usize) -> usize {
935        i / self.width
936    }
937
938    /// Keyspace `i` of [`Server::slots`].
939    fn slot(&self, i: usize) -> Held<'_, Keyspace> {
940        let (db, stripe) = (i / self.width, i % self.width);
941        self.dbs[db].hold_stripe(stripe)
942    }
943
944    /// Where `BACKUP` writes and what `CONFIG GET dir` answers.
945    #[must_use]
946    pub fn dir(&self) -> &Path {
947        &self.dir
948    }
949
950    /// Point the server at a different directory, which `yodb serve --dir` does.
951    ///
952    /// Only before it is serving. There is no `CONFIG SET dir` here and there
953    /// is none on a real server either without turning protected configs on,
954    /// for the good reason that moving it out from under a running backup would
955    /// leave files nothing can find again.
956    pub fn set_dir(&mut self, dir: PathBuf) {
957        self.dir = dir;
958    }
959
960    /// Drop a sealed backup that has outlived `backup-sealed-ttl`.
961    ///
962    /// Once per batch, from the same maintenance turn that collects the arena.
963    /// It reads two fields and returns on a server that has never taken a
964    /// backup, which is nearly all of them.
965    pub fn backup_expire(&self) {
966        backup::expire(self);
967    }
968
969    /// Ask for the server to stop, which is what `SHUTDOWN` does.
970    ///
971    /// It sets a flag and returns. Nothing here closes a socket, flushes a file
972    /// or ends the process, because none of those belong to this layer, and a
973    /// batch that is halfway through still has to finish and be written out.
974    pub fn stop(&self) {
975        self.stopping.store(true, Release);
976    }
977
978    /// Whether somebody has asked the server to stop.
979    ///
980    /// Read once per turn by the loop, next to the flag a signal sets. The two
981    /// mean the same thing and are separate only because one arrives from the
982    /// operating system and the other from a client.
983    #[must_use]
984    pub fn stopping(&self) -> bool {
985        self.stopping.load(Acquire)
986    }
987
988    /// One database, by index, without taking it mutably.
989    ///
990    /// What the prefetch stage needs. It runs for all 64 commands in a batch
991    /// before any of them executes, so it cannot hold the mutable borrow `run`
992    /// is about to want, and it does not need one: warming a cache line reads
993    /// nothing and changes nothing.
994    #[must_use]
995    pub fn striped_ref(&self, i: usize) -> &Db {
996        &self.dbs[i]
997    }
998
999    /// The stripe that answers for a database when a setting is read back.
1000    ///
1001    /// A ladder setting and an eviction policy are one number on a real server,
1002    /// and the fact that every stripe of every database carries a copy of it is
1003    /// ours rather than the client's problem. A write puts the same value on
1004    /// every one of them, so any stripe answers for all of them and this is the
1005    /// first one.
1006    fn settings(&self) -> Held<'_, Keyspace> {
1007        self.dbs[0].hold_stripe(0)
1008    }
1009
1010    /// Take a new clock reading, which every database is looking at.
1011    ///
1012    /// Once per turn of the event loop, which is the only place time moves. A
1013    /// command asking what the time is gets the answer the whole batch got, so
1014    /// two keys written by the same batch expire together (`04` section 3).
1015    ///
1016    /// Every thread does this on every turn of its own loop and they do not
1017    /// have to agree about when. The reading is only stored when the
1018    /// millisecond has changed, so what the threads are sharing is a line that
1019    /// is written about a thousand times a second and read millions.
1020    pub fn refresh_clock(&self) {
1021        self.clock.refresh();
1022    }
1023
1024    /// Move every clock here on by `ms`, for tests about expiry.
1025    ///
1026    /// The same thing [`Server::set_clock_ms`] does and by the same argument,
1027    /// except that it moves from wherever the clock is rather than to a stated
1028    /// moment, which is what a test that wants a key to have expired asks for.
1029    pub fn advance_clock_ms(&self, ms: u64) {
1030        let now = self.clock.now_ms() + ms;
1031        self.set_clock_ms(now);
1032    }
1033
1034    /// Move every clock here to `ms` by hand, for tests about expiry.
1035    ///
1036    /// A test cannot wait a hundred seconds and a test that waits a hundred
1037    /// milliseconds is a test that fails on a loaded machine, so time moves on
1038    /// request. The system clock underneath will overwrite this on the next
1039    /// [`Server::refresh_clock`], which is why this is only useful in a test
1040    /// that drives commands directly rather than through the event loop.
1041    pub fn set_clock_ms(&self, ms: u64) {
1042        self.clock.set(ms);
1043    }
1044
1045    /// Seconds since this server was built.
1046    #[must_use]
1047    pub fn uptime_secs(&self) -> u64 {
1048        self.clock.now_ms().saturating_sub(self.started_ms) / 1000
1049    }
1050
1051    /// Bytes held by every database's index and arena, plus the read and reply
1052    /// buffers of every connection.
1053    ///
1054    /// The buffers are in here because they are real and because Redis counts
1055    /// its own, so leaving them out would make the one number people compare
1056    /// flattering rather than true. They are not a database, so nothing in the
1057    /// keyspace can change them and the engine has to say when they move.
1058    #[must_use]
1059    pub fn memory_bytes(&self) -> usize {
1060        self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1061    }
1062
1063    /// What the keyspace itself is holding, live records only.
1064    ///
1065    /// `used_memory` minus this is what the store costs to run: the index, the
1066    /// space dead records are sitting in until compaction gets to them, and the
1067    /// connections' buffers.
1068    #[must_use]
1069    pub fn dataset_bytes(&self) -> usize {
1070        self.keyspaces()
1071            .map(|db| db.map().arena().live_bytes() as usize)
1072            .sum()
1073    }
1074
1075    /// Bytes the arenas are holding, live and dead together.
1076    #[must_use]
1077    pub fn arena_bytes(&self) -> usize {
1078        self.keyspaces()
1079            .map(|db| db.map().arena().reserved_bytes() as usize)
1080            .sum()
1081    }
1082
1083    /// Bytes the indexes are holding.
1084    #[must_use]
1085    pub fn index_bytes(&self) -> usize {
1086        self.keyspaces()
1087            .map(|db| db.map().index().memory_bytes())
1088            .sum()
1089    }
1090
1091    /// What arena compaction has cost, across every database.
1092    ///
1093    /// The write amplification of value separation, which is invisible from the
1094    /// outside otherwise: a client that writes a megabyte can leave the store
1095    /// copying several more, and the only sign of it without these is that the
1096    /// writes got slower.
1097    #[must_use]
1098    pub fn compaction(&self) -> yo_kv::Compaction {
1099        self.keyspaces().map(|db| db.map().compaction()).fold(
1100            yo_kv::Compaction::default(),
1101            |a, b| yo_kv::Compaction {
1102                walked: a.walked + b.walked,
1103                moved: a.moved + b.moved,
1104                bytes: a.bytes + b.bytes,
1105            },
1106        )
1107    }
1108
1109    /// Arena segments whose pages are real, across every database.
1110    #[must_use]
1111    pub fn segment_count(&self) -> usize {
1112        self.keyspaces()
1113            .map(|db| db.map().arena().resident_segments())
1114            .sum()
1115    }
1116
1117    /// What the connections' read and reply buffers are holding.
1118    #[must_use]
1119    pub fn conn_bytes(&self) -> usize {
1120        self.conn_bytes.load(Relaxed)
1121    }
1122
1123    /// Note that the connections are holding `delta` bytes more than they were,
1124    /// or fewer when it is negative.
1125    ///
1126    /// A delta and not a total because the alternative is a walk over every
1127    /// connection, and the walk would have to happen on a turn of the loop
1128    /// rather than when `INFO` asks, which puts the cost of a report on the
1129    /// command path of a server nobody is asking.
1130    pub fn note_conn_bytes(&self, delta: isize) {
1131        // A read and a write and not a fetch and add, because the number is a
1132        // sum of signed changes and the saturating part has to happen in the
1133        // middle. Two threads that change their buffers in the same instant can
1134        // lose one of the two changes, which is a report that is a few kilobytes
1135        // out until the next connection on either thread moves it again.
1136        self.conn_bytes
1137            .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1138    }
1139
1140    /// Keys reclaimed by running into them after their deadline.
1141    #[must_use]
1142    pub fn expired_keys(&self) -> u64 {
1143        self.keyspaces().map(|db| db.expired_keys()).sum()
1144    }
1145
1146    /// Keys thrown away to make room, which is the other number entirely.
1147    #[must_use]
1148    pub fn evicted_keys(&self) -> u64 {
1149        self.keyspaces().map(|db| db.evicted_keys()).sum()
1150    }
1151
1152    /// Every command that has been seen, with its counters.
1153    ///
1154    /// Only the ones that have. A server reports a handful of lines rather than
1155    /// one per command in the table, which is what Redis does and is the
1156    /// difference between a section a person can read and one they cannot.
1157    pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1158        (0..table::count())
1159            .map(|at| (table::name_at(at), self.command_stat(at)))
1160            .filter(|(_, row)| row.seen())
1161    }
1162
1163    /// One command's counters, added up over every thread.
1164    fn command_stat(&self, at: usize) -> CommandStat {
1165        let mut sum = CommandStat::default();
1166        for thread in &self.locals {
1167            let row = &thread.cmdstats.0[at];
1168            sum.calls += row.calls.get();
1169            sum.rejected += row.rejected.get();
1170            sum.failed += row.failed.get();
1171        }
1172        sum
1173    }
1174
1175    /// The counters the calling thread writes into.
1176    ///
1177    /// The first call on a thread claims a set and every call after it is a
1178    /// thread local read and an index. A server asked to count from more threads
1179    /// than it was built for wraps round and shares a set, which loses the odd
1180    /// count between two threads and cannot happen to a server `yodb serve`
1181    /// built, because that one is told how many threads it will have before it
1182    /// starts any of them.
1183    pub fn counted(&self) -> &Stats {
1184        &self.mine().stats
1185    }
1186
1187    /// The next client id, taken.
1188    ///
1189    /// Every accept anywhere on this server comes through here, so no two
1190    /// clients share a number however many threads are accepting.
1191    pub fn next_client(&self) -> u64 {
1192        self.next_client.fetch_add(1, Relaxed)
1193    }
1194
1195    /// Which set of per thread state the calling thread is on.
1196    ///
1197    /// The number a blocked client is filed under, so that the thread holding
1198    /// that client's connection is the one that answers it. Claims a set on the
1199    /// first call the same way [`Server::counted`] does, and gives back the same
1200    /// number every time after.
1201    pub fn my_slot(&self) -> usize {
1202        self.mine_at()
1203    }
1204
1205    /// Everything the calling thread keeps to itself.
1206    fn mine(&self) -> &Local {
1207        &self.locals[self.mine_at()]
1208    }
1209
1210    /// The calling thread's place in `locals`, claiming one if it has none.
1211    ///
1212    /// Wraps round when more threads count here than the server was built for,
1213    /// which shares a set between two threads and loses the odd count. That
1214    /// cannot happen to the server `yodb serve` builds, because it is told how
1215    /// many threads it will have before it starts any of them.
1216    fn mine_at(&self) -> usize {
1217        let mut slot = SLOT.get();
1218        if slot == usize::MAX {
1219            slot = self.claimed.fetch_add(1, Relaxed);
1220            SLOT.set(slot);
1221        }
1222        slot % self.locals.len()
1223    }
1224
1225    /// Every thread's numbers added together, which is what `INFO` reports.
1226    #[must_use]
1227    pub fn totals(&self) -> Totals {
1228        let mut sum = Totals::default();
1229        for thread in &self.locals {
1230            sum.clients += thread.stats.clients.get();
1231            sum.connections += thread.stats.connections.get();
1232            sum.commands += thread.stats.commands.get();
1233        }
1234        sum
1235    }
1236
1237    /// Put the totals back to zero, which is `CONFIG RESETSTAT`.
1238    ///
1239    /// Every thread's set and not only the one asking, since the number the
1240    /// client is resetting is the sum it was just shown. The open connections
1241    /// are left alone because that is a gauge and not a total: the connections
1242    /// are still open.
1243    pub fn reset_stats(&self) {
1244        for thread in &self.locals {
1245            thread.stats.connections.zero();
1246            thread.stats.commands.zero();
1247        }
1248    }
1249
1250    /// Say how many threads will run commands here, before any of them does.
1251    ///
1252    /// What it changes is how many sets of counters there are, and how many
1253    /// pub/sub mailboxes. Called once at startup by whoever is about to start
1254    /// the threads, and calling it on a running server throws away what has been
1255    /// counted so far, which is why it wants the server to itself.
1256    pub fn set_threads(&mut self, threads: usize) {
1257        self.locals = slots(threads);
1258        self.mail = pubsub::boxes(threads);
1259        self.claimed = AtomicUsize::new(0);
1260    }
1261
1262    /// The `maxmemory` limit in bytes, zero when there is not one.
1263    #[must_use]
1264    pub fn maxmemory(&self) -> u64 {
1265        self.maxmemory.load(Relaxed)
1266    }
1267
1268    /// Set the limit, and take a reading straight away.
1269    ///
1270    /// The reading is here rather than left to the next maintenance turn because
1271    /// a client that sets the limit and sends a write in the same batch expects
1272    /// the write to be judged against the limit it just set, and because the
1273    /// cached number is meaningless until the first time there is a limit to
1274    /// compare it with.
1275    ///
1276    /// Turning the limit on also turns on the running total every slab keeps of
1277    /// what its collections hold, and turning it off turns that back off, so a
1278    /// server with no limit is not paying to count something nobody reads. The
1279    /// first reading after switching it on is the walk that the total starts
1280    /// from, and it is the only walk.
1281    pub fn set_maxmemory(&self, bytes: u64) {
1282        self.maxmemory.store(bytes, Relaxed);
1283        for db in &self.dbs {
1284            db.track_memory(bytes != 0);
1285        }
1286        self.used.store(self.settled_memory(), Relaxed);
1287    }
1288
1289    /// Say where a database should get its store from when it needs one.
1290    ///
1291    /// This is what turns the eviction inversion on. Until it is called every
1292    /// database answers a memory limit by evicting, which is Redis, and after it
1293    /// is called a database under memory pressure moves values to whatever the
1294    /// closure hands back instead of throwing keys away.
1295    ///
1296    /// Called at most once per database and only under pressure, so a server
1297    /// that is given a file and never fills memory never touches it.
1298    pub fn set_store_source(
1299        &mut self,
1300        source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1301    ) {
1302        *self.store.lock() = Some(Box::new(source));
1303    }
1304
1305    /// Whether this server has been given somewhere to put cold values.
1306    #[must_use]
1307    pub fn has_store_source(&self) -> bool {
1308        self.store.lock().is_some()
1309    }
1310
1311    /// Open database `at`'s store, if it has not got one and there is one to be
1312    /// had.
1313    ///
1314    /// A store that will not open leaves the database where it was, which is
1315    /// evicting, because a memory limit that cannot be answered by moving data
1316    /// still has to be answered.
1317    fn attach_store(&self, at: usize) {
1318        if self.slot(at).store_bytes().is_some() {
1319            return;
1320        }
1321        // The closure is run with its lock held and the keyspace is taken after
1322        // it has answered, so the file is opened once however many threads asked
1323        // for it and the stripe is not held while a file is being opened.
1324        let mut source = self.store.lock();
1325        let Some(source) = source.as_mut() else {
1326            return;
1327        };
1328        if let Some(blocks) = source(at) {
1329            self.slot(at).attach(blocks);
1330        }
1331    }
1332
1333    /// The `maxstore` limit in bytes, `None` when there is not one.
1334    #[must_use]
1335    pub fn maxstore(&self) -> Option<u64> {
1336        match self.maxstore.load(Relaxed) {
1337            NO_MAXSTORE => None,
1338            bytes => Some(bytes),
1339        }
1340    }
1341
1342    /// Set the storage limit, or clear it with `None`.
1343    ///
1344    /// Nothing is read here the way [`Server::set_maxmemory`] reads the memory
1345    /// total, because this limit is compared against a number the store keeps
1346    /// and answers on demand, not against a walk.
1347    pub fn set_maxstore(&self, bytes: Option<u64>) {
1348        self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1349    }
1350
1351    /// What every attached store is holding, for `INFO memory`.
1352    ///
1353    /// Zero on a server with nothing attached, which is not the same as a server
1354    /// whose file is empty, and [`Server::regime`] is the field that tells those
1355    /// two apart.
1356    #[must_use]
1357    pub fn store_bytes(&self) -> u64 {
1358        self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1359    }
1360
1361    /// What the file has been asked to do, added up over every database.
1362    ///
1363    /// Counters and not levels, so they only ever go up and a run is the
1364    /// difference between two readings. G9 is a ratio over these: the faults a
1365    /// run took, divided by the point reads it issued, has to come out at 1.05
1366    /// or less with a working set ten times memory. There is no way to work that
1367    /// out from outside the server, so it is reported rather than inferred.
1368    ///
1369    /// A fault is a read that went to the store. Whether it also went to the
1370    /// device depends on the store: a log serves a read out of a resident page
1371    /// without touching anything. At ten times memory almost every fault is a
1372    /// real read, which is why the gate is written against this number, but the
1373    /// two are not the same thing and a run tight against the bar should be
1374    /// checked against what the operating system says.
1375    #[must_use]
1376    pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1377        let mut total = yo_kv::tier::Stats::default();
1378        for db in self.keyspaces() {
1379            let Some(tier) = db.tier() else { continue };
1380            let s = tier.stats();
1381            total.demoted += s.demoted;
1382            total.promoted += s.promoted;
1383            total.faults += s.faults;
1384            total.served += s.served;
1385            total.bytes_out += s.bytes_out;
1386            total.bytes_in += s.bytes_in;
1387        }
1388        total
1389    }
1390
1391    /// Which way this server answers a memory limit, in one word for `INFO`.
1392    ///
1393    /// `evict` is Redis: a memory limit throws keys away. `migrate` is the
1394    /// inversion: a memory limit moves values to the file and nothing stored is
1395    /// lost. A server reports one word rather than leaving an operator to work
1396    /// it out from a limit, a setting and whether a file happens to be open.
1397    #[must_use]
1398    pub fn regime(&self) -> &'static str {
1399        if (0..self.slots()).any(|at| self.migrates(at)) {
1400            "migrate"
1401        } else {
1402            "evict"
1403        }
1404    }
1405
1406    /// Whether database `at` answers a memory limit by moving values to the
1407    /// file rather than by throwing keys away.
1408    ///
1409    /// Three things have to hold. There has to be somewhere to move them, which
1410    /// is a store attached to that database or a source that can open one, and
1411    /// on a server that was never given a file this is false everywhere and
1412    /// every database behaves exactly as it did.
1413    /// The storage budget has to be more than nothing, which is what
1414    /// `maxstore 0` says it is not. And the file has to be under that budget,
1415    /// because a full file is a storage limit reached and eviction is the right
1416    /// answer to a storage limit.
1417    fn migrates(&self, at: usize) -> bool {
1418        let cap = self.maxstore();
1419        if cap == Some(0) {
1420            return false;
1421        }
1422        // Out of the stripe first. A match keeps whatever it is looking at
1423        // alive for the whole of itself, and that would be this stripe held
1424        // across the arms for no reason.
1425        let bytes = self.slot(at).store_bytes();
1426        match bytes {
1427            Some(held) => cap.is_none_or(|cap| held < cap),
1428            // Nothing attached, but somewhere to get one from the moment this
1429            // database needs it, which is what makes the answer yes rather than
1430            // no. Opening it here would mean `INFO` opened files.
1431            None => self.store.lock().is_some(),
1432        }
1433    }
1434
1435    /// Take a fresh memory reading, which the maintenance turn does once a batch.
1436    ///
1437    /// Nothing at all when there is no limit, which is the default and is every
1438    /// server that has not asked for one.
1439    pub fn refresh_memory(&self) {
1440        if self.maxmemory() != 0 {
1441            self.used.store(self.settled_memory(), Relaxed);
1442        }
1443    }
1444
1445    /// [`Server::memory_bytes`], asked the cheap way.
1446    ///
1447    /// The same number. The difference is that this asks each database only
1448    /// about the collections that could have moved since the last time, which is
1449    /// what a batch touched rather than what the server holds, so it can be
1450    /// asked once a batch and again on every command that is over the limit.
1451    fn settled_memory(&self) -> usize {
1452        self.keyspaces()
1453            .map(|mut db| db.settled_memory_bytes())
1454            .sum::<usize>()
1455            + self.conn_bytes()
1456    }
1457
1458    /// Make room under the `maxmemory` limit, throwing keys away if that is what
1459    /// it takes. Answers whether there is anything left it could throw away.
1460    ///
1461    /// Redis runs the same thing from `processCommand` before every command and
1462    /// so does this: a client that writes has to be judged at the moment it
1463    /// writes, not a batch later, or the limit is a suggestion.
1464    ///
1465    /// Three things happen in the loop and all three are needed. Eviction picks
1466    /// a key and drops it. Compaction gives the pages back, because dropping a
1467    /// key marks its record dead and returns nothing on its own, so a loop that
1468    /// only evicted would throw the whole keyspace away and watch the number
1469    /// stay where it was. The reading is taken again each time round, because
1470    /// the two of them together are the only thing that moves it.
1471    ///
1472    /// # Why running out of budget is not a no
1473    ///
1474    /// `false` means there was nothing left to evict, which is `noeviction`, or
1475    /// a `volatile` policy on a database where nothing has a deadline, or a
1476    /// keyspace that is already empty. It does not mean the server is still over
1477    /// its limit, and that difference is Redis's: `performEvictions` answers
1478    /// `EVICT_FAIL` only when it has run out of things to delete, and
1479    /// `processCommand` refuses the client on that and on nothing else. Running
1480    /// out of time part way through a job it is doing well comes back as
1481    /// `EVICT_RUNNING` and the command goes through, because a server that is
1482    /// evicting steadily and refusing every write while it does it is worse for
1483    /// the client than a little overshoot.
1484    ///
1485    /// # What the limit is worth
1486    ///
1487    /// Space comes back a segment at a time and a segment is two megabytes, so
1488    /// this holds a server to its limit give or take a segment. A `maxmemory` of
1489    /// a few hundred megabytes gets what it asked for. A `maxmemory` of four
1490    /// megabytes is asking for a precision this store does not have.
1491    pub fn make_room(&self) -> bool {
1492        let limit = self.maxmemory();
1493        if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1494            return true;
1495        }
1496        // The cached reading is a batch old and the batch may have compacted
1497        // since, so take a fresh one before throwing anything away. It is the
1498        // settled reading and not the walk, so what this costs is the handful of
1499        // collections the last batch touched and not the whole database.
1500        let mut used = self.settled_memory();
1501        self.used.store(used, Relaxed);
1502        let mut budget = EVICT_BUDGET;
1503        while used as u64 > limit {
1504            let over = used - limit as usize;
1505            if !self.relieve_step(over) {
1506                return false;
1507            }
1508            self.compact_hard_step();
1509            used = self.settled_memory();
1510            self.used.store(used, Relaxed);
1511            budget -= 1;
1512            if budget == 0 {
1513                break;
1514            }
1515        }
1516        true
1517    }
1518
1519    /// Give back `over` bytes from whichever database can, by moving values to
1520    /// the file where there is one and by throwing keys away where there is not.
1521    ///
1522    /// The two answers are the eviction inversion and which one a database gets
1523    /// is [`Server::migrates`]. Answers whether anything was given back at all,
1524    /// and `false` is what refuses the client's write.
1525    ///
1526    /// A store that will not take the bytes counts as nothing given back, so the
1527    /// write is refused rather than turned into a deletion. A disk that is
1528    /// misbehaving is a reason to stop accepting writes and it is not a reason
1529    /// to start losing data that was accepted already.
1530    ///
1531    /// Round robin from a cursor rather than always starting at database zero,
1532    /// so a server using more than one of them does not empty the first before
1533    /// touching the second. Almost every server is on database zero only, where
1534    /// this is one call that answers and fifteen that say the map is empty.
1535    fn relieve_step(&self, over: usize) -> bool {
1536        let from = self.evict_db.load(Relaxed);
1537        for turn in 0..self.slots() {
1538            let i = (from + turn) % self.slots();
1539            // An empty keyspace has nothing to move and opening a log for one
1540            // would cost a resident page window to find that out.
1541            let used = !self.slot(i).is_empty();
1542            let gave = if used && self.migrates(i) {
1543                self.attach_store(i);
1544                // Whether it made room and not whether it moved a key. A round
1545                // that demoted nothing and handed back a segment is a round
1546                // that made room, and reading only the count refuses the write
1547                // that provoked it.
1548                self.slot(i)
1549                    .relieve(over)
1550                    .is_ok_and(yo_kv::tier::Relief::made_room)
1551            } else {
1552                self.slot(i).evict_one()
1553            };
1554            if gave {
1555                self.evict_db.store((i + 1) % self.slots(), Relaxed);
1556                self.mine().mark(1u64 << self.slot_db(i));
1557                return true;
1558            }
1559        }
1560        false
1561    }
1562
1563    /// The sweep the shard loop calls, at most once a millisecond.
1564    ///
1565    /// The gate is the whole difference between this and [`Server::expire_step`].
1566    /// A maintenance slice runs on every turn of the loop and a turn is a
1567    /// hundred nanoseconds, so an ungated sweep would draw a fresh sample ten
1568    /// thousand times per millisecond and spend a real share of the shard on
1569    /// looking for keys that cannot have died since the last look. Nothing in a
1570    /// database changes fast enough to be worth asking about more often than the
1571    /// clock can tell the difference, and the clock here is milliseconds.
1572    ///
1573    /// A millisecond is also far finer than Redis, whose slow cycle runs at ten
1574    /// hertz, so this is not the thing that decides how promptly memory comes
1575    /// back. What it decides is that an idle server sweeps a thousand times a
1576    /// second rather than a million.
1577    pub fn expire_slice(&self, budget: usize) -> usize {
1578        let now = self.clock.now_ms();
1579        if now == self.expire_ms.load(Relaxed) {
1580            return 0;
1581        }
1582        self.expire_ms.store(now, Relaxed);
1583        self.expire_step(budget)
1584    }
1585
1586    /// Sweep dead keys out of the databases, spending at most `budget` looks.
1587    ///
1588    /// Answers what it spent, so the caller can charge its maintenance slice for
1589    /// it. See [`yo_kv::expiry`] for why the budget is in keys looked at.
1590    ///
1591    /// Round robin from its own cursor, and every database gets offered whatever
1592    /// is left of the budget rather than a sixteenth of it each, so a server on
1593    /// database zero only, which is nearly every server, spends the whole slice
1594    /// where the keys are. The fifteen empty ones cost a comparison apiece
1595    /// because a database with no key carrying a deadline says so without
1596    /// drawing anything.
1597    ///
1598    /// The cursor moves to the database after whichever one did the work, so two
1599    /// busy databases take turns instead of the lower numbered one starving the
1600    /// other.
1601    pub fn expire_step(&self, budget: usize) -> usize {
1602        let mut spent = 0;
1603        let from = self.expire_db.load(Relaxed);
1604        for turn in 0..self.slots() {
1605            if spent >= budget {
1606                break;
1607            }
1608            let i = (from + turn) % self.slots();
1609            let c = self.slot(i).expire_cycle(budget - spent);
1610            spent += c.examined;
1611            if c.expired > 0 {
1612                self.expire_db.store((i + 1) % self.slots(), Relaxed);
1613                self.mine().note(1u64 << self.slot_db(i));
1614            }
1615        }
1616        spent
1617    }
1618
1619    /// One slice of compaction for a server that is over its limit.
1620    ///
1621    /// Takes the databases in the same order [`Server::compact_step`] does and
1622    /// stops at the first one that had something to move, and it asks with the
1623    /// ratios off. See [`Keyspace::compact_hard`] for what that changes.
1624    fn compact_hard_step(&self) -> Option<usize> {
1625        let from = self.next_db.load(Relaxed);
1626        for turn in 0..self.slots() {
1627            let i = (from + turn) % self.slots();
1628            if let Some(moved) = self.slot(i).compact_hard() {
1629                self.next_db.store((i + 1) % self.slots(), Relaxed);
1630                return Some(moved);
1631            }
1632        }
1633        None
1634    }
1635
1636    /// Take what every thread has marked and add it to the turn's own mask.
1637    ///
1638    /// The mask the turn works from is its own and not a shared one, because a
1639    /// mask it read in place and then cleared a bit of would be a mask that lost
1640    /// whatever another thread marked in between. A swap cannot lose a mark: a
1641    /// thread that ors while the swap happens either gets its bit in before the
1642    /// swap or leaves it there afterwards, and the second one costs one look at
1643    /// a database the turn has already been through.
1644    fn collect_marks(&self) {
1645        let mut marked = 0;
1646        for thread in &self.locals {
1647            marked |= thread.dirty.swap(0, Relaxed);
1648        }
1649        self.mine().note(marked);
1650    }
1651
1652    /// Give one database's dead space back, if any database has enough of it to
1653    /// be worth the move. `None` when no database had a candidate.
1654    ///
1655    /// Once per batch, next to the clock. Overwriting a key writes a new record
1656    /// and counts the old one dead, so without this a server holds everything
1657    /// it has ever written: 400000 sets over 100000 keys measured at 742 bytes
1658    /// a key against Redis at 144 for the same load, and the whole difference
1659    /// was dead records nothing ever came back for.
1660    ///
1661    /// At most one segment moves per call and the search starts one database
1662    /// further along each time, so the cost of asking is a comparison per
1663    /// database and the cost of acting is bounded by a segment.
1664    pub fn compact_step(&self) -> Option<usize> {
1665        self.collect_marks();
1666        let mine = self.mine();
1667        let from = self.next_db.load(Relaxed);
1668        for turn in 0..self.slots() {
1669            let i = (from + turn) % self.slots();
1670            // Nothing has run against this database since it last said it had
1671            // nothing to collect, so it still has nothing to collect and the
1672            // line it lives on stays where it is.
1673            let at = self.slot_db(i);
1674            if !mine.wanted(at) {
1675                continue;
1676            }
1677            if let Some(moved) = self.slot(i).compact_step() {
1678                self.next_db.store((i + 1) % self.slots(), Relaxed);
1679                return Some(moved);
1680            }
1681            // Only once every stripe of the database has said it has nothing,
1682            // since the bit is per database and one stripe answering for all of
1683            // them would stop the others being asked at all.
1684            if i % self.width == self.width - 1 {
1685                mine.done(at);
1686            }
1687        }
1688        None
1689    }
1690}
1691
1692impl Server {
1693    /// Whether anybody is watching anything.
1694    ///
1695    /// The one thing every write asks about watches, and it is a relaxed load of
1696    /// a word that is zero and shared on a server where no client has ever sent
1697    /// `WATCH`. Relaxed is enough because the answer only has to be right by the
1698    /// time it matters: a `WATCH` that has not been published yet has not
1699    /// returned to its client either, so no client can have started a
1700    /// transaction that depends on it.
1701    fn watching(&self) -> bool {
1702        self.watched.load(Relaxed) != 0
1703    }
1704
1705    /// Which classes of keyspace notification are turned on.
1706    ///
1707    /// Zero is off, which is the default and is what nearly every server runs
1708    /// with. Relaxed for the same reason the watch count is: a `CONFIG SET` that
1709    /// has not been published to another thread yet has not answered its client
1710    /// either.
1711    pub(crate) fn notify_flags(&self) -> u32 {
1712        self.notify.load(Relaxed)
1713    }
1714
1715    /// Turn a set of notification classes on, or turn them all off with zero.
1716    pub(crate) fn set_notify_flags(&self, flags: u32) {
1717        self.notify.store(flags, Relaxed);
1718    }
1719
1720    /// Note how many watched keys there are, after the table changed.
1721    ///
1722    /// Taken from the table under the same lock the change was made under, so
1723    /// the count can never say nobody is watching while somebody is.
1724    fn recount(&self, watches: &Watches) {
1725        self.watched.store(watches.len(), Relaxed);
1726    }
1727}
1728
1729impl Default for Server {
1730    fn default() -> Server {
1731        Server::new()
1732    }
1733}
1734
1735/// What one connection has chosen.
1736pub struct Session {
1737    db: usize,
1738    id: u64,
1739    /// Which connection slot on the front this session belongs to.
1740    ///
1741    /// Carried here so that a command can say where a reply for this connection
1742    /// goes without the front having to be asked. Pub/sub is what needs it: a
1743    /// subscription is a row on the server naming a slot, and the subscribe
1744    /// command is the only moment the connection and the server are both in
1745    /// hand. [`u32::MAX`] for a session that is not on a front, which is a test.
1746    conn: u32,
1747    name: Vec<u8>,
1748    /// The `HIMPORT` fieldsets this connection has prepared.
1749    ///
1750    /// Connection state and not keyspace state, which is the reference's design
1751    /// and not a shortcut: a fieldset is invisible to every other connection and
1752    /// the keys built from one outlive it.
1753    sets: himport::Fieldsets,
1754    /// Whether the command running right now was called by a script.
1755    ///
1756    /// The one thing it changes is what a blocking command does when it finds
1757    /// nothing to take. A client that sent `BLPOP` waits; a script that called
1758    /// `BLPOP` cannot, because the whole server is waiting on the script, and a
1759    /// script that parked would park everything behind it. So inside a script a
1760    /// blocking command times out at once and answers the null a client that
1761    /// waited its full timeout would have got. That is a real server's rule and
1762    /// it is why `BLPOP` is not on the list a script may not call.
1763    scripted: bool,
1764    /// The commands held since `MULTI`, `None` when no transaction is open.
1765    ///
1766    /// Connection state and nothing else. A transaction is invisible to every
1767    /// other connection until `EXEC` runs it, and a connection that goes away
1768    /// with one open has simply not run it.
1769    multi: Option<multi::Queue>,
1770    /// What this connection asked `WATCH` about, and what those keys looked
1771    /// like at the time.
1772    ///
1773    /// The other half is on the server, beside the keys, because a write by
1774    /// another thread has to reach it. See `multi` for why keeping the value
1775    /// here and comparing it at `EXEC` is not the same thing.
1776    watching: Vec<multi::Watched>,
1777    /// Whether the command running right now was handed over by `EXEC`.
1778    ///
1779    /// The one thing it changes is the RESP2 subscribe mode refusal, which a
1780    /// real server makes in `processCommand` and so does not make for a command
1781    /// that was queued: `MULTI`, `SUBSCRIBE z`, `GET x`, `EXEC` runs the `GET`
1782    /// on 8.10.1 even though sending it on its own would have been refused.
1783    running: bool,
1784    /// What this connection has subscribed to, `None` until it subscribes to
1785    /// anything.
1786    ///
1787    /// Boxed so that a connection that never subscribes carries a null pointer
1788    /// rather than three empty vectors. The other half is on the server, keyed
1789    /// by name, because a publish arrives on a connection that cannot see this
1790    /// one. See the `pubsub` module.
1791    subs: Option<Box<pubsub::Subs>>,
1792}
1793
1794impl Session {
1795    /// A new connection, on database zero with no name.
1796    #[must_use]
1797    pub fn new(id: u64) -> Session {
1798        Session {
1799            db: 0,
1800            id,
1801            conn: u32::MAX,
1802            name: Vec::new(),
1803            sets: himport::Fieldsets::default(),
1804            scripted: false,
1805            multi: None,
1806            watching: Vec::new(),
1807            running: false,
1808            subs: None,
1809        }
1810    }
1811
1812    /// Whether a script is what is asking, which only a blocking command reads.
1813    pub(crate) const fn scripted(&self) -> bool {
1814        self.scripted
1815    }
1816
1817    /// Whether `EXEC` is what is asking.
1818    pub(crate) const fn running(&self) -> bool {
1819        self.running
1820    }
1821
1822    /// Say which connection slot this session is in.
1823    ///
1824    /// Called by the front when it opens the connection, which is the only place
1825    /// that knows. A session nobody tells is not on a front, and the one thing
1826    /// that reads this checks the client id before it acts on it.
1827    pub(crate) const fn set_conn(&mut self, conn: u32) {
1828        self.conn = conn;
1829    }
1830
1831    /// The connection id, which `HELLO` reports and `CLIENT` will.
1832    #[must_use]
1833    pub const fn id(&self) -> u64 {
1834        self.id
1835    }
1836
1837    /// Which database this connection is working in.
1838    #[must_use]
1839    pub const fn db(&self) -> usize {
1840        self.db
1841    }
1842
1843    /// The name the client gave itself, empty if it gave none.
1844    #[must_use]
1845    pub fn name(&self) -> &[u8] {
1846        &self.name
1847    }
1848
1849    /// Put everything back the way it was when the connection was opened.
1850    ///
1851    /// The protocol is not here because it is not here: it lives in the reply
1852    /// buffer, and `RESET` sets it back there.
1853    pub fn reset(&mut self) {
1854        self.db = 0;
1855        self.name.clear();
1856        // `SELECT` leaves these alone and `RESET` does not, both checked
1857        // against 8.10.1, which is the one pair of answers you could not guess
1858        // from what the command is for.
1859        self.sets.clear();
1860    }
1861
1862    /// Record the name from `HELLO ... SETNAME`.
1863    fn set_name(&mut self, name: &[u8]) {
1864        yo_alloc::allow(|| {
1865            self.name.clear();
1866            self.name.extend_from_slice(name);
1867        });
1868    }
1869}
1870
1871/// Give back everything a connection was holding on the server.
1872///
1873/// The transaction, the watches and the subscriptions, and it is here rather
1874/// than in [`Session::reset`] because letting go of any of the three is a change
1875/// to the server. A `Session` on its own cannot reach one, and a connection that
1876/// dropped its lists without saying so would leave rows nobody is watching and
1877/// subscriptions nobody is listening to, which would keep every write and every
1878/// publish on the server paying for clients that are not there.
1879pub fn forget_session(server: &Server, session: &mut Session) {
1880    multi::release(server, session);
1881    pubsub::release(server, session);
1882}
1883
1884/// Run one command and write its reply.
1885///
1886/// The name is looked up and the arity is checked here, once, so that no body
1887/// has to. Everything after that is the command's own.
1888pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1889    // The decoder never produces a command with no name. If one ever arrives,
1890    // it is not something to answer.
1891    if args.is_empty() {
1892        return Flow::Continue;
1893    }
1894    resolved(server, session, lookup(args.name()), args, out)
1895}
1896
1897/// The same, for a caller that has already found the command.
1898///
1899/// The engine frames a command before it runs it, and between those two it also
1900/// asks which key the command touches so the record can be prefetched. That is
1901/// two more chances to look the name up, and looking it up three times to run it
1902/// once is three times the cost of the cheapest thing in the path. So the engine
1903/// resolves the name where it frames the command, carries the answer on the
1904/// framed command, and both the other two take it from there.
1905///
1906/// `spec` is `None` for a name that is not a command, which is the same thing
1907/// [`lookup`] says and lands in the same reply.
1908pub fn resolved(
1909    server: &Server,
1910    session: &mut Session,
1911    spec: Option<&'static Spec>,
1912    args: Args<'_>,
1913    out: &mut Out,
1914) -> Flow {
1915    if args.is_empty() {
1916        return Flow::Continue;
1917    }
1918    server.mine().stats.commands.bump();
1919
1920    // The four refusals below are the ones a real server makes in
1921    // `processCommand`, before the command's own body is reached, and they are
1922    // the ones that kill an open transaction. That is the whole of the rule: an
1923    // error raised here means `EXEC` will refuse to run anything, and an error
1924    // raised by a command body does not, which is why `MULTI` inside `MULTI`
1925    // complains and leaves the transaction alive.
1926    let Some(spec) = spec else {
1927        multi::refuse(server, session, None, &args::unknown_command(args), out);
1928        return Flow::Continue;
1929    };
1930    if !arity_ok(spec, args.len()) {
1931        server.mine().cmdstats.at(spec).rejected.bump();
1932        multi::refuse(
1933            server,
1934            session,
1935            Some(spec),
1936            &args::wrong_arity(spec.name),
1937            out,
1938        );
1939        return Flow::Continue;
1940    }
1941    if session.in_multi()
1942        && let Some(e) = multi::refused_in_multi(spec)
1943    {
1944        server.mine().cmdstats.at(spec).rejected.bump();
1945        multi::refuse(server, session, Some(spec), &e, out);
1946        return Flow::Continue;
1947    }
1948
1949    // The limit first, so a server with no `maxmemory`, which is the default and
1950    // is nearly all of them, pays one comparison against a field that is already
1951    // warm. Every command and not only the writes, because that is where Redis
1952    // puts it: making room is the server's job whatever the client asked for,
1953    // and the flag only decides who gets told no when there is no room to make.
1954    //
1955    // The flag is Redis's own `denyoom` and the list of commands carrying it is
1956    // Redis's list, so a command that only frees is let through with nothing
1957    // left, which is what lets a client dig itself out with `DEL`.
1958    if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1959        server.mine().cmdstats.at(spec).rejected.bump();
1960        session.dirty_multi();
1961        out.error_line(b"OOM ", OOM);
1962        return Flow::Continue;
1963    }
1964
1965    // A RESP2 connection that has subscribed to something may only send a
1966    // handful of commands, because RESP2 sends a published message as an
1967    // ordinary array and a client with a reply outstanding could not tell the
1968    // two apart. Here, after the refusals above and before the queue below,
1969    // which is where a real server puts it: `EXEC` sent while subscribed comes
1970    // back as an `EXECABORT` rather than as this error, and a command `EXEC`
1971    // hands over is not asked at all.
1972    if let Some(e) = pubsub::refused(session, spec, out) {
1973        server.mine().cmdstats.at(spec).rejected.bump();
1974        multi::refuse(server, session, Some(spec), &e, out);
1975        return Flow::Continue;
1976    }
1977
1978    // Held rather than run, and the reply is `QUEUED`. After the refusals above
1979    // and before everything below, which is where a real server puts it: a
1980    // command has to be a real command with the right number of arguments to be
1981    // queued at all, and nothing it would have done gets done now.
1982    if session.queues(spec.name) {
1983        return multi::queue(session, args, out);
1984    }
1985
1986    // Which databases the maintenance turn after this batch has to ask. Marked
1987    // for every command and not only for the writes, because a read can make
1988    // garbage too: a `GET` on a key whose expiry has passed reaps it, and the
1989    // record it dropped is exactly the kind of thing the collector is for.
1990    // `COPY`, `SWAPDB` and `FLUSHALL` reach a database nobody selected, so the
1991    // two groups that hold them mark all of them rather than the session's.
1992    server.mine().mark(match spec.group {
1993        "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
1994        | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
1995            1u64 << session.db
1996        }
1997        _ => ALL_DATABASES,
1998    });
1999
2000    let mark = out.len();
2001    // Before the group, because the five that block are list commands and would
2002    // otherwise land in `lists`, which is handed one database and nothing that
2003    // could park a client. The flag is the right thing to branch on rather than
2004    // a list of names: it is what `COMMAND INFO` reports about exactly these
2005    // commands, and the sorted set and stream ones that arrive later carry it
2006    // too.
2007    // What the command is about to do to the keyspace, for anybody subscribed to
2008    // hear about it. Armed here and drained after the group, because the bodies
2009    // below are handed a database and their arguments and have no way to reach
2010    // the pub/sub registry from there. Off costs one thread local store.
2011    let armed = notify::arm(server);
2012    let done = if spec.flags.contains(&"blocking") {
2013        blocking::execute(server, session, spec, args, out)
2014    } else {
2015        match spec.group {
2016            "string" => {
2017                let db = session.db;
2018                strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2019            }
2020            // Its own group and its own file, and the same values underneath:
2021            // a bitmap is a string, so `STRLEN` on one answers and `SETBIT` on
2022            // something a `SET` left behind works.
2023            "bitmap" => {
2024                let db = session.db;
2025                bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2026            }
2027            // The same again: a sketch is a string with a documented layout, so
2028            // `GET` hands one to a client and `SET` takes it back.
2029            "hyperloglog" => {
2030                let db = session.db;
2031                hll::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2032            }
2033            "set" => {
2034                let db = session.db;
2035                sets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2036            }
2037            // The one hash command whose state is not in the keyspace. A
2038            // fieldset belongs to the connection, so this is handed the session
2039            // as well as the database, the same exception `MIGRATE` gets in the
2040            // keyspace group for the socket it keeps.
2041            "hash" if spec.name == "himport" => {
2042                let db = session.db;
2043                himport::execute(&server.dbs[db], &mut session.sets, args, out)
2044                    .map(|()| Flow::Continue)
2045            }
2046            // The one group that reaches back into the server after it has
2047            // written its reply, because a hash is what a search index is
2048            // made of. What comes back is what the indexes have to be told,
2049            // which is not the same as whether the command was a write.
2050            "hash" => {
2051                let db = session.db;
2052                let changed = hashes::execute(&server.dbs[db], spec, args, out);
2053                changed.map(|changed| {
2054                    indexing::changed(server, db, args.get(1), changed);
2055                    Flow::Continue
2056                })
2057            }
2058            "list" => {
2059                let db = session.db;
2060                lists::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2061            }
2062            "zset" => {
2063                let db = session.db;
2064                zsets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2065            }
2066            // A geo key is a sorted set and these are sorted set commands with
2067            // arithmetic on the way in and on the way out, so a client can ZREM
2068            // a place out of one and ZCARD it to count them.
2069            "geo" => {
2070                let db = session.db;
2071                geo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2072            }
2073            "array" => {
2074                let db = session.db;
2075                arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2076            }
2077            "graph" => {
2078                let db = session.db;
2079                graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2080            }
2081            // A document under a key, reached by a path. The group is Redis's
2082            // module surface and the storage is ours, the same trade the vector
2083            // set group makes.
2084            "json" => {
2085                let db = session.db;
2086                json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2087            }
2088            "vector" => {
2089                let db = session.db;
2090                vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2091            }
2092            "bloom" => {
2093                let db = session.db;
2094                bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2095            }
2096            "cuckoo" => {
2097                let db = session.db;
2098                cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2099            }
2100            "cms" => {
2101                let db = session.db;
2102                cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2103            }
2104            "topk" => {
2105                let db = session.db;
2106                topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2107            }
2108            "tdigest" => {
2109                let db = session.db;
2110                tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2111            }
2112            "ts" => {
2113                let db = session.db;
2114                ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2115            }
2116            // The clock is read before the database is borrowed, because every
2117            // stream command needs the time and it lives on the server. An
2118            // `XADD` with no ID, an `XCLAIM` working out what is idle and an
2119            // `XINFO` reporting it all have to agree about what moment this is.
2120            "stream" => {
2121                let db = session.db;
2122                let now = server.now_ms();
2123                streams::execute(&server.dbs[db], spec, args, now, out).map(|()| Flow::Continue)
2124            }
2125            // The one keyspace command that needs more than the databases,
2126            // because the socket it talks down is held on the server between
2127            // commands and not opened again for each one.
2128            "keyspace" if spec.name == "migrate" => {
2129                migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
2130            }
2131            // Every database and not the one the session is on, because `COPY` takes
2132            // a `DB n` and writes into a database nobody selected. The other group
2133            // that reaches back into the server afterwards, and it hands back a list
2134            // rather than one answer, because `DEL a b c` is three keys and a rename
2135            // is two.
2136            "keyspace" => {
2137                let mut touched = indexing::Touched::new(server);
2138                let done =
2139                    keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
2140                done.map(|()| {
2141                    indexing::touched(server, &touched);
2142                    Flow::Continue
2143                })
2144            }
2145            // No database at all, because an index is not a key. The registry
2146            // is the whole of what these sixteen commands touch, and then
2147            // `FT.CREATE` hands back the name it made so the keys that
2148            // already match its prefix can be read into it. The lock goes
2149            // before the scan runs, since the scan takes it again for every
2150            // key it reads.
2151            "search" if spec.name == "FT.SEARCH" => {
2152                // The two search commands that read documents, and so the two
2153                // that need the keyspace as well as the registry. They take and
2154                // let go of the registry themselves, because they cannot hold
2155                // that and a stripe at the same time.
2156                search::find(server, session.db, args, out).map(|()| Flow::Continue)
2157            }
2158            "search" if spec.name == "FT.AGGREGATE" => {
2159                search::roll(server, session.db, args, out).map(|()| Flow::Continue)
2160            }
2161            "search" if spec.name == "FT.HYBRID" => {
2162                search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
2163            }
2164            "search" if spec.name == "FT.PROFILE" => {
2165                // Which is one of those two with the working shown, so it needs
2166                // everything they need and takes the same route to it.
2167                search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
2168            }
2169            // The four search commands that name a key rather than an index.
2170            // A suggestion dictionary is a real key with a type of its own, so
2171            // these are handed a database and never touch the registry.
2172            "search" if spec.name.starts_with("FT.SUG") => {
2173                let db = session.db;
2174                suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2175            }
2176            // The five deprecated document commands, which are the other search
2177            // commands that need the keyspace as well as the registry: what they
2178            // write and read is an ordinary hash.
2179            "search"
2180                if matches!(
2181                    spec.name,
2182                    "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
2183                ) =>
2184            {
2185                let db = session.db;
2186                search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
2187            }
2188            "search" if spec.name == "FT.CURSOR" => {
2189                // Its own arm because the cursors are not in the registry, and
2190                // it takes and lets go of the registry itself to look up the
2191                // index name it is given.
2192                search::cursor::execute(server, args, out).map(|()| Flow::Continue)
2193            }
2194            "search" => {
2195                let db = session.db;
2196                let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
2197                made.map(|made| {
2198                    match made {
2199                        Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
2200                        Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
2201                        None => {}
2202                    }
2203                    Flow::Continue
2204                })
2205            }
2206            "scripting" => {
2207                scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
2208            }
2209            "transactions" => multi::execute(server, session, spec, args, out),
2210            // No database either, and the one group whose replies do not all go
2211            // to the connection that asked. The session is in it because a
2212            // subscription is connection state as well as server state.
2213            "pubsub" => pubsub::execute(server, session, spec, args, out),
2214            _ => server::execute(server, session, spec, args, out),
2215        }
2216    };
2217    // Before the error is written and not after, because a command that failed
2218    // half way through still changed whatever it changed before it failed and a
2219    // real server has already published those. Draining here also keeps the
2220    // notifications of a command run by `EXEC` in front of the next one's.
2221    notify::drain(server, armed);
2222
2223    let flow = match done {
2224        Ok(flow) => flow,
2225        Err(e) => {
2226            out.truncate(mark);
2227            write_error(out, &e);
2228            Flow::Continue
2229        }
2230    };
2231
2232    // After the command rather than before, so that whether each key it named is
2233    // there is read at the moment a real server would have signalled the change.
2234    // The load is what this costs a server nobody has sent `WATCH` to, and the
2235    // flag is Redis's own, so a command that only reads is never asked.
2236    if server.watching() && spec.flags.contains(&"write") {
2237        multi::touched(server, session, spec, args);
2238    }
2239
2240    // Counted here and not before the call, which is where Redis counts it, so
2241    // that `INFO commandstats` leaves out the `INFO` that asked for it in the
2242    // same way theirs does.
2243    //
2244    // Failure is read off the reply rather than off the `Result`, because the
2245    // two are not the same set. A command that ran out of arguments comes back
2246    // as an `Err` and a command that was sent the wrong password writes its own
2247    // error line and comes back `Ok`, and both of those are a call that failed.
2248    // The first byte at the mark is what a client would branch on, and it is `-`
2249    // for an error on either protocol and `!` for RESP3's long form.
2250    let row = server.mine().cmdstats.at(spec);
2251    row.calls.bump();
2252    if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2253        row.failed.bump();
2254    }
2255    flow
2256}
2257
2258/// The error line for an error value.
2259///
2260/// The prefix is what a client branches on, and there are three of them:
2261/// `WRONGTYPE` for a command sent at the wrong kind of value, `INVALIDOBJ` for a
2262/// HyperLogLog whose opcodes do not add up, and `ERR` for everything else. The three errors that need a different one,
2263/// `NOPROTO`, `WRONGPASS` and `OOM`, are written where they are decided rather
2264/// than routed through here. `OOM` is not a [`Code`] of its own because
2265/// [`Code::Full`] already covers the string that is too long for
2266/// `proto-max-bulk-len`, and that one goes out as `ERR` on a real server.
2267fn write_error(out: &mut Out, e: &Error) {
2268    let prefix: &[u8] = match e.code() {
2269        Code::WrongType => b"WRONGTYPE ",
2270        // Only the HyperLogLog commands answer this one, and the prefix is the
2271        // sentence a client branches on to tell a sketch it cannot read from a
2272        // sketch it sent wrong.
2273        Code::Corrupt => b"INVALIDOBJ ",
2274        _ => b"ERR ",
2275    };
2276    out.error_line(prefix, e.message().as_bytes());
2277}
2278
2279#[cfg(test)]
2280mod tests {
2281    use super::*;
2282    use crate::proto::{Limits, Proto};
2283    use crate::request::Argv;
2284
2285    /// Build the wire bytes for a command.
2286    ///
2287    /// Tests go through the codec rather than around it, so an argument in a
2288    /// test is the same borrowed slice a connection produces.
2289    pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2290        let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2291        for p in parts {
2292            wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2293            wire.extend_from_slice(p);
2294            wire.extend_from_slice(b"\r\n");
2295        }
2296        wire
2297    }
2298
2299    /// A server, a connection and a buffer, driven the way the reactor will.
2300    struct Fixture {
2301        server: Server,
2302        session: Session,
2303        argv: Argv,
2304        out: Out,
2305    }
2306
2307    impl Fixture {
2308        fn new() -> Fixture {
2309            Fixture::on(Server::new())
2310        }
2311
2312        /// The same, on a server whose databases are cut into `width` stripes.
2313        fn striped(width: usize) -> Fixture {
2314            Fixture::on(Server::with_width(width))
2315        }
2316
2317        fn on(server: Server) -> Fixture {
2318            Fixture {
2319                server,
2320                session: Session::new(7),
2321                argv: Argv::new(),
2322                out: Out::new(Proto::Resp2),
2323            }
2324        }
2325
2326        /// Run one command and answer with the bytes it wrote.
2327        fn run(&mut self, parts: &[&[u8]]) -> String {
2328            self.flow(parts).1
2329        }
2330
2331        /// Run one command and answer with the bytes exactly as written.
2332        ///
2333        /// [`Fixture::run`] goes through `from_utf8_lossy`, which is fine for
2334        /// every reply that is text and destroys a `DUMP` payload, since a
2335        /// payload is arbitrary bytes and a checksum on the end of them.
2336        fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2337            let wire = encode(parts);
2338            self.argv.decode(&wire, &Limits::default()).unwrap();
2339            self.out.clear();
2340            execute(
2341                &self.server,
2342                &mut self.session,
2343                Args::new(&self.argv, &wire),
2344                &mut self.out,
2345            );
2346            self.out.as_slice().to_vec()
2347        }
2348
2349        /// Move every clock in the server on by `ms`.
2350        fn advance(&mut self, ms: u64) {
2351            self.server.advance_clock_ms(ms);
2352        }
2353
2354        /// Run one command as a second connection to the same server.
2355        ///
2356        /// What `WATCH` is for is a write another connection made, and a test
2357        /// that only has one connection cannot tell the two apart.
2358        fn other(&mut self, parts: &[&[u8]]) -> String {
2359            self.other_in(self.session.db(), parts)
2360        }
2361
2362        /// The same, on a database of its own.
2363        fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
2364            let mut session = Session::new(8);
2365            session.db = db;
2366            let reply = self.by(&mut session, parts);
2367            forget_session(&self.server, &mut session);
2368            reply
2369        }
2370
2371        /// Run one command on a session the caller holds.
2372        fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
2373            let wire = encode(parts);
2374            let mut argv = Argv::new();
2375            argv.decode(&wire, &Limits::default()).unwrap();
2376            let mut out = Out::new(Proto::Resp2);
2377            execute(&self.server, session, Args::new(&argv, &wire), &mut out);
2378            String::from_utf8_lossy(out.as_slice()).into_owned()
2379        }
2380
2381        /// The same, with what the connection should do next.
2382        fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2383            let wire = encode(parts);
2384            self.argv.decode(&wire, &Limits::default()).unwrap();
2385            self.out.clear();
2386            let flow = execute(
2387                &self.server,
2388                &mut self.session,
2389                Args::new(&self.argv, &wire),
2390                &mut self.out,
2391            );
2392            (
2393                flow,
2394                String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2395            )
2396        }
2397    }
2398
2399    #[test]
2400    fn multi_holds_commands_and_exec_runs_them() {
2401        let mut f = Fixture::new();
2402        assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
2403        assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
2404        assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
2405        // Nothing ran while it was being queued.
2406        assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
2407        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
2408        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
2409    }
2410
2411    #[test]
2412    fn an_empty_transaction_answers_an_empty_array() {
2413        let mut f = Fixture::new();
2414        f.run(&[b"MULTI"]);
2415        assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
2416    }
2417
2418    #[test]
2419    fn exec_and_discard_want_a_transaction_to_be_open() {
2420        let mut f = Fixture::new();
2421        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2422        assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
2423        // And `UNWATCH` does not, which is the one of the three that is happy
2424        // being sent for no reason.
2425        assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
2426    }
2427
2428    #[test]
2429    fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
2430        let mut f = Fixture::new();
2431        f.run(&[b"MULTI"]);
2432        assert_eq!(
2433            f.run(&[b"MULTI"]),
2434            "-ERR MULTI calls can not be nested\r\n",
2435            "nested MULTI is raised by the command and not by the funnel"
2436        );
2437        assert_eq!(
2438            f.run(&[b"WATCH", b"k"]),
2439            "-ERR WATCH inside MULTI is not allowed\r\n"
2440        );
2441        f.run(&[b"SET", b"k", b"1"]);
2442        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
2443    }
2444
2445    #[test]
2446    fn an_error_the_funnel_raises_kills_the_transaction() {
2447        for bad in [
2448            &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
2449            &[b"GET".as_slice()],
2450        ] {
2451            let mut f = Fixture::new();
2452            f.run(&[b"MULTI"]);
2453            assert!(f.run(bad).starts_with("-ERR "));
2454            assert_eq!(
2455                f.run(&[b"SET", b"k", b"1"]),
2456                "+QUEUED\r\n",
2457                "a dead transaction still answers QUEUED, which is Redis"
2458            );
2459            assert_eq!(
2460                f.run(&[b"EXEC"]),
2461                "-EXECABORT Transaction discarded because of previous errors.\r\n"
2462            );
2463            assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2464        }
2465    }
2466
2467    #[test]
2468    fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
2469        let mut f = Fixture::new();
2470        f.run(&[b"MULTI"]);
2471        f.run(&[b"SET", b"k", b"1"]);
2472        assert_eq!(
2473            f.run(&[b"EXEC", b"x"]),
2474            "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
2475        );
2476        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2477        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2478    }
2479
2480    #[test]
2481    fn a_command_a_transaction_may_not_hold_kills_it() {
2482        let mut f = Fixture::new();
2483        f.run(&[b"MULTI"]);
2484        assert_eq!(
2485            f.run(&[b"SHUTDOWN", b"NOSAVE"]),
2486            "-ERR Command not allowed inside a transaction\r\n"
2487        );
2488        assert_eq!(
2489            f.run(&[b"EXEC"]),
2490            "-EXECABORT Transaction discarded because of previous errors.\r\n"
2491        );
2492    }
2493
2494    #[test]
2495    fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
2496        let mut f = Fixture::new();
2497        f.run(&[b"RPUSH", b"l", b"v"]);
2498        f.run(&[b"MULTI"]);
2499        f.run(&[b"INCR", b"l"]);
2500        f.run(&[b"SET", b"y", b"2"]);
2501        assert_eq!(
2502            f.run(&[b"EXEC"]),
2503            "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
2504        );
2505        assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
2506    }
2507
2508    #[test]
2509    fn discard_and_reset_both_throw_the_queue_away() {
2510        let mut f = Fixture::new();
2511        f.run(&[b"MULTI"]);
2512        f.run(&[b"SET", b"k", b"1"]);
2513        assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
2514        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2515        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2516
2517        f.run(&[b"MULTI"]);
2518        f.run(&[b"SET", b"k", b"1"]);
2519        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2520        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2521        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2522    }
2523
2524    #[test]
2525    fn select_is_queued_and_applied_when_exec_runs_it() {
2526        let mut f = Fixture::new();
2527        f.run(&[b"MULTI"]);
2528        assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
2529        f.run(&[b"SET", b"k", b"1"]);
2530        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
2531        assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
2532        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
2533    }
2534
2535    #[test]
2536    fn a_write_by_another_connection_fails_the_transaction() {
2537        let mut f = Fixture::new();
2538        f.run(&[b"SET", b"k", b"1"]);
2539        assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
2540        f.other(&[b"SET", b"k", b"2"]);
2541        f.run(&[b"MULTI"]);
2542        f.run(&[b"GET", b"k"]);
2543        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2544    }
2545
2546    #[test]
2547    fn a_write_that_puts_the_same_value_back_still_fails_it() {
2548        let mut f = Fixture::new();
2549        f.run(&[b"SET", b"k", b"1"]);
2550        f.run(&[b"WATCH", b"k"]);
2551        f.other(&[b"SET", b"k", b"1"]);
2552        f.run(&[b"MULTI"]);
2553        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2554    }
2555
2556    #[test]
2557    fn a_read_by_another_connection_does_not() {
2558        let mut f = Fixture::new();
2559        f.run(&[b"SET", b"k", b"1"]);
2560        f.run(&[b"WATCH", b"k"]);
2561        f.other(&[b"GET", b"k"]);
2562        f.other(&[b"STRLEN", b"k"]);
2563        f.run(&[b"MULTI"]);
2564        f.run(&[b"GET", b"k"]);
2565        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2566    }
2567
2568    #[test]
2569    fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
2570        let mut f = Fixture::new();
2571        f.run(&[b"WATCH", b"k"]);
2572        f.other(&[b"DEL", b"k"]);
2573        f.run(&[b"MULTI"]);
2574        f.run(&[b"PING"]);
2575        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2576        // And creating it does, which is the other half of the same rule.
2577        f.run(&[b"WATCH", b"k"]);
2578        f.other(&[b"SET", b"k", b"1"]);
2579        f.run(&[b"MULTI"]);
2580        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2581    }
2582
2583    #[test]
2584    fn a_watched_key_that_expires_fails_the_transaction() {
2585        let mut f = Fixture::new();
2586        f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
2587        f.run(&[b"WATCH", b"k"]);
2588        f.run(&[b"MULTI"]);
2589        f.advance(100);
2590        assert_eq!(
2591            f.run(&[b"EXEC"]),
2592            "*-1\r\n",
2593            "nothing wrote to the key, so only the liveness check can catch this"
2594        );
2595    }
2596
2597    #[test]
2598    fn every_way_a_transaction_ends_lets_go_of_the_watches() {
2599        for end in [
2600            &[b"EXEC".as_slice()] as &[&[u8]],
2601            &[b"DISCARD".as_slice()],
2602            &[b"UNWATCH".as_slice()],
2603            &[b"RESET".as_slice()],
2604        ] {
2605            let mut f = Fixture::new();
2606            f.run(&[b"SET", b"k", b"1"]);
2607            f.run(&[b"WATCH", b"k"]);
2608            if end[0] != b"UNWATCH" && end[0] != b"RESET" {
2609                f.run(&[b"MULTI"]);
2610            }
2611            f.run(end);
2612            assert!(!f.server.watching(), "{end:?} left a row behind");
2613            // And the connection can start again with nothing carried over.
2614            f.other(&[b"SET", b"k", b"2"]);
2615            f.run(&[b"MULTI"]);
2616            f.run(&[b"GET", b"k"]);
2617            assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
2618        }
2619    }
2620
2621    #[test]
2622    fn a_connection_going_away_lets_go_of_its_watches() {
2623        let mut f = Fixture::new();
2624        f.run(&[b"SET", b"k", b"1"]);
2625        f.run(&[b"WATCH", b"k"]);
2626        assert!(f.server.watching());
2627        forget_session(&f.server, &mut f.session);
2628        assert!(!f.server.watching());
2629    }
2630
2631    #[test]
2632    fn watching_the_same_key_twice_is_one_watch() {
2633        let mut f = Fixture::new();
2634        f.run(&[b"SET", b"k", b"1"]);
2635        f.run(&[b"WATCH", b"k", b"k"]);
2636        f.run(&[b"UNWATCH"]);
2637        assert!(
2638            !f.server.watching(),
2639            "the row counts watchers, so a doubled watch would leave one behind"
2640        );
2641    }
2642
2643    #[test]
2644    fn two_connections_can_watch_the_same_key() {
2645        let mut f = Fixture::new();
2646        f.run(&[b"SET", b"k", b"1"]);
2647        f.run(&[b"WATCH", b"k"]);
2648        let mut second = Session::new(9);
2649        second.db = f.session.db();
2650        assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
2651        // One lets go and the other's watch still works.
2652        forget_session(&f.server, &mut second);
2653        assert!(f.server.watching());
2654        f.other(&[b"SET", b"k", b"2"]);
2655        f.run(&[b"MULTI"]);
2656        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2657    }
2658
2659    #[test]
2660    fn flushdb_fails_a_watch_on_a_key_that_was_there() {
2661        let mut f = Fixture::new();
2662        f.run(&[b"SET", b"k", b"1"]);
2663        f.run(&[b"WATCH", b"k"]);
2664        f.other(&[b"FLUSHDB"]);
2665        f.run(&[b"MULTI"]);
2666        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2667    }
2668
2669    #[test]
2670    fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
2671        let mut f = Fixture::new();
2672        f.run(&[b"WATCH", b"k"]);
2673        f.other(&[b"FLUSHDB"]);
2674        f.run(&[b"MULTI"]);
2675        f.run(&[b"PING"]);
2676        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2677    }
2678
2679    #[test]
2680    fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
2681        let mut f = Fixture::new();
2682        f.run(&[b"SET", b"k", b"1"]);
2683        f.run(&[b"WATCH", b"k"]);
2684        // The same name in another database is another key.
2685        let elsewhere = f.session.db() + 1;
2686        f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
2687        f.run(&[b"MULTI"]);
2688        f.run(&[b"GET", b"k"]);
2689        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2690    }
2691
2692    #[test]
2693    fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
2694        let mut f = Fixture::new();
2695        f.run(&[b"RPUSH", b"src", b"1"]);
2696        f.run(&[b"WATCH", b"dst"]);
2697        f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
2698        f.run(&[b"MULTI"]);
2699        assert_eq!(
2700            f.run(&[b"EXEC"]),
2701            "*-1\r\n",
2702            "SORT is movablekeys, so every watched key in the database is asked"
2703        );
2704    }
2705
2706    #[test]
2707    fn a_server_nobody_is_watching_says_so() {
2708        let mut f = Fixture::new();
2709        assert!(!f.server.watching());
2710        f.run(&[b"SET", b"k", b"1"]);
2711        assert!(!f.server.watching());
2712    }
2713
2714    /// The count on the end of a subscribe reply is channels and patterns
2715    /// together, which is a thing a client uses to know when it is out of
2716    /// subscribe mode and so has to be the number the mode is decided on.
2717    /// Shard channels are counted on their own because they are their own
2718    /// namespace.
2719    #[test]
2720    fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
2721        let mut f = Fixture::new();
2722        assert_eq!(
2723            f.run(&[b"SUBSCRIBE", b"a", b"b"]),
2724            "*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"
2725        );
2726        assert_eq!(
2727            f.run(&[b"PSUBSCRIBE", b"c*"]),
2728            "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
2729        );
2730        assert_eq!(
2731            f.run(&[b"SSUBSCRIBE", b"s"]),
2732            "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
2733        );
2734        // Subscribing again to something already held answers again with the
2735        // count unchanged, rather than counting it twice or saying nothing.
2736        assert_eq!(
2737            f.run(&[b"SUBSCRIBE", b"a"]),
2738            "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
2739        );
2740    }
2741
2742    /// Unsubscribe has three shapes and a client has to be able to tell them
2743    /// apart, because the last one is what tells it the mode is over.
2744    #[test]
2745    fn unsubscribe_answers_for_names_it_was_not_holding_too() {
2746        let mut f = Fixture::new();
2747        f.run(&[b"SUBSCRIBE", b"a"]);
2748
2749        // A name that was never subscribed still gets a reply, with the count
2750        // as it stands.
2751        assert_eq!(
2752            f.run(&[b"UNSUBSCRIBE", b"zz"]),
2753            "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
2754        );
2755        // With no names, one reply per channel held, counting down.
2756        f.run(&[b"SUBSCRIBE", b"b"]);
2757        f.run(&[b"PSUBSCRIBE", b"p*"]);
2758        assert_eq!(
2759            f.run(&[b"UNSUBSCRIBE"]),
2760            "*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"
2761        );
2762        // With no names and none of that family held, one reply with a nil
2763        // where the name goes and the count that is left.
2764        assert_eq!(
2765            f.run(&[b"UNSUBSCRIBE"]),
2766            "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
2767            "the pattern is still held, so the count is one"
2768        );
2769        assert_eq!(
2770            f.run(&[b"SUNSUBSCRIBE"]),
2771            "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
2772            "shard channels are counted on their own"
2773        );
2774    }
2775
2776    /// The gate is on the funnel and the funnel is what `EXEC` goes through
2777    /// for the commands it queued, so it has to know it is running one.
2778    /// Redis lets a queued command through, and a transaction that subscribes
2779    /// and then reads is the case that says which way round it is.
2780    #[test]
2781    fn the_subscribe_gate_does_not_reach_inside_exec() {
2782        let mut f = Fixture::new();
2783        f.run(&[b"SET", b"k", b"1"]);
2784        f.run(&[b"MULTI"]);
2785        assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
2786        assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
2787        assert_eq!(
2788            f.run(&[b"EXEC"]),
2789            "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
2790        );
2791        // And once EXEC is done the connection really is subscribed, so the
2792        // gate is back on.
2793        assert_eq!(
2794            f.run(&[b"GET", b"k"]),
2795            "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
2796        );
2797    }
2798
2799    /// `EXEC` sent by a subscribed RESP2 client is refused by the gate like
2800    /// anything else, and a refusal on the funnel kills the transaction.
2801    #[test]
2802    fn exec_sent_by_a_subscriber_aborts_the_transaction() {
2803        let mut f = Fixture::new();
2804        f.run(&[b"MULTI"]);
2805        f.run(&[b"SET", b"k", b"1"]);
2806        f.run(&[b"SUBSCRIBE", b"z"]);
2807        f.run(&[b"EXEC"]);
2808        f.run(&[b"MULTI"]);
2809        assert_eq!(
2810            f.run(&[b"EXEC"]),
2811            "-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"
2812        );
2813    }
2814
2815    /// `RESET` is one of the few things a subscriber may send, and what it
2816    /// resets includes every subscription it is holding.
2817    #[test]
2818    fn reset_lets_go_of_every_subscription() {
2819        let mut f = Fixture::new();
2820        f.run(&[b"SUBSCRIBE", b"a"]);
2821        f.run(&[b"PSUBSCRIBE", b"p*"]);
2822        f.run(&[b"SSUBSCRIBE", b"s"]);
2823        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2824        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2825        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2826        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
2827        // And the connection takes ordinary commands again.
2828        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2829    }
2830
2831    /// What `PUBSUB` can be asked, on a server with one subscriber holding one
2832    /// of each.
2833    #[test]
2834    fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
2835        let mut f = Fixture::new();
2836        let mut sub = Session::new(9);
2837        f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
2838        f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
2839        f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
2840
2841        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
2842        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
2843        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
2844        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
2845        assert_eq!(
2846            f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
2847            "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
2848        );
2849        assert_eq!(
2850            f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
2851            "*2\r\n$1\r\na\r\n:1\r\n",
2852            "the shard channel and the channel share a name and not a count"
2853        );
2854        assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
2855
2856        forget_session(&f.server, &mut sub);
2857        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2858        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2859    }
2860
2861    /// The one setting whose value is neither a number nor a word, and whose
2862    /// spelling on the way out is not the spelling on the way in.
2863    #[test]
2864    fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
2865        let mut f = Fixture::new();
2866        assert_eq!(
2867            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2868            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2869        );
2870        assert_eq!(
2871            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
2872            "+OK\r\n"
2873        );
2874        // `A` is a class of its own on the way in and stays one on the way out,
2875        // and the two channel letters move to the end.
2876        assert_eq!(
2877            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2878            "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
2879        );
2880        assert_eq!(
2881            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
2882            "+OK\r\n"
2883        );
2884        assert_eq!(
2885            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2886            "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
2887        );
2888    }
2889
2890    #[test]
2891    fn a_letter_the_notification_setting_does_not_know_is_refused() {
2892        let mut f = Fixture::new();
2893        assert_eq!(
2894            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
2895            "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
2896             - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
2897        );
2898        // And nothing was applied, since the whole setting is parsed before any
2899        // of it is stored.
2900        assert_eq!(
2901            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2902            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2903        );
2904    }
2905
2906    /// One mistake in a `PUBSUB` subcommand has two error shapes depending on
2907    /// which subcommand it is, because the ones with a fixed argument count are
2908    /// checked by the subcommand table and the ones without fall through to
2909    /// the generic syntax error. Both are copied here rather than tidied,
2910    /// since a client that matches on the text sees the difference.
2911    #[test]
2912    fn pubsub_says_no_two_different_ways() {
2913        let mut f = Fixture::new();
2914        assert_eq!(
2915            f.run(&[b"PUBSUB"]),
2916            "-ERR wrong number of arguments for 'pubsub' command\r\n"
2917        );
2918        assert_eq!(
2919            f.run(&[b"PUBSUB", b"NOPE"]),
2920            "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
2921        );
2922        assert_eq!(
2923            f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
2924            "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
2925        );
2926        assert_eq!(
2927            f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
2928            "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
2929        );
2930        assert_eq!(
2931            f.run(&[b"PUBSUB", b"HELP", b"x"]),
2932            "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
2933        );
2934    }
2935
2936    /// Publishing to nobody costs a lookup and answers zero, which is the
2937    /// common case on a server that has pub/sub compiled in and not in use.
2938    #[test]
2939    fn publishing_to_nobody_answers_zero() {
2940        let mut f = Fixture::new();
2941        assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
2942        assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
2943        // An empty channel name is a name like any other.
2944        assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
2945    }
2946
2947    /// A publish counts everybody it reached, which is not the same as the
2948    /// number of subscribers: one connection holding two patterns that both
2949    /// match is two.
2950    #[test]
2951    fn a_publish_counts_the_deliveries_and_not_the_clients() {
2952        let mut f = Fixture::new();
2953        let mut sub = Session::new(9);
2954        f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
2955        f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
2956        f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
2957        assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
2958        forget_session(&f.server, &mut sub);
2959    }
2960
2961    /// What a client does all day: write the same keys again and again. Every
2962    /// one of those writes leaves the previous record behind, so a server that
2963    /// never compacts holds every version of every key it has ever been sent.
2964    ///
2965    /// Not under Miri, and not because of anything it would find. The bound
2966    /// only means something once several megabytes have gone through the
2967    /// arena, which reclaims a segment at a time and has segments of two
2968    /// megabytes, so a server that reclaimed nothing would still be under the
2969    /// bound in any smaller version of this. Thirty two megabytes is thirty
2970    /// two thousand commands and was over forty minutes interpreted. The paths
2971    /// it walks are walked by the hundreds of tests around it that write a key
2972    /// and read it back, which do run there.
2973    #[cfg_attr(miri, ignore = "megabytes through the arena")]
2974    #[test]
2975    fn rewriting_the_same_keys_does_not_grow_the_server() {
2976        let mut f = Fixture::new();
2977        let val = vec![b'v'; 1024];
2978        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2979
2980        for k in &keys {
2981            f.run(&[b"SET", k, &val]);
2982        }
2983        f.server.compact_step();
2984        let after_first = f.server.memory_bytes();
2985
2986        // 64 KiB a pass, five hundred passes, and the same 64 keys at the end
2987        // of it. Thirty two megabytes written to hold sixty four kilobytes,
2988        // which is the shape of a real workload and is enough churn to fill
2989        // sixteen segments if nothing ever comes back.
2990        for _ in 0..500 {
2991            for k in &keys {
2992                f.run(&[b"SET", k, &val]);
2993            }
2994            f.server.compact_step();
2995        }
2996
2997        assert!(
2998            f.server.memory_bytes() <= after_first * 2,
2999            "held {} after five hundred passes against {after_first} after one",
3000            f.server.memory_bytes()
3001        );
3002        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3003        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3004    }
3005
3006    /// The same churn on a database nobody starts on, either side of a quiet
3007    /// spell long enough for the maintenance turn to stop asking about it.
3008    ///
3009    /// The turn after each batch skips a database that has already said it has
3010    /// nothing to collect and has not been touched since, which is what keeps a
3011    /// server whose clients are all on database zero from loading and storing
3012    /// in the other fifteen every batch to be told no. Two things could go
3013    /// wrong with that. A database might never be marked at all, so this uses
3014    /// database nine, which nothing marks by accident. And a database whose
3015    /// mark was cleared might never get it back, so this drains the collector
3016    /// until it says there is nothing left, checks the mark really is gone, and
3017    /// then writes another thirty two megabytes through the same sixty four
3018    /// keys. If either went wrong the server would hold all of it.
3019    ///
3020    /// Not under Miri, for the reason on the test above: the volume is the
3021    /// claim, and the volume is what the interpreter charges for.
3022    #[cfg_attr(miri, ignore = "megabytes through the arena")]
3023    #[test]
3024    fn a_database_nobody_started_on_is_still_collected() {
3025        let mut f = Fixture::new();
3026        assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
3027        let val = vec![b'v'; 1024];
3028        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3029
3030        for k in &keys {
3031            f.run(&[b"SET", k, &val]);
3032        }
3033        while f.server.compact_step().is_some() {}
3034        assert!(
3035            !f.server.mine().wanted(9),
3036            "database nine was drained and should not be asked again until it is written to"
3037        );
3038        let after_first = f.server.memory_bytes();
3039
3040        for _ in 0..500 {
3041            for k in &keys {
3042                f.run(&[b"SET", k, &val]);
3043            }
3044            f.server.compact_step();
3045        }
3046
3047        assert!(
3048            f.server.memory_bytes() <= after_first * 2,
3049            "held {} after five hundred passes against {after_first} after one",
3050            f.server.memory_bytes()
3051        );
3052        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3053        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3054        // And nothing landed anywhere else on the way.
3055        f.run(&[b"SELECT", b"0"]);
3056        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3057    }
3058
3059    #[test]
3060    fn a_command_goes_from_bytes_to_bytes() {
3061        let mut f = Fixture::new();
3062        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3063        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3064        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3065        assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
3066        // The name is matched whatever case it came in, and so are the options.
3067        assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
3068        assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
3069    }
3070
3071    #[test]
3072    fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
3073        let mut f = Fixture::new();
3074        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
3075        // A key named twice exists twice and can only be deleted once, and both
3076        // of those are Redis's answers rather than tidier ones.
3077        assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
3078        assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
3079        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3080        // UNLINK is the same body and reports the same way.
3081        assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
3082        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3083    }
3084
3085    #[test]
3086    fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
3087        let mut f = Fixture::new();
3088        f.run(&[b"SET", b"k", b"v"]);
3089        // A simple string on both protocols, which is unusual: most replies
3090        // that carry a word are bulk strings.
3091        assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
3092        assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
3093    }
3094
3095    #[test]
3096    fn touch_counts_the_way_exists_counts() {
3097        let mut f = Fixture::new();
3098        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3099        assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
3100        assert_eq!(
3101            f.run(&[b"TOUCH", b"a", b"a"]),
3102            ":2\r\n",
3103            "twice counts twice"
3104        );
3105        assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
3106        assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
3107    }
3108
3109    #[test]
3110    fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
3111        let mut f = Fixture::new();
3112        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3113        f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
3114
3115        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
3116        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3117        assert_eq!(
3118            f.run(&[b"TTL", b"b"]),
3119            ":100\r\n",
3120            "the source's and not b's"
3121        );
3122        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3123    }
3124
3125    #[test]
3126    fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
3127        let mut f = Fixture::new();
3128        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
3129        // The source is checked before the destination, so this is the error
3130        // and not the zero RENAMENX would otherwise answer for a taken name.
3131        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
3132    }
3133
3134    #[test]
3135    fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
3136        let mut f = Fixture::new();
3137        f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
3138
3139        assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
3140        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3141        // Renaming onto itself is 0 here and OK for plain RENAME, which is the
3142        // one call the two disagree about and neither does any work for.
3143        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
3144        assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
3145        assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
3146        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
3147    }
3148
3149    #[test]
3150    fn renaming_a_set_does_not_touch_a_member() {
3151        let mut f = Fixture::new();
3152        for i in 0..300 {
3153            f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
3154        }
3155        let before = f.server.memory_bytes();
3156
3157        assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
3158        assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
3159        assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
3160        assert!(
3161            f.server.memory_bytes().abs_diff(before) < 256,
3162            "the members were copied: {} against {before}",
3163            f.server.memory_bytes()
3164        );
3165    }
3166
3167    #[test]
3168    fn a_copy_is_a_second_value_and_not_a_second_name() {
3169        let mut f = Fixture::new();
3170        f.run(&[b"SADD", b"s", b"m1", b"m2"]);
3171
3172        assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
3173        f.run(&[b"SADD", b"t", b"m3"]);
3174        assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
3175        assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
3176    }
3177
3178    /// Every type a key can hold, copied, because two of them used to panic.
3179    ///
3180    /// `COPY` reads the value out of the source through one match on the type
3181    /// tag, and that match had a catch all at the bottom from back when a set
3182    /// and a hash were the only bodies. The list and the sorted set landed after
3183    /// it and nobody came back, so `COPY mylist other` took the shard down. It
3184    /// is an ordinary command against a type the server supports everywhere
3185    /// else, so this walks all five rather than the two that were broken: the
3186    /// point is that the next type cannot land the same way.
3187    #[test]
3188    fn every_type_can_be_copied() {
3189        let mut f = Fixture::new();
3190        f.run(&[b"SET", b"str", b"v1"]);
3191        f.run(&[b"SADD", b"set", b"m1"]);
3192        f.run(&[b"HSET", b"hash", b"f", b"v"]);
3193        f.run(&[b"RPUSH", b"list", b"a", b"b"]);
3194        f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
3195
3196        for name in [
3197            &b"str"[..],
3198            &b"set"[..],
3199            &b"hash"[..],
3200            &b"list"[..],
3201            &b"zset"[..],
3202        ] {
3203            let dst = [name, b":copy"].concat();
3204            assert_eq!(
3205                f.run(&[b"COPY", name, &dst]),
3206                ":1\r\n",
3207                "copying {}",
3208                String::from_utf8_lossy(name)
3209            );
3210            assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
3211        }
3212
3213        assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
3214            let mut want = String::from("*2\r\n");
3215            want.push_str("$1\r\na\r\n$1\r\nb\r\n");
3216            want
3217        });
3218        assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
3219
3220        // And the copy is its own value, not a second name for the source.
3221        f.run(&[b"RPUSH", b"list:copy", b"c"]);
3222        assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
3223        assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
3224    }
3225
3226    #[test]
3227    fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
3228        let mut f = Fixture::new();
3229        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3230        f.run(&[b"SET", b"b", b"v2"]);
3231
3232        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
3233        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3234        assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
3235        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3236        assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
3237        assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
3238    }
3239
3240    #[test]
3241    fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
3242        let mut f = Fixture::new();
3243        f.run(&[b"SET", b"a", b"v1"]);
3244
3245        // Same key, different database, so this is not the same object and is
3246        // an ordinary copy. Same key in the same database is the error below.
3247        assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
3248        f.run(&[b"SELECT", b"1"]);
3249        assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
3250        assert_eq!(
3251            f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
3252            ":0\r\n",
3253            "taken"
3254        );
3255        assert_eq!(
3256            f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
3257            ":1\r\n"
3258        );
3259    }
3260
3261    #[test]
3262    fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
3263        let mut f = Fixture::new();
3264        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3265        assert_eq!(
3266            f.run(&[b"SORT", b"l"]),
3267            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3268        );
3269        // DESC then ASC is ASC, because the only thing ASC does is undo a DESC.
3270        assert_eq!(
3271            f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
3272            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3273        );
3274        assert_eq!(
3275            f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
3276            "*1\r\n$1\r\n2\r\n"
3277        );
3278    }
3279
3280    #[test]
3281    fn sort_reads_a_key_per_element_for_by_and_for_get() {
3282        let mut f = Fixture::new();
3283        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3284        f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
3285        // `b` weighs less so it comes first, and its `GET` hits where `a`'s
3286        // misses, which is a nil in the middle of the array and not a short one.
3287        assert_eq!(
3288            f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
3289            "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
3290        );
3291    }
3292
3293    #[test]
3294    fn sort_store_writes_a_list_and_answers_its_length() {
3295        let mut f = Fixture::new();
3296        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3297        assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
3298        assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
3299        assert_eq!(
3300            f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
3301            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3302        );
3303        // An empty result takes the destination with it rather than leaving a
3304        // list that holds nothing.
3305        assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
3306        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
3307    }
3308
3309    #[test]
3310    fn sort_ro_does_not_know_the_word_store() {
3311        let mut f = Fixture::new();
3312        f.run(&[b"RPUSH", b"l", b"2", b"1"]);
3313        assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
3314        assert_eq!(
3315            f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
3316            "-ERR syntax error\r\n"
3317        );
3318        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3319    }
3320
3321    #[test]
3322    fn sort_refuses_what_it_cannot_sort() {
3323        let mut f = Fixture::new();
3324        assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
3325        f.run(&[b"SET", b"s", b"x"]);
3326        assert_eq!(
3327            f.run(&[b"SORT", b"s"]),
3328            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
3329        );
3330        f.run(&[b"RPUSH", b"words", b"one", b"two"]);
3331        assert_eq!(
3332            f.run(&[b"SORT", b"words"]),
3333            "-ERR One or more scores can't be converted into double\r\n"
3334        );
3335        assert_eq!(
3336            f.run(&[b"SORT", b"words", b"ALPHA"]),
3337            "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
3338        );
3339        assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
3340    }
3341
3342    #[test]
3343    fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
3344        let mut f = Fixture::new();
3345        assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
3346        assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
3347        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3348        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3349        assert_eq!(
3350            f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
3351            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3352        );
3353        // And back, which proves the body survived the trip rather than being
3354        // rebuilt from a copy that happened to look the same.
3355        assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
3356        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3357    }
3358
3359    #[test]
3360    fn move_answers_zero_when_either_end_says_no() {
3361        let mut f = Fixture::new();
3362        assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
3363        assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
3364        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3365        assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
3366        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3367        // The destination is taken, so nothing moves and the source is still
3368        // there with what it had.
3369        assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
3370        assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
3371        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3372        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
3373    }
3374
3375    #[test]
3376    fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
3377        let mut f = Fixture::new();
3378        assert_eq!(
3379            f.run(&[b"MOVE", b"a", b"0"]),
3380            "-ERR source and destination objects are the same\r\n"
3381        );
3382        assert_eq!(
3383            f.run(&[b"MOVE", b"a", b"99"]),
3384            "-ERR DB index is out of range\r\n"
3385        );
3386        assert_eq!(
3387            f.run(&[b"MOVE", b"a", b"-1"]),
3388            "-ERR DB index is out of range\r\n"
3389        );
3390        assert_eq!(
3391            f.run(&[b"MOVE", b"a", b"x"]),
3392            "-ERR value is not an integer or out of range\r\n"
3393        );
3394    }
3395
3396    #[test]
3397    fn swapdb_swaps_what_two_connections_would_see() {
3398        let mut f = Fixture::new();
3399        assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
3400        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3401        assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
3402        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3403
3404        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3405        // Still on database zero, and database zero is a different database.
3406        assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
3407        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3408        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3409        // A database swapped with itself is fine and changes nothing.
3410        assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
3411        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3412    }
3413
3414    /// Every database on a server reads the server's clock and not one of its
3415    /// own. They used to be told the time one at a time and now they share the
3416    /// reading, so a server that built its databases from a second clock would
3417    /// answer a deadline worked out against a time nobody had set.
3418    #[test]
3419    fn a_wide_server_puts_its_databases_on_its_own_clock() {
3420        let mut f = Fixture::striped(8);
3421        f.server.set_clock_ms(1_700_000_000_000);
3422        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
3423        assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
3424        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3425        f.server.set_clock_ms(1_700_000_050_000);
3426        assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
3427    }
3428
3429    /// The swap is stripe by stripe, so a database cut into more than one
3430    /// stripe is the case that would catch it exchanging some of the keys and
3431    /// leaving the rest. Sixteen keys over four stripes is enough that every
3432    /// stripe has something in it whatever the hashes come out as.
3433    #[test]
3434    fn swapdb_swaps_every_stripe_of_a_wide_database() {
3435        let mut f = Fixture::striped(4);
3436        for i in 0..16u32 {
3437            let key = format!("k{i}");
3438            assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
3439        }
3440        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3441        assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
3442        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3443
3444        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3445        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3446        assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
3447        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3448        assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
3449        for i in 0..16u32 {
3450            let key = format!("k{i}");
3451            assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
3452        }
3453    }
3454
3455    #[test]
3456    fn swapdb_says_which_index_it_could_not_read() {
3457        let mut f = Fixture::new();
3458        assert_eq!(
3459            f.run(&[b"SWAPDB", b"x", b"1"]),
3460            "-ERR invalid first DB index\r\n"
3461        );
3462        assert_eq!(
3463            f.run(&[b"SWAPDB", b"0", b"y"]),
3464            "-ERR invalid second DB index\r\n"
3465        );
3466        // A number too big to be an index on a server that keeps one in an int
3467        // is the same complaint, and a plausible one that is not ours is the
3468        // range complaint instead. The split is Redis's.
3469        assert_eq!(
3470            f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
3471            "-ERR invalid first DB index\r\n"
3472        );
3473        assert_eq!(
3474            f.run(&[b"SWAPDB", b"0", b"99"]),
3475            "-ERR DB index is out of range\r\n"
3476        );
3477        assert_eq!(
3478            f.run(&[b"SWAPDB", b"-1", b"0"]),
3479            "-ERR DB index is out of range\r\n"
3480        );
3481    }
3482
3483    #[test]
3484    fn wait_answers_zero_replicas_without_waiting() {
3485        let mut f = Fixture::new();
3486        assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
3487        assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
3488        // A replica that is never going to arrive, and a timeout that would be
3489        // a real wait on a server that had one.
3490        assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
3491        // Negative replicas is not an error, because zero is already more than
3492        // it asked for.
3493        assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
3494        assert_eq!(
3495            f.run(&[b"WAIT", b"x", b"0"]),
3496            "-ERR value is not an integer or out of range\r\n"
3497        );
3498        assert_eq!(
3499            f.run(&[b"WAIT", b"0", b"-1"]),
3500            "-ERR timeout is negative\r\n"
3501        );
3502        assert_eq!(
3503            f.run(&[b"WAIT", b"0", b"1.5"]),
3504            "-ERR timeout is not an integer or out of range\r\n"
3505        );
3506    }
3507
3508    #[test]
3509    fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
3510        let mut f = Fixture::new();
3511        assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
3512        assert_eq!(
3513            f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
3514            "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
3515        );
3516        assert_eq!(
3517            f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
3518            "-ERR value is out of range, value must between 0 and 1\r\n"
3519        );
3520        assert_eq!(
3521            f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
3522            "-ERR value is out of range, must be positive\r\n"
3523        );
3524        // The arguments are all read before the server looks at itself, so a
3525        // bad timeout beats the append only complaint even with numlocal set.
3526        assert_eq!(
3527            f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
3528            "-ERR timeout is negative\r\n"
3529        );
3530    }
3531
3532    /// The bytes inside a bulk reply, with the header and the trailing break
3533    /// taken off. Every `DUMP` test needs this and none of them care how the
3534    /// length was written.
3535    fn payload(reply: &[u8]) -> Vec<u8> {
3536        let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
3537        reply[head + 2..reply.len() - 2].to_vec()
3538    }
3539
3540    #[test]
3541    fn a_value_survives_a_dump_and_a_restore() {
3542        let mut f = Fixture::new();
3543        f.run(&[b"SET", b"s", b"hello"]);
3544        f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
3545        f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
3546        f.run(&[b"SADD", b"u", b"x", b"y"]);
3547        f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
3548        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
3549
3550        for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
3551            let mut copy = key.to_vec();
3552            copy.push(b'2');
3553            let bytes = payload(&f.raw(&[b"DUMP", key]));
3554            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
3555            assert_eq!(f.run(&[b"TYPE", &copy]), f.run(&[b"TYPE", key]));
3556        }
3557
3558        assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
3559        assert_eq!(
3560            f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
3561            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
3562        );
3563        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
3564        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
3565        assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
3566        assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
3567        // The encoding survives too, since the payload names the plainest legal
3568        // type and the loader puts the value back on the rung it belongs on.
3569        assert_eq!(
3570            f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
3571            f.run(&[b"OBJECT", b"ENCODING", b"t"])
3572        );
3573    }
3574
3575    #[test]
3576    fn a_dumped_hash_keeps_its_field_deadlines() {
3577        let mut f = Fixture::new();
3578        f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
3579        assert_eq!(
3580            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
3581            "*1\r\n:1\r\n"
3582        );
3583        let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
3584        assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
3585        assert_eq!(
3586            f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
3587            "*2\r\n:-1\r\n:100\r\n"
3588        );
3589    }
3590
3591    #[test]
3592    fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
3593        let mut f = Fixture::new();
3594        f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
3595        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3596        assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
3597        assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
3598        assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
3599        assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
3600        // An absolute deadline that has already gone is not an error. The key is
3601        // not created and the reply is the same OK a live one gets.
3602        assert_eq!(
3603            f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
3604            "+OK\r\n"
3605        );
3606        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3607    }
3608
3609    #[test]
3610    fn dump_answers_nothing_for_a_key_that_is_not_there() {
3611        let mut f = Fixture::new();
3612        assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
3613        f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
3614        f.advance(50);
3615        assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
3616    }
3617
3618    #[test]
3619    fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
3620        let mut f = Fixture::new();
3621        f.run(&[b"SET", b"a", b"first"]);
3622        f.run(&[b"SET", b"b", b"second"]);
3623        let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
3624        assert_eq!(
3625            f.run(&[b"RESTORE", b"a", b"0", &bytes]),
3626            "-BUSYKEY Target key name already exists.\r\n"
3627        );
3628        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
3629        assert_eq!(
3630            f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
3631            "+OK\r\n"
3632        );
3633        assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
3634    }
3635
3636    /// The busy key comes before the payload, which is not the order the
3637    /// arguments read in. Whether a key is taken should not depend on whether
3638    /// the bytes behind it happened to be good.
3639    #[test]
3640    fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
3641        let mut f = Fixture::new();
3642        f.run(&[b"SET", b"a", b"v"]);
3643        assert_eq!(
3644            f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
3645            "-BUSYKEY Target key name already exists.\r\n"
3646        );
3647        // And the options come before even that, so a bad FREQ beats the busy
3648        // key the same way a bad DB beats a missing source in COPY.
3649        assert_eq!(
3650            f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
3651            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3652        );
3653    }
3654
3655    #[test]
3656    fn restore_can_tell_a_bad_footer_from_bad_bytes() {
3657        let mut f = Fixture::new();
3658        f.run(&[b"SET", b"a", b"hello"]);
3659        let good = payload(&f.raw(&[b"DUMP", b"a"]));
3660
3661        let mut flipped = good.clone();
3662        flipped[2] ^= 0x40;
3663        assert_eq!(
3664            f.run(&[b"RESTORE", b"b", b"0", &flipped]),
3665            "-ERR DUMP payload version or checksum are wrong\r\n"
3666        );
3667        assert_eq!(
3668            f.run(&[b"RESTORE", b"b", b"0", b"short"]),
3669            "-ERR DUMP payload version or checksum are wrong\r\n"
3670        );
3671        // A footer that is right over a body that is not. The type byte says
3672        // string and there is nothing behind it, so the checksum agrees and the
3673        // value does not exist.
3674        let mut truncated = good[..1].to_vec();
3675        truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
3676        let crc = yo_common::crc::crc64(0, &truncated);
3677        truncated.extend_from_slice(&crc.to_le_bytes());
3678        assert_eq!(
3679            f.run(&[b"RESTORE", b"b", b"0", &truncated]),
3680            "-ERR Bad data format\r\n"
3681        );
3682        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
3683    }
3684
3685    #[test]
3686    fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
3687        let mut f = Fixture::new();
3688        f.run(&[b"SET", b"a", b"v"]);
3689        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3690        assert_eq!(
3691            f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
3692            "-ERR Invalid TTL value, must be >= 0\r\n"
3693        );
3694        assert_eq!(
3695            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
3696            "-ERR Invalid IDLETIME value, must be >= 0\r\n"
3697        );
3698        assert_eq!(
3699            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
3700            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3701        );
3702        // Both are accepted and both are then dropped, which is D-26.
3703        assert_eq!(
3704            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
3705            "+OK\r\n"
3706        );
3707        assert_eq!(
3708            f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
3709            "+OK\r\n"
3710        );
3711    }
3712
3713    /// Neither word is refused for being the wrong one. Each is only accepted
3714    /// while the other is unset, so the second of the two falls through to the
3715    /// plain syntax error rather than getting a message of its own.
3716    #[test]
3717    fn restore_takes_idletime_or_freq_and_not_both() {
3718        let mut f = Fixture::new();
3719        f.run(&[b"SET", b"a", b"v"]);
3720        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3721        assert_eq!(
3722            f.run(&[
3723                b"RESTORE",
3724                b"b",
3725                b"0",
3726                &bytes,
3727                b"IDLETIME",
3728                b"1",
3729                b"FREQ",
3730                b"2"
3731            ]),
3732            "-ERR syntax error\r\n"
3733        );
3734        assert_eq!(
3735            f.run(&[
3736                b"RESTORE",
3737                b"b",
3738                b"0",
3739                &bytes,
3740                b"FREQ",
3741                b"2",
3742                b"IDLETIME",
3743                b"1"
3744            ]),
3745            "-ERR syntax error\r\n"
3746        );
3747        assert_eq!(
3748            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
3749            "-ERR syntax error\r\n"
3750        );
3751        assert_eq!(
3752            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
3753            "-ERR syntax error\r\n"
3754        );
3755    }
3756
3757    #[test]
3758    fn copy_checks_its_options_before_it_looks_for_anything() {
3759        let mut f = Fixture::new();
3760        // No key exists at all, and every one of these is still the option
3761        // complaint rather than a zero, which is the order a real server uses.
3762        assert_eq!(
3763            f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
3764            "-ERR DB index is out of range\r\n"
3765        );
3766        assert_eq!(
3767            f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
3768            "-ERR DB index is out of range\r\n"
3769        );
3770        assert_eq!(
3771            f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
3772            "-ERR value is not an integer or out of range\r\n"
3773        );
3774        assert_eq!(
3775            f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
3776            "-ERR syntax error\r\n"
3777        );
3778        assert_eq!(
3779            f.run(&[b"COPY", b"a", b"a"]),
3780            "-ERR source and destination objects are the same\r\n"
3781        );
3782        // Repeated, reordered and lowercased, and the last DB wins.
3783        assert_eq!(
3784            f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
3785            ":0\r\n"
3786        );
3787    }
3788
3789    #[test]
3790    fn time_is_two_bulk_strings_and_moves() {
3791        let mut f = Fixture::new();
3792        let first = f.run(&[b"TIME"]);
3793        assert!(first.starts_with("*2\r\n$"), "got {first}");
3794        let parts: Vec<&str> = first.split("\r\n").collect();
3795        let secs: i64 = parts[2].parse().expect("seconds as decimal text");
3796        let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
3797        assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
3798        assert!((0..1_000_000).contains(&micros), "got {micros}");
3799        // The coarse clock the keyspace uses is a cached millisecond that a
3800        // background tick refreshes, so a TIME built on it would answer the
3801        // same microsecond twice in a row here.
3802        assert_ne!(first, f.run(&[b"TIME"]));
3803    }
3804
3805    #[test]
3806    fn a_keyspace_scan_walks_every_key_once() {
3807        // The count below is thirty two, so ninety six keys is three pages of
3808        // cursor and says the same thing as five hundred at a fifth of the
3809        // interpreted work.
3810        let n = if cfg!(miri) { 96 } else { 500 };
3811        let mut f = Fixture::new();
3812        for i in 0..n {
3813            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
3814        }
3815
3816        let mut seen: Vec<String> = Vec::new();
3817        let mut cursor = "0".to_owned();
3818        let mut calls = 0;
3819        loop {
3820            let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
3821            seen.extend(keys);
3822            cursor = next;
3823            calls += 1;
3824            assert!(calls < 10_000, "the cursor is not advancing");
3825            if cursor == "0" {
3826                break;
3827            }
3828        }
3829
3830        seen.sort();
3831        seen.dedup();
3832        assert_eq!(seen.len(), n, "every key once and only once");
3833        // And more than one call to get them, or the COUNT is being ignored and
3834        // the loop above proved nothing about resuming.
3835        assert!(calls > 1, "{n} keys came back in one batch");
3836    }
3837
3838    #[test]
3839    fn a_scan_narrows_by_pattern_and_by_type() {
3840        let mut f = Fixture::new();
3841        f.run(&[b"SET", b"str", b"v"]);
3842        f.run(&[b"SADD", b"members", b"a"]);
3843        f.run(&[b"HSET", b"fields", b"f", b"v"]);
3844
3845        let all = |f: &mut Fixture, args: &[&[u8]]| {
3846            let mut out: Vec<String> = Vec::new();
3847            let mut cursor = "0".to_owned();
3848            loop {
3849                let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
3850                line.extend_from_slice(args);
3851                let (next, keys) = scan_reply(&f.run(&line));
3852                out.extend(keys);
3853                cursor = next;
3854                if cursor == "0" {
3855                    break;
3856                }
3857            }
3858            out.sort();
3859            out
3860        };
3861
3862        assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
3863        assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
3864        assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
3865        // Case insensitive, the same as Redis's own comparison.
3866        assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
3867        // A type nothing can hold is not an error, it just matches nothing.
3868        assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
3869        assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
3870        // Both filters at once, and they are an and rather than an or.
3871        assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
3872    }
3873
3874    #[test]
3875    fn a_scan_says_what_is_wrong_with_it() {
3876        let mut f = Fixture::new();
3877        assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3878        assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3879        assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3880        assert_eq!(
3881            f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3882            "-ERR syntax error\r\n"
3883        );
3884        assert_eq!(
3885            f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3886            "-ERR value is not an integer or out of range\r\n"
3887        );
3888        assert_eq!(
3889            f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3890            "-ERR syntax error\r\n"
3891        );
3892        // A cursor the client made up is a cursor. It resumes somewhere
3893        // arbitrary and answers whatever is there, which is what Redis does and
3894        // is the only behaviour that does not need the server to remember every
3895        // cursor it has handed out.
3896        assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3897    }
3898
3899    #[test]
3900    fn keys_and_randomkey_look_at_the_whole_database() {
3901        let mut f = Fixture::new();
3902        assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3903        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3904
3905        for name in ["one", "two", "three"] {
3906            f.run(&[b"SET", name.as_bytes(), b"v"]);
3907        }
3908        assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3909        assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3910        assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3911
3912        for _ in 0..50 {
3913            let got = f.run(&[b"RANDOMKEY"]);
3914            assert!(
3915                ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3916                "got {got}"
3917            );
3918        }
3919    }
3920
3921    #[test]
3922    fn a_walk_does_not_answer_keys_that_have_expired() {
3923        let mut f = Fixture::new();
3924        f.run(&[b"SET", b"alive", b"v"]);
3925        f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3926        f.server.advance_clock_ms(2);
3927        assert_eq!(
3928            f.run(&[b"DBSIZE"]),
3929            ":2\r\n",
3930            "nothing has collected it yet"
3931        );
3932
3933        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3934        let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3935        assert_eq!(keys, ["alive"]);
3936        for _ in 0..20 {
3937            assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
3938        }
3939        // The walk collected it on the way past, which is what makes DBSIZE
3940        // here answer what Redis answers once its own cycle has been round.
3941        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3942    }
3943
3944    #[test]
3945    fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
3946        let mut f = Fixture::new();
3947        f.run(&[b"SET", b"k", b"v"]);
3948        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
3949        assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
3950
3951        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
3952        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3953        let ms = int(&f.run(&[b"PTTL", b"k"]));
3954        assert!((99_000..=100_000).contains(&ms), "got {ms}");
3955
3956        // The absolute pair, derived from the same one number the store kept.
3957        let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
3958        let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3959        assert_eq!(at, (at_ms + 500) / 1000);
3960        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
3961
3962        assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
3963        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
3964        assert_eq!(
3965            f.run(&[b"PERSIST", b"k"]),
3966            ":0\r\n",
3967            "nothing to take off the second time"
3968        );
3969        assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
3970        assert_eq!(
3971            f.run(&[b"GET", b"k"]),
3972            "$1\r\nv\r\n",
3973            "and the value went through all of that untouched"
3974        );
3975    }
3976
3977    #[test]
3978    fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
3979        let mut f = Fixture::new();
3980        f.run(&[b"SET", b"str", b"v"]);
3981        f.run(&[b"SADD", b"set", b"a", b"b"]);
3982        f.run(&[b"HSET", b"hash", b"f", b"v"]);
3983
3984        for key in [b"str".as_slice(), b"set", b"hash"] {
3985            assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
3986            assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
3987        }
3988        // The body is not touched by any of that, which is the whole reason the
3989        // deadline lives in the record and the body lives somewhere else.
3990        assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
3991        assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
3992        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
3993    }
3994
3995    #[test]
3996    fn a_deadline_that_has_already_gone_deletes_the_key_now() {
3997        let mut f = Fixture::new();
3998        for key in [b"a".as_slice(), b"b", b"c", b"d"] {
3999            f.run(&[b"SET", key, b"v"]);
4000        }
4001        // Four ways of naming a moment that has passed, and all four are a
4002        // delete answering 1 rather than an error. Zero is a moment, minus one
4003        // is a moment, and the hash field commands refuse the negative one.
4004        assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
4005        assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
4006        assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
4007        assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
4008        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4009        assert_eq!(
4010            f.run(&[b"EXPIRE", b"a", b"100"]),
4011            ":0\r\n",
4012            "and the key really went, so there is nothing to put a deadline on"
4013        );
4014    }
4015
4016    #[test]
4017    fn the_four_conditions_decide_whether_the_deadline_moves() {
4018        let mut f = Fixture::new();
4019        f.run(&[b"SET", b"k", b"v"]);
4020
4021        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
4022        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
4023        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
4024        assert_eq!(
4025            f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
4026            ":1\r\n",
4027            "no deadline reads as infinitely far away, so LT passes where GT fails"
4028        );
4029
4030        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
4031        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
4032        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4033        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
4034        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
4035        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4036
4037        // The condition is answered before the past check, so this is a 0 and
4038        // the key survives. The other order would delete it.
4039        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
4040        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
4041        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
4042        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
4043    }
4044
4045    #[test]
4046    fn the_conditions_are_a_set_and_not_a_keyword() {
4047        let mut f = Fixture::new();
4048        f.run(&[b"SET", b"k", b"v"]);
4049
4050        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
4051        assert_eq!(
4052            f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
4053            ":0\r\n",
4054            "the same keyword twice means it once, and NX now has a deadline to fail on"
4055        );
4056
4057        // XX with LT is the one pair that is not either of them on its own: LT
4058        // alone would accept a key with no deadline and this does not.
4059        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
4060        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4061        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
4062        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
4063        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4064        f.run(&[b"PERSIST", b"k"]);
4065        assert_eq!(
4066            f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
4067            ":0\r\n",
4068            "where LT on its own would have taken it"
4069        );
4070        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
4071    }
4072
4073    #[test]
4074    fn a_key_is_gone_once_its_moment_passes() {
4075        let mut f = Fixture::new();
4076        f.run(&[b"SET", b"k", b"v"]);
4077        f.run(&[b"EXPIRE", b"k", b"100"]);
4078
4079        let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4080        f.server.set_clock_ms(at as u64 + 1);
4081        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4082        assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
4083        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
4084        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4085    }
4086
4087    #[test]
4088    fn the_expiry_commands_refuse_what_a_real_server_refuses() {
4089        let mut f = Fixture::new();
4090        f.run(&[b"SET", b"k", b"v"]);
4091        for (bad, want) in [
4092            (
4093                &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
4094                "-ERR value is not an integer or out of range\r\n",
4095            ),
4096            (
4097                &[b"EXPIRE", b"k", b"100", b"MAYBE"],
4098                "-ERR Unsupported option MAYBE\r\n",
4099            ),
4100            (
4101                &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
4102                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4103            ),
4104            (
4105                &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
4106                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4107            ),
4108            (
4109                &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
4110                "-ERR GT and LT options at the same time are not compatible\r\n",
4111            ),
4112            // Seconds that overflow when multiplied into milliseconds. Every
4113            // message names the command it came from.
4114            (
4115                &[b"EXPIRE", b"k", b"9223372036854775807"],
4116                "-ERR invalid expire time in 'expire' command\r\n",
4117            ),
4118            (
4119                &[b"EXPIREAT", b"k", b"9223372036854775807"],
4120                "-ERR invalid expire time in 'expireat' command\r\n",
4121            ),
4122            (
4123                &[b"PEXPIRE", b"k", b"9223372036854775807"],
4124                "-ERR invalid expire time in 'pexpire' command\r\n",
4125            ),
4126        ] {
4127            assert_eq!(f.run(bad), want, "for {bad:?}");
4128        }
4129        assert_eq!(
4130            f.run(&[b"TTL", b"k"]),
4131            ":-1\r\n",
4132            "and none of those put a deadline on anything"
4133        );
4134
4135        // The one of the four that has no arithmetic to overflow. Redis takes
4136        // it and holds the number as given, and a record here holds forty six
4137        // bits, so it lands in the year 4199 instead. D-17.
4138        assert_eq!(
4139            f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
4140            ":1\r\n"
4141        );
4142        assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
4143    }
4144
4145    #[test]
4146    fn flushing_empties_this_database_or_every_one_of_them() {
4147        let mut f = Fixture::new();
4148        f.run(&[b"SELECT", b"0"]);
4149        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4150        f.run(&[b"SELECT", b"1"]);
4151        f.run(&[b"SET", b"c", b"3"]);
4152        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4153        // ASYNC and SYNC are both taken and neither changes anything, since the
4154        // keyspace is empty before the OK goes out either way.
4155        assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
4156        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4157        // Only database one was emptied.
4158        f.run(&[b"SELECT", b"0"]);
4159        assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
4160        assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
4161        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4162        f.run(&[b"SELECT", b"1"]);
4163        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4164        // Anything else after the name is a syntax error, and so is a third
4165        // argument even when the second one is a word we take.
4166        assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
4167        assert_eq!(
4168            f.run(&[b"FLUSHDB", b"sync", b"sync"]),
4169            "-ERR syntax error\r\n"
4170        );
4171    }
4172
4173    #[test]
4174    fn the_script_cache_and_the_library_set_answer_for_being_empty() {
4175        let mut f = Fixture::new();
4176        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4177        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
4178        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
4179        // Nothing is cached, so nothing is there, one answer per hash asked
4180        // about.
4181        assert_eq!(
4182            f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
4183            "*2\r\n:0\r\n:0\r\n"
4184        );
4185        assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
4186        assert_eq!(
4187            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
4188            "*0\r\n"
4189        );
4190        assert_eq!(
4191            f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
4192            "-ERR Library not found\r\n"
4193        );
4194
4195        // Redis's two messages here are its own, one per container, and one of
4196        // them reads like a typo.
4197        assert_eq!(
4198            f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
4199            "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
4200        );
4201        assert_eq!(
4202            f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
4203            "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
4204        );
4205        // A second argument after the mode is the generic one instead, because
4206        // the count is checked before the word is looked at. The subcommand in
4207        // the sentence is the client's own spelling and not the canonical one,
4208        // which is the same thing `unknown subcommand` does.
4209        assert_eq!(
4210            f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
4211            "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
4212        );
4213        assert_eq!(
4214            f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
4215            "-ERR Unknown argument bogus\r\n"
4216        );
4217        assert_eq!(
4218            f.run(&[b"SCRIPT", b"EXISTS"]),
4219            "-ERR wrong number of arguments for 'script|exists' command\r\n"
4220        );
4221
4222        assert_eq!(
4223            f.run(&[b"FUNCTION", b"NOPE"]),
4224            "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
4225        );
4226    }
4227
4228    #[test]
4229    fn the_script_cache_holds_what_was_loaded_into_it() {
4230        let mut f = Fixture::new();
4231        // The hash is the sha1 of the body and nothing else, so it is the same
4232        // number a real server answers and a client can compute it itself.
4233        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4234        assert_eq!(
4235            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4236            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4237        );
4238        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4239        assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
4240        // Loading is idempotent and a body that will not parse is refused
4241        // where it was written rather than where it is called.
4242        assert_eq!(
4243            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4244            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4245        );
4246        assert!(
4247            f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
4248                .starts_with("-ERR Error compiling script"),
4249        );
4250
4251        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4252        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
4253        assert_eq!(
4254            f.run(&[b"EVALSHA", sha, b"0"]),
4255            "-NOSCRIPT No matching script. Please use EVAL.\r\n"
4256        );
4257
4258        // Running the body puts it in the cache too, which is what makes the
4259        // load then call then fall back to load pattern a client uses work.
4260        assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
4261        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4262
4263        // Nothing here can run long enough to be killed, which is D-101, so
4264        // the answer is the one a real server gives when nothing is stuck.
4265        assert_eq!(
4266            f.run(&[b"SCRIPT", b"KILL"]),
4267            "-NOTBUSY No scripts in execution right now.\r\n"
4268        );
4269        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
4270        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
4271        assert_eq!(
4272            f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
4273            "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
4274        );
4275    }
4276
4277    #[test]
4278    fn eval_counts_its_keys_before_it_compiles_anything() {
4279        let mut f = Fixture::new();
4280        assert_eq!(
4281            f.run(&[b"EVAL", b"return 1"]),
4282            "-ERR wrong number of arguments for 'eval' command\r\n"
4283        );
4284        assert_eq!(
4285            f.run(&[b"EVAL", b"return 1", b"abc"]),
4286            "-ERR value is not an integer or out of range\r\n"
4287        );
4288        assert_eq!(
4289            f.run(&[b"EVAL", b"return 1", b"-1"]),
4290            "-ERR Number of keys can't be negative\r\n"
4291        );
4292        assert_eq!(
4293            f.run(&[b"EVAL", b"return 1", b"1"]),
4294            "-ERR Number of keys can't be greater than number of args\r\n"
4295        );
4296        // The count splits the tail, and everything past the keys is ARGV.
4297        assert_eq!(
4298            f.run(&[
4299                b"EVAL",
4300                b"return {KEYS[1],KEYS[2],ARGV[1]}",
4301                b"2",
4302                b"a",
4303                b"b",
4304                b"c"
4305            ]),
4306            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4307        );
4308        assert_eq!(
4309            f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
4310            ":0\r\n"
4311        );
4312        assert_eq!(
4313            f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
4314            ":2\r\n"
4315        );
4316    }
4317
4318    #[test]
4319    fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
4320        let mut f = Fixture::new();
4321        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4322
4323        // A number is truncated toward zero rather than rounded, and the two
4324        // ends of the range saturate the way the cast does.
4325        assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
4326        assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
4327        assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
4328        assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
4329        assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
4330        assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
4331        assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
4332
4333        assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
4334        assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
4335        // Everything that is not there is the same nothing.
4336        assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
4337        assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
4338        assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
4339        assert_eq!(eval(&mut f, b""), "$-1\r\n");
4340
4341        // A table is an array that stops at the first hole, which is what makes
4342        // a script build a reply by appending rather than by indexing.
4343        assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
4344        assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
4345        assert_eq!(
4346            eval(&mut f, b"return {1,'a',{2}}"),
4347            "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
4348        );
4349
4350        // The named fields, in the order a real server looks for them.
4351        assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
4352        assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
4353        assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
4354        assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
4355        // A line break inside one of them becomes a space, because the reply is
4356        // a single line and a client that saw the break would lose the frame.
4357        assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a  b\r\n");
4358        // A field of the wrong type is not that kind of reply at all, and falls
4359        // through to the array walk, which finds nothing.
4360        assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
4361        assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
4362    }
4363
4364    #[test]
4365    fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
4366        let mut f = Fixture::new();
4367        // Under RESP2 the four typed tables have to come back as something a
4368        // client that only knows RESP2 can read.
4369        assert_eq!(
4370            f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
4371            "$3\r\n3.5\r\n"
4372        );
4373        assert_eq!(
4374            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4375            "$3\r\n123\r\n"
4376        );
4377        assert_eq!(
4378            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4379            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4380        );
4381        assert_eq!(
4382            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4383            "*1\r\n$1\r\na\r\n"
4384        );
4385        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
4386
4387        f.out = Out::new(Proto::Resp3);
4388        assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
4389        assert_eq!(
4390            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4391            "(123\r\n"
4392        );
4393        assert_eq!(
4394            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4395            "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
4396        );
4397        assert_eq!(
4398            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4399            "~1\r\n$1\r\na\r\n"
4400        );
4401        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
4402    }
4403
4404    #[test]
4405    fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
4406        let mut f = Fixture::new();
4407        f.run(&[b"SET", b"s", b"hello"]);
4408        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4409        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4410
4411        assert_eq!(
4412            eval(&mut f, b"return type(redis.call('get','s'))"),
4413            "$6\r\nstring\r\n"
4414        );
4415        assert_eq!(
4416            eval(&mut f, b"return type(redis.call('llen','l'))"),
4417            "$6\r\nnumber\r\n"
4418        );
4419        assert_eq!(
4420            eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
4421            "$5\r\ntable\r\n"
4422        );
4423        // A status is a table with one field, which is what lets a script pass
4424        // one straight back out again.
4425        assert_eq!(
4426            eval(&mut f, b"return redis.call('set','s','v')['ok']"),
4427            "$2\r\nOK\r\n"
4428        );
4429        // A missing key is false under RESP2 and nil once the script asks for
4430        // RESP3, which is the one conversion the script gets to choose.
4431        assert_eq!(
4432            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4433            "$5\r\nfalse\r\n"
4434        );
4435        assert_eq!(
4436            eval(
4437                &mut f,
4438                b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
4439            ),
4440            "$3\r\nnil\r\n"
4441        );
4442        // The choice does not outlive the script that made it.
4443        assert_eq!(
4444            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4445            "$5\r\nfalse\r\n"
4446        );
4447    }
4448
4449    #[test]
4450    fn an_error_from_a_script_names_the_line_it_came_from() {
4451        let mut f = Fixture::new();
4452        // The position is the script's own, not the prelude's, and the suffix
4453        // names the script so a client can find it in the cache.
4454        assert_eq!(
4455            f.run(&[b"EVAL", b"error('boom')", b"0"]),
4456            "-ERR user_script:1: boom script: \
4457             82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
4458        );
4459        // Level zero says the message already knows where it came from.
4460        assert_eq!(
4461            f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
4462            "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
4463        );
4464        // A table with an err field keeps its own text and gets the suffix.
4465        assert!(
4466            f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
4467                .starts_with("-structured script: "),
4468        );
4469        // A script that will not parse is refused before it runs, so there is
4470        // no script and nothing to name.
4471        assert_eq!(
4472            f.run(&[b"EVAL", b"return this is not lua", b"0"]),
4473            "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
4474        );
4475
4476        // A table that came out of pcall is a string by the time the script
4477        // sees it, which is a real server's own wrapping and not Lua's.
4478        assert_eq!(
4479            f.run(&[
4480                b"EVAL",
4481                b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
4482                b"0"
4483            ]),
4484            "$8\r\nstring:z\r\n"
4485        );
4486        assert_eq!(
4487            f.run(&[
4488                b"EVAL",
4489                b"local a, b = pcall(function() error({a=1}) end) return type(b)",
4490                b"0"
4491            ]),
4492            "$5\r\ntable\r\n"
4493        );
4494    }
4495
4496    #[test]
4497    fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
4498        let mut f = Fixture::new();
4499        let sentence = |f: &mut Fixture, body: &[u8]| {
4500            let reply = f.run(&[b"EVAL", body, b"0"]);
4501            reply.split(" script: ").next().unwrap().to_owned()
4502        };
4503
4504        assert_eq!(
4505            sentence(&mut f, b"return redis.call()"),
4506            "-ERR Please specify at least one argument for this redis lib call"
4507        );
4508        assert_eq!(
4509            sentence(&mut f, b"return redis.call('get', {})"),
4510            "-ERR Lua redis lib command arguments must be strings or integers"
4511        );
4512        assert_eq!(
4513            sentence(&mut f, b"return redis.call('nosuchcmd')"),
4514            "-ERR Unknown Redis command called from script"
4515        );
4516        assert_eq!(
4517            sentence(&mut f, b"return redis.call('get')"),
4518            "-ERR Wrong number of args calling Redis command from script"
4519        );
4520        // The commands that make no sense inside a script are refused by name
4521        // rather than by not being implemented, so the sentence is the same one
4522        // a real server writes for each of them.
4523        for name in [
4524            &b"return redis.call('multi')"[..],
4525            b"return redis.call('exec')",
4526            b"return redis.call('watch','k')",
4527            b"return redis.call('subscribe','c')",
4528            b"return redis.call('debug','jmap')",
4529            b"return redis.call('eval','return 1',0)",
4530            b"return redis.call('config','get','maxmemory')",
4531        ] {
4532            assert_eq!(
4533                sentence(&mut f, name),
4534                "-ERR This Redis command is not allowed from script",
4535                "for {}",
4536                String::from_utf8_lossy(name)
4537            );
4538        }
4539        // HELP is the one subcommand of a refused container that is allowed,
4540        // because it reads nothing and changes nothing.
4541        assert!(
4542            f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
4543                .starts_with('*'),
4544        );
4545
4546        // pcall answers the same sentence as a value instead of raising it, and
4547        // the value has an err field a script can read.
4548        assert_eq!(
4549            f.run(&[
4550                b"EVAL",
4551                b"local x = redis.pcall('nosuchcmd') return x.err",
4552                b"0"
4553            ]),
4554            "$44\r\nERR Unknown Redis command called from script\r\n"
4555        );
4556        // Returning it unread raises it, because the table has an err field.
4557        assert_eq!(
4558            f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
4559            "-ERR Unknown Redis command called from script\r\n"
4560        );
4561    }
4562
4563    #[test]
4564    fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
4565        let mut f = Fixture::new();
4566        f.run(&[b"SET", b"k", b"v"]);
4567        assert_eq!(
4568            f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
4569            "$1\r\nv\r\n"
4570        );
4571        assert!(
4572            f.run(&[
4573                b"EVAL_RO",
4574                b"return redis.call('set', KEYS[1], 'x')",
4575                b"1",
4576                b"k"
4577            ])
4578            .starts_with("-ERR Write commands are not allowed from read-only scripts."),
4579        );
4580        // The write did not happen, and the same body under EVAL does.
4581        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
4582        assert_eq!(
4583            f.run(&[
4584                b"EVAL",
4585                b"return redis.call('set', KEYS[1], 'x')",
4586                b"1",
4587                b"k"
4588            ]),
4589            "+OK\r\n"
4590        );
4591        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
4592
4593        // EVALSHA_RO runs a cached body under the same rule.
4594        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4595        f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
4596        assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
4597    }
4598
4599    #[test]
4600    fn a_script_cannot_leave_anything_behind_for_the_next_one() {
4601        let mut f = Fixture::new();
4602        // A plain global write and a write through a name on the redis table
4603        // both raise, with the position the script wrote them at.
4604        for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
4605            let reply = f.run(&[b"EVAL", body, b"0"]);
4606            assert!(
4607                reply
4608                    .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
4609                "{body:?} gave {reply}",
4610            );
4611        }
4612        // Walking round the guard with rawset or setmetatable raises too, and
4613        // without the position, which is where a real server raises it from.
4614        for body in [
4615            &b"rawset(redis, 'call', 1)"[..],
4616            b"rawset(_G, 'zz', 1)",
4617            b"setmetatable(_G, {})",
4618            b"setmetatable(redis, {})",
4619        ] {
4620            let reply = f.run(&[b"EVAL", body, b"0"]);
4621            assert!(
4622                reply.starts_with("-ERR Attempt to modify a readonly table script: "),
4623                "{body:?} gave {reply}",
4624            );
4625        }
4626        // Reading a name that is not there is a mistake rather than a nil, so a
4627        // misspelled global stops the script instead of doing nothing quietly.
4628        assert!(
4629            f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
4630                .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
4631        );
4632        // Reading a name that is not on the redis table is a nil, which is how
4633        // a script tests for a helper that an older server does not have.
4634        assert_eq!(
4635            f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
4636            "$3\r\nnil\r\n"
4637        );
4638
4639        // The one write that lands, D-103, is taken back out before the next
4640        // script starts, so nothing a script does reaches the one after it.
4641        assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
4642        assert_eq!(
4643            f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
4644            "$8\r\nfunction\r\n"
4645        );
4646        assert_eq!(
4647            f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
4648            "$8\r\nfunction\r\n"
4649        );
4650    }
4651
4652    #[test]
4653    fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
4654        let mut f = Fixture::new();
4655        // The guard in front of the table is empty, so the three base library
4656        // readers that skip a metatable are pointed at the real table behind
4657        // it. A script counts what a real server counts.
4658        assert_eq!(
4659            f.run(&[
4660                b"EVAL",
4661                b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
4662                b"0",
4663            ]),
4664            ":23\r\n"
4665        );
4666        assert_eq!(
4667            f.run(&[
4668                b"EVAL",
4669                b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
4670                  table.sort(t) return table.concat(t, ' ')",
4671                b"0",
4672            ]),
4673            "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
4674             REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
4675             acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
4676             set_repl setresp sha1hex status_reply\r\n"
4677        );
4678        // The loop hands over the values as well as the names, so the twelve
4679        // helpers are callable from inside a traversal and not just findable.
4680        assert_eq!(
4681            f.run(&[
4682                b"EVAL",
4683                b"local n = 0 for k, v in pairs(redis) do \
4684                  if type(v) == 'function' then n = n + 1 end end return n",
4685                b"0",
4686            ]),
4687            ":12\r\n"
4688        );
4689        // The other two readers agree with it.
4690        assert_eq!(
4691            f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
4692            "$6\r\nstring\r\n"
4693        );
4694        assert_eq!(
4695            f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
4696            "$8\r\nfunction\r\n"
4697        );
4698        assert_eq!(
4699            f.run(&[
4700                b"EVAL",
4701                b"return tostring(rawget(redis, 'nosuchfield'))",
4702                b"0",
4703            ]),
4704            "$3\r\nnil\r\n"
4705        );
4706        // Reading round the guard is the only thing that was given back. A
4707        // write still lands on the guard and still raises.
4708        for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
4709            assert!(
4710                f.run(&[b"EVAL", body, b"0"])
4711                    .contains("Attempt to modify a readonly table script: "),
4712                "{body:?}",
4713            );
4714        }
4715        // A table nobody guards walks the way it always did, whether a script
4716        // made it or the standard library did.
4717        assert_eq!(
4718            f.run(&[
4719                b"EVAL",
4720                b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
4721                b"0",
4722            ]),
4723            ":2\r\n"
4724        );
4725        assert_eq!(
4726            f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
4727            "$3\r\nnil\r\n"
4728        );
4729        assert_eq!(
4730            f.run(&[
4731                b"EVAL",
4732                b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
4733                  return type(f)",
4734                b"0",
4735            ]),
4736            "$8\r\nfunction\r\n"
4737        );
4738    }
4739
4740    #[test]
4741    fn a_script_gets_the_bit_library_a_real_server_carries() {
4742        let mut f = Fixture::new();
4743        // Every answer is a signed word, which is why the ones past two to the
4744        // thirty one come back negative.
4745        for (body, want) in [
4746            ("bit.tobit(1)", ":1\r\n"),
4747            ("bit.tobit(2^32 + 1)", ":1\r\n"),
4748            ("bit.tobit(2^31)", ":-2147483648\r\n"),
4749            ("bit.tobit(0xffffffff)", ":-1\r\n"),
4750            // The rounding is to the nearest and not toward zero.
4751            ("bit.tobit(1.5)", ":2\r\n"),
4752            ("bit.tobit(2.5)", ":2\r\n"),
4753            ("bit.bnot(0)", ":-1\r\n"),
4754            ("bit.band(0xff, 0x0f)", ":15\r\n"),
4755            ("bit.band(1, 2, 3)", ":0\r\n"),
4756            ("bit.bor(1, 2, 4)", ":7\r\n"),
4757            ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
4758            // Only the low five bits of a count are read.
4759            ("bit.lshift(1, 31)", ":-2147483648\r\n"),
4760            ("bit.lshift(1, 32)", ":1\r\n"),
4761            ("bit.lshift(1, 33)", ":2\r\n"),
4762            ("bit.rshift(-1, 1)", ":2147483647\r\n"),
4763            ("bit.arshift(-1, 1)", ":-1\r\n"),
4764            ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
4765            ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
4766            ("bit.bswap(0x12345678)", ":2018915346\r\n"),
4767            // A string that reads as a number is a number, which is Lua's rule
4768            // and not a courtesy of this library.
4769            ("bit.tobit('0x10')", ":16\r\n"),
4770        ] {
4771            let script = format!("return {body}");
4772            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4773        }
4774        // The digits are the low ones, a negative count asks for upper case,
4775        // and a count outside eight is brought back to it.
4776        for (body, want) in [
4777            ("bit.tohex(1)", "00000001"),
4778            ("bit.tohex(-1)", "ffffffff"),
4779            ("bit.tohex(255, 2)", "ff"),
4780            ("bit.tohex(255, -8)", "000000FF"),
4781            ("bit.tohex(0x87654321, 4)", "4321"),
4782            ("bit.tohex(1, 0)", ""),
4783            ("bit.tohex(1, 9)", "00000001"),
4784        ] {
4785            let script = format!("return {body}");
4786            assert_eq!(
4787                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4788                format!("${}\r\n{want}\r\n", want.len()),
4789                "{body}",
4790            );
4791        }
4792        // A bad argument names the position, the function and what was passed,
4793        // and the line in front of it is the script's own.
4794        for (body, want) in [
4795            (
4796                "return bit.band()",
4797                "bad argument #1 to 'band' (number expected, got no value)",
4798            ),
4799            (
4800                "return bit.band('x')",
4801                "bad argument #1 to 'band' (number expected, got string)",
4802            ),
4803            (
4804                "return bit.tobit(true)",
4805                "bad argument #1 to 'tobit' (number expected, got boolean)",
4806            ),
4807            (
4808                "return bit.lshift(1)",
4809                "bad argument #2 to 'lshift' (number expected, got no value)",
4810            ),
4811        ] {
4812            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4813            assert!(
4814                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4815                "{body} gave {reply}",
4816            );
4817        }
4818        // The name in the message is the one the call site used, so a call that
4819        // went through `pcall` has no name to report.
4820        assert_eq!(
4821            f.run(&[
4822                b"EVAL",
4823                b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
4824                b"0",
4825            ]),
4826            "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
4827        );
4828        // The table is readable and not writable, the same as `redis`.
4829        assert_eq!(
4830            f.run(&[
4831                b"EVAL",
4832                b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
4833                  table.sort(t) return table.concat(t, ' ')",
4834                b"0",
4835            ]),
4836            "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
4837        );
4838        for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
4839            assert!(
4840                f.run(&[b"EVAL", body, b"0"])
4841                    .contains("Attempt to modify a readonly table script: "),
4842                "{body:?}",
4843            );
4844        }
4845    }
4846
4847    #[test]
4848    fn a_script_gets_the_cjson_library_a_real_server_carries() {
4849        let mut f = Fixture::new();
4850        // Encoding, including the three shapes nobody guesses right: an empty
4851        // table is an object, a number is fourteen significant digits, and a
4852        // hole in an array is a null rather than a shorter array.
4853        for (body, want) in [
4854            ("cjson.encode(nil)", "null"),
4855            ("cjson.encode(true)", "true"),
4856            ("cjson.encode(cjson.null)", "null"),
4857            ("cjson.encode(100)", "100"),
4858            ("cjson.encode(1/3)", "0.33333333333333"),
4859            ("cjson.encode(1e300)", "1e+300"),
4860            ("cjson.encode(2^53)", "9.007199254741e+15"),
4861            ("cjson.encode({})", "{}"),
4862            ("cjson.encode({1,2,3})", "[1,2,3]"),
4863            ("cjson.encode({a=1})", "{\"a\":1}"),
4864            ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
4865            ("cjson.encode({[0]=1})", "{\"0\":1}"),
4866            ("cjson.encode('a\\nb')", "\"a\\nb\""),
4867            // A tab and a backslash have short escapes, a vertical tab does not.
4868            ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
4869            ("cjson.encode('\\11')", "\"\\u000b\""),
4870            // Reading and writing again is the shortest way to say the decoder
4871            // built what the encoder expected.
4872            (
4873                "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
4874                "[1,[2,{\"a\":null}]]",
4875            ),
4876            // An empty array comes back as an object, because a table with
4877            // nothing in it has nothing to say about which it was.
4878            ("cjson.encode(cjson.decode('[]'))", "{}"),
4879        ] {
4880            let script = format!("return {body}");
4881            assert_eq!(
4882                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4883                format!("${}\r\n{want}\r\n", want.len()),
4884                "{body}",
4885            );
4886        }
4887        // Decoding, where the leniency about numbers is on by default and a
4888        // null is a value of its own rather than a missing key.
4889        for (body, want) in [
4890            ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
4891            ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
4892            ("cjson.decode('0x10')", ":16\r\n"),
4893            ("cjson.decode('+1')", ":1\r\n"),
4894            ("cjson.decode('01')", ":1\r\n"),
4895            ("cjson.decode(1) + 1", ":2\r\n"),
4896            // A long bracket, because Lua 5.1 would eat the backslash first.
4897            ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
4898            ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
4899            ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
4900        ] {
4901            let script = format!("return {body}");
4902            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4903        }
4904        // The settings, each of which answers with what it now holds.
4905        for (body, want) in [
4906            (
4907                "cjson.encode_number_precision(3) return cjson.encode(1/3)",
4908                "0.333",
4909            ),
4910            (
4911                "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
4912                "null",
4913            ),
4914            (
4915                "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
4916                "inf",
4917            ),
4918            (
4919                "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
4920                "{\"1\":1,\"100\":1}",
4921            ),
4922            (
4923                "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
4924                "[]",
4925            ),
4926            ("return tostring(cjson.encode_max_depth())", "1000"),
4927            ("return tostring(cjson.encode_keep_buffer(false))", "false"),
4928            ("return tostring(cjson.encode_sparse_array())", "false"),
4929            // A setting one script changed is not a setting the next one sees,
4930            // which is D-105.
4931            ("return tostring(cjson.encode_number_precision())", "14"),
4932        ] {
4933            assert_eq!(
4934                f.run(&[b"EVAL", body.as_bytes(), b"0"]),
4935                format!("${}\r\n{want}\r\n", want.len()),
4936                "{body}",
4937            );
4938        }
4939        // A failure names what stopped it and, when it was the text, where.
4940        for (body, want) in [
4941            (
4942                "return cjson.encode(1/0)",
4943                "Cannot serialise number: must not be NaN or Inf",
4944            ),
4945            (
4946                "return cjson.encode({[1]=1,[100]=1})",
4947                "Cannot serialise table: excessively sparse array",
4948            ),
4949            (
4950                "return cjson.encode({[true]=1})",
4951                "Cannot serialise boolean: table key must be a number or string",
4952            ),
4953            (
4954                "return cjson.encode(tostring)",
4955                "Cannot serialise function: type not supported",
4956            ),
4957            (
4958                "return cjson.encode()",
4959                "bad argument #1 to 'encode' (expected 1 argument)",
4960            ),
4961            (
4962                "return cjson.decode('[1,2')",
4963                "Expected comma or array end but found T_END at character 5",
4964            ),
4965            (
4966                "return cjson.decode('{\"a\" 1}')",
4967                "Expected colon but found T_NUMBER at character 6",
4968            ),
4969            (
4970                "return cjson.decode('tru')",
4971                "Expected value but found invalid token at character 1",
4972            ),
4973            (
4974                "return cjson.decode('[1] 2')",
4975                "Expected the end but found T_NUMBER at character 5",
4976            ),
4977            (
4978                "return cjson.encode_max_depth(0)",
4979                "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
4980            ),
4981            (
4982                "return cjson.encode_invalid_numbers('yes')",
4983                "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
4984            ),
4985            (
4986                "return cjson.encode_max_depth(1, 2)",
4987                "bad argument #2 to 'encode_max_depth' (found too many arguments)",
4988            ),
4989        ] {
4990            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4991            assert!(
4992                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4993                "{body} gave {reply}",
4994            );
4995        }
4996        // A module of its own, with settings of its own and no guard on it,
4997        // which is what a real server hands back.
4998        assert_eq!(
4999            f.run(&[
5000                b"EVAL",
5001                b"local n = cjson.new() n.encode_number_precision(3) \
5002                  return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
5003                b"0",
5004            ]),
5005            "$22\r\n0.33333333333333 0.333\r\n"
5006        );
5007        // The table is readable and not writable, the same as `redis`.
5008        let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
5009                     decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
5010                     encode_max_depth encode_number_precision encode_sparse_array new null";
5011        assert_eq!(
5012            f.run(&[
5013                b"EVAL",
5014                b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
5015                  table.sort(t) return table.concat(t, ' ')",
5016                b"0",
5017            ]),
5018            format!("${}\r\n{names}\r\n", names.len())
5019        );
5020        for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
5021            assert!(
5022                f.run(&[b"EVAL", body, b"0"])
5023                    .contains("Attempt to modify a readonly table script: "),
5024                "{body:?}",
5025            );
5026        }
5027    }
5028
5029    #[test]
5030    fn a_script_gets_the_struct_library_a_real_server_carries() {
5031        let mut f = Fixture::new();
5032        // Packing, where the sizes are the ones a sixty four bit build gives
5033        // and the order is the machine's own unless the format says otherwise.
5034        for (body, want) in [
5035            ("#struct.pack('i4', 1)", ":4\r\n"),
5036            ("#struct.pack('l', 1)", ":8\r\n"),
5037            ("#struct.pack('d', 1)", ":8\r\n"),
5038            ("#struct.pack('f', 1)", ":4\r\n"),
5039            ("#struct.pack('s', 'abc')", ":4\r\n"),
5040            ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
5041            ("#struct.pack('x')", ":1\r\n"),
5042            ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
5043            ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
5044            ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
5045            // Past eight bytes the C shifts an unsigned long off the end, so
5046            // the rest of the bytes are zero and a negative is not carried.
5047            ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
5048            ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
5049            // A count of zero on `c` writes the whole string, `s` adds the
5050            // terminator, and `x` writes a zero byte nobody reads back.
5051            ("#struct.pack('c0', 'abcd')", ":4\r\n"),
5052            ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
5053            ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
5054        ] {
5055            let script = format!("return {body}");
5056            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5057        }
5058        // Sizes, including the two the C is lenient about: an unknown letter
5059        // and a bare digit are both nothing at all rather than a complaint.
5060        for (body, want) in [
5061            ("struct.size('i')", ":4\r\n"),
5062            ("struct.size('l')", ":8\r\n"),
5063            ("struct.size('T')", ":8\r\n"),
5064            ("struct.size('h')", ":2\r\n"),
5065            ("struct.size('c10')", ":10\r\n"),
5066            ("struct.size('ic')", ":5\r\n"),
5067            ("struct.size('!8ic')", ":5\r\n"),
5068            ("struct.size('!4i')", ":4\r\n"),
5069            // Nothing is padded until `!` turns alignment on, and then a
5070            // double is pushed out to the next eight byte boundary.
5071            ("struct.size('bd')", ":9\r\n"),
5072            ("struct.size('!bd')", ":16\r\n"),
5073            ("struct.size('A')", ":0\r\n"),
5074            ("struct.size('7')", ":0\r\n"),
5075        ] {
5076            let script = format!("return {body}");
5077            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5078        }
5079        // Unpacking, which hands back the values and then where it stopped, so
5080        // the last number can be passed straight back in as the next offset.
5081        for (body, want) in [
5082            ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
5083            ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
5084            ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
5085            ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
5086            ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
5087            (
5088                "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
5089                ":-70000\r\n",
5090            ),
5091            ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
5092            // A `c0` takes its length from the value read just before it and
5093            // swallows it, so one byte says how long the next three are and
5094            // only the string and the position come back.
5095            ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
5096            ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
5097        ] {
5098            let script = format!("return {body}");
5099            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5100        }
5101        for (body, want) in [
5102            ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
5103            ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
5104            ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
5105        ] {
5106            let script = format!("return {body}");
5107            assert_eq!(
5108                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5109                format!("${}\r\n{want}\r\n", want.len()),
5110                "{body}",
5111            );
5112        }
5113        // A failure names the argument the C names, which is not always the
5114        // argument a reader would pick.
5115        for (body, want) in [
5116            (
5117                "return struct.pack()",
5118                "bad argument #1 to 'pack' (string expected, got no value)",
5119            ),
5120            // The C pushes a nil before it reads anything, so a missing value
5121            // is a nil rather than nothing at all.
5122            (
5123                "return struct.pack('i4')",
5124                "bad argument #2 to 'pack' (number expected, got nil)",
5125            ),
5126            // And it reads the string with a post increment before it checks
5127            // the length, so the number here is one past the real argument.
5128            (
5129                "return struct.pack('c6', 'abc')",
5130                "bad argument #3 to 'pack' (string too short)",
5131            ),
5132            (
5133                "return struct.pack('A', 'x')",
5134                "bad argument #1 to 'pack' (invalid format option 'A')",
5135            ),
5136            (
5137                "return struct.pack('i33', 1)",
5138                "integral size 33 is larger than limit of 32",
5139            ),
5140            (
5141                "return struct.pack('!3i', 1)",
5142                "alignment 3 is not a power of 2",
5143            ),
5144            (
5145                "return struct.unpack()",
5146                "bad argument #1 to 'unpack' (string expected, got no value)",
5147            ),
5148            (
5149                "return struct.unpack('i4')",
5150                "bad argument #2 to 'unpack' (string expected, got no value)",
5151            ),
5152            (
5153                "return struct.unpack('i4', 'ab')",
5154                "bad argument #2 to 'unpack' (data string too short)",
5155            ),
5156            (
5157                "return struct.unpack('i1', 'abc', 0)",
5158                "bad argument #3 to 'unpack' (offset must be 1 or greater)",
5159            ),
5160            (
5161                "return struct.unpack('c0', 'abc')",
5162                "format 'c0' needs a previous size",
5163            ),
5164            (
5165                "return struct.unpack('s', 'abc')",
5166                "unfinished string in data",
5167            ),
5168            (
5169                "return struct.size()",
5170                "bad argument #1 to 'size' (string expected, got no value)",
5171            ),
5172            (
5173                "return struct.size('s')",
5174                "bad argument #1 to 'size' (option 's' has no fixed size)",
5175            ),
5176            (
5177                "return struct.size('c0')",
5178                "bad argument #1 to 'size' (option 'c0' has no fixed size)",
5179            ),
5180        ] {
5181            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5182            assert!(
5183                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5184                "{body} gave {reply}",
5185            );
5186        }
5187        // Three members and no version, which is all the C registers.
5188        let names = "pack size unpack";
5189        assert_eq!(
5190            f.run(&[
5191                b"EVAL",
5192                b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
5193                  table.sort(t) return table.concat(t, ' ')",
5194                b"0",
5195            ]),
5196            format!("${}\r\n{names}\r\n", names.len())
5197        );
5198        for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
5199            assert!(
5200                f.run(&[b"EVAL", body, b"0"])
5201                    .contains("Attempt to modify a readonly table script: "),
5202                "{body:?}",
5203            );
5204        }
5205    }
5206
5207    #[test]
5208    fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
5209        let mut f = Fixture::new();
5210        // Every value goes out in the shortest form that holds it, and several
5211        // arguments are packed one after another into one string.
5212        let hex = "local function hx(s) return (string.gsub(s, '.', \
5213                   function(c) return string.format('%02x', string.byte(c)) end)) end ";
5214        for (body, want) in [
5215            ("cmsgpack.pack(nil)", "c0"),
5216            ("cmsgpack.pack(true)", "c3"),
5217            ("cmsgpack.pack(false)", "c2"),
5218            ("cmsgpack.pack(0)", "00"),
5219            ("cmsgpack.pack(127)", "7f"),
5220            ("cmsgpack.pack(128)", "cc80"),
5221            ("cmsgpack.pack(-1)", "ff"),
5222            ("cmsgpack.pack(-33)", "d0df"),
5223            ("cmsgpack.pack(65535)", "cdffff"),
5224            ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
5225            ("cmsgpack.pack(2^53)", "cf0020000000000000"),
5226            ("cmsgpack.pack(-2^63)", "d38000000000000000"),
5227            // Past what an integer holds it is a number again, and a number
5228            // goes out narrow whenever four bytes give it back unchanged.
5229            ("cmsgpack.pack(2^64)", "ca5f800000"),
5230            ("cmsgpack.pack(1.5)", "ca3fc00000"),
5231            ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
5232            ("cmsgpack.pack('abc')", "a3616263"),
5233            ("cmsgpack.pack('')", "a0"),
5234            ("cmsgpack.pack({})", "90"),
5235            ("cmsgpack.pack({1, 2})", "920102"),
5236            ("cmsgpack.pack({a = 1})", "81a16101"),
5237            ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
5238            // Sixteen levels of table are packed and the seventeenth is a nil,
5239            // which is what the C does rather than refusing the whole thing.
5240            (
5241                "(function() local t = {} local c = t \
5242                 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
5243                "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
5244                 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
5245            ),
5246        ] {
5247            let script = format!("{hex} return hx({body})");
5248            assert_eq!(
5249                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5250                format!("${}\r\n{want}\r\n", want.len()),
5251                "{body}",
5252            );
5253        }
5254        // Unpacking reads the whole stream, so a string holding three values
5255        // hands back three. The two that take an offset put where they got to
5256        // in front of the values, and answer minus one when nothing is left.
5257        for (body, want) in [
5258            ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
5259            ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
5260            ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
5261            ("select('#', cmsgpack.unpack(''))", 0),
5262            ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
5263            ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5264            ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5265            ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
5266            ("select(1, cmsgpack.unpack_one('\\1'))", -1),
5267            ("select(1, cmsgpack.unpack_one('', 0))", -1),
5268            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
5269            ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
5270            // A limit of nothing at all takes the read everything path, which
5271            // has no offset in front of it.
5272            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
5273            ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
5274        ] {
5275            let script = format!("return {body}");
5276            assert_eq!(
5277                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5278                format!(":{want}\r\n"),
5279                "{body}",
5280            );
5281        }
5282        for (body, want) in [
5283            ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
5284            ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
5285            ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
5286            (
5287                "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
5288                "inf",
5289            ),
5290            ("cmsgpack._NAME", "cmsgpack"),
5291            ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
5292            (
5293                "cmsgpack._COPYRIGHT",
5294                "Copyright (C) 2012, Salvatore Sanfilippo",
5295            ),
5296            (
5297                "cmsgpack._DESCRIPTION",
5298                "MessagePack C implementation for Lua",
5299            ),
5300        ] {
5301            let script = format!("return {body}");
5302            assert_eq!(
5303                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5304                format!("${}\r\n{want}\r\n", want.len()),
5305                "{body}",
5306            );
5307        }
5308        for (body, want) in [
5309            // The C counts the arguments before it reads any of them, so the
5310            // one it names when there are none is the one before the first.
5311            (
5312                "return cmsgpack.pack()",
5313                "bad argument #0 to 'pack' (MessagePack pack needs input.)",
5314            ),
5315            (
5316                "return cmsgpack.unpack()",
5317                "bad argument #1 to 'unpack' (string expected, got no value)",
5318            ),
5319            (
5320                "return cmsgpack.unpack(string.char(193))",
5321                "Bad data format in input.",
5322            ),
5323            (
5324                "return cmsgpack.unpack(string.char(204))",
5325                "Missing bytes in input.",
5326            ),
5327            (
5328                "return cmsgpack.unpack(string.char(146, 1))",
5329                "Missing bytes in input.",
5330            ),
5331            (
5332                "return cmsgpack.unpack_one('\\1', 5)",
5333                "Start offset 5 greater than input length 1.",
5334            ),
5335            (
5336                "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
5337                "Start offset 5 greater than input length 2.",
5338            ),
5339            // The second number here is the length of the input rather than
5340            // the limit, which is a mixed up argument in the C kept on purpose.
5341            (
5342                "return cmsgpack.unpack_one('\\1', -1)",
5343                "Invalid request to unpack with offset of -1 and limit of 1.",
5344            ),
5345            (
5346                "return cmsgpack.unpack_limit('\\1', -1, 0)",
5347                "Invalid request to unpack with offset of 0 and limit of 1.",
5348            ),
5349        ] {
5350            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5351            assert!(
5352                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5353                "{body} gave {reply}",
5354            );
5355        }
5356        // Four calls and the four names the C sets on the table beside them.
5357        let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
5358        assert_eq!(
5359            f.run(&[
5360                b"EVAL",
5361                b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
5362                  table.sort(t) return table.concat(t, ' ')",
5363                b"0",
5364            ]),
5365            format!("${}\r\n{names}\r\n", names.len())
5366        );
5367        for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
5368            assert!(
5369                f.run(&[b"EVAL", body, b"0"])
5370                    .contains("Attempt to modify a readonly table script: "),
5371                "{body:?}",
5372            );
5373        }
5374        // A library is a table like any other from a script's side, so packing
5375        // one walks its members rather than finding the guard in front empty.
5376        assert_eq!(
5377            f.run(&[
5378                b"EVAL",
5379                b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
5380                b"0",
5381            ]),
5382            "$8\r\ncmsgpack\r\n"
5383        );
5384    }
5385
5386    /// The library used by most of the function tests below.
5387    ///
5388    /// Written out once because every one of them wants a library that has
5389    /// something to call, and because the line numbers in the failures a couple
5390    /// of them check are line numbers in this.
5391    const LIB: &[u8] = b"#!lua name=mylib\n\
5392        local counter = 0\n\
5393        redis.register_function{function_name = 'ping', description = 'says pong',\n\
5394        callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
5395        redis.register_function('count', function() counter = counter + 1 return counter end)\n\
5396        redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
5397        redis.register_function('setit', function(keys, args) \
5398        return redis.call('SET', keys[1], args[1]) end)\n\
5399        redis.register_function('raise', function() error('boom') end)\n";
5400
5401    /// A second library, for the tests that need two of them.
5402    const OTHER: &[u8] = b"#!lua name=other\n\
5403        redis.register_function('twice', function(keys, args) return 2 end)\n";
5404
5405    #[test]
5406    fn a_library_is_loaded_once_and_called_by_name_forever_after() {
5407        let mut f = Fixture::new();
5408        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5409        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5410        // The dictionary FCALL looks in is one for the whole server and it does
5411        // not care about case, which is why this finds the same function.
5412        assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
5413        // Keys and arguments arrive as the two arguments of the callback rather
5414        // than as globals, and a function that reads KEYS is reading a name
5415        // that is not there.
5416        assert_eq!(
5417            f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
5418            "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5419        );
5420        assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
5421        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
5422        // A library's own local outlives the call that made it, which is the
5423        // whole reason a library is not a script.
5424        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5425        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
5426        // The name a failure ends with is the function's, where a script's is
5427        // its digest, and the line is a line in the library.
5428        assert_eq!(
5429            f.run(&[b"FCALL", b"raise", b"0"]),
5430            "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
5431        );
5432        // Deleting is by the exact name, so the upper case spelling that found
5433        // the function a moment ago does not find the library.
5434        assert_eq!(
5435            f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
5436            "-ERR Library not found\r\n"
5437        );
5438        assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
5439        assert_eq!(
5440            f.run(&[b"FCALL", b"ping", b"0"]),
5441            "-ERR Function not found\r\n"
5442        );
5443    }
5444
5445    #[test]
5446    fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
5447        let mut f = Fixture::new();
5448        for (code, want) in [
5449            (&b"return 1"[..], "ERR Missing library metadata"),
5450            (b"#!lua name=x", "ERR Invalid library metadata"),
5451            (b"#!\n", "ERR Library name was not given"),
5452            (b"#!lua\nx", "ERR Library name was not given"),
5453            (
5454                b"#!lua name=a name=b\nx",
5455                "ERR Invalid metadata value, name argument was given multiple times",
5456            ),
5457            (
5458                b"#!lua nome=a\nx",
5459                "ERR Invalid metadata value given: nome=a",
5460            ),
5461            (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
5462            (
5463                b"#!lua name=a-b\nx",
5464                "ERR Library names can only contain letters, numbers, or underscores(_) \
5465                 and must be at least one character long",
5466            ),
5467            (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
5468            (
5469                b"#!lua name=c\nthis is not lua",
5470                "ERR Error compiling function: user_function:2: '=' expected near 'is'",
5471            ),
5472            // Nothing at all is on the global table during a load except one
5473            // table with eight names on it, so `error` is as absent as anything
5474            // a library misspelled would be.
5475            (
5476                b"#!lua name=r\nerror('boom')",
5477                "ERR Error registering functions: ERR user_function:2: \
5478                 Script attempted to access nonexistent global variable 'error'",
5479            ),
5480            // And `redis` is there but `redis.call` is not, so the name the
5481            // complaint gives is `call` and not `redis`.
5482            (
5483                b"#!lua name=r\nredis.call('PING')",
5484                "ERR Error registering functions: ERR user_function:2: \
5485                 Script attempted to access nonexistent global variable 'call'",
5486            ),
5487            (
5488                b"#!lua name=r\nx = 1",
5489                "ERR Error registering functions: ERR user_function:2: \
5490                 Attempt to modify a readonly table",
5491            ),
5492            (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
5493        ] {
5494            assert_eq!(
5495                f.run(&[b"FUNCTION", b"LOAD", code]),
5496                format!("-{want}\r\n"),
5497                "{}",
5498                String::from_utf8_lossy(code),
5499            );
5500        }
5501    }
5502
5503    #[test]
5504    fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
5505        let mut f = Fixture::new();
5506        for (call, want) in [
5507            (
5508                &b"redis.register_function()"[..],
5509                "wrong number of arguments to redis.register_function",
5510            ),
5511            (
5512                b"redis.register_function('a', function() end, 1)",
5513                "wrong number of arguments to redis.register_function",
5514            ),
5515            (
5516                b"redis.register_function('a')",
5517                "calling redis.register_function with a single argument is only \
5518                 applicable to Lua table (representing named arguments).",
5519            ),
5520            (
5521                b"redis.register_function({foo = 'a'})",
5522                "unknown argument given to redis.register_function",
5523            ),
5524            (
5525                b"redis.register_function({callback = function() end})",
5526                "redis.register_function must get a function name argument",
5527            ),
5528            (
5529                b"redis.register_function({function_name = 'a'})",
5530                "redis.register_function must get a callback argument",
5531            ),
5532            (
5533                b"redis.register_function({function_name = {}, callback = function() end})",
5534                "function_name argument given to redis.register_function must be a string",
5535            ),
5536            (
5537                b"redis.register_function({function_name = 'a', description = {}, \
5538                  callback = function() end})",
5539                "description argument given to redis.register_function must be a string",
5540            ),
5541            (
5542                b"redis.register_function({function_name = 'a', callback = 1})",
5543                "callback argument given to redis.register_function must be a function",
5544            ),
5545            (
5546                b"redis.register_function({function_name = 'a', callback = function() end, \
5547                  flags = 1})",
5548                "flags argument to redis.register_function must be a table \
5549                 representing function flags",
5550            ),
5551            (
5552                b"redis.register_function({function_name = 'a', callback = function() end, \
5553                  flags = {'zz'}})",
5554                "unknown flag given",
5555            ),
5556            (
5557                b"redis.register_function({}, function() end)",
5558                "first argument to redis.register_function must be a string",
5559            ),
5560            (
5561                b"redis.register_function('a', 1)",
5562                "second argument to redis.register_function must be a function",
5563            ),
5564            (
5565                b"redis.register_function('a-b', function() end)",
5566                "Library names can only contain letters, numbers, or underscores(_) \
5567                 and must be at least one character long",
5568            ),
5569            (
5570                b"redis.register_function('d', function() end) \
5571                  redis.register_function('d', function() end)",
5572                "Function already exists in the library",
5573            ),
5574        ] {
5575            let mut code = b"#!lua name=e\n".to_vec();
5576            code.extend_from_slice(call);
5577            // Two `ERR` in a row on purpose. The sentence comes back as a table
5578            // with the code already on it, which is what keeps the position off
5579            // the front of it, and then the code goes on the line as well.
5580            assert_eq!(
5581                f.run(&[b"FUNCTION", b"LOAD", &code]),
5582                format!("-ERR Error registering functions: ERR {want}\r\n"),
5583                "{}",
5584                String::from_utf8_lossy(call),
5585            );
5586        }
5587        // A number is a name, because the C reads an argument that should be a
5588        // string through a helper that takes a number and prints it.
5589        assert_eq!(
5590            f.run(&[
5591                b"FUNCTION",
5592                b"LOAD",
5593                b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
5594            ]),
5595            "$1\r\nn\r\n"
5596        );
5597        assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
5598        // The dictionary inside one library is case sensitive where the one
5599        // across libraries is not, so these are two functions.
5600        assert_eq!(
5601            f.run(&[
5602                b"FUNCTION",
5603                b"LOAD",
5604                b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
5605                  redis.register_function('D', function() return 2 end)",
5606            ]),
5607            "$1\r\nc\r\n"
5608        );
5609    }
5610
5611    #[test]
5612    fn a_library_cannot_take_a_name_another_library_already_has() {
5613        let mut f = Fixture::new();
5614        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5615        assert_eq!(
5616            f.run(&[b"FUNCTION", b"LOAD", LIB]),
5617            "-ERR Library 'mylib' already exists\r\n"
5618        );
5619        // A different library that registers a name the first one already has,
5620        // which is checked without regard to case because the dictionary it is
5621        // checked against is.
5622        assert_eq!(
5623            f.run(&[
5624                b"FUNCTION",
5625                b"LOAD",
5626                b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
5627            ]),
5628            "-ERR Function PING already exists\r\n"
5629        );
5630        // REPLACE reloads a library over itself, and the collision check leaves
5631        // the library being replaced out or nothing could ever be reloaded.
5632        assert_eq!(
5633            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
5634            "$5\r\nmylib\r\n"
5635        );
5636        // The counter went back to zero with the reload, since the library is a
5637        // new one and its locals are new with it.
5638        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5639        assert_eq!(
5640            f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
5641            "-ERR Unknown option given: NOPE\r\n"
5642        );
5643        // The loop that reads the options stops one short of the end, so the
5644        // last argument is the code whatever it looks like.
5645        assert_eq!(
5646            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
5647            "-ERR Missing library metadata\r\n"
5648        );
5649        assert_eq!(
5650            f.run(&[b"FUNCTION", b"LOAD"]),
5651            "-ERR wrong number of arguments for 'function|load' command\r\n"
5652        );
5653    }
5654
5655    #[test]
5656    fn fcall_checks_the_name_before_it_looks_at_anything_else() {
5657        let mut f = Fixture::new();
5658        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5659        for (args, want) in [
5660            (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
5661            (&[b"ping", b"x"], "ERR Bad number of keys provided"),
5662            (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
5663            (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
5664            (
5665                &[b"ping", b"99999999999999999999"],
5666                "ERR Bad number of keys provided",
5667            ),
5668            (
5669                &[b"ping", b"3", b"a"],
5670                "ERR Number of keys can't be greater than number of args",
5671            ),
5672            (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
5673        ] {
5674            let mut wire: Vec<&[u8]> = vec![b"FCALL"];
5675            wire.extend_from_slice(args);
5676            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5677        }
5678        // The read-only spelling refuses a function the library did not mark
5679        // no-writes, and it refuses it before anything runs.
5680        assert_eq!(
5681            f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
5682            "-ERR Can not execute a script with write flag using *_ro command.\r\n"
5683        );
5684        assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
5685        assert_eq!(
5686            f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
5687            "-ERR Function not found\r\n"
5688        );
5689        // And a function that was marked no-writes is held to it whichever
5690        // spelling called it.
5691        assert_eq!(
5692            f.run(&[
5693                b"FUNCTION",
5694                b"LOAD",
5695                b"#!lua name=w\nredis.register_function{function_name = 'w', \
5696                  flags = {'no-writes'}, callback = function(keys) \
5697                  return redis.call('SET', keys[1], 'x') end}",
5698            ]),
5699            "$1\r\nw\r\n"
5700        );
5701        assert!(
5702            f.run(&[b"FCALL", b"w", b"1", b"k"])
5703                .starts_with("-ERR Write commands are not allowed from read-only scripts."),
5704        );
5705    }
5706
5707    #[test]
5708    fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
5709        let mut f = Fixture::new();
5710        // The three names on the `redis` table that only mean something inside
5711        // EVAL are not there, and neither is the error handler EVAL installs.
5712        let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
5713                     REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
5714                     acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
5715                     status_reply";
5716        let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
5717                       gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
5718                       rawequal rawget rawset redis select setmetatable string struct table \
5719                       tonumber tostring type unpack xpcall";
5720        assert_eq!(
5721            f.run(&[
5722                b"FUNCTION",
5723                b"LOAD",
5724                b"#!lua name=g\n\
5725                  local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
5726                  table.sort(o) return table.concat(o, ' ') end\n\
5727                  redis.register_function('names', function() return sorted(redis) end)\n\
5728                  redis.register_function('globals', function() return sorted(_G) end)\n\
5729                  redis.register_function('keysg', function() return KEYS[1] end)\n\
5730                  redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
5731                  redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
5732                  redis.register_function('gwr', function() _G.pcall = 1 end)\n",
5733            ]),
5734            "$1\r\ng\r\n"
5735        );
5736        assert_eq!(
5737            f.run(&[b"FCALL", b"names", b"0"]),
5738            format!("${}\r\n{names}\r\n", names.len())
5739        );
5740        assert_eq!(
5741            f.run(&[b"FCALL", b"globals", b"0"]),
5742            format!("${}\r\n{globals}\r\n", globals.len())
5743        );
5744        // No `KEYS`, and reading a global that is not there is a mistake rather
5745        // than a nil, so this is the sandbox's own complaint.
5746        assert!(
5747            f.run(&[b"FCALL", b"keysg", b"1", b"k"])
5748                .contains("nonexistent global variable 'KEYS'"),
5749        );
5750        // The `redis` table has no error metatable on it, unlike the global
5751        // table, so a name that is not on it is a nil and not a complaint.
5752        assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
5753        // The global table cannot be written to either way round, which is a
5754        // stricter rule than the one a script runs under.
5755        for name in [&b"wr"[..], b"gwr"] {
5756            assert!(
5757                f.run(&[b"FCALL", name, b"0"])
5758                    .contains("Attempt to modify a readonly table"),
5759                "{}",
5760                String::from_utf8_lossy(name),
5761            );
5762        }
5763    }
5764
5765    #[test]
5766    fn function_list_says_what_every_library_registered() {
5767        let mut f = Fixture::new();
5768        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5769        // One map per library on RESP3, and the functions inside it in the
5770        // order the library registered them, which is D-109.
5771        f.out = Out::new(Proto::Resp3);
5772        let listed = f.run(&[b"FUNCTION", b"LIST"]);
5773        assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
5774        assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
5775        assert!(listed.contains(
5776            "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
5777             $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
5778        ));
5779        // A function with no description gets a null rather than an empty
5780        // string, and no flags is an empty set rather than a missing field.
5781        assert!(listed.contains(
5782            "$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"
5783        ));
5784        assert!(!listed.contains("library_code"));
5785        assert!(
5786            f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
5787                .contains("library_code")
5788        );
5789        // The pattern is matched without regard to case, which is a third rule
5790        // again next to the two the two dictionaries use.
5791        assert!(
5792            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
5793                .starts_with("*1\r\n")
5794        );
5795        assert_eq!(
5796            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
5797            "*0\r\n"
5798        );
5799        // On RESP2 the same reply is a flat array of six, which is what `map`
5800        // means on a protocol that has no map.
5801        f.out = Out::new(Proto::Resp2);
5802        assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
5803        for (args, want) in [
5804            (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
5805            (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
5806            (
5807                &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
5808                "ERR Unknown argument LIBRARYNAME",
5809            ),
5810            (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
5811        ] {
5812            let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
5813            wire.extend_from_slice(args);
5814            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5815        }
5816    }
5817
5818    #[test]
5819    fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
5820        let mut f = Fixture::new();
5821        f.out = Out::new(Proto::Resp3);
5822        assert_eq!(
5823            f.run(&[b"FUNCTION", b"STATS"]),
5824            "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
5825             %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
5826        );
5827        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5828        assert!(
5829            f.run(&[b"FUNCTION", b"STATS"])
5830                .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
5831        );
5832        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5833        assert!(
5834            f.run(&[b"FUNCTION", b"STATS"])
5835                .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
5836        );
5837    }
5838
5839    #[test]
5840    fn every_function_subcommand_complains_about_its_own_arity() {
5841        let mut f = Fixture::new();
5842        for (args, want) in [
5843            (
5844                &[&b"STATS"[..], b"X"][..],
5845                "ERR wrong number of arguments for 'function|stats' command",
5846            ),
5847            (
5848                &[b"KILL", b"X"],
5849                "ERR wrong number of arguments for 'function|kill' command",
5850            ),
5851            (
5852                &[b"HELP", b"X"],
5853                "ERR wrong number of arguments for 'function|help' command",
5854            ),
5855            (
5856                &[b"DELETE"],
5857                "ERR wrong number of arguments for 'function|delete' command",
5858            ),
5859            (
5860                &[b"DELETE", b"a", b"b"],
5861                "ERR wrong number of arguments for 'function|delete' command",
5862            ),
5863            (
5864                &[b"DUMP", b"X"],
5865                "ERR wrong number of arguments for 'function|dump' command",
5866            ),
5867            (
5868                &[b"RESTORE"],
5869                "ERR wrong number of arguments for 'function|restore' command",
5870            ),
5871            // RESTORE is the other one that falls through to the generic
5872            // sentence, and for the same reason FLUSH does.
5873            (
5874                &[b"RESTORE", b"a", b"FLUSH", b"X"],
5875                "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
5876                 Try FUNCTION HELP.",
5877            ),
5878            (
5879                &[b"RESTORE", b"a", b"ZZ"],
5880                "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
5881                 or REPLACE.",
5882            ),
5883            // FLUSH is the one that does not, because it checks the count
5884            // itself before it looks at the argument.
5885            (
5886                &[b"FLUSH", b"SYNC", b"X"],
5887                "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
5888                 Try FUNCTION HELP.",
5889            ),
5890            (
5891                &[b"FLUSH", b"ZZ"],
5892                "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
5893            ),
5894            (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
5895        ] {
5896            let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
5897            wire.extend_from_slice(args);
5898            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5899        }
5900        assert_eq!(
5901            f.run(&[b"FUNCTION"]),
5902            "-ERR wrong number of arguments for 'function' command\r\n"
5903        );
5904        assert_eq!(
5905            f.run(&[b"FUNCTION", b"KILL"]),
5906            "-NOTBUSY No scripts in execution right now.\r\n"
5907        );
5908    }
5909
5910    /// The two ends of the same pipe, so they are tested as one.
5911    ///
5912    /// An empty server dumps ten bytes rather than nothing, because the footer
5913    /// is there whether or not a library is in front of it, and restoring those
5914    /// ten bytes is a working no op.
5915    #[test]
5916    fn a_library_survives_a_dump_and_a_restore() {
5917        let mut f = Fixture::new();
5918        let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5919        assert_eq!(empty.len(), 10);
5920        assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
5921
5922        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5923        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5924        assert!(full.len() > empty.len());
5925
5926        // The default policy is APPEND, so restoring onto the library the
5927        // payload came from is a name collision and not a quiet replacement.
5928        assert_eq!(
5929            f.run(&[b"FUNCTION", b"RESTORE", &full]),
5930            "-ERR Library mylib already exists\r\n"
5931        );
5932        assert_eq!(
5933            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5934            "+OK\r\n"
5935        );
5936        assert_eq!(
5937            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5938            "+OK\r\n"
5939        );
5940        // Whichever way it went back, the functions in it still run.
5941        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5942
5943        // FLUSH keeps only what the payload held, so a library that was there
5944        // and is not in the payload is gone.
5945        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
5946        assert_eq!(
5947            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5948            "+OK\r\n"
5949        );
5950        assert_eq!(
5951            f.run(&[b"FUNCTION", b"DELETE", b"other"]),
5952            "-ERR Library not found\r\n"
5953        );
5954    }
5955
5956    /// A payload that is going to be refused has to leave the server alone.
5957    ///
5958    /// Every one of these is refused for a different reason and at a different
5959    /// depth, from bytes that are not a payload at all down to a library that
5960    /// compiles and then collides, and the library that was already there has to
5961    /// still be there afterwards in every case.
5962    #[test]
5963    fn a_restore_that_fails_changes_nothing() {
5964        let mut f = Fixture::new();
5965        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5966        let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5967
5968        // Put the footer back on, so that each of these is refused for the
5969        // reason it is meant to be testing rather than for a checksum the edit
5970        // broke on the way.
5971        let reseal = |body: &[u8], version: u16| {
5972            let mut out = body.to_vec();
5973            out.extend_from_slice(&version.to_le_bytes());
5974            let crc = yo_common::crc::crc64(0, &out);
5975            out.extend_from_slice(&crc.to_le_bytes());
5976            out
5977        };
5978        let body = &good[..good.len() - 10];
5979
5980        let mut torn = good.clone();
5981        let n = torn.len();
5982        torn[n - 1] ^= 0xff;
5983        let future = reseal(body, 999);
5984        // The opcode in front of the one library, changed to the one the 7.0
5985        // release candidates wrote and then to one that is not a library at all.
5986        let mut pre_ga = body.to_vec();
5987        pre_ga[0] = 246;
5988        let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
5989        let mut other = body.to_vec();
5990        other[0] = 0;
5991        let other = reseal(&other, yo_kv::rdb::VERSION);
5992        // A library whose length says there is more of it than there is.
5993        let mut cut = body.to_vec();
5994        cut.truncate(body.len() - 1);
5995        let cut = reseal(&cut, yo_kv::rdb::VERSION);
5996
5997        for (bytes, want) in [
5998            (vec![], "ERR DUMP payload version or checksum are wrong"),
5999            (
6000                b"0123456789".to_vec(),
6001                "ERR DUMP payload version or checksum are wrong",
6002            ),
6003            (torn, "ERR DUMP payload version or checksum are wrong"),
6004            (future, "ERR DUMP payload version or checksum are wrong"),
6005            (pre_ga, "ERR Pre-GA function format not supported"),
6006            (other, "ERR given type is not a function"),
6007            (cut, "ERR Failed loading library payload"),
6008        ] {
6009            assert_eq!(
6010                f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
6011                format!("-{want}\r\n")
6012            );
6013        }
6014
6015        // Still exactly the one library, and it still runs.
6016        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6017        let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6018        assert_eq!(again, good);
6019    }
6020
6021    /// A REPLACE takes a library's name off another library and still refuses to
6022    /// take a function name off one it is leaving alone.
6023    #[test]
6024    fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
6025        let mut f = Fixture::new();
6026        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6027        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6028        // A second library registering the name the payload's library uses.
6029        let clash =
6030            b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
6031                .as_slice();
6032        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6033        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
6034        assert_eq!(
6035            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6036            "-ERR Function ping already exists\r\n"
6037        );
6038        // Untouched, so the name still belongs to the library that had it.
6039        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
6040    }
6041
6042    #[test]
6043    fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
6044        let mut f = Fixture::new();
6045        assert_eq!(
6046            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
6047            "*1\r\n$1\r\nk\r\n"
6048        );
6049        assert_eq!(
6050            f.run(&[
6051                b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
6052            ]),
6053            "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
6054        );
6055        // None is a real answer for a script and the arguments past the count
6056        // are not keys, so they are not listed.
6057        assert_eq!(
6058            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
6059            "*0\r\n"
6060        );
6061        // A count that makes no sense finds no keys rather than being an error,
6062        // which is what a real server's key spec does with it.
6063        assert_eq!(
6064            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
6065            "*0\r\n"
6066        );
6067        assert_eq!(
6068            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
6069            "*0\r\n"
6070        );
6071        assert_eq!(
6072            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
6073            "*0\r\n"
6074        );
6075        // The count itself has to be there, and that is an arity question.
6076        assert_eq!(
6077            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
6078            "-ERR Invalid number of arguments specified for command\r\n"
6079        );
6080    }
6081
6082    #[test]
6083    fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
6084        let mut f = Fixture::new();
6085        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
6086
6087        assert_eq!(
6088            eval(&mut f, b"return redis.sha1hex('')"),
6089            "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
6090        );
6091        assert_eq!(
6092            eval(&mut f, b"return redis.sha1hex('return 1')"),
6093            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
6094        );
6095        // A message with no space in it gets the generic code in front, and one
6096        // that already looks like a coded error is left alone.
6097        assert_eq!(
6098            eval(&mut f, b"return redis.error_reply('boom')"),
6099            "-ERR boom\r\n"
6100        );
6101        assert_eq!(
6102            eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
6103            "-WRONGTYPE nope\r\n"
6104        );
6105        assert_eq!(
6106            eval(&mut f, b"return redis.status_reply('fine')"),
6107            "+fine\r\n"
6108        );
6109        // Neither of them raises when it is called wrongly, they answer a value
6110        // that is an error, which is a difference a script can see.
6111        assert_eq!(
6112            eval(&mut f, b"return redis.error_reply(1)"),
6113            "-ERR wrong number or type of arguments\r\n"
6114        );
6115        assert_eq!(
6116            eval(&mut f, b"local x = redis.status_reply() return x.err"),
6117            "$37\r\nERR wrong number or type of arguments\r\n"
6118        );
6119
6120        // The constants a script branches on.
6121        assert_eq!(
6122            eval(
6123                &mut f,
6124                b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
6125            ),
6126            "$4\r\n0123\r\n"
6127        );
6128        assert_eq!(
6129            eval(
6130                &mut f,
6131                b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
6132            ),
6133            "$5\r\n01223\r\n"
6134        );
6135        // The calls that exist so an old script keeps working.
6136        assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
6137        assert_eq!(
6138            eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
6139            ":1\r\n"
6140        );
6141        assert_eq!(
6142            eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
6143            ":1\r\n"
6144        );
6145        assert_eq!(
6146            eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
6147            ":1\r\n"
6148        );
6149        // Each of those checks its arguments the way a real server does.
6150        assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
6151        assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
6152        assert!(
6153            eval(&mut f, b"redis.log('x', 'y')")
6154                .contains("First argument must be a number (log level)."),
6155        );
6156        assert!(
6157            eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
6158                .contains("Invalid command passed to redis.acl_check_cmd()"),
6159        );
6160        assert!(
6161            eval(&mut f, b"return redis.acl_check_cmd('get')")
6162                .contains("Wrong number of args for redis.acl_check_cmd()"),
6163        );
6164    }
6165
6166    #[test]
6167    fn a_counter_is_an_integer_and_not_a_string_of_digits() {
6168        let mut f = Fixture::new();
6169        assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
6170        assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
6171        assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
6172        // Read back as a string it is still an integer, written out as digits
6173        // only because somebody asked for them.
6174        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
6175        assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
6176        // A counter that is not a number is the error the store raises and this
6177        // layer only spells, which is the whole point of the split.
6178        f.run(&[b"SET", b"k", b"hello"]);
6179        assert_eq!(
6180            f.run(&[b"INCR", b"k"]),
6181            "-ERR value is not an integer or out of range\r\n"
6182        );
6183        assert_eq!(
6184            f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
6185            "-ERR increment would produce NaN or Infinity\r\n"
6186        );
6187    }
6188
6189    /// Every one of these was read off a running 8.8. They are the answers a
6190    /// client library's own test suite checks, and the shapes are not
6191    /// guessable: `DIGEST` is hexadecimal in a bulk string, `MSETEX` is an
6192    /// integer, `INCREX` is a pair.
6193    #[test]
6194    fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
6195        let mut f = Fixture::new();
6196        assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
6197        // The same digest a real 8.8 answers for the same five bytes, which is
6198        // what makes `IFDEQ` usable against a mixed deployment.
6199        assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
6200        assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
6201        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
6202        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
6203        assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
6204        assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
6205        assert_eq!(
6206            f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
6207            "*2\r\n:1\r\n:0\r\n",
6208            "a refused increment reports the value it left alone and applied nothing"
6209        );
6210        assert_eq!(
6211            f.run(&[
6212                b"INCREX",
6213                b"n",
6214                b"BYINT",
6215                b"5",
6216                b"UBOUND",
6217                b"3",
6218                b"SATURATE"
6219            ]),
6220            "*2\r\n:3\r\n:2\r\n"
6221        );
6222        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
6223        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
6224    }
6225
6226    #[test]
6227    fn the_same_answers_come_out_in_resp3_spelling() {
6228        let mut f = Fixture::new();
6229        assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
6230        assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
6231        // A float counter is a double on RESP3 and the digits in a bulk string
6232        // on RESP2, and `INCRBYFLOAT` is a bulk string on both.
6233        assert_eq!(
6234            f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
6235            "*2\r\n,1.5\r\n,1.5\r\n"
6236        );
6237        assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
6238        // `RESET` puts the protocol back, which is the part that is easy to
6239        // miss and leaves a pooled connection speaking the wrong one.
6240        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6241        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
6242    }
6243
6244    #[test]
6245    fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
6246        let mut f = Fixture::new();
6247        let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
6248        assert_eq!(flow, Flow::Continue);
6249        assert_eq!(
6250            reply,
6251            "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
6252        );
6253        // A name with a line ending in it cannot write its own frame into the
6254        // stream, which is the reason the error writer maps them to spaces.
6255        let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
6256        assert_eq!(reply.matches("\r\n").count(), 1);
6257    }
6258
6259    #[test]
6260    fn arity_is_checked_before_the_command_is() {
6261        let mut f = Fixture::new();
6262        assert_eq!(
6263            f.run(&[b"GET"]),
6264            "-ERR wrong number of arguments for 'get' command\r\n"
6265        );
6266        assert_eq!(
6267            f.run(&[b"MSET", b"k"]),
6268            "-ERR wrong number of arguments for 'mset' command\r\n"
6269        );
6270        // The table says `PING` takes one or more and a real server then
6271        // refuses three, which is the sort of thing that only shows up against
6272        // the real thing.
6273        assert_eq!(
6274            f.run(&[b"PING", b"a", b"b"]),
6275            "-ERR wrong number of arguments for 'ping' command\r\n"
6276        );
6277        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
6278        assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
6279        // `DELEX` takes two or four and nothing between.
6280        assert_eq!(
6281            f.run(&[b"DELEX", b"k", b"IFEQ"]),
6282            "-ERR wrong number of arguments for 'delex' command\r\n"
6283        );
6284    }
6285
6286    /// The option rules, all of them measured against 8.8 rather than read off
6287    /// the documentation. The surprising one is that `SET` accepts the same
6288    /// keyword twice and `INCREX` does not.
6289    #[test]
6290    fn the_option_combinations_are_the_ones_a_real_server_accepts() {
6291        let mut f = Fixture::new();
6292        let syntax = "-ERR syntax error\r\n";
6293        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
6294        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
6295        assert_eq!(
6296            f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
6297            syntax
6298        );
6299        assert_eq!(
6300            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
6301            syntax
6302        );
6303        assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
6304        // Twice is fine, and the last one wins.
6305        assert_eq!(
6306            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
6307            "+OK\r\n"
6308        );
6309        assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
6310        assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
6311        // `INCREX` refuses what `SET` allows.
6312        assert_eq!(
6313            f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
6314            syntax
6315        );
6316        assert_eq!(
6317            f.run(&[b"INCREX", b"n", b"ENX"]),
6318            "-ERR ENX flag requires an expiration\r\n"
6319        );
6320        assert_eq!(
6321            f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
6322            "-ERR UBOUND is not an integer or out of range\r\n"
6323        );
6324        assert_eq!(
6325            f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
6326            "-ERR LBOUND can't be greater than UBOUND\r\n"
6327        );
6328        assert_eq!(
6329            f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
6330            "-ERR If you want both the length and indexes, please just use IDX.\r\n"
6331        );
6332    }
6333
6334    /// Where the expiration rules bite. The one worth the test is `GETEX` on a
6335    /// key that is not there, which answers null without ever looking at the
6336    /// expiration it was given.
6337    #[test]
6338    fn the_expiry_rules_are_redis_own() {
6339        let mut f = Fixture::new();
6340        let bad = "-ERR invalid expire time in 'set' command\r\n";
6341        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
6342        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
6343        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
6344        assert_eq!(
6345            f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
6346            bad
6347        );
6348        assert_eq!(
6349            f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
6350            "-ERR value is not an integer or out of range\r\n"
6351        );
6352        assert_eq!(
6353            f.run(&[b"SETEX", b"k", b"0", b"v"]),
6354            "-ERR invalid expire time in 'setex' command\r\n"
6355        );
6356        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
6357        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
6358        assert_eq!(
6359            f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
6360            "-ERR syntax error\r\n",
6361            "the option list is still checked before the key is looked up"
6362        );
6363        // A deadline in the past is accepted and the key goes with it.
6364        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6365        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
6366        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6367    }
6368
6369    #[test]
6370    fn mset_takes_its_pairs_from_the_read_buffer() {
6371        let mut f = Fixture::new();
6372        assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
6373        assert_eq!(
6374            f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
6375            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
6376        );
6377        assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
6378        assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
6379        assert_eq!(
6380            f.run(&[b"MSETEX", b"2", b"e", b"5"]),
6381            "-ERR wrong number of key-value pairs\r\n"
6382        );
6383        assert_eq!(
6384            f.run(&[b"MSETEX", b"0", b"e", b"5"]),
6385            "-ERR invalid numkeys value\r\n"
6386        );
6387        assert_eq!(
6388            f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
6389            "-ERR invalid numkeys value\r\n"
6390        );
6391    }
6392
6393    #[test]
6394    fn lcs_answers_the_length_the_string_and_the_runs() {
6395        let mut f = Fixture::new();
6396        f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
6397        assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
6398        assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
6399        assert_eq!(
6400            f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
6401            "*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"
6402        );
6403        // Without `IDX` the two options that only mean something with it are
6404        // accepted and ignored, which is what a real server does.
6405        assert_eq!(
6406            f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
6407            "$6\r\nmytext\r\n"
6408        );
6409    }
6410
6411    #[test]
6412    fn select_moves_the_connection_and_the_databases_stay_apart() {
6413        let mut f = Fixture::new();
6414        f.run(&[b"SET", b"k", b"zero"]);
6415        assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
6416        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6417        f.run(&[b"SET", b"k", b"four"]);
6418        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
6419        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6420        assert_eq!(
6421            f.run(&[b"SELECT", b"99"]),
6422            "-ERR DB index is out of range\r\n"
6423        );
6424        assert_eq!(
6425            f.run(&[b"SELECT", b"-1"]),
6426            "-ERR DB index is out of range\r\n"
6427        );
6428        assert_eq!(
6429            f.run(&[b"SELECT", b"abc"]),
6430            "-ERR value is not an integer or out of range\r\n"
6431        );
6432        // `RESET` brings it back to zero.
6433        f.run(&[b"SELECT", b"4"]);
6434        f.run(&[b"RESET"]);
6435        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6436    }
6437
6438    #[test]
6439    fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
6440        let mut f = Fixture::new();
6441        let reply = f.run(&[b"HELLO"]);
6442        assert!(reply.starts_with("*14\r\n"), "{reply}");
6443        assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
6444        assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
6445        assert!(
6446            reply.contains(":7\r\n"),
6447            "the connection id is in there: {reply}"
6448        );
6449        assert_eq!(
6450            f.run(&[b"HELLO", b"4"]),
6451            "-NOPROTO unsupported protocol version\r\n"
6452        );
6453        assert_eq!(
6454            f.run(&[b"HELLO", b"abc"]),
6455            "-ERR Protocol version is not an integer or out of range\r\n"
6456        );
6457        assert_eq!(
6458            f.run(&[b"HELLO", b"3", b"SETNAME"]),
6459            "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
6460        );
6461        assert!(
6462            f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
6463                .starts_with("%7\r\n")
6464        );
6465        assert_eq!(f.session.name(), b"bob");
6466        f.run(&[b"RESET"]);
6467        assert_eq!(f.session.name(), b"");
6468    }
6469
6470    #[test]
6471    fn command_describes_this_server_in_the_shape_a_driver_reads() {
6472        let mut f = Fixture::new();
6473        let count = format!(":{}\r\n", COMMANDS.len());
6474        assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
6475        let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
6476        assert_eq!(
6477            info,
6478            "*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\
6479             *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
6480        );
6481        // A null in the list, and the plain one: `$-1` and not `*-1`.
6482        assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
6483        assert_eq!(
6484            f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
6485            "*1\r\n$8\r\ngetrange\r\n"
6486        );
6487        assert_eq!(
6488            f.run(&[b"COMMAND", b"NOPE"]),
6489            "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
6490        );
6491    }
6492
6493    /// A cluster aware client asks this question and then routes on the
6494    /// answer, so `MSETEX`, whose keys are not where the table says, is the one
6495    /// that matters.
6496    #[test]
6497    fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
6498        let mut f = Fixture::new();
6499        assert_eq!(
6500            f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
6501            "*1\r\n$1\r\nk\r\n"
6502        );
6503        assert_eq!(
6504            f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
6505            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6506        );
6507        assert_eq!(
6508            f.run(&[
6509                b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
6510            ]),
6511            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6512        );
6513        assert_eq!(
6514            f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
6515            "-ERR The command has no key arguments\r\n"
6516        );
6517        assert_eq!(
6518            f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
6519            "-ERR Invalid number of arguments specified for command\r\n"
6520        );
6521    }
6522
6523    #[test]
6524    fn config_answers_what_it_can_and_refuses_what_it_cannot() {
6525        let mut f = Fixture::new();
6526        assert_eq!(
6527            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6528            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
6529        );
6530        // A pattern matches more than one, and a setting two patterns both ask
6531        // for is still sent once.
6532        let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
6533        assert!(both.starts_with("*6\r\n"), "{both}");
6534        assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
6535        assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
6536        assert_eq!(
6537            f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
6538            "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
6539        );
6540        assert_eq!(
6541            f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
6542            "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
6543        );
6544        assert_eq!(
6545            f.run(&[b"CONFIG", b"GET"]),
6546            "-ERR wrong number of arguments for 'config|get' command\r\n"
6547        );
6548        // Too few arguments and an odd number of them are different
6549        // complaints, which is the sort of thing only the real server tells
6550        // you.
6551        assert_eq!(
6552            f.run(&[b"CONFIG", b"SET", b"appendonly"]),
6553            "-ERR wrong number of arguments for 'config|set' command\r\n"
6554        );
6555        assert_eq!(
6556            f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
6557            "-ERR syntax error\r\n"
6558        );
6559        assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
6560        assert_eq!(
6561            f.run(&[b"CONFIG", b"REWRITE"]),
6562            "-ERR The server is running without a config file\r\n"
6563        );
6564    }
6565
6566    #[test]
6567    fn the_eviction_policy_reads_back_what_was_written_to_it() {
6568        let mut f = Fixture::new();
6569        assert_eq!(
6570            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6571            "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
6572        );
6573        assert_eq!(
6574            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
6575            "+OK\r\n",
6576            "the name is matched without regard to case, like every other one"
6577        );
6578        assert_eq!(
6579            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6580            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6581        );
6582        // And INFO agrees with CONFIG, which it did not when it was a literal.
6583        assert!(
6584            f.run(&[b"INFO", b"memory"])
6585                .contains("maxmemory_policy:allkeys-lfu"),
6586            "INFO and CONFIG disagree about the policy"
6587        );
6588        // The refusal names every legal value in the order the real server's
6589        // enum table lists them, because a client comparing the message compares
6590        // the whole string.
6591        assert_eq!(
6592            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
6593            "-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"
6594        );
6595        // A bad pair leaves the good one in the same command alone, and the
6596        // policy is checked by the same pass that checks the numbers.
6597        assert_eq!(
6598            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6599            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6600        );
6601        f.run(&[
6602            b"CONFIG",
6603            b"SET",
6604            b"hash-max-listpack-entries",
6605            b"7",
6606            b"maxmemory-policy",
6607            b"nonsense",
6608        ]);
6609        assert_eq!(
6610            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6611            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
6612        );
6613    }
6614
6615    #[test]
6616    fn the_three_eviction_numbers_read_back_too() {
6617        let mut f = Fixture::new();
6618        for (name, default, set) in [
6619            ("maxmemory-samples", "5", "12"),
6620            ("lfu-log-factor", "10", "3"),
6621            ("lfu-decay-time", "1", "60"),
6622        ] {
6623            let get = || {
6624                format!(
6625                    "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
6626                    name.len(),
6627                    default.len()
6628                )
6629            };
6630            assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
6631            assert_eq!(
6632                f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
6633                "+OK\r\n"
6634            );
6635            assert_eq!(
6636                f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
6637                format!(
6638                    "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
6639                    name.len(),
6640                    set.len()
6641                )
6642            );
6643            // A number that is not a number is refused with the same sentence
6644            // every other number gets, which names the setting the client typed.
6645            assert_eq!(
6646                f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
6647                format!(
6648                    "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
6649                )
6650            );
6651        }
6652    }
6653
6654    #[test]
6655    fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
6656        let mut f = Fixture::new();
6657        assert_eq!(
6658            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6659            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
6660            "no limit is the default"
6661        );
6662        // The pairing is Redis's and it is a trap: the bare letter is a power of
6663        // ten and the one with the b is a power of two.
6664        for (typed, bytes) in [
6665            (&b"1024"[..], "1024"),
6666            (b"1k", "1000"),
6667            (b"1kb", "1024"),
6668            (b"1M", "1000000"),
6669            (b"1Mb", "1048576"),
6670            (b"1gb", "1073741824"),
6671            (b"100mb", "104857600"),
6672        ] {
6673            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
6674            assert_eq!(
6675                f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6676                format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
6677                "set {}",
6678                String::from_utf8_lossy(typed)
6679            );
6680        }
6681        assert!(
6682            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6683            "the report agrees with the setting"
6684        );
6685
6686        // A unit nobody has heard of, and a negative number, which is not a very
6687        // large one however it is spelled.
6688        for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
6689            assert_eq!(
6690                f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
6691                "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
6692                "refused {}",
6693                String::from_utf8_lossy(bad)
6694            );
6695        }
6696        assert!(
6697            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6698            "and the refusal left the old one alone"
6699        );
6700    }
6701
6702    #[test]
6703    fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
6704        let mut f = Fixture::new();
6705        f.run(&[b"SET", b"here", b"already"]);
6706        // A byte, which is under what an empty server holds, so nothing this
6707        // command could do would get it under. The default policy is
6708        // `noeviction`, so nothing is what it does.
6709        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
6710        assert_eq!(
6711            f.run(&[b"SET", b"k", b"v"]),
6712            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6713        );
6714        assert_eq!(
6715            f.run(&[b"LPUSH", b"l", b"v"]),
6716            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6717        );
6718        // Reading is allowed, and so is the one thing that would help.
6719        assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
6720        assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
6721        assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
6722
6723        // Taking the limit away lets the write through again.
6724        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6725        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6726    }
6727
6728    /// Not under Miri, for the reason in `filled`: what it is watching is a
6729    /// whole two megabyte segment going back, so the megabytes are the claim
6730    /// and there is no smaller version of it that says the same thing.
6731    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
6732    #[test]
6733    fn an_allkeys_policy_makes_room_instead_of_refusing() {
6734        let mut f = Fixture::new();
6735        let val = vec![b'v'; 256];
6736        for i in 0..24000u32 {
6737            let k = format!("key:{i:08}");
6738            f.run(&[b"SET", k.as_bytes(), &val]);
6739        }
6740        let full = f.server.memory_bytes();
6741        assert!(
6742            full > 3 * 1024 * 1024,
6743            "the arena is several segments: {full}"
6744        );
6745
6746        // Two megabytes under what it is holding, which is one segment's worth,
6747        // so getting there means giving a whole segment back and not just
6748        // dropping a few records.
6749        let limit = full - 2 * 1024 * 1024;
6750        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
6751        f.run(&[
6752            b"CONFIG",
6753            b"SET",
6754            b"maxmemory",
6755            limit.to_string().as_bytes(),
6756        ]);
6757
6758        // Writes keep working the whole way down. The budget means one command
6759        // does not do it all, so this runs until the server has settled and
6760        // checks that nothing was refused on the way.
6761        for i in 0..2000u32 {
6762            let k = format!("new:{i:08}");
6763            assert_eq!(
6764                f.run(&[b"SET", k.as_bytes(), &val]),
6765                "+OK\r\n",
6766                "write {i} was refused"
6767            );
6768            f.server.refresh_memory();
6769            if f.server.memory_bytes() <= limit {
6770                break;
6771            }
6772        }
6773        assert!(
6774            f.server.memory_bytes() <= limit,
6775            "it never got under: {} against {limit}",
6776            f.server.memory_bytes()
6777        );
6778        let info = f.run(&[b"INFO", b"stats"]);
6779        assert!(!info.contains("evicted_keys:0"), "{info}");
6780        assert!(
6781            f.run(&[b"DBSIZE"]) != ":0\r\n",
6782            "and it did not empty the database to get there"
6783        );
6784    }
6785
6786    /// Not under Miri. Every round is eleven commands over six collections
6787    /// holding two hundred byte values, which is a third of a second each
6788    /// interpreted, and the rounds cannot come down far: one in seven takes an
6789    /// entry back out, so under about a hundred and seventy of them the
6790    /// collections never reach the hundred and twenty eight entries where the
6791    /// small representations give up and become the big ones, and a
6792    /// representation changing under the running total is one of the five
6793    /// things this is here to watch. What is left is an hour, for an accounting
6794    /// claim rather than a safety one, and the commands it sends are sent a few
6795    /// at a time by the tests around it.
6796    #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
6797    #[test]
6798    fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
6799        // The limit is judged against a number kept as the collections move,
6800        // rather than found by asking all of them, and the two have to be the
6801        // same number or the limit is enforced against a fiction. This does the
6802        // things that move it, which is growing a collection, shrinking one,
6803        // changing its representation, deleting it and reusing its slot, across
6804        // all five types, and checks the two against each other as it goes.
6805        let mut f = Fixture::new();
6806        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6807        let big = vec![b'v'; 200];
6808
6809        for i in 0..400u32 {
6810            let n = i.to_string();
6811            let n = n.as_bytes();
6812            f.run(&[b"SADD", b"s", n]);
6813            f.run(&[b"SADD", b"s2", &big]);
6814            f.run(&[b"HSET", b"h", n, &big]);
6815            f.run(&[b"RPUSH", b"l", &big]);
6816            f.run(&[b"ZADD", b"z", n, n]);
6817            f.run(&[b"ARSET", b"a", n, &big]);
6818            if i % 7 == 0 {
6819                f.run(&[b"SREM", b"s", n]);
6820                f.run(&[b"HDEL", b"h", n]);
6821                f.run(&[b"LPOP", b"l"]);
6822                f.run(&[b"ZREM", b"z", n]);
6823                f.run(&[b"ARDEL", b"a", n]);
6824            }
6825            if i % 53 == 0 {
6826                // Every type deleted and made again, so a slot goes on the free
6827                // list and comes back holding something else.
6828                f.run(&[b"DEL", b"s2"]);
6829            }
6830            assert_eq!(
6831                f.server.settled_memory(),
6832                f.server.memory_bytes(),
6833                "after round {i}"
6834            );
6835        }
6836
6837        // The run has to have built something, or the two numbers agreeing is
6838        // two zeroes agreeing.
6839        assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
6840        assert!(
6841            f.server.memory_bytes() > 512 * 1024,
6842            "{}",
6843            f.server.memory_bytes()
6844        );
6845
6846        // And it survives the collections going away entirely.
6847        f.run(&[b"FLUSHALL"]);
6848        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6849    }
6850
6851    #[test]
6852    fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
6853        // A server with no limit does not keep the running total, so setting a
6854        // limit on a database that is already full has to start it from a walk.
6855        // If it did not, the first reading would be zero and the server would
6856        // think it had all the room in the world.
6857        let mut f = Fixture::new();
6858        for i in 0..200u32 {
6859            let n = i.to_string();
6860            f.run(&[b"SADD", b"s", n.as_bytes()]);
6861            f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
6862        }
6863        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6864        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6865
6866        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6867        for i in 200..400u32 {
6868            let n = i.to_string();
6869            f.run(&[b"SADD", b"s", n.as_bytes()]);
6870        }
6871        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6872        assert_eq!(
6873            f.server.settled_memory(),
6874            f.server.memory_bytes(),
6875            "the writes it was not watching are in the number it started from"
6876        );
6877    }
6878
6879    #[test]
6880    fn evicted_keys_and_expired_keys_are_different_numbers() {
6881        let mut f = Fixture::new();
6882        // Nothing has been evicted and nothing can be under the default policy,
6883        // so this stays at zero while the other one moves.
6884        f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
6885        f.server.advance_clock_ms(20);
6886        f.run(&[b"GET", b"gone"]);
6887        let info = f.run(&[b"INFO", b"stats"]);
6888        assert!(info.contains("expired_keys:1"), "{info}");
6889        assert!(info.contains("evicted_keys:0"), "{info}");
6890    }
6891
6892    #[test]
6893    fn the_object_subcommands_follow_the_policy() {
6894        let mut f = Fixture::new();
6895        f.run(&[b"SET", b"s", b"v"]);
6896        // Under the default the clock is kept and the counter is not, and under
6897        // an LFU policy it is the other way round. Each subcommand refuses on
6898        // the side where its reading of the three bytes means nothing.
6899        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6900        assert!(
6901            f.run(&[b"OBJECT", b"FREQ", b"s"])
6902                .starts_with("-ERR An LFU maxmemory policy is not selected"),
6903        );
6904
6905        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
6906        assert!(
6907            f.run(&[b"OBJECT", b"IDLETIME", b"s"])
6908                .starts_with("-ERR An LFU maxmemory policy is selected"),
6909        );
6910        // The key was written under a clock policy, so what comes back is that
6911        // clock read as a counter. It is a number and not an error, which is the
6912        // point: switching at runtime does not invalidate anything, it only makes
6913        // the old field mean something else until the key is used again.
6914        assert!(
6915            f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
6916            "FREQ should answer under an LFU policy"
6917        );
6918    }
6919
6920    #[test]
6921    fn object_says_which_rung_of_the_ladder_a_key_is_on() {
6922        let mut f = Fixture::new();
6923        f.run(&[b"SET", b"s", b"hello"]);
6924        f.run(&[b"SET", b"n", b"123"]);
6925        f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
6926        f.run(&[b"SADD", b"ss", b"a", b"b"]);
6927        f.run(&[b"HSET", b"h", b"f", b"v"]);
6928        for (key, want) in [
6929            (b"s".as_slice(), "embstr"),
6930            (b"n", "int"),
6931            (b"si", "intset"),
6932            (b"ss", "listpack"),
6933            (b"h", "listpack"),
6934        ] {
6935            let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
6936            assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
6937        }
6938
6939        // A field deadline widens the blob rather than promoting it, and this
6940        // is the only place a client can see that happen.
6941        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
6942        assert_eq!(
6943            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6944            "$10\r\nlistpackex\r\n"
6945        );
6946
6947        assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
6948        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6949        assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
6950    }
6951
6952    #[test]
6953    fn object_answers_nil_for_a_key_that_is_not_there() {
6954        let mut f = Fixture::new();
6955        for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
6956            assert_eq!(
6957                f.run(&[b"OBJECT", sub, b"nokey"]),
6958                "$-1\r\n",
6959                "a nil and not an error, which is what 8.10.1 does"
6960            );
6961        }
6962        // And the key is looked up before FREQ has its complaint, so the
6963        // complaint only reaches a key that exists.
6964        f.run(&[b"SET", b"s", b"v"]);
6965        assert!(
6966            f.run(&[b"OBJECT", b"FREQ", b"s"])
6967                .starts_with("-ERR An LFU maxmemory policy is not"),
6968        );
6969        assert_eq!(
6970            f.run(&[b"OBJECT", b"NOPE", b"s"]),
6971            "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
6972        );
6973        assert_eq!(
6974            f.run(&[b"OBJECT", b"ENCODING"]),
6975            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
6976        );
6977        assert_eq!(
6978            f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
6979            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
6980        );
6981        assert_eq!(
6982            f.run(&[b"OBJECT"]),
6983            "-ERR wrong number of arguments for 'object' command\r\n"
6984        );
6985    }
6986
6987    #[test]
6988    fn config_moves_the_ladder_and_object_encoding_agrees() {
6989        let mut f = Fixture::new();
6990        assert_eq!(
6991            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6992            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
6993            "512 and not the 128 everyone remembers, which is what 8.10.1 says"
6994        );
6995        // The old spelling is the same number under a different name, and a
6996        // glob that catches both sends both.
6997        assert_eq!(
6998            f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
6999            "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
7000        );
7001        assert!(
7002            f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
7003                .starts_with("*8\r\n")
7004        );
7005        assert!(
7006            f.run(&[b"CONFIG", b"GET", b"set-max-*"])
7007                .starts_with("*6\r\n")
7008        );
7009
7010        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
7011        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
7012
7013        assert_eq!(
7014            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
7015            "+OK\r\n",
7016            "written under the old name and read back under the new one"
7017        );
7018        assert_eq!(
7019            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7020            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
7021        );
7022        assert_eq!(
7023            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7024            "$8\r\nlistpack\r\n",
7025            "the hash that already exists is left exactly where it was"
7026        );
7027        f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
7028        assert_eq!(
7029            f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
7030            "$9\r\nhashtable\r\n",
7031            "and the next one built goes straight to a table"
7032        );
7033
7034        // The set has three of these and all three move.
7035        f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
7036        f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
7037        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
7038        f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
7039        f.run(&[b"SADD", b"s2", b"abcdefgh"]);
7040        assert_eq!(
7041            f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
7042            "$9\r\nhashtable\r\n"
7043        );
7044    }
7045
7046    #[test]
7047    fn config_set_takes_all_of_the_ladder_or_none_of_it() {
7048        let mut f = Fixture::new();
7049        assert_eq!(
7050            f.run(&[
7051                b"CONFIG",
7052                b"SET",
7053                b"hash-max-listpack-entries",
7054                b"7",
7055                b"set-max-listpack-entries",
7056                b"abc"
7057            ]),
7058            "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
7059        );
7060        assert_eq!(
7061            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7062            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7063            "the pair in front of the bad one did not go in"
7064        );
7065        // The name in the complaint is the one that was typed, so the old
7066        // spelling comes back as the old spelling.
7067        assert_eq!(
7068            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
7069            "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
7070        );
7071        assert_eq!(
7072            f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
7073            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
7074        );
7075        // A number past what an i64 holds is the parse complaint and not the
7076        // range one, which is upstream reading it before it checks it.
7077        assert_eq!(
7078            f.run(&[
7079                b"CONFIG",
7080                b"SET",
7081                b"set-max-intset-entries",
7082                b"99999999999999999999"
7083            ]),
7084            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
7085        );
7086        assert_eq!(
7087            f.run(&[
7088                b"CONFIG",
7089                b"SET",
7090                b"set-max-intset-entries",
7091                b"9223372036854775807"
7092            ]),
7093            "+OK\r\n"
7094        );
7095    }
7096
7097    #[test]
7098    fn a_setting_moved_on_one_database_moved_on_all_of_them() {
7099        let mut f = Fixture::new();
7100        f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
7101        f.run(&[b"SELECT", b"3"]);
7102        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7103        assert_eq!(
7104            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7105            "$9\r\nhashtable\r\n",
7106            "these are one server wide number in Redis, whatever a Keyspace carries"
7107        );
7108    }
7109
7110    #[test]
7111    fn info_reports_the_numbers_it_can_stand_behind() {
7112        let mut f = Fixture::new();
7113        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
7114        let all = f.run(&[b"INFO"]);
7115        assert!(all.contains("redis_version:8.8.0"), "{all}");
7116        assert!(
7117            all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
7118            "{all}"
7119        );
7120        assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
7121        assert!(all.contains("role:master"), "{all}");
7122        // One section is one section.
7123        let clients = f.run(&[b"INFO", b"clients"]);
7124        assert!(clients.contains("connected_clients:0"), "{clients}");
7125        assert!(!clients.contains("redis_version"), "{clients}");
7126        assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
7127    }
7128
7129    /// The sections a bare `INFO` gives back, and the ones you have to ask for.
7130    ///
7131    /// This is Redis's `unit/info-command` written against the fixture. Every
7132    /// assertion in it is one of theirs, in their order, and the two fields it
7133    /// turns on are the two that suite was failing on: `master_repl_offset`,
7134    /// which is in the default set, and `rejected_calls`, which is not.
7135    #[test]
7136    fn commandstats_is_asked_for_and_replication_is_not() {
7137        let mut f = Fixture::new();
7138        for arg in ["", "all", "default", "everything"] {
7139            let info = if arg.is_empty() {
7140                f.run(&[b"INFO"])
7141            } else {
7142                f.run(&[b"INFO", arg.as_bytes()])
7143            };
7144            assert!(info.contains("redis_version"), "{arg}: {info}");
7145            assert!(info.contains("used_cpu_user"), "{arg}: {info}");
7146            assert!(info.contains("used_memory"), "{arg}: {info}");
7147            assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
7148            let asked = arg == "all" || arg == "everything";
7149            assert_eq!(
7150                info.contains("rejected_calls"),
7151                asked,
7152                "{arg} should{} carry the command counters: {info}",
7153                if asked { "" } else { " not" }
7154            );
7155        }
7156
7157        let cpu = f.run(&[b"INFO", b"cpu"]);
7158        assert!(cpu.contains("used_cpu_user"), "{cpu}");
7159        assert!(!cpu.contains("used_memory"), "{cpu}");
7160
7161        // Their case, to make the point that a section name is not case
7162        // sensitive any more than a command name is.
7163        let stats = f.run(&[b"INFO", b"commandSTATS"]);
7164        assert!(!stats.contains("used_memory"), "{stats}");
7165        assert!(stats.contains("rejected_calls"), "{stats}");
7166
7167        // Two sections named, and neither of them pulls in a third.
7168        let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
7169        assert!(pair.contains("used_cpu_user"), "{pair}");
7170        assert!(!pair.contains("master_repl_offset"), "{pair}");
7171
7172        let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
7173        assert!(with_all.contains("used_memory"), "{with_all}");
7174        assert!(with_all.contains("master_repl_offset"), "{with_all}");
7175        assert!(with_all.contains("rejected_calls"), "{with_all}");
7176        // A section named twice is still written once.
7177        assert_eq!(
7178            with_all.matches("used_cpu_user_children").count(),
7179            1,
7180            "{with_all}"
7181        );
7182
7183        let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
7184        assert!(with_default.contains("used_memory"), "{with_default}");
7185        assert!(
7186            with_default.contains("master_repl_offset"),
7187            "{with_default}"
7188        );
7189        assert!(!with_default.contains("rejected_calls"), "{with_default}");
7190        assert_eq!(
7191            with_default.matches("used_cpu_user_children").count(),
7192            1,
7193            "{with_default}"
7194        );
7195    }
7196
7197    /// The memory section says what this process may use, not what the machine
7198    /// has.
7199    ///
7200    /// The distinction is the whole point of it. A server inside a container
7201    /// that reports the host's memory is a server whose operator sizes it for
7202    /// memory it will be killed for touching, so all three numbers are there:
7203    /// what the machine has, what the cgroup allows, and the quarter of the
7204    /// tighter one that pools are sized from.
7205    #[test]
7206    fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
7207        let mut f = Fixture::new();
7208        let info = f.run(&[b"INFO", b"memory"]);
7209        for field in [
7210            "total_system_memory:",
7211            "mem_cgroup_limit:",
7212            "mem_limit:",
7213            "mem_budget:",
7214        ] {
7215            assert!(info.contains(field), "no {field} in {info}");
7216        }
7217
7218        let field = |name: &str| -> u64 {
7219            info.lines()
7220                .find_map(|l| l.strip_prefix(name))
7221                .unwrap_or_else(|| panic!("no {name} in {info}"))
7222                .trim()
7223                .parse()
7224                .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
7225        };
7226        let limit = field("mem_limit:");
7227        assert_eq!(field("mem_budget:"), limit / 4, "{info}");
7228        // Zero means there is no limit to report, which is a real answer on a
7229        // machine with no cgroups and no way to ask how big it is.
7230        if limit != 0 {
7231            let host = field("total_system_memory:");
7232            let cgroup = field("mem_cgroup_limit:");
7233            assert!(
7234                limit == host || limit == cgroup,
7235                "the limit came from neither number: {info}"
7236            );
7237        }
7238    }
7239
7240    /// The three counters, each on the path that raises it.
7241    ///
7242    /// `calls` on a command that worked, `failed_calls` on one that ran and
7243    /// answered with an error, and `rejected_calls` on one that never ran at
7244    /// all. The last two are the pair that is easy to collapse into one number
7245    /// and that Redis keeps apart, because a client sending the wrong number of
7246    /// arguments and a client asking for a list element that is not there are
7247    /// not the same problem.
7248    #[test]
7249    fn a_command_counts_what_it_did_separately_from_what_it_refused() {
7250        let mut f = Fixture::new();
7251        f.run(&[b"SET", b"k", b"v"]);
7252        f.run(&[b"SET", b"k", b"w"]);
7253        // Ran, and answered with an error, because `k` is not a list.
7254        f.run(&[b"LPUSH", b"k", b"x"]);
7255        // Never ran: `LPUSH` takes at least three arguments.
7256        f.run(&[b"LPUSH", b"k"]);
7257
7258        let stats = f.run(&[b"INFO", b"commandstats"]);
7259        assert!(
7260            stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
7261            "{stats}"
7262        );
7263        assert!(
7264            stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
7265            "{stats}"
7266        );
7267        assert!(
7268            !stats.contains("cmdstat_zadd"),
7269            "a command nobody has sent has no row: {stats}"
7270        );
7271    }
7272
7273    /// A cache that writes with a deadline and never reads back used to hold
7274    /// every key it had ever written, because lazy expiry needs somebody to walk
7275    /// past a key before it can reclaim it and nobody ever did.
7276    #[test]
7277    fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
7278        // Four thousand keys is four thousand trips through dispatch, and what
7279        // Miri charges for is trips rather than keys, so this was over five
7280        // minutes there. An eighth of each keeps everything the test is about,
7281        // which is three keys with a deadline for every one without and a
7282        // sweep that has to reclaim all of the first kind and none of the
7283        // second.
7284        let (dead, live) = if cfg!(miri) {
7285            (375, 125)
7286        } else {
7287            (3_000, 1_000)
7288        };
7289        let mut f = Fixture::new();
7290        for i in 0..dead {
7291            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7292        }
7293        for i in 0..live {
7294            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7295        }
7296        let all = format!(":{}\r\n", dead + live);
7297        assert_eq!(f.run(&[b"DBSIZE"]), all);
7298        f.advance(100);
7299        assert_eq!(
7300            f.run(&[b"DBSIZE"]),
7301            all,
7302            "DBSIZE counts records and nothing has read past the dead ones yet"
7303        );
7304
7305        // What the shard loop does, one slice at a time.
7306        let rest = format!(":{live}\r\n");
7307        let mut spent = 0;
7308        for _ in 0..2_000 {
7309            spent += f.server.expire_step(4096);
7310            if f.run(&[b"DBSIZE"]) == rest {
7311                break;
7312            }
7313        }
7314        assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
7315        assert!(
7316            f.run(&[b"INFO", b"stats"])
7317                .contains(&format!("expired_keys:{dead}"))
7318        );
7319        for i in 0..live {
7320            assert_eq!(
7321                f.run(&[b"GET", format!("k{i}").as_bytes()]),
7322                "$1\r\nv\r\n",
7323                "it took a key that had no deadline"
7324            );
7325        }
7326    }
7327
7328    #[test]
7329    fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
7330        // The keys are only here so that the database the sweep walks is not an
7331        // empty one. Two hundred of them fills as many slots as a sweep looks
7332        // at and is a tenth of the interpreted work.
7333        let n = if cfg!(miri) { 200 } else { 2_000 };
7334        let mut f = Fixture::new();
7335        for i in 0..n {
7336            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7337        }
7338        assert_eq!(f.server.expire_step(4096), 0);
7339        // And one database having them does not make the other fifteen pay.
7340        f.run(&[b"SELECT", b"3"]);
7341        f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
7342        f.advance(100);
7343        for _ in 0..64 {
7344            f.server.expire_step(4096);
7345        }
7346        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7347        f.run(&[b"SELECT", b"0"]);
7348        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
7349        assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
7350    }
7351
7352    /// The gate, which is what stops a maintenance slice that runs every hundred
7353    /// nanoseconds from drawing a sample every hundred nanoseconds.
7354    #[test]
7355    fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
7356        let mut f = Fixture::new();
7357        for i in 0..500u32 {
7358            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7359        }
7360        f.advance(100);
7361        let at = f.server.striped(0).now_ms();
7362        f.server.set_clock_ms(at);
7363        // A small budget, so that one slice cannot finish the job and a second
7364        // one having nothing to do would mean the gate and not an empty
7365        // database.
7366        assert!(f.server.expire_slice(8) > 0, "the first one works");
7367        for _ in 0..1_000 {
7368            assert_eq!(
7369                f.server.expire_slice(8),
7370                0,
7371                "the millisecond has not moved and neither should this"
7372            );
7373        }
7374        assert!(
7375            f.server.striped(0).expires() > 400,
7376            "there is plenty left to take"
7377        );
7378        f.server.set_clock_ms(at + 1);
7379        assert!(f.server.expire_slice(8) > 0, "and then it goes again");
7380    }
7381
7382    /// `expires=` used to be a hardcoded zero, which meant a dashboard watching
7383    /// how much of a cache is volatile was reading a constant.
7384    #[test]
7385    fn info_keyspace_counts_the_keys_that_have_a_deadline() {
7386        let mut f = Fixture::new();
7387        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
7388        assert!(
7389            f.run(&[b"INFO", b"keyspace"])
7390                .contains("db0:keys=3,expires=0"),
7391            "none of them has one yet"
7392        );
7393        f.run(&[b"EXPIRE", b"a", b"1000"]);
7394        f.run(&[b"EXPIRE", b"b", b"1000"]);
7395        let two = f.run(&[b"INFO", b"keyspace"]);
7396        assert!(two.contains("db0:keys=3,expires=2"), "{two}");
7397        f.run(&[b"PERSIST", b"a"]);
7398        f.run(&[b"DEL", b"b"]);
7399        let none = f.run(&[b"INFO", b"keyspace"]);
7400        assert!(none.contains("db0:keys=2,expires=0"), "{none}");
7401
7402        // Each database answers for itself, the way Redis reports it.
7403        f.run(&[b"SELECT", b"1"]);
7404        f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
7405        let both = f.run(&[b"INFO", b"keyspace"]);
7406        assert!(both.contains("db0:keys=2,expires=0"), "{both}");
7407        assert!(both.contains("db1:keys=1,expires=1"), "{both}");
7408    }
7409
7410    /// Not under Miri, which reads a zero on purpose because it has no
7411    /// `getrusage` to call, so the second half of this would burn a billion
7412    /// interpreted multiplications waiting for a number that is never going to
7413    /// move. The first half, that the section is there and has the fields Redis
7414    /// clients look for, is checked by the `INFO` tests above as well, and
7415    /// those do run there.
7416    #[cfg(unix)]
7417    #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
7418    #[test]
7419    fn info_cpu_reports_processor_time_that_was_really_measured() {
7420        let mut f = Fixture::new();
7421        let cpu = f.run(&[b"INFO", b"cpu"]);
7422        assert!(cpu.contains("# CPU"), "{cpu}");
7423        // Redis's unit/info-command asks for this one by name in three tests.
7424        assert!(cpu.contains("used_cpu_user:"), "{cpu}");
7425        assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
7426        assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
7427        assert!(!cpu.contains("redis_version"), "{cpu}");
7428
7429        // It is a measurement and not a constant, so it goes up when work
7430        // happens. A tight loop rather than a sleep, because sleeping is the
7431        // one thing that does not move this number.
7432        let before = used_cpu_user(&cpu);
7433        let mut n = 0u64;
7434        let mut rounds = 0;
7435        while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
7436            for i in 0..1_000_000u64 {
7437                n = n.wrapping_add(i.wrapping_mul(i));
7438            }
7439            rounds += 1;
7440            // A bound rather than a spin, so a platform where this number does
7441            // not move fails here instead of hanging. Even a clock with whole
7442            // millisecond granularity gets there in the first round or two.
7443            assert!(rounds < 1_000, "cpu time never moved, n is {n}");
7444        }
7445    }
7446
7447    /// Pull `used_cpu_user` back out of an `INFO cpu` reply.
7448    #[cfg(unix)]
7449    fn used_cpu_user(info: &str) -> f64 {
7450        info.lines()
7451            .find_map(|l| l.strip_prefix("used_cpu_user:"))
7452            .expect("no used_cpu_user in the reply")
7453            .trim()
7454            .parse()
7455            .expect("used_cpu_user is not a number")
7456    }
7457
7458    /// The safety net under the rule that a body checks its arguments before
7459    /// it writes anything. `MGET` writes its array header first and then reads
7460    /// each key, so if a later argument could fail the header would already be
7461    /// out. Nothing in the string group does that today and this is what would
7462    /// catch the first one that did.
7463    #[test]
7464    fn a_command_that_fails_leaves_nothing_half_written() {
7465        let mut f = Fixture::new();
7466        let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
7467        assert_eq!(reply, "-ERR offset is out of range\r\n");
7468        assert!(!reply.contains(':'), "no integer went out in front of it");
7469    }
7470
7471    #[test]
7472    fn quit_answers_first_and_closes_after() {
7473        let mut f = Fixture::new();
7474        let (flow, reply) = f.flow(&[b"QUIT"]);
7475        assert_eq!(reply, "+OK\r\n");
7476        assert_eq!(flow, Flow::Close);
7477    }
7478
7479    /// A server that has not been asked to stop is not stopping, and one that
7480    /// has says so without writing anything back.
7481    ///
7482    /// The empty reply is the point. Redis answers nothing at all here and the
7483    /// client sees the socket close, and an `OK` would be a promise from a
7484    /// process that is about to not exist.
7485    #[test]
7486    fn shutdown_writes_nothing_and_sets_the_flag() {
7487        let mut f = Fixture::new();
7488        assert!(!f.server.stopping(), "nobody has asked yet");
7489
7490        let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
7491        assert_eq!(reply, "");
7492        assert_eq!(flow, Flow::Close);
7493        assert!(f.server.stopping());
7494    }
7495
7496    /// Every flag combination 8.10.1 takes, and every one it refuses.
7497    ///
7498    /// The refusals are the half worth pinning down. `SAVE` and `NOSAVE`
7499    /// contradict each other, `ABORT` says to do nothing so it cannot be
7500    /// combined with a word about how to do it, and repeating any one of them
7501    /// is fine. All of it was read off a running 8.10.1 rather than worked out
7502    /// from the documentation, which does not say.
7503    #[test]
7504    fn shutdown_takes_the_flags_redis_takes() {
7505        for flags in [
7506            &[b"NOSAVE".as_slice()][..],
7507            &[b"SAVE"],
7508            &[b"NOW"],
7509            &[b"FORCE"],
7510            &[b"nosave"],
7511            &[b"NOW", b"NOW"],
7512            &[b"SAVE", b"SAVE"],
7513            &[b"NOSAVE", b"NOW", b"FORCE"],
7514        ] {
7515            let mut f = Fixture::new();
7516            let mut parts = vec![b"SHUTDOWN".as_slice()];
7517            parts.extend_from_slice(flags);
7518            let (flow, reply) = f.flow(&parts);
7519            assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
7520            assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
7521            assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
7522        }
7523
7524        for flags in [
7525            &[b"BOGUS".as_slice()][..],
7526            &[b"SAVE", b"NOSAVE"],
7527            &[b"NOSAVE", b"SAVE"],
7528            &[b"ABORT", b"NOW"],
7529            &[b"NOSAVE", b"ABORT"],
7530            &[b"NOW", b"FORCE", b"ABORT"],
7531        ] {
7532            let mut f = Fixture::new();
7533            let mut parts = vec![b"SHUTDOWN".as_slice()];
7534            parts.extend_from_slice(flags);
7535            assert_eq!(
7536                f.run(&parts),
7537                "-ERR syntax error\r\n",
7538                "SHUTDOWN {flags:?} was accepted"
7539            );
7540            assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
7541        }
7542    }
7543
7544    /// `ABORT` has nothing to call off, ever.
7545    ///
7546    /// A shutdown here is decided and done inside one turn of the loop, so
7547    /// there is no window in which one is in progress. That makes Redis's
7548    /// message for a cancel with nothing to cancel the right answer every time
7549    /// rather than only when nothing happens to be pending. Two `ABORT`s is
7550    /// still one `ABORT`, which is what 8.10.1 does.
7551    #[test]
7552    fn shutdown_abort_never_has_anything_to_abort() {
7553        let mut f = Fixture::new();
7554        for parts in [
7555            &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
7556            &[b"SHUTDOWN", b"ABORT", b"ABORT"],
7557        ] {
7558            assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
7559            assert!(!f.server.stopping(), "an abort stopped the server");
7560        }
7561    }
7562
7563    /// A fixture whose server writes into a directory of its own.
7564    ///
7565    /// Every test here really writes files, because the whole point of the
7566    /// command is the files and a backup that is only a state machine would
7567    /// pass a test suite and fail the first person who tried to restore one.
7568    /// The directory carries the test's name so that the suite can run its
7569    /// tests in parallel the way it always does.
7570    struct Backups {
7571        f: Fixture,
7572        dir: PathBuf,
7573    }
7574
7575    impl Backups {
7576        fn new(name: &str) -> Backups {
7577            let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
7578            let _ = std::fs::remove_dir_all(&dir);
7579            std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
7580            let mut f = Fixture::new();
7581            f.server.set_dir(dir.clone());
7582            Backups { f, dir }
7583        }
7584
7585        fn run(&mut self, parts: &[&[u8]]) -> String {
7586            self.f.run(parts)
7587        }
7588
7589        /// The names in `backupdir`, sorted, so a test can say what is on disk.
7590        fn files(&self) -> Vec<String> {
7591            let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
7592                Ok(entries) => entries
7593                    .filter_map(|e| e.ok())
7594                    .map(|e| e.file_name().to_string_lossy().into_owned())
7595                    .collect(),
7596                Err(_) => Vec::new(),
7597            };
7598            names.sort();
7599            names
7600        }
7601
7602        fn read(&self, name: &str) -> Vec<u8> {
7603            std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
7604        }
7605    }
7606
7607    impl Drop for Backups {
7608        fn drop(&mut self) {
7609            let _ = std::fs::remove_dir_all(&self.dir);
7610        }
7611    }
7612
7613    /// The four states and the moves between them, in the order a client walks
7614    /// them, with the files checked at every step.
7615    #[test]
7616    fn backup_walks_the_states_the_reference_walks() {
7617        let mut b = Backups::new("states");
7618        let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
7619
7620        assert!(status(&mut b).contains("idle"));
7621        assert!(b.files().is_empty(), "an idle server has written a backup");
7622
7623        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7624        assert!(status(&mut b).contains("incrementing"));
7625        assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
7626
7627        assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
7628        assert!(status(&mut b).contains("sealed"));
7629        assert_eq!(
7630            b.files(),
7631            [
7632                "appendonly.aof.1.base.rdb",
7633                "appendonly.aof.1.incr.aof",
7634                "appendonly.aof.manifest",
7635            ]
7636        );
7637
7638        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7639        assert!(status(&mut b).contains("idle"));
7640        assert!(b.files().is_empty(), "cleanup left something behind");
7641    }
7642
7643    /// Every move that is refused, in the reference's words.
7644    #[test]
7645    fn backup_refuses_the_moves_the_reference_refuses() {
7646        let mut b = Backups::new("refusals");
7647
7648        assert_eq!(
7649            b.run(&[b"BACKUP", b"SEAL"]),
7650            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7651        );
7652        assert_eq!(
7653            b.run(&[b"BACKUP", b"ABORT"]),
7654            "-ERR No backup in progress\r\n"
7655        );
7656        // Cleanup from idle is not an error, it is a way of saying there was
7657        // nothing to clean up.
7658        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7659
7660        b.run(&[b"BACKUP", b"START"]);
7661        assert_eq!(
7662            b.run(&[b"BACKUP", b"START"]),
7663            "-ERR A backup is already in progress, ABORT it first\r\n"
7664        );
7665        assert_eq!(
7666            b.run(&[b"BACKUP", b"CLEANUP"]),
7667            "-ERR Backup is in progress\r\n"
7668        );
7669
7670        b.run(&[b"BACKUP", b"SEAL"]);
7671        assert_eq!(
7672            b.run(&[b"BACKUP", b"START"]),
7673            "-ERR A sealed backup exists, CLEANUP it first\r\n"
7674        );
7675        assert_eq!(
7676            b.run(&[b"BACKUP", b"SEAL"]),
7677            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7678        );
7679        assert_eq!(
7680            b.run(&[b"BACKUP", b"ABORT"]),
7681            "-ERR No backup in progress\r\n"
7682        );
7683    }
7684
7685    /// An abort takes the base file away and leaves a state saying who did it.
7686    ///
7687    /// The next backup takes the next sequence number rather than reusing the
7688    /// one whose files were just thrown away, so a directory somebody copied a
7689    /// half finished backup out of cannot end up with two different files under
7690    /// one name.
7691    #[test]
7692    fn backup_abort_removes_the_file_and_says_who_did_it() {
7693        let mut b = Backups::new("abort");
7694        b.run(&[b"BACKUP", b"START"]);
7695        assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
7696
7697        let status = b.run(&[b"BACKUP", b"STATUS"]);
7698        assert!(status.contains("failed"), "{status}");
7699        assert!(status.contains("aborted by user"), "{status}");
7700        assert!(b.files().is_empty(), "abort left the base file behind");
7701        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7702
7703        // A start from failed works, and is the second backup.
7704        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7705        assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
7706        let status = b.run(&[b"BACKUP", b"STATUS"]);
7707        assert!(status.contains("incrementing"), "{status}");
7708        assert!(!status.contains("aborted"), "the old error was kept");
7709    }
7710
7711    /// `LIST` names nothing, then one file, then three, and they are absolute.
7712    #[test]
7713    fn backup_list_names_the_files_that_are_pinned_so_far() {
7714        let mut b = Backups::new("list");
7715        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7716
7717        b.run(&[b"BACKUP", b"START"]);
7718        let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
7719        let base = base.to_string_lossy().into_owned();
7720        assert_eq!(
7721            b.run(&[b"BACKUP", b"LIST"]),
7722            format!("*1\r\n${}\r\n{base}\r\n", base.len())
7723        );
7724
7725        b.run(&[b"BACKUP", b"SEAL"]);
7726        let listed = b.run(&[b"BACKUP", b"LIST"]);
7727        assert!(listed.starts_with("*3\r\n"), "{listed}");
7728        // The order is the manifest's order, base then incremental then the
7729        // manifest itself, which is the order a restore needs them in.
7730        let names: Vec<&str> = listed
7731            .lines()
7732            .filter(|l| l.starts_with('/') || l.contains(":\\"))
7733            .collect();
7734        assert_eq!(names.len(), 3, "{listed}");
7735        assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
7736        assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
7737        assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
7738    }
7739
7740    /// The base file is the dataset as it was at `START` and not at `SEAL`.
7741    ///
7742    /// That is D-46 and it is the one thing about this a client can notice, so
7743    /// it is pinned here rather than left to be discovered by whoever restores
7744    /// one. The incremental file is empty for the same reason: there is no
7745    /// append only log underneath this server to copy the writes in between out
7746    /// of.
7747    #[test]
7748    fn a_backup_holds_the_dataset_as_it_was_at_start() {
7749        let mut b = Backups::new("contents");
7750        b.run(&[b"SET", b"bk", b"v1"]);
7751        b.run(&[b"BACKUP", b"START"]);
7752        b.run(&[b"SET", b"bk", b"v2"]);
7753        b.run(&[b"BACKUP", b"SEAL"]);
7754
7755        let base = b.read("appendonly.aof.1.base.rdb");
7756        assert!(base.starts_with(b"REDIS"), "not an RDB file");
7757        assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
7758        assert!(
7759            !base.windows(2).any(|w| w == b"v2"),
7760            "the base file moved on after START"
7761        );
7762        // The aux field a loader acts on, and the one that says this file is
7763        // the base of an append only file rather than a standalone dump. Its
7764        // value is the one byte string 1, which the encoder writes as an
7765        // integer the way a real server writes it.
7766        let at = base
7767            .windows(8)
7768            .position(|w| w == b"aof-base")
7769            .expect("no aof-base aux field");
7770        assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
7771
7772        assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
7773        assert_eq!(
7774            String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
7775            "file appendonly.aof.1.base.rdb seq 1 type b\n\
7776             file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
7777        );
7778    }
7779
7780    /// `STATUS` is a map of four pairs on RESP3 and the same pairs flat on
7781    /// RESP2, which is what every other map shaped reply in this server does.
7782    #[test]
7783    fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
7784        let mut b = Backups::new("status");
7785        b.f.server.set_clock_ms(1_700_000_000_000);
7786
7787        assert_eq!(
7788            b.run(&[b"BACKUP", b"STATUS"]),
7789            "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
7790             $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
7791        );
7792
7793        b.f.out = Out::new(Proto::Resp3);
7794        b.run(&[b"BACKUP", b"START"]);
7795        assert_eq!(
7796            b.run(&[b"BACKUP", b"STATUS"]),
7797            "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
7798             $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
7799        );
7800
7801        b.run(&[b"BACKUP", b"SEAL"]);
7802        let sealed = b.run(&[b"BACKUP", b"STATUS"]);
7803        assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
7804    }
7805
7806    /// A sealed backup that nobody cleans up goes away on its own once
7807    /// `backup-sealed-ttl` seconds have passed since the seal.
7808    #[test]
7809    fn a_sealed_backup_is_swept_away_after_the_timeout() {
7810        let mut b = Backups::new("ttl");
7811        b.f.server.set_clock_ms(1_000_000);
7812        assert_eq!(
7813            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
7814            "+OK\r\n"
7815        );
7816        b.run(&[b"BACKUP", b"START"]);
7817        b.run(&[b"BACKUP", b"SEAL"]);
7818
7819        // A minute short of the deadline, nothing happens.
7820        b.f.server.set_clock_ms(1_000_000 + 59_000);
7821        b.f.server.backup_expire();
7822        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7823        assert_eq!(b.files().len(), 3);
7824
7825        b.f.server.set_clock_ms(1_000_000 + 60_000);
7826        b.f.server.backup_expire();
7827        let status = b.run(&[b"BACKUP", b"STATUS"]);
7828        assert!(status.contains("idle"), "{status}");
7829        assert!(b.files().is_empty(), "the timeout left the files behind");
7830
7831        // Zero is the default and means a sealed backup is kept for ever.
7832        b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
7833        b.run(&[b"BACKUP", b"START"]);
7834        b.run(&[b"BACKUP", b"SEAL"]);
7835        b.f.server.set_clock_ms(9_000_000_000);
7836        b.f.server.backup_expire();
7837        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7838    }
7839
7840    /// The three settings around the command, read and written the way 8.10.1
7841    /// reads and writes them.
7842    #[test]
7843    fn the_backup_settings_behave_the_way_the_reference_does() {
7844        let mut b = Backups::new("config");
7845        let dir = b.dir.to_string_lossy().into_owned();
7846
7847        assert_eq!(
7848            b.run(&[b"CONFIG", b"GET", b"dir"]),
7849            format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
7850        );
7851        assert_eq!(
7852            b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
7853            "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
7854        );
7855        assert_eq!(
7856            b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
7857            "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
7858        );
7859
7860        // `dir` is a protected config, so it is refused even for the value it
7861        // already holds, and `backupdirname` is immutable.
7862        assert_eq!(
7863            b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
7864            "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
7865        );
7866        assert_eq!(
7867            b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
7868            "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
7869        );
7870        assert!(
7871            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
7872                .contains("argument couldn't be parsed into an integer")
7873        );
7874        assert!(
7875            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
7876                .contains("argument must be between 0 and 9223372036854775807 inclusive")
7877        );
7878    }
7879
7880    /// The help text, which has `HELP` in it twice because the reference's does.
7881    #[test]
7882    fn backup_help_is_the_text_the_reference_sends() {
7883        let mut f = Fixture::new();
7884        let help = f.run(&[b"BACKUP", b"HELP"]);
7885        assert!(help.starts_with("*17\r\n"), "{help}");
7886        assert!(
7887            help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
7888        );
7889        assert!(help.contains("+    Start a new backup into the configured 'backupdirname'.\r\n"));
7890        assert!(help.contains("+    Freeze the current backup (BASE + INCR + manifest).\r\n"));
7891        assert!(help.contains("+    Return this help.\r\n+HELP\r\n+    Print this help.\r\n"));
7892    }
7893
7894    /// What a mistyped `BACKUP` gets told.
7895    ///
7896    /// The arity error names `backup` where the reference names `backup|start`,
7897    /// which is D-46: the table reports one arity for the container the way the
7898    /// reference does, and the per subcommand table that would carry the better
7899    /// name is not built yet. Every subcommand is exactly two words, so nothing
7900    /// legal is refused by it.
7901    #[test]
7902    fn backup_refuses_what_it_cannot_read() {
7903        let mut f = Fixture::new();
7904        assert_eq!(
7905            f.run(&[b"BACKUP"]),
7906            "-ERR wrong number of arguments for 'backup' command\r\n"
7907        );
7908        assert_eq!(
7909            f.run(&[b"BACKUP", b"START", b"x"]),
7910            "-ERR wrong number of arguments for 'backup' command\r\n"
7911        );
7912        assert_eq!(
7913            f.run(&[b"BACKUP", b"NOPE"]),
7914            "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
7915        );
7916    }
7917
7918    #[test]
7919    fn the_command_counter_counts_every_command_including_the_bad_ones() {
7920        let mut f = Fixture::new();
7921        f.run(&[b"PING"]);
7922        f.run(&[b"NOPE"]);
7923        f.run(&[b"GET"]);
7924        assert_eq!(f.server.totals().commands, 3);
7925    }
7926
7927    #[test]
7928    fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
7929        let mut server = Server::new();
7930        server.set_threads(2);
7931        // A fresh server has every database on the turn's list, so start from
7932        // nothing to see the one mark arrive.
7933        server.mine().turn.store(0, Relaxed);
7934        server.locals[1].mark(1 << 9);
7935        server.collect_marks();
7936        assert!(server.mine().wanted(9));
7937        // And taken once rather than left to be taken again next turn.
7938        assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
7939    }
7940
7941    #[test]
7942    fn what_two_threads_counted_is_added_up_when_info_asks() {
7943        let mut server = Server::new();
7944        server.set_threads(2);
7945        // Written into the two sets by hand, because what is under test is the
7946        // adding up and not the claiming, and one test thread can only ever
7947        // claim one set.
7948        let ping = lookup(b"PING").expect("PING is a command");
7949        for (at, calls) in [(0, 2), (1, 3)] {
7950            let counters = &server.locals[at];
7951            for _ in 0..calls {
7952                counters.stats.commands.bump();
7953                counters.cmdstats.at(ping).calls.bump();
7954            }
7955            counters.stats.opened();
7956        }
7957        assert_eq!(server.totals().commands, 5);
7958        assert_eq!(server.totals().clients, 2);
7959        assert_eq!(server.totals().connections, 2);
7960        let rows: Vec<_> = server.command_stats().collect();
7961        assert_eq!(rows.len(), 1);
7962        assert_eq!(rows[0].0, "ping");
7963        assert_eq!(rows[0].1.calls, 5);
7964        // A reset takes the totals and leaves the open connections, which are
7965        // still open.
7966        server.reset_stats();
7967        assert_eq!(server.totals().commands, 0);
7968        assert_eq!(server.totals().connections, 0);
7969        assert_eq!(server.totals().clients, 2);
7970    }
7971
7972    #[test]
7973    fn the_parked_count_says_what_the_waiter_list_says() {
7974        let mut f = Fixture::new();
7975        assert_eq!(f.server.parked(), 0);
7976        for client in 1..=3u64 {
7977            f.session = Session::new(client);
7978            assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
7979        }
7980        assert_eq!(f.server.parked(), 3);
7981        assert_eq!(f.server.waiters().len(), 3);
7982
7983        // The three ways the list gets shorter, each of which has to move the
7984        // number with it, because a number left behind is either a walk of the
7985        // list that never happens or one that runs off the end of it.
7986        f.server.forget_waiters(2);
7987        assert_eq!(f.server.parked(), f.server.waiters().len());
7988        f.server.forget_waiters(1);
7989        assert_eq!(f.server.parked(), f.server.waiters().len());
7990        f.run(&[b"RPUSH", b"q", b"v"]);
7991        let mut out = Out::new(Proto::Resp2);
7992        assert!(f.server.serve_waiter(3, 0, &mut out));
7993        f.server.forget_waiters(3);
7994        assert_eq!(f.server.parked(), 0);
7995        assert!(f.server.waiters().is_empty());
7996    }
7997
7998    #[test]
7999    fn a_set_goes_from_bytes_to_bytes() {
8000        let mut f = Fixture::new();
8001        assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
8002        assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
8003        assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
8004        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
8005        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
8006        assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
8007        assert_eq!(
8008            f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
8009            "*3\r\n:1\r\n:0\r\n:1\r\n"
8010        );
8011        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
8012        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8013    }
8014
8015    #[test]
8016    fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
8017        let mut f = Fixture::new();
8018        assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
8019        assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
8020        assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
8021        assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
8022        assert_eq!(
8023            f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
8024            "*2\r\n:0\r\n:0\r\n"
8025        );
8026        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
8027    }
8028
8029    #[test]
8030    fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
8031        // Not cosmetic. A RESP3 client that gets a `~` hands the caller a set
8032        // and one that gets a `*` hands it a list, without either of them being
8033        // told which command was sent.
8034        let mut f = Fixture::new();
8035        f.run(&[b"SADD", b"s", b"one"]);
8036        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
8037
8038        f.run(&[b"HELLO", b"3"]);
8039        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
8040    }
8041
8042    #[test]
8043    fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
8044        // An intset holds the number, so these digits exist for the first time
8045        // in the reply buffer.
8046        let mut f = Fixture::new();
8047        f.run(&[b"SADD", b"s", b"42"]);
8048        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
8049        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
8050        assert_eq!(
8051            f.run(&[b"SISMEMBER", b"s", b"042"]),
8052            ":0\r\n",
8053            "the member is the bytes and not the number they parse to"
8054        );
8055    }
8056
8057    #[test]
8058    fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
8059        let mut f = Fixture::new();
8060        f.run(&[b"SET", b"str", b"v"]);
8061        f.run(&[b"SADD", b"set", b"a"]);
8062
8063        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8064        assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
8065        assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
8066        assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
8067        assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
8068        assert_eq!(f.run(&[b"GET", b"set"]), wrong);
8069        assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
8070        assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
8071        assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
8072
8073        // MGET is the one that does not, because Redis gives nil for the odd
8074        // key out rather than failing the good keys next to it.
8075        assert_eq!(
8076            f.run(&[b"MGET", b"str", b"set", b"nope"]),
8077            "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
8078        );
8079        // And plain SET overwrites any type, which takes the body with it.
8080        assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
8081        assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
8082    }
8083
8084    #[test]
8085    fn a_wrongtype_leaves_nothing_half_written() {
8086        // SMISMEMBER writes an array header and then one reply per member, so
8087        // it is the first command in the server that could get a header out in
8088        // front of an error if it checked its key in the wrong order.
8089        let mut f = Fixture::new();
8090        f.run(&[b"SET", b"k", b"v"]);
8091        let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
8092        assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
8093        assert!(!reply.contains('*'), "an array header went out in front");
8094    }
8095
8096    #[test]
8097    fn emptying_a_set_takes_the_key_with_it() {
8098        let mut f = Fixture::new();
8099        f.run(&[b"SADD", b"s", b"a", b"b"]);
8100        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
8101        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
8102        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8103        assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
8104        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8105    }
8106
8107    /// Pull the cursor and the members out of one `SSCAN` reply.
8108    ///
8109    /// Crude on purpose. A test that walked a set through a real client would
8110    /// be testing the client, and what these tests are about is the shape of
8111    /// the bytes and the fact that a walk sees every member once.
8112    fn split_scan(reply: &str) -> (String, Vec<String>) {
8113        let mut lines = reply.split("\r\n");
8114        assert_eq!(lines.next(), Some("*2"), "got {reply}");
8115        lines.next().expect("the cursor header");
8116        let cursor = lines.next().expect("the cursor").to_owned();
8117        let header = lines.next().expect("the member header");
8118        let n: usize = header[1..].parse().expect("a member count");
8119        let mut members = Vec::with_capacity(n);
8120        for _ in 0..n {
8121            lines.next().expect("a member header");
8122            members.push(lines.next().expect("a member").to_owned());
8123        }
8124        (cursor, members)
8125    }
8126
8127    #[test]
8128    fn popping_takes_a_member_off_the_set_and_hands_it_back() {
8129        let mut f = Fixture::new();
8130        f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
8131
8132        let one = f.run(&[b"SPOP", b"s"]);
8133        assert!(
8134            ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
8135            "got {one}"
8136        );
8137        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8138
8139        // A count takes that many, and the last one takes the key with it.
8140        let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
8141        assert!(rest.starts_with("*3\r\n"), "got {rest}");
8142        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8143        // And a pop at a key that is not there is a nil, not an empty bulk.
8144        assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
8145        assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
8146    }
8147
8148    #[test]
8149    fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
8150        // The one place in the server where the reply type carries something
8151        // the command name does not. SPOP's members are distinct so a RESP3
8152        // client can build a set out of them. SRANDMEMBER with a negative count
8153        // can hand back the same member three times, and a set would lose two.
8154        let mut f = Fixture::new();
8155        f.run(&[b"HELLO", b"3"]);
8156        f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
8157
8158        assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
8159        // And a positive count is an array too, since Redis makes it one.
8160        assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
8161
8162        // A negative count against a set of one is where the difference bites:
8163        // the same member three times, which is a three element reply and would
8164        // have been a one element reply if it had gone out as a set.
8165        f.run(&[b"SADD", b"one", b"z"]);
8166        assert_eq!(
8167            f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
8168            "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
8169        );
8170    }
8171
8172    #[test]
8173    fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
8174        let mut f = Fixture::new();
8175        f.run(&[b"SADD", b"s", b"only"]);
8176        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8177        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8178        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
8179
8180        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
8181        // The count form answers an empty array rather than a nil, which is the
8182        // pair of answers Redis gives and is not the pair it looks like.
8183        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
8184        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
8185        // Asking for more than is there answers all of it once and not padding.
8186        assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
8187    }
8188
8189    #[test]
8190    fn a_pop_count_that_is_not_a_positive_number_says_so() {
8191        let mut f = Fixture::new();
8192        f.run(&[b"SADD", b"s", b"a"]);
8193        let bad = "-ERR value is out of range, must be positive\r\n";
8194        assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
8195        assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
8196        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
8197        // Zero is allowed and is a real answer rather than an error.
8198        assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
8199        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
8200    }
8201
8202    #[test]
8203    fn a_scan_walks_a_set_of_any_size_exactly_once() {
8204        let mut f = Fixture::new();
8205        let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
8206        let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
8207            .into_iter()
8208            .chain(members.iter().map(Vec::as_slice))
8209            .collect();
8210        f.run(&args);
8211
8212        let mut seen = Vec::new();
8213        let mut cursor = "0".to_owned();
8214        loop {
8215            let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
8216            let (next, got) = split_scan(&reply);
8217            seen.extend(got);
8218            cursor = next;
8219            if cursor == "0" {
8220                break;
8221            }
8222        }
8223        seen.sort();
8224        seen.dedup();
8225        assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
8226
8227        // A set small enough to be a listpack answers in one call whatever
8228        // cursor it was handed, which is what Redis does for that encoding.
8229        f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
8230        let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
8231        assert_eq!(cursor, "0");
8232        assert_eq!(got.len(), 3);
8233        // And a key that is not there is a finished scan of nothing.
8234        assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
8235    }
8236
8237    #[test]
8238    fn a_scan_takes_match_and_count_and_refuses_anything_else() {
8239        let mut f = Fixture::new();
8240        f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
8241
8242        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
8243        let mut got = got;
8244        got.sort();
8245        assert_eq!(got, ["aa", "ab"]);
8246
8247        // An integer member has no digits stored anywhere, so MATCH is the one
8248        // place a scan pays to write some.
8249        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
8250        let mut got = got;
8251        got.sort();
8252        assert_eq!(got, ["12", "13"]);
8253
8254        assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
8255        assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
8256        assert_eq!(
8257            f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
8258            "-ERR syntax error\r\n"
8259        );
8260        // A count under one is a syntax error and not a range error, which is
8261        // the odder of Redis's two answers and the reason it is copied exactly.
8262        assert_eq!(
8263            f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
8264            "-ERR syntax error\r\n"
8265        );
8266    }
8267
8268    #[test]
8269    fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
8270        let mut f = Fixture::new();
8271        f.run(&[b"SADD", b"src", b"a", b"b"]);
8272        f.run(&[b"SADD", b"dst", b"c"]);
8273
8274        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
8275        assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
8276        assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
8277        // A member that is not in the source is a zero and moves nothing.
8278        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
8279        assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
8280
8281        // A destination that does not exist gets made, and a source that runs
8282        // out goes away.
8283        assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
8284        assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
8285        assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
8286    }
8287
8288    #[test]
8289    fn moving_checks_the_types_in_the_order_redis_checks_them() {
8290        // Not the order it looks like it should be. A source that is not there
8291        // answers zero without ever looking at the destination, so this is a
8292        // zero and not a WRONGTYPE even though the destination is a string.
8293        let mut f = Fixture::new();
8294        f.run(&[b"SET", b"str", b"v"]);
8295        f.run(&[b"SADD", b"set", b"a"]);
8296
8297        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8298        assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
8299        assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
8300        assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
8301        assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
8302        assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
8303        assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
8304        assert_eq!(
8305            f.run(&[b"SISMEMBER", b"set", b"a"]),
8306            ":1\r\n",
8307            "and none of that moved anything"
8308        );
8309    }
8310
8311    #[test]
8312    fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8313        // SSCAN writes an outer array header before it walks, so it is the
8314        // command most likely to get bytes out in front of an error.
8315        let mut f = Fixture::new();
8316        f.run(&[b"SADD", b"s", b"a"]);
8317        for bad in [
8318            &[b"SSCAN".as_slice(), b"s", b"abc"][..],
8319            &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
8320            &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
8321        ] {
8322            let reply = f.run(bad);
8323            assert!(reply.starts_with("-ERR"), "got {reply}");
8324            assert!(!reply.contains('*'), "an array header went out in front");
8325        }
8326    }
8327
8328    #[test]
8329    fn a_hash_writes_reads_and_deletes_its_fields() {
8330        let mut f = Fixture::new();
8331        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
8332        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
8333        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8334        assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
8335        assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
8336        assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
8337        assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
8338        assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
8339        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
8340        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
8341
8342        // The value the client sent is `9`, so HGET h b must not find the `2`
8343        // that is a value. A search with a step of one would have.
8344        assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
8345
8346        assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
8347        assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
8348        assert_eq!(
8349            f.run(&[b"EXISTS", b"h"]),
8350            ":0\r\n",
8351            "and losing the last field lost the key"
8352        );
8353    }
8354
8355    #[test]
8356    fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
8357        let mut f = Fixture::new();
8358        f.run(&[b"HSET", b"h", b"a", b"1"]);
8359        assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
8360        assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
8361        assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
8362        assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
8363        assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
8364
8365        f.run(&[b"HELLO", b"3"]);
8366        assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
8367        assert_eq!(
8368            f.run(&[b"HGETALL", b"nokey"]),
8369            "%0\r\n",
8370            "a missing key is the empty hash and never a nil"
8371        );
8372        assert_eq!(
8373            f.run(&[b"HKEYS", b"h"]),
8374            "*1\r\n$1\r\na\r\n",
8375            "and the two that answer one side stay arrays"
8376        );
8377    }
8378
8379    #[test]
8380    fn hmget_answers_once_per_field_and_hmset_answers_ok() {
8381        let mut f = Fixture::new();
8382        assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
8383        assert_eq!(
8384            f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
8385            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
8386            "the reply is positional, so b is a nil and not a gap"
8387        );
8388        assert_eq!(
8389            f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
8390            "*2\r\n$-1\r\n$-1\r\n",
8391            "and a missing key is all nils rather than an empty array"
8392        );
8393
8394        assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
8395        assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
8396        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8397    }
8398
8399    #[test]
8400    fn a_hash_counts_up_and_says_so_when_it_cannot() {
8401        let mut f = Fixture::new();
8402        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
8403        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
8404        assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
8405        assert_eq!(
8406            f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
8407            "$4\r\n10.5\r\n",
8408            "a bulk string and not a double, on both protocols"
8409        );
8410
8411        f.run(&[b"HSET", b"h", b"s", b"words"]);
8412        let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
8413        assert!(
8414            bad.starts_with("-ERR hash value is not an integer"),
8415            "{bad}"
8416        );
8417        let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
8418        assert!(
8419            bad.starts_with("-ERR value is not an integer"),
8420            "a bad argument is not yet a hash value, {bad}"
8421        );
8422        assert_eq!(
8423            f.run(&[b"HGET", b"h", b"s"]),
8424            "$5\r\nwords\r\n",
8425            "and neither of them wrote anything"
8426        );
8427    }
8428
8429    #[test]
8430    fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
8431        // Fourteen minutes under Miri at five hundred, which was the slowest
8432        // test in this crate that was not about megabytes. What the count has
8433        // to be is more than one page of the cursor, and the count below is
8434        // thirty two, so ninety six is three pages and asks the same question.
8435        let fields = if cfg!(miri) { 96 } else { 500 };
8436        let mut f = Fixture::new();
8437        for i in 0..fields {
8438            let field = format!("field-{i}");
8439            let value = format!("value-{i}");
8440            f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
8441        }
8442
8443        let mut seen: Vec<String> = Vec::new();
8444        let mut cursor = "0".to_owned();
8445        loop {
8446            let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
8447            let (next, items) = scan_reply(&reply);
8448            assert_eq!(items.len() % 2, 0, "a pair went out half written");
8449            for pair in items.chunks(2) {
8450                assert_eq!(
8451                    pair[0].strip_prefix("field-"),
8452                    pair[1].strip_prefix("value-"),
8453                    "a field came back with someone else's value"
8454                );
8455                seen.push(pair[0].clone());
8456            }
8457            cursor = next;
8458            if cursor == "0" {
8459                break;
8460            }
8461        }
8462        seen.sort();
8463        seen.dedup();
8464        assert_eq!(seen.len(), fields, "every field once and only once");
8465
8466        let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
8467        assert!(
8468            items.iter().all(|s| s.starts_with("field-")),
8469            "NOVALUES still sent the values"
8470        );
8471
8472        let last = fields - 1;
8473        let (_, one) = scan_reply(&f.run(&[
8474            b"HSCAN",
8475            b"h",
8476            b"0",
8477            b"MATCH",
8478            format!("field-{last}").as_bytes(),
8479            b"COUNT",
8480            b"1000",
8481        ]));
8482        assert_eq!(
8483            one,
8484            [format!("field-{last}"), format!("value-{last}")],
8485            "MATCH is on the field"
8486        );
8487    }
8488
8489    #[test]
8490    fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
8491        let mut f = Fixture::new();
8492        f.run(&[b"HSET", b"h", b"a", b"1"]);
8493        assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
8494        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
8495        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
8496        assert_eq!(
8497            f.run(&[b"HRANDFIELD", b"h", b"3"]),
8498            "*1\r\n$1\r\na\r\n",
8499            "a positive count is capped at the size of the hash"
8500        );
8501        assert_eq!(
8502            f.run(&[b"HRANDFIELD", b"h", b"-3"]),
8503            "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
8504            "and a negative one repeats itself"
8505        );
8506        assert_eq!(
8507            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8508            "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8509            "flat on RESP2"
8510        );
8511
8512        f.run(&[b"HELLO", b"3"]);
8513        assert_eq!(
8514            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8515            "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8516            "and nested on RESP3, but still an array and never a map"
8517        );
8518    }
8519
8520    #[test]
8521    fn every_hash_command_says_wrongtype_and_writes_nothing() {
8522        let mut f = Fixture::new();
8523        f.run(&[b"SET", b"str", b"v"]);
8524        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8525
8526        for cmd in [
8527            &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
8528            &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
8529            &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
8530            &[b"HGET".as_slice(), b"str", b"f"][..],
8531            &[b"HMGET".as_slice(), b"str", b"f"][..],
8532            &[b"HDEL".as_slice(), b"str", b"f"][..],
8533            &[b"HLEN".as_slice(), b"str"][..],
8534            &[b"HEXISTS".as_slice(), b"str", b"f"][..],
8535            &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
8536            &[b"HGETALL".as_slice(), b"str"][..],
8537            &[b"HKEYS".as_slice(), b"str"][..],
8538            &[b"HVALS".as_slice(), b"str"][..],
8539            &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
8540            &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
8541            &[b"HRANDFIELD".as_slice(), b"str"][..],
8542            &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
8543            &[b"HSCAN".as_slice(), b"str", b"0"][..],
8544        ] {
8545            let reply = f.run(cmd);
8546            assert_eq!(reply, wrong, "{:?}", cmd[0]);
8547        }
8548        assert_eq!(
8549            f.run(&[b"GET", b"str"]),
8550            "$1\r\nv\r\n",
8551            "and none of them touched the value"
8552        );
8553    }
8554
8555    #[test]
8556    fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8557        let mut f = Fixture::new();
8558        f.run(&[b"HSET", b"h", b"f", b"v"]);
8559        for bad in [
8560            &[b"HSCAN".as_slice(), b"h", b"abc"][..],
8561            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
8562            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
8563            &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
8564        ] {
8565            let reply = f.run(bad);
8566            assert!(reply.starts_with("-ERR"), "got {reply}");
8567            assert!(!reply.contains('*'), "an array header went out in front");
8568        }
8569    }
8570
8571    #[test]
8572    fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
8573        let mut f = Fixture::new();
8574        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8575        assert_eq!(
8576            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
8577            "*1\r\n:1\r\n"
8578        );
8579        assert_eq!(
8580            f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8581            "*3\r\n:100\r\n:-1\r\n:-2\r\n",
8582            "one answer per field, and the two sentinels are TTL's own"
8583        );
8584
8585        // The same deadline in the other three units, all of them derived from
8586        // the one number the store kept.
8587        let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
8588        assert!((99_000..=100_000).contains(&ms), "got {ms}");
8589        let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8590        let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8591        assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
8592        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
8593
8594        assert_eq!(
8595            f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8596            "*3\r\n:1\r\n:-1\r\n:-2\r\n",
8597            "one for the deadline taken off, and it does not say what it was"
8598        );
8599        assert_eq!(
8600            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8601            "*1\r\n:-1\r\n"
8602        );
8603        assert_eq!(
8604            f.run(&[b"HGET", b"h", b"a"]),
8605            "$1\r\n1\r\n",
8606            "and the field is still there with the value it had"
8607        );
8608    }
8609
8610    #[test]
8611    fn a_deadline_that_has_already_gone_deletes_the_field_now() {
8612        let mut f = Fixture::new();
8613        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8614        assert_eq!(
8615            f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
8616            "*1\r\n:2\r\n",
8617            "two, and not one, because nothing was stored"
8618        );
8619        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8620        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8621
8622        assert_eq!(
8623            f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
8624            "*1\r\n:2\r\n"
8625        );
8626        assert_eq!(
8627            f.run(&[b"EXISTS", b"h"]),
8628            ":0\r\n",
8629            "and the last field going took the key with it"
8630        );
8631
8632        // Zero is a delete and not an error, where minus one is an error. That
8633        // is Redis's split and it is easy to get backwards.
8634        f.run(&[b"HSET", b"h", b"a", b"1"]);
8635        assert_eq!(
8636            f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
8637            "*1\r\n:2\r\n"
8638        );
8639    }
8640
8641    #[test]
8642    fn a_field_is_gone_once_its_moment_passes() {
8643        let mut f = Fixture::new();
8644        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8645        assert_eq!(
8646            f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
8647            "*1\r\n:1\r\n"
8648        );
8649        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
8650
8651        // Time moves once per turn of the event loop and nowhere else, so a
8652        // test moves it by hand rather than by sleeping. There is nothing to
8653        // sleep for: the deadline is a number and so is the clock.
8654        f.server.advance_clock_ms(60);
8655        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8656        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8657        assert_eq!(
8658            f.run(&[b"HGETALL", b"h"]),
8659            "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
8660            "and the walks do not hand back a field that has expired"
8661        );
8662    }
8663
8664    #[test]
8665    fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
8666        let mut f = Fixture::new();
8667        for cmd in [
8668            &[
8669                b"HEXPIRE".as_slice(),
8670                b"nokey",
8671                b"100",
8672                b"FIELDS",
8673                b"2",
8674                b"a",
8675                b"b",
8676            ][..],
8677            &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8678            &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8679            &[
8680                b"HEXPIRETIME".as_slice(),
8681                b"nokey",
8682                b"FIELDS",
8683                b"2",
8684                b"a",
8685                b"b",
8686            ][..],
8687            &[
8688                b"HPERSIST".as_slice(),
8689                b"nokey",
8690                b"FIELDS",
8691                b"2",
8692                b"a",
8693                b"b",
8694            ][..],
8695        ] {
8696            assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
8697        }
8698    }
8699
8700    #[test]
8701    fn writing_a_field_clears_the_deadline_that_was_on_it() {
8702        let mut f = Fixture::new();
8703        f.run(&[b"HSET", b"h", b"a", b"1"]);
8704        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
8705        f.run(&[b"HSET", b"h", b"a", b"2"]);
8706        assert_eq!(
8707            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8708            "*1\r\n:-1\r\n",
8709            "Redis has done this since 7.4, and it is why HGETEX exists"
8710        );
8711    }
8712
8713    #[test]
8714    fn the_four_conditions_reach_the_store_the_way_they_were_written() {
8715        let mut f = Fixture::new();
8716        f.run(&[b"HSET", b"h", b"a", b"1"]);
8717        assert_eq!(
8718            f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
8719            "*1\r\n:0\r\n",
8720            "XX on a field with no deadline changes nothing"
8721        );
8722        assert_eq!(
8723            f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
8724            "*1\r\n:1\r\n"
8725        );
8726        assert_eq!(
8727            f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
8728            "*1\r\n:0\r\n",
8729            "and NX will not move one that is already there"
8730        );
8731        assert_eq!(
8732            f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
8733            "*1\r\n:0\r\n"
8734        );
8735        assert_eq!(
8736            f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
8737            "*1\r\n:1\r\n"
8738        );
8739        assert_eq!(
8740            f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
8741            "*1\r\n:1\r\n"
8742        );
8743        assert_eq!(
8744            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8745            "*1\r\n:50\r\n"
8746        );
8747    }
8748
8749    #[test]
8750    fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
8751        let mut f = Fixture::new();
8752        f.run(&[b"HSET", b"h", b"a", b"1"]);
8753        for (bad, want) in [
8754            (
8755                &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
8756                "-ERR invalid expire time, must be >= 0",
8757            ),
8758            (
8759                &[
8760                    b"HEXPIRE".as_slice(),
8761                    b"h",
8762                    b"9999999999999999",
8763                    b"FIELDS",
8764                    b"1",
8765                    b"a",
8766                ][..],
8767                "-ERR invalid expire time in 'hexpire' command",
8768            ),
8769            (
8770                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
8771                "-ERR wrong number of arguments for 'hexpire' command",
8772            ),
8773            (
8774                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
8775                "-ERR Parameter `numFields` should be greater than 0",
8776            ),
8777            (
8778                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
8779                "-ERR wrong number of arguments",
8780            ),
8781            (
8782                &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
8783                "-ERR wrong number of arguments",
8784            ),
8785        ] {
8786            let reply = f.run(bad);
8787            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
8788            assert!(!reply.contains('*'), "an array header went out in front");
8789        }
8790        assert_eq!(
8791            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8792            "*1\r\n:-1\r\n",
8793            "and not one of them put a deadline on anything"
8794        );
8795    }
8796
8797    #[test]
8798    fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
8799        let mut f = Fixture::new();
8800        f.run(&[b"SET", b"str", b"v"]);
8801        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8802
8803        for cmd in [
8804            &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
8805            &[
8806                b"HPEXPIRE".as_slice(),
8807                b"str",
8808                b"100",
8809                b"FIELDS",
8810                b"1",
8811                b"f",
8812            ][..],
8813            &[
8814                b"HEXPIREAT".as_slice(),
8815                b"str",
8816                b"9999999999",
8817                b"FIELDS",
8818                b"1",
8819                b"f",
8820            ][..],
8821            &[
8822                b"HPEXPIREAT".as_slice(),
8823                b"str",
8824                b"9999999999999",
8825                b"FIELDS",
8826                b"1",
8827                b"f",
8828            ][..],
8829            &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8830            &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8831            &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8832            &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8833            &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8834        ] {
8835            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8836        }
8837        assert_eq!(
8838            f.run(&[b"GET", b"str"]),
8839            "$1\r\nv\r\n",
8840            "and none of them touched the value"
8841        );
8842    }
8843
8844    #[test]
8845    fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
8846        let mut f = Fixture::new();
8847        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8848        assert_eq!(
8849            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
8850            "*2\r\n$1\r\n1\r\n$-1\r\n",
8851            "positional, so the field that was not there is a nil in its place"
8852        );
8853        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8854        assert_eq!(
8855            f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
8856            "*1\r\n$-1\r\n"
8857        );
8858        assert_eq!(
8859            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
8860            "*1\r\n$1\r\n2\r\n"
8861        );
8862        assert_eq!(
8863            f.run(&[b"EXISTS", b"h"]),
8864            ":0\r\n",
8865            "and the last field took the key"
8866        );
8867    }
8868
8869    #[test]
8870    fn hgetex_reads_and_moves_the_deadline_in_one_command() {
8871        let mut f = Fixture::new();
8872        f.run(&[b"HSET", b"h", b"a", b"1"]);
8873        assert_eq!(
8874            f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
8875            "*1\r\n$1\r\n1\r\n"
8876        );
8877        assert_eq!(
8878            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8879            "*1\r\n:-1\r\n",
8880            "no option means leave it alone, which is the one place this is not GETEX"
8881        );
8882
8883        f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
8884        assert_eq!(
8885            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8886            "*1\r\n:100\r\n"
8887        );
8888        f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
8889        assert_eq!(
8890            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8891            "*1\r\n:100\r\n",
8892            "and a plain read really does leave it alone"
8893        );
8894        assert_eq!(
8895            f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
8896            "*1\r\n$1\r\n1\r\n"
8897        );
8898        assert_eq!(
8899            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8900            "*1\r\n:-1\r\n"
8901        );
8902
8903        assert_eq!(
8904            f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
8905            "*1\r\n$1\r\n1\r\n",
8906            "the value goes out before the deadline that has already gone is applied"
8907        );
8908        assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
8909        assert_eq!(
8910            f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
8911            "*1\r\n$-1\r\n"
8912        );
8913    }
8914
8915    #[test]
8916    fn hsetex_writes_all_of_it_or_none_of_it() {
8917        let mut f = Fixture::new();
8918        assert_eq!(
8919            f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
8920            ":1\r\n"
8921        );
8922        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8923        assert_eq!(
8924            f.run(&[
8925                b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
8926            ]),
8927            ":0\r\n",
8928            "FNX wants every field named to be missing"
8929        );
8930        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8931        assert_eq!(
8932            f.run(&[b"HEXISTS", b"h", b"new"]),
8933            ":0\r\n",
8934            "and none of the list was written"
8935        );
8936        assert_eq!(
8937            f.run(&[
8938                b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
8939            ]),
8940            ":0\r\n",
8941            "and FXX wants every one of them to be there"
8942        );
8943        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8944        assert_eq!(
8945            f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
8946            ":1\r\n"
8947        );
8948        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8949
8950        assert_eq!(
8951            f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
8952            ":0\r\n"
8953        );
8954        assert_eq!(
8955            f.run(&[b"EXISTS", b"gone"]),
8956            ":0\r\n",
8957            "a key with no fields cannot meet FXX and is not created trying"
8958        );
8959    }
8960
8961    #[test]
8962    fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
8963        let mut f = Fixture::new();
8964        f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
8965        assert_eq!(
8966            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8967            "*1\r\n:100\r\n"
8968        );
8969
8970        f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
8971        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
8972        assert_eq!(
8973            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8974            "*1\r\n:100\r\n",
8975            "KEEPTTL put back what the write cleared"
8976        );
8977
8978        f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
8979        assert_eq!(
8980            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8981            "*1\r\n:-1\r\n",
8982            "and without it a write clears the deadline the way HSET does"
8983        );
8984
8985        // Any order, because Redis reads these in a loop and not in a fixed
8986        // sequence.
8987        assert_eq!(
8988            f.run(&[
8989                b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
8990            ]),
8991            ":1\r\n"
8992        );
8993        assert_eq!(
8994            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8995            "*1\r\n:100\r\n"
8996        );
8997
8998        assert_eq!(
8999            f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
9000            ":1\r\n",
9001            "written, and not the separate code the HEXPIRE family has for this"
9002        );
9003        assert_eq!(
9004            f.run(&[b"EXISTS", b"h"]),
9005            ":0\r\n",
9006            "and storing it and then removing it emptied the hash"
9007        );
9008    }
9009
9010    #[test]
9011    fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
9012        let mut f = Fixture::new();
9013        f.run(&[b"HSET", b"h", b"a", b"1"]);
9014        for (bad, want) in [
9015            // HGETDEL has three sentences of its own for these three mistakes.
9016            (
9017                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9018                "-ERR Number of fields must be a positive integer",
9019            ),
9020            (
9021                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9022                "-ERR The `numfields` parameter must match the number of arguments",
9023            ),
9024            (
9025                &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9026                "-ERR Mandatory argument FIELDS is missing or not at the right position",
9027            ),
9028            // And HGETEX and HSETEX have three different ones between them.
9029            (
9030                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9031                "-ERR invalid number of fields",
9032            ),
9033            (
9034                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9035                "-ERR wrong number of arguments",
9036            ),
9037            (
9038                &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9039                "-ERR unknown argument: FIELD",
9040            ),
9041            (
9042                &[
9043                    b"HGETEX".as_slice(),
9044                    b"h",
9045                    b"KEEPTTL",
9046                    b"FIELDS",
9047                    b"1",
9048                    b"a",
9049                ][..],
9050                "-ERR unknown argument: KEEPTTL",
9051            ),
9052            (
9053                &[
9054                    b"HGETEX".as_slice(),
9055                    b"h",
9056                    b"EX",
9057                    b"100",
9058                    b"PERSIST",
9059                    b"FIELDS",
9060                    b"1",
9061                    b"a",
9062                ][..],
9063                "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
9064            ),
9065            (
9066                &[
9067                    b"HSETEX".as_slice(),
9068                    b"h",
9069                    b"EX",
9070                    b"1",
9071                    b"KEEPTTL",
9072                    b"FIELDS",
9073                    b"1",
9074                    b"a",
9075                    b"1",
9076                ][..],
9077                "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
9078            ),
9079            (
9080                &[
9081                    b"HSETEX".as_slice(),
9082                    b"h",
9083                    b"FNX",
9084                    b"FXX",
9085                    b"FIELDS",
9086                    b"1",
9087                    b"a",
9088                    b"1",
9089                ][..],
9090                "-ERR Only one of FXX or FNX arguments can be specified",
9091            ),
9092            (
9093                &[
9094                    b"HSETEX".as_slice(),
9095                    b"h",
9096                    b"FIELDS",
9097                    b"2",
9098                    b"a",
9099                    b"1",
9100                    b"b",
9101                ][..],
9102                "-ERR wrong number of arguments",
9103            ),
9104            (
9105                &[
9106                    b"HGETEX".as_slice(),
9107                    b"h",
9108                    b"EX",
9109                    b"-1",
9110                    b"FIELDS",
9111                    b"1",
9112                    b"a",
9113                ][..],
9114                "-ERR invalid expire time, must be >= 0",
9115            ),
9116            (
9117                &[
9118                    b"HGETEX".as_slice(),
9119                    b"h",
9120                    b"PXAT",
9121                    b"99999999999999",
9122                    b"FIELDS",
9123                    b"1",
9124                    b"a",
9125                ][..],
9126                "-ERR invalid expire time in 'hgetex' command",
9127            ),
9128            (
9129                &[
9130                    b"HSETEX".as_slice(),
9131                    b"h",
9132                    b"EX",
9133                    b"abc",
9134                    b"FIELDS",
9135                    b"1",
9136                    b"a",
9137                    b"1",
9138                ][..],
9139                "-ERR value is not an integer or out of range",
9140            ),
9141        ] {
9142            let reply = f.run(bad);
9143            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9144            assert!(!reply.contains('*'), "an array header went out in front");
9145        }
9146        assert_eq!(
9147            f.run(&[b"HGET", b"h", b"a"]),
9148            "$1\r\n1\r\n",
9149            "and not one of them wrote anything"
9150        );
9151        assert_eq!(
9152            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9153            "*1\r\n:-1\r\n"
9154        );
9155    }
9156
9157    #[test]
9158    fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
9159        let mut f = Fixture::new();
9160        f.run(&[b"SET", b"str", b"v"]);
9161        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9162        for cmd in [
9163            &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9164            &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9165            &[
9166                b"HGETEX".as_slice(),
9167                b"str",
9168                b"EX",
9169                b"100",
9170                b"FIELDS",
9171                b"1",
9172                b"f",
9173            ][..],
9174            &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
9175        ] {
9176            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9177        }
9178        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
9179    }
9180
9181    /// The two orders `HIMPORT` juggles, which are not the same order.
9182    ///
9183    /// Values arrive in the order the fields were declared in and the hash is
9184    /// built in sorted order, so the first value is not generally the first
9185    /// field. And the sort is by length before bytes, which nothing else here
9186    /// sorts names with: `b` comes before `aa` where a plain byte comparison
9187    /// would put `aa` first. Both read off 8.10.1.
9188    #[test]
9189    fn himport_writes_declared_values_into_sorted_fields() {
9190        let mut f = Fixture::new();
9191        assert_eq!(
9192            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
9193            "+OK\r\n"
9194        );
9195        assert_eq!(
9196            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
9197            "+OK\r\n"
9198        );
9199        assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
9200        assert_eq!(
9201            f.run(&[b"HGETALL", b"k"]),
9202            bulks(&["a", "3", "b", "1", "aa", "2"])
9203        );
9204    }
9205
9206    /// It replaces the key rather than writing over it, so a field the fieldset
9207    /// does not name is gone afterwards and so is the deadline.
9208    #[test]
9209    fn himport_set_replaces_the_whole_key() {
9210        let mut f = Fixture::new();
9211        f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
9212        f.run(&[b"EXPIRE", b"k", b"100"]);
9213        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9214        assert_eq!(
9215            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9216            "+OK\r\n"
9217        );
9218        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9219        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
9220    }
9221
9222    /// A fieldset is connection state. `SELECT` leaves them alone and `RESET`
9223    /// throws them away, and a key built from one outlives it.
9224    #[test]
9225    fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
9226        let mut f = Fixture::new();
9227        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
9228        f.run(&[b"SELECT", b"1"]);
9229        assert_eq!(
9230            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9231            "+OK\r\n"
9232        );
9233        f.run(&[b"SELECT", b"0"]);
9234        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
9235        assert_eq!(
9236            f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
9237            "-ERR no such fieldset\r\n"
9238        );
9239    }
9240
9241    /// Which complaint wins when a line is wrong in more than one place.
9242    ///
9243    /// The type of the key beats both of the others, so a `HIMPORT SET` against
9244    /// a string is a WRONGTYPE even when the fieldset is missing too, which is
9245    /// the ordering a real server has and not the one the argument order
9246    /// suggests.
9247    #[test]
9248    fn himport_complains_in_the_order_a_real_server_does() {
9249        let mut f = Fixture::new();
9250        f.run(&[b"SET", b"str", b"v"]);
9251        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9252        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9253        assert_eq!(
9254            f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
9255            wrong,
9256            "the type beats a missing fieldset"
9257        );
9258        assert_eq!(
9259            f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
9260            wrong,
9261            "and it beats a value count that does not fit"
9262        );
9263        assert_eq!(
9264            f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
9265            "-ERR no such fieldset\r\n"
9266        );
9267        // One sentence for too few and for too many alike.
9268        for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
9269            let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
9270            line.extend_from_slice(values);
9271            assert_eq!(
9272                f.run(&line),
9273                "-ERR value count does not match fieldset field count\r\n",
9274                "{} values into two fields",
9275                values.len()
9276            );
9277        }
9278        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9279    }
9280
9281    /// The arity of each subcommand, and the unknown one.
9282    #[test]
9283    fn himport_checks_each_subcommand_count_under_its_own_name() {
9284        let mut f = Fixture::new();
9285        assert_eq!(
9286            f.run(&[b"HIMPORT"]),
9287            "-ERR wrong number of arguments for 'himport' command\r\n"
9288        );
9289        for (rest, name) in [
9290            (&["PREPARE"][..], "prepare"),
9291            (&["PREPARE", "fs"][..], "prepare"),
9292            (&["SET"][..], "set"),
9293            (&["SET", "k"][..], "set"),
9294            (&["SET", "k", "fs"][..], "set"),
9295            (&["DISCARD"][..], "discard"),
9296            (&["DISCARD", "a", "b"][..], "discard"),
9297            (&["DISCARDALL", "x"][..], "discardall"),
9298        ] {
9299            let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
9300            line.extend(rest.iter().map(|a| a.as_bytes()));
9301            assert_eq!(
9302                f.run(&line),
9303                format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
9304                "HIMPORT {}",
9305                rest.join(" ")
9306            );
9307        }
9308        assert_eq!(
9309            f.run(&[b"HIMPORT", b"NOPE", b"x"]),
9310            "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
9311        );
9312    }
9313
9314    /// A `PREPARE` that fails leaves the name pointing where it pointed, which
9315    /// is the answer of the two that could not be guessed from outside.
9316    #[test]
9317    fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
9318        let mut f = Fixture::new();
9319        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9320        assert_eq!(
9321            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
9322            "-ERR duplicate field name in fieldset\r\n"
9323        );
9324        assert_eq!(
9325            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9326            "+OK\r\n"
9327        );
9328        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9329    }
9330
9331    /// Preparing the same name twice replaces it, and the two discards count
9332    /// what they took rather than answering OK.
9333    #[test]
9334    fn himport_prepare_replaces_and_the_discards_count() {
9335        let mut f = Fixture::new();
9336        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9337        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
9338        assert_eq!(
9339            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9340            "+OK\r\n"
9341        );
9342        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
9343
9344        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
9345        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
9346        f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
9347        f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
9348        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
9349        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
9350    }
9351
9352    /// The one integer of a single element array reply.
9353    /// The number out of a plain integer reply.
9354    ///
9355    /// [`int_reply`] is the same thing wrapped in a one element array, which is
9356    /// the shape every hash field command answers in.
9357    fn int(reply: &str) -> i64 {
9358        let body = reply
9359            .strip_prefix(':')
9360            .and_then(|s| s.strip_suffix("\r\n"))
9361            .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
9362        body.parse().expect("an integer")
9363    }
9364
9365    fn int_reply(reply: &str) -> i64 {
9366        let body = reply
9367            .strip_prefix("*1\r\n:")
9368            .and_then(|s| s.strip_suffix("\r\n"))
9369            .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
9370        body.parse().expect("an integer")
9371    }
9372
9373    /// The cursor and the flat items of a scan reply.
9374    fn scan_reply(reply: &str) -> (String, Vec<String>) {
9375        let mut lines = reply.split("\r\n");
9376        assert_eq!(lines.next(), Some("*2"), "got {reply}");
9377        lines.next().expect("the cursor header");
9378        let cursor = lines.next().expect("a cursor").to_owned();
9379        let header = lines.next().expect("an item count");
9380        let n: usize = header[1..].parse().expect("a count");
9381        let mut items = Vec::with_capacity(n);
9382        for _ in 0..n {
9383            lines.next().expect("an item header");
9384            items.push(lines.next().expect("an item").to_owned());
9385        }
9386        (cursor, items)
9387    }
9388
9389    /// The members of a set reply, sorted, since none of these promise an
9390    /// order and a test that asserted one would be asserting an accident.
9391    fn sorted(reply: &str) -> Vec<String> {
9392        let mut lines = reply.split("\r\n");
9393        let header = lines.next().expect("a header");
9394        assert!(
9395            header.starts_with('*') || header.starts_with('~'),
9396            "got {reply}"
9397        );
9398        let n: usize = header[1..].parse().expect("a member count");
9399        let mut got = Vec::with_capacity(n);
9400        for _ in 0..n {
9401            lines.next().expect("a member header");
9402            got.push(lines.next().expect("a member").to_owned());
9403        }
9404        got.sort();
9405        got
9406    }
9407
9408    #[test]
9409    fn the_algebra_answers_what_the_sets_share_and_do_not() {
9410        let mut f = Fixture::new();
9411        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9412        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9413        f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
9414
9415        assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
9416        assert_eq!(
9417            sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
9418            ["1", "2", "3", "4", "5"]
9419        );
9420        assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
9421        assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
9422
9423        // A key that is not there is an empty set, which empties an
9424        // intersection and does nothing at all to a union.
9425        assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
9426        assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
9427        assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
9428        assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
9429    }
9430
9431    #[test]
9432    fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
9433        let mut f = Fixture::new();
9434        f.run(&[b"SADD", b"a", b"x"]);
9435        assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
9436        assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
9437        assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
9438
9439        f.run(&[b"HELLO", b"3"]);
9440        assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
9441        assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
9442        assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
9443        assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
9444    }
9445
9446    #[test]
9447    fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
9448        let mut f = Fixture::new();
9449        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9450        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9451
9452        assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
9453        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
9454        assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
9455        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
9456        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
9457        assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
9458
9459        // An empty answer deletes the destination rather than leaving an empty
9460        // set behind, and the destination may be one of the sources.
9461        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
9462        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
9463        assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
9464        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
9465
9466        // And a destination holding something else is overwritten, the same way
9467        // SET overwrites, rather than refused.
9468        f.run(&[b"SET", b"str", b"v"]);
9469        assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
9470        assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
9471    }
9472
9473    #[test]
9474    fn sintercard_counts_without_building_and_stops_at_a_limit() {
9475        let mut f = Fixture::new();
9476        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9477        f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
9478
9479        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
9480        assert_eq!(
9481            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9482            ":2\r\n"
9483        );
9484        assert_eq!(
9485            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9486            ":3\r\n",
9487            "a limit of zero is no limit"
9488        );
9489        assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
9490        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
9491
9492        // The counted keys are what make its three error messages its own.
9493        assert_eq!(
9494            f.run(&[b"SINTERCARD", b"0", b"a"]),
9495            "-ERR numkeys should be greater than 0\r\n"
9496        );
9497        assert_eq!(
9498            f.run(&[b"SINTERCARD", b"abc", b"a"]),
9499            "-ERR numkeys should be greater than 0\r\n"
9500        );
9501        assert_eq!(
9502            f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
9503            "-ERR Number of keys can't be greater than number of args\r\n"
9504        );
9505        assert_eq!(
9506            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
9507            "-ERR LIMIT can't be negative\r\n"
9508        );
9509        assert_eq!(
9510            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
9511            "-ERR syntax error\r\n"
9512        );
9513        // A key really can be called LIMIT, which is why the count exists.
9514        f.run(&[b"SADD", b"LIMIT", b"2"]);
9515        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
9516    }
9517
9518    /// The two Redis 8.10 added, which are SINTERCARD's shape over a union and
9519    /// over a difference. Every number here was read off 8.10.1 first.
9520    #[test]
9521    fn sunioncard_and_sdiffcard_count_without_building() {
9522        let mut f = Fixture::new();
9523        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9524        f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
9525
9526        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
9527        assert_eq!(
9528            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9529            ":2\r\n"
9530        );
9531        assert_eq!(
9532            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9533            ":6\r\n",
9534            "a limit of zero is no limit"
9535        );
9536        assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
9537        assert_eq!(
9538            f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
9539            ":4\r\n",
9540            "a missing key adds nothing to a union"
9541        );
9542
9543        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
9544        assert_eq!(
9545            f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
9546            ":1\r\n"
9547        );
9548        assert_eq!(
9549            f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
9550            ":2\r\n",
9551            "a difference is not symmetric"
9552        );
9553        assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
9554        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
9555        assert_eq!(
9556            f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
9557            ":0\r\n",
9558            "nothing taken away from nothing"
9559        );
9560
9561        // The same three messages SINTERCARD has, because the line is the same
9562        // line and is parsed once for all three.
9563        for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
9564            assert_eq!(
9565                f.run(&[name, b"0", b"a"]),
9566                "-ERR numkeys should be greater than 0\r\n"
9567            );
9568            assert_eq!(
9569                f.run(&[name, b"abc", b"a"]),
9570                "-ERR numkeys should be greater than 0\r\n"
9571            );
9572            assert_eq!(
9573                f.run(&[name, b"-1", b"a"]),
9574                "-ERR numkeys should be greater than 0\r\n"
9575            );
9576            assert_eq!(
9577                f.run(&[name, b"3", b"a", b"b"]),
9578                "-ERR Number of keys can't be greater than number of args\r\n"
9579            );
9580            assert_eq!(
9581                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
9582                "-ERR LIMIT can't be negative\r\n"
9583            );
9584            assert_eq!(
9585                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
9586                "-ERR LIMIT can't be negative\r\n",
9587                "a LIMIT that is not a number gets the negative message too"
9588            );
9589            assert_eq!(
9590                f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
9591                "-ERR syntax error\r\n"
9592            );
9593            assert_eq!(
9594                f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
9595                "-ERR syntax error\r\n"
9596            );
9597            assert_eq!(
9598                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
9599                "-ERR syntax error\r\n"
9600            );
9601        }
9602
9603        // And a key called LIMIT is a key, here as much as on SINTERCARD.
9604        f.run(&[b"SADD", b"LIMIT", b"2"]);
9605        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
9606        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
9607    }
9608
9609    #[test]
9610    fn the_algebra_answers_wrongtype_before_it_writes_anything() {
9611        let mut f = Fixture::new();
9612        f.run(&[b"SADD", b"a", b"1"]);
9613        f.run(&[b"SADD", b"d", b"old"]);
9614        f.run(&[b"SET", b"str", b"v"]);
9615
9616        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9617        for bad in [
9618            &[b"SINTER".as_slice(), b"a", b"str"][..],
9619            &[b"SUNION".as_slice(), b"str"][..],
9620            &[b"SDIFF".as_slice(), b"a", b"str"][..],
9621            &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
9622            &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
9623            &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
9624            &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
9625        ] {
9626            let reply = f.run(bad);
9627            assert_eq!(reply, wrong, "for {:?}", bad[0]);
9628        }
9629        assert_eq!(
9630            f.run(&[b"SMEMBERS", b"d"]),
9631            "*1\r\n$3\r\nold\r\n",
9632            "and the destination was left alone every time"
9633        );
9634    }
9635
9636    /// The leak a set can spring that nothing on the wire would ever show: the
9637    /// key goes, the body does not, and `DBSIZE` looks right the whole time.
9638    /// Not under Miri. What this claims is that memory does not grow over two
9639    /// hundred passes, so the passes are the claim rather than the way it
9640    /// happens to be written, and two hundred passes of a two hundred member
9641    /// collection is forty thousand trips through dispatch, which is what an
9642    /// interpreter charges for. A count small enough to run there would leave a
9643    /// server that reclaims nothing inside the bound and the test would pass on
9644    /// a leak. Nothing about memory safety goes uninterpreted either way: this
9645    /// is an accounting claim, and the same commands are run a few at a time by
9646    /// the tests around it.
9647    #[cfg_attr(miri, ignore = "the volume is the claim")]
9648    #[test]
9649    fn churning_sets_does_not_grow_the_server() {
9650        let mut f = Fixture::new();
9651        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
9652        let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
9653            .chain(std::iter::once(&b"s"[..]))
9654            .chain(members.iter().map(Vec::as_slice))
9655            .collect();
9656
9657        f.run(&args);
9658        f.run(&[b"DEL", b"s"]);
9659        f.server.compact_step();
9660        let after_first = f.server.memory_bytes();
9661
9662        for _ in 0..200 {
9663            f.run(&args);
9664            f.run(&[b"DEL", b"s"]);
9665            f.server.compact_step();
9666        }
9667        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9668        assert!(
9669            f.server.memory_bytes() <= after_first * 2,
9670            "held {} after two hundred passes against {after_first} after one",
9671            f.server.memory_bytes()
9672        );
9673    }
9674
9675    // --------------------------------------------------------------- bitmaps
9676
9677    /// The two single bit commands, and the encoding rule underneath them.
9678    ///
9679    /// A write always leaves the value `raw` and a read never re-encodes, which
9680    /// is why the `int` key here is still `int` after a `GETBIT` and is `raw`
9681    /// with its first digit changed after a `SETBIT`.
9682    #[test]
9683    fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
9684        let mut f = Fixture::new();
9685        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
9686        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
9687        assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
9688        assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
9689        assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
9690        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
9691
9692        // Writing a nought past the end still creates the key and still pads.
9693        assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
9694        assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
9695        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
9696
9697        f.run(&[b"SET", b"num", b"12345"]);
9698        assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
9699        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
9700        assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
9701        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
9702        assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
9703    }
9704
9705    /// Counting, in bytes and in bits.
9706    ///
9707    /// The `0 -5 BIT` row is 25 on a real 8.10.1 and Redis's own documentation
9708    /// says 22 for it. The server is the thing being copied here.
9709    #[test]
9710    fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
9711        let mut f = Fixture::new();
9712        f.run(&[b"SET", b"mykey", b"foobar"]);
9713        assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
9714        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
9715        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
9716        assert_eq!(
9717            f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
9718            ":6\r\n"
9719        );
9720        assert_eq!(
9721            f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
9722            ":25\r\n"
9723        );
9724        assert_eq!(
9725            f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
9726            ":17\r\n"
9727        );
9728        assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
9729
9730        // A start past the end is left where it is and the end is pulled back,
9731        // so the range comes out backwards and counts nothing.
9732        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
9733
9734        // A lone start is a syntax error here, where BITPOS allows it.
9735        assert_eq!(
9736            f.run(&[b"BITCOUNT", b"mykey", b"0"]),
9737            "-ERR syntax error\r\n"
9738        );
9739        assert_eq!(
9740            f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
9741            "-ERR syntax error\r\n"
9742        );
9743    }
9744
9745    /// Searching, and the one place a miss is not minus one.
9746    ///
9747    /// A search for a nought that runs to the end of the string answers the
9748    /// length in bits, because the string is treated as if it had noughts after
9749    /// it forever. Give it an explicit end and it answers minus one instead.
9750    #[test]
9751    fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
9752        let mut f = Fixture::new();
9753        f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
9754        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
9755        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
9756        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
9757        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
9758        assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
9759
9760        f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
9761        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
9762        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
9763        assert_eq!(
9764            f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
9765            ":8\r\n"
9766        );
9767
9768        // A missing key is all noughts, so a one is never found and a nought is
9769        // at position zero.
9770        assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
9771        assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
9772    }
9773
9774    /// The eight operations, with the answers a real server gives for them.
9775    #[test]
9776    fn the_eight_combinations_write_what_a_real_server_writes() {
9777        let mut f = Fixture::new();
9778        f.run(&[b"SET", b"a", b"abc"]);
9779        f.run(&[b"SET", b"b", b"abd"]);
9780        let cases: &[(&[u8], &str)] = &[
9781            (b"AND", "ab`"),
9782            (b"OR", "abg"),
9783            (b"XOR", "\u{0}\u{0}\u{7}"),
9784            (b"DIFF", "\u{0}\u{0}\u{3}"),
9785            (b"DIFF1", "\u{0}\u{0}\u{4}"),
9786            (b"ANDOR", "ab`"),
9787            (b"ONE", "\u{0}\u{0}\u{7}"),
9788        ];
9789        for (op, want) in cases {
9790            assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
9791            assert_eq!(
9792                f.run(&[b"GET", b"d"]),
9793                format!("$3\r\n{want}\r\n"),
9794                "{op:?}"
9795            );
9796        }
9797        // The one whose answer is not text, so it is compared as bytes.
9798        assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
9799        assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
9800
9801        // A missing source is a string of noughts as long as it needs to be, so
9802        // an AND against one writes three zero bytes rather than nothing.
9803        assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
9804        assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
9805
9806        // Every source missing is an empty result, and an empty result takes
9807        // the destination with it.
9808        f.run(&[b"SET", b"dest", b"x"]);
9809        assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
9810        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
9811    }
9812
9813    /// What `BITOP` says when it is asked for something it cannot do.
9814    #[test]
9815    fn bitop_names_the_operation_in_its_own_complaints() {
9816        let mut f = Fixture::new();
9817        f.run(&[b"SET", b"a", b"abc"]);
9818        assert_eq!(
9819            f.run(&[b"BITOP", b"nope", b"d", b"a"]),
9820            "-ERR syntax error\r\n"
9821        );
9822        assert_eq!(
9823            f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
9824            "-ERR BITOP NOT must be called with a single source key.\r\n"
9825        );
9826        for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
9827            assert_eq!(
9828                f.run(&[b"BITOP", op, b"d", b"a"]),
9829                format!(
9830                    "-ERR BITOP {} must be called with at least two source keys.\r\n",
9831                    String::from_utf8_lossy(op)
9832                )
9833            );
9834        }
9835        f.run(&[b"LPUSH", b"l", b"x"]);
9836        assert_eq!(
9837            f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
9838            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9839        );
9840    }
9841
9842    /// Packed fields, the three overflow policies and the `#` offset.
9843    #[test]
9844    fn bitfield_reads_and_writes_packed_fields() {
9845        let mut f = Fixture::new();
9846        assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
9847        assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
9848
9849        assert_eq!(
9850            f.run(&[
9851                b"BITFIELD",
9852                b"bf",
9853                b"INCRBY",
9854                b"u2",
9855                b"100",
9856                b"1",
9857                b"GET",
9858                b"u4",
9859                b"0"
9860            ]),
9861            "*2\r\n:1\r\n:0\r\n"
9862        );
9863        // The field at bit 100 is two bits wide, so it ends in the thirteenth
9864        // byte and the value grew to thirteen bytes to hold it.
9865        assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
9866
9867        // A `#` offset counts in fields rather than in bits.
9868        assert_eq!(
9869            f.run(&[
9870                b"BITFIELD",
9871                b"bf",
9872                b"SET",
9873                b"u8",
9874                b"#0",
9875                b"255",
9876                b"GET",
9877                b"u8",
9878                b"#0"
9879            ]),
9880            "*2\r\n:0\r\n:255\r\n"
9881        );
9882
9883        assert_eq!(
9884            f.run(&[
9885                b"BITFIELD",
9886                b"bf",
9887                b"OVERFLOW",
9888                b"SAT",
9889                b"INCRBY",
9890                b"i8",
9891                b"0",
9892                b"120",
9893                b"INCRBY",
9894                b"i8",
9895                b"0",
9896                b"120"
9897            ]),
9898            "*2\r\n:119\r\n:127\r\n"
9899        );
9900        assert_eq!(
9901            f.run(&[
9902                b"BITFIELD",
9903                b"bf2",
9904                b"OVERFLOW",
9905                b"FAIL",
9906                b"INCRBY",
9907                b"u2",
9908                b"0",
9909                b"5"
9910            ]),
9911            "*1\r\n$-1\r\n"
9912        );
9913        assert_eq!(
9914            f.run(&[
9915                b"BITFIELD",
9916                b"bf3",
9917                b"OVERFLOW",
9918                b"WRAP",
9919                b"INCRBY",
9920                b"u2",
9921                b"0",
9922                b"5"
9923            ]),
9924            "*1\r\n:1\r\n"
9925        );
9926        assert_eq!(
9927            f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
9928            "*1\r\n:4611686018427387904\r\n"
9929        );
9930    }
9931
9932    /// A bad subcommand anywhere in the line stops all of it.
9933    ///
9934    /// Redis checks the whole argument list before it runs any of it, so the
9935    /// `SET` in front of the bad type here never happens and the key it would
9936    /// have created is not there afterwards.
9937    #[test]
9938    fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
9939        let mut f = Fixture::new();
9940        let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
9941        assert_eq!(
9942            f.run(&[
9943                b"BITFIELD",
9944                b"bad",
9945                b"SET",
9946                b"u8",
9947                b"0",
9948                b"1",
9949                b"GET",
9950                b"u99",
9951                b"0"
9952            ]),
9953            bad_type
9954        );
9955        assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
9956        assert_eq!(
9957            f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
9958            bad_type
9959        );
9960        assert_eq!(
9961            f.run(&[b"BITFIELD", b"bad", b"GET"]),
9962            "-ERR syntax error\r\n"
9963        );
9964        assert_eq!(
9965            f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
9966            "-ERR syntax error\r\n"
9967        );
9968        assert_eq!(
9969            f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
9970            "-ERR syntax error\r\n"
9971        );
9972        assert_eq!(
9973            f.run(&[
9974                b"BITFIELD",
9975                b"bad",
9976                b"OVERFLOW",
9977                b"NOPE",
9978                b"GET",
9979                b"u8",
9980                b"0"
9981            ]),
9982            "-ERR Invalid OVERFLOW type specified\r\n"
9983        );
9984        assert_eq!(
9985            f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
9986            "-ERR value is not an integer or out of range\r\n"
9987        );
9988        for at in [&b"#-1"[..], b"abc"] {
9989            assert_eq!(
9990                f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
9991                "-ERR bit offset is not an integer or out of range\r\n"
9992            );
9993        }
9994    }
9995
9996    /// The read only twin reads, refuses to write, and creates nothing.
9997    #[test]
9998    fn bitfield_ro_answers_gets_and_refuses_the_rest() {
9999        let mut f = Fixture::new();
10000        f.run(&[b"SET", b"n", b"123"]);
10001        assert_eq!(
10002            f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
10003            "*1\r\n:49\r\n"
10004        );
10005        // A read does not unpack an int the way a write does.
10006        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
10007
10008        // An OVERFLOW word is allowed even though nothing here can overflow.
10009        assert_eq!(
10010            f.run(&[
10011                b"BITFIELD_RO",
10012                b"n",
10013                b"OVERFLOW",
10014                b"SAT",
10015                b"GET",
10016                b"u8",
10017                b"0"
10018            ]),
10019            "*1\r\n:49\r\n"
10020        );
10021        for sub in [&b"SET"[..], b"INCRBY"] {
10022            assert_eq!(
10023                f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
10024                "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
10025            );
10026        }
10027
10028        assert_eq!(
10029            f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
10030            "*1\r\n:0\r\n"
10031        );
10032        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10033    }
10034
10035    /// The offsets a bitmap command will not take.
10036    #[test]
10037    fn an_offset_off_the_end_of_the_world_is_refused() {
10038        let mut f = Fixture::new();
10039        let bad = "-ERR bit offset is not an integer or out of range\r\n";
10040        for arg in [&b"abc"[..], b"-1", b"4294967296"] {
10041            assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
10042            assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
10043        }
10044        for arg in [&b"2"[..], b"-1"] {
10045            assert_eq!(
10046                f.run(&[b"BITPOS", b"k", arg]),
10047                "-ERR The bit argument must be 1 or 0.\r\n"
10048            );
10049        }
10050        assert_eq!(
10051            f.run(&[b"BITPOS", b"k", b"abc"]),
10052            "-ERR value is not an integer or out of range\r\n"
10053        );
10054        assert_eq!(
10055            f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
10056            "-ERR value is not an integer or out of range\r\n"
10057        );
10058        let bad_bit = "-ERR bit is not an integer or out of range\r\n";
10059        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
10060        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
10061    }
10062
10063    /// Every one of the seven refuses a key that is not a string.
10064    #[test]
10065    fn every_bitmap_command_says_wrongtype() {
10066        let mut f = Fixture::new();
10067        f.run(&[b"LPUSH", b"l", b"x"]);
10068        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10069        let cases: &[&[&[u8]]] = &[
10070            &[b"SETBIT", b"l", b"0", b"1"],
10071            &[b"GETBIT", b"l", b"0"],
10072            &[b"BITCOUNT", b"l"],
10073            &[b"BITPOS", b"l", b"1"],
10074            &[b"BITOP", b"AND", b"d", b"l"],
10075            &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
10076            &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
10077        ];
10078        for case in cases {
10079            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10080        }
10081    }
10082
10083    // --------------------------------------------------------- hyperloglogs
10084
10085    #[test]
10086    fn a_sketch_is_added_to_and_counted() {
10087        let mut f = Fixture::new();
10088        // Creating the key counts as a change, even with nothing to add.
10089        assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
10090        assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
10091        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
10092        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
10093        // And it is a string, which is not an implementation detail: a client
10094        // can `GET` a sketch out of one server and `SET` it into another.
10095        assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
10096        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
10097
10098        assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
10099        assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
10100        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10101    }
10102
10103    #[test]
10104    fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
10105        let mut f = Fixture::new();
10106        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10107        // Not text, so it is compared as bytes.
10108        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";
10109        let mut reply = b"$27\r\n".to_vec();
10110        reply.extend_from_slice(want);
10111        reply.extend_from_slice(b"\r\n");
10112        assert_eq!(f.raw(&[b"GET", b"h"]), reply);
10113    }
10114
10115    #[test]
10116    fn counting_several_keys_counts_their_union() {
10117        let mut f = Fixture::new();
10118        f.run(&[b"PFADD", b"a", b"x", b"y"]);
10119        f.run(&[b"PFADD", b"b", b"y", b"z"]);
10120        assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
10121        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
10122        // A key that is not there is an empty sketch, not an error and not
10123        // something that gets created by being counted.
10124        assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
10125        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
10126        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10127    }
10128
10129    #[test]
10130    fn a_merge_keeps_what_the_destination_had() {
10131        let mut f = Fixture::new();
10132        f.run(&[b"PFADD", b"a", b"x", b"y"]);
10133        f.run(&[b"PFADD", b"b", b"z"]);
10134        assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
10135        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
10136        // The destination is one of the sources, so a second merge adds to it.
10137        f.run(&[b"PFADD", b"c", b"w"]);
10138        assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
10139        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
10140        // And with no sources it is a no-op that still answers OK and still
10141        // creates a destination that was not there.
10142        assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
10143        assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
10144    }
10145
10146    /// Not under Miri, and not for the number of commands: a dense sketch is
10147    /// sixteen thousand three hundred and eighty four registers and every
10148    /// command here walks all of them, so one `PFCOUNT` is more interpreted
10149    /// work than a hundred ordinary tests. The registers and the walking are in
10150    /// `yo-kv`, where fifteen tests of their own cover both encodings and where
10151    /// the interpreter does run over them. What is left here is the dispatch
10152    /// around it, which is the same dispatch every other command in this file
10153    /// goes through.
10154    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
10155    #[test]
10156    fn the_debug_forms_answer_four_different_shapes() {
10157        let mut f = Fixture::new();
10158        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10159        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
10160        assert_eq!(
10161            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10162            "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
10163        );
10164        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
10165        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
10166        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
10167        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
10168        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10169        // A dense sketch has no opcodes left to print.
10170        assert_eq!(
10171            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10172            "-ERR HLL encoding is not sparse\r\n"
10173        );
10174
10175        // All 16384 registers, of which three are not nought.
10176        let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
10177        assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
10178        assert_eq!(reply.matches(":0\r\n").count(), 16381);
10179        assert_eq!(reply.matches(":1\r\n").count(), 2);
10180        assert_eq!(reply.matches(":2\r\n").count(), 1);
10181
10182        assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
10183    }
10184
10185    #[test]
10186    fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
10187        let mut f = Fixture::new();
10188        f.run(&[b"SET", b"plain", b"not a sketch"]);
10189        let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
10190        assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
10191        assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
10192        assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
10193        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
10194
10195        // A key that is not a string at all gets the ordinary sentence, and a
10196        // destination that would have been written is not created.
10197        f.run(&[b"RPUSH", b"l", b"x"]);
10198        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10199        assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
10200        assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
10201        assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
10202        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10203        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
10204    }
10205
10206    #[test]
10207    fn pfdebug_has_its_own_complaints() {
10208        let mut f = Fixture::new();
10209        f.run(&[b"PFADD", b"h", b"a"]);
10210        // The word is quoted exactly as the client spelled it, and this is not
10211        // the "Try X HELP." sentence every other container command uses.
10212        assert_eq!(
10213            f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
10214            "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
10215        );
10216        // Where all three of the real commands take a missing key as empty.
10217        let gone = "-ERR The specified key does not exist\r\n";
10218        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
10219        assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
10220        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
10221        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
10222        assert_eq!(
10223            f.run(&[b"PFDEBUG"]),
10224            "-ERR wrong number of arguments for 'pfdebug' command\r\n"
10225        );
10226        assert_eq!(
10227            f.run(&[b"PFSELFTEST", b"x"]),
10228            "-ERR wrong number of arguments for 'pfselftest' command\r\n"
10229        );
10230    }
10231
10232    #[test]
10233    fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
10234        let mut f = Fixture::new();
10235        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10236        // The sketch with its last byte cut off, which is still a header and a
10237        // magic and is a run length encoding that stops short of register 16384.
10238        let reply = f.raw(&[b"GET", b"h"]);
10239        let short = reply[5..reply.len() - 3].to_vec();
10240        f.run(&[b"SET", b"h", &short]);
10241        assert_eq!(
10242            f.run(&[b"PFCOUNT", b"h"]),
10243            "-INVALIDOBJ Corrupted HLL object detected\r\n"
10244        );
10245    }
10246
10247    #[test]
10248    fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
10249        let mut f = Fixture::new();
10250        // One that stays sparse and one that has gone dense, since the payload
10251        // carries the bytes and the two encodings are different lengths.
10252        f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
10253        // Ten thousand elements is what takes a sketch dense on its own, and it
10254        // is ten thousand trips through dispatch, which is what Miri charges
10255        // for. There the same sketch is taken across by hand. What this test is
10256        // about is a dense payload surviving a round trip and the encoding is
10257        // dense either way: that a sketch converts when it fills up is what
10258        // `the_debug_forms_answer_four_different_shapes` is for.
10259        if cfg!(miri) {
10260            f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
10261            f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
10262        } else {
10263            for i in 0..10_000u32 {
10264                let ele = format!("e{i}");
10265                f.run(&[b"PFADD", b"big", ele.as_bytes()]);
10266            }
10267        }
10268        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
10269        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
10270
10271        for key in [&b"small"[..], b"big"] {
10272            let mut copy = key.to_vec();
10273            copy.push(b'2');
10274            let bytes = payload(&f.raw(&[b"DUMP", key]));
10275            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
10276            // The bytes, the encoding and the estimate all come back, which is
10277            // the whole of what byte compatibility across a round trip means.
10278            assert_eq!(f.raw(&[b"GET", &copy]), f.raw(&[b"GET", key]));
10279            assert_eq!(
10280                f.run(&[b"PFDEBUG", b"ENCODING", &copy]),
10281                f.run(&[b"PFDEBUG", b"ENCODING", key])
10282            );
10283            assert_eq!(f.run(&[b"PFCOUNT", &copy]), f.run(&[b"PFCOUNT", key]));
10284        }
10285        assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
10286        assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
10287    }
10288
10289    /// One RESP2 bulk string. The JSON replies are almost all one of these and
10290    /// the text inside them has quotes in it, so writing the frame out by hand
10291    /// buries the part of the assertion that matters.
10292    fn bulk(s: &str) -> String {
10293        format!("${}\r\n{s}\r\n", s.len())
10294    }
10295
10296    /// A RESP2 array of bulk strings, which is what most of the list replies
10297    /// are and what writing them out by hand in every assertion looks like.
10298    fn bulks(parts: &[&str]) -> String {
10299        let mut s = format!("*{}\r\n", parts.len());
10300        for p in parts {
10301            s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
10302        }
10303        s
10304    }
10305
10306    #[test]
10307    fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
10308        let mut f = Fixture::new();
10309        // Each element in turn goes at the head, so the last one sent is at the
10310        // front when it is over. That reads like a bug in the client and it is
10311        // what every Redis has always done.
10312        assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
10313        assert_eq!(
10314            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10315            bulks(&["c", "b", "a"])
10316        );
10317        assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
10318        assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
10319        assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
10320        assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
10321        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
10322        assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
10323    }
10324
10325    #[test]
10326    fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
10327        let mut f = Fixture::new();
10328        assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
10329        assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
10330        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10331        f.run(&[b"RPUSH", b"k", b"a"]);
10332        assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
10333        assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
10334        assert_eq!(
10335            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10336            bulks(&["z", "a", "y"])
10337        );
10338    }
10339
10340    /// The four ways a pop can come back with nothing, which are three
10341    /// different replies and a RESP2 client can tell all of them apart.
10342    #[test]
10343    fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
10344        let mut f = Fixture::new();
10345        assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
10346        assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
10347        assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
10348        assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
10349        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10350        // A count of zero against a list that is there is an empty array and
10351        // not a null array, which is the fourth answer.
10352        assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
10353        assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
10354        // More than there is takes what there is and the key goes with it.
10355        assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
10356        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10357    }
10358
10359    #[test]
10360    fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
10361        let mut f = Fixture::new();
10362        f.run(&[b"RPUSH", b"k", b"a"]);
10363        let range = "-ERR value is out of range, must be positive\r\n";
10364        assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
10365        assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
10366        assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
10367        // Redis calls this an arity error and not a syntax error, which is a
10368        // distinction it does not always make.
10369        assert_eq!(
10370            f.run(&[b"LPOP", b"k", b"1", b"2"]),
10371            "-ERR wrong number of arguments for 'lpop' command\r\n"
10372        );
10373        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10374    }
10375
10376    #[test]
10377    fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
10378        let mut f = Fixture::new();
10379        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10380        assert_eq!(
10381            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10382            bulks(&["a", "b", "c"])
10383        );
10384        assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
10385        assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
10386        assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
10387        assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
10388        assert_eq!(
10389            f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
10390            bulks(&["a", "b", "c"])
10391        );
10392        // A key that is not there is an empty range and not a nil, which is the
10393        // one place a list disagrees with a set.
10394        assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
10395        assert_eq!(
10396            f.run(&[b"LRANGE", b"k", b"a", b"b"]),
10397            "-ERR value is not an integer or out of range\r\n"
10398        );
10399    }
10400
10401    #[test]
10402    fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
10403        let mut f = Fixture::new();
10404        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10405        assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
10406        assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
10407        assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
10408        assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
10409        assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
10410        assert_eq!(
10411            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10412            bulks(&["a", "b", "z"])
10413        );
10414        // Both ways of missing are errors here rather than a nil, because a
10415        // list is never empty and there is nothing else the reply could be.
10416        assert_eq!(
10417            f.run(&[b"LSET", b"k", b"99", b"z"]),
10418            "-ERR index out of range\r\n"
10419        );
10420        assert_eq!(
10421            f.run(&[b"LSET", b"nope", b"0", b"z"]),
10422            "-ERR no such key\r\n"
10423        );
10424    }
10425
10426    #[test]
10427    fn linsert_says_three_things_with_one_signed_number() {
10428        let mut f = Fixture::new();
10429        // Zero for a key that is not there, which is not the same as minus one
10430        // for a pivot that is not in a list that is.
10431        assert_eq!(
10432            f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
10433            ":0\r\n"
10434        );
10435        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10436        assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
10437        assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
10438        assert_eq!(
10439            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10440            bulks(&["X", "a", "b", "Y"])
10441        );
10442        assert_eq!(
10443            f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
10444            ":-1\r\n"
10445        );
10446        assert_eq!(
10447            f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
10448            "-ERR syntax error\r\n"
10449        );
10450    }
10451
10452    #[test]
10453    fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
10454        let mut f = Fixture::new();
10455        f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
10456        assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
10457        assert_eq!(
10458            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10459            bulks(&["b", "c", "a"])
10460        );
10461        assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
10462        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10463        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
10464        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
10465        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10466        assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
10467    }
10468
10469    #[test]
10470    fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
10471        let mut f = Fixture::new();
10472        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
10473        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
10474        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10475        // `LTRIM k 1 0` is the documented way to empty a list, so it has to
10476        // leave `EXISTS` answering zero rather than leaving an empty one.
10477        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
10478        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10479        assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
10480    }
10481
10482    #[test]
10483    fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
10484        let mut f = Fixture::new();
10485        f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
10486        assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
10487        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
10488        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
10489        assert_eq!(
10490            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
10491            "*2\r\n:0\r\n:3\r\n"
10492        );
10493        assert_eq!(
10494            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
10495            "*3\r\n:6\r\n:3\r\n:0\r\n"
10496        );
10497        // MAXLEN counts elements looked at and not matches found, so three
10498        // stops after `a b c` and finds the one match in it.
10499        assert_eq!(
10500            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
10501            "*1\r\n:0\r\n"
10502        );
10503        // Nothing found is three different replies depending on how it was
10504        // asked and whether the key is there at all.
10505        assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
10506        assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
10507        assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
10508        assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
10509    }
10510
10511    #[test]
10512    fn lpos_words_its_three_mistakes_the_way_redis_does() {
10513        let mut f = Fixture::new();
10514        f.run(&[b"RPUSH", b"p", b"a"]);
10515        // The whole sentence and not a prefix, because the older wording of it
10516        // is still all over the internet and clients match on the text.
10517        assert_eq!(
10518            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
10519            "-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"
10520        );
10521        assert_eq!(
10522            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
10523            "-ERR COUNT can't be negative\r\n"
10524        );
10525        assert_eq!(
10526            f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
10527            "-ERR MAXLEN can't be negative\r\n"
10528        );
10529        assert_eq!(
10530            f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
10531            "-ERR syntax error\r\n"
10532        );
10533        assert_eq!(
10534            f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
10535            "-ERR syntax error\r\n"
10536        );
10537    }
10538
10539    #[test]
10540    fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
10541        let mut f = Fixture::new();
10542        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10543        assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
10544        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10545        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
10546        assert_eq!(
10547            f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
10548            "$1\r\na\r\n"
10549        );
10550        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
10551        // The same key twice is the documented way to rotate a list and falls
10552        // out of taking the element before deciding where to put it.
10553        f.run(&[b"DEL", b"r"]);
10554        f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
10555        assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
10556        assert_eq!(
10557            f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
10558            bulks(&["3", "1", "2"])
10559        );
10560        assert_eq!(
10561            f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
10562            "$-1\r\n"
10563        );
10564        assert_eq!(
10565            f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
10566            "-ERR syntax error\r\n"
10567        );
10568    }
10569
10570    #[test]
10571    fn a_move_checks_the_destination_before_it_takes_anything() {
10572        let mut f = Fixture::new();
10573        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10574        f.run(&[b"SET", b"str", b"v"]);
10575        assert_eq!(
10576            f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
10577            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10578        );
10579        // The element is still where it was, rather than having gone nowhere.
10580        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10581    }
10582
10583    #[test]
10584    fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
10585        // OBO is what you get from sending LMOVE that many times, BULK keeps
10586        // the source order. The two only differ when both ends are the same,
10587        // which is the whole reason the word exists.
10588        for (from, to, order, want) in [
10589            ("LEFT", "RIGHT", "OBO", ["a", "b"]),
10590            ("LEFT", "RIGHT", "BULK", ["a", "b"]),
10591            ("LEFT", "LEFT", "OBO", ["b", "a"]),
10592            ("LEFT", "LEFT", "BULK", ["a", "b"]),
10593            ("RIGHT", "LEFT", "OBO", ["d", "e"]),
10594            ("RIGHT", "LEFT", "BULK", ["d", "e"]),
10595            ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
10596            ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
10597        ] {
10598            let mut f = Fixture::new();
10599            f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
10600            let how = format!("{from} {to} {order}");
10601            let reply = f.run(&[
10602                b"LMOVEM",
10603                b"s",
10604                b"d",
10605                from.as_bytes(),
10606                to.as_bytes(),
10607                b"COUNT",
10608                b"2",
10609                order.as_bytes(),
10610            ]);
10611            assert_eq!(reply, bulks(&want), "the reply for {how}");
10612            assert_eq!(
10613                f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
10614                bulks(&want),
10615                "the destination for {how}"
10616            );
10617        }
10618    }
10619
10620    #[test]
10621    fn a_block_move_of_one_needs_no_count_at_all() {
10622        let mut f = Fixture::new();
10623        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10624        assert_eq!(
10625            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
10626            bulks(&["a"])
10627        );
10628        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
10629        // Six and seven arguments are neither of the two forms, so the
10630        // reference calls both of them a syntax error rather than guessing.
10631        assert_eq!(
10632            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
10633            "-ERR syntax error\r\n"
10634        );
10635        assert_eq!(
10636            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
10637            "-ERR syntax error\r\n"
10638        );
10639    }
10640
10641    #[test]
10642    fn a_block_move_with_exactly_takes_all_of_them_or_none() {
10643        let mut f = Fixture::new();
10644        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10645        // A null array and not a null bulk string, which `redis-cli` prints as
10646        // `(nil)` either way and only the raw wire tells apart. What it would
10647        // have sent is an array, so its nothing is an array's nothing.
10648        assert_eq!(
10649            f.run(&[
10650                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
10651            ]),
10652            "*-1\r\n"
10653        );
10654        assert_eq!(
10655            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10656            bulks(&["a", "b", "c"])
10657        );
10658        // COUNT takes what there is, and an emptied source goes away.
10659        assert_eq!(
10660            f.run(&[
10661                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
10662            ]),
10663            bulks(&["a", "b", "c"])
10664        );
10665        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
10666        assert_eq!(
10667            f.run(&[
10668                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10669            ]),
10670            "*-1\r\n"
10671        );
10672    }
10673
10674    #[test]
10675    fn a_block_move_onto_itself_rotates_by_the_count() {
10676        let mut f = Fixture::new();
10677        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10678        assert_eq!(
10679            f.run(&[
10680                b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
10681            ]),
10682            bulks(&["a", "b"])
10683        );
10684        assert_eq!(
10685            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10686            bulks(&["c", "a", "b"])
10687        );
10688    }
10689
10690    #[test]
10691    fn a_block_move_reads_the_count_before_the_ordering_word() {
10692        let mut f = Fixture::new();
10693        f.run(&[b"RPUSH", b"s", b"a", b"b"]);
10694        f.run(&[b"SET", b"str", b"v"]);
10695        let count = "-ERR count should be greater than 0\r\n";
10696        assert_eq!(
10697            f.run(&[
10698                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
10699            ]),
10700            count
10701        );
10702        assert_eq!(
10703            f.run(&[
10704                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
10705            ]),
10706            count
10707        );
10708        assert_eq!(
10709            f.run(&[
10710                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
10711            ]),
10712            "-ERR syntax error\r\n"
10713        );
10714        assert_eq!(
10715            f.run(&[
10716                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
10717            ]),
10718            "-ERR syntax error\r\n"
10719        );
10720        // Every argument is read before the keys are looked at, so a bad count
10721        // beats a wrong type even when the type is wrong on the source.
10722        assert_eq!(
10723            f.run(&[
10724                b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
10725            ]),
10726            count
10727        );
10728        assert_eq!(
10729            f.run(&[
10730                b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10731            ]),
10732            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10733        );
10734        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
10735    }
10736
10737    #[test]
10738    fn lmpop_answers_from_the_first_key_that_has_anything() {
10739        let mut f = Fixture::new();
10740        f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
10741        // The name of the key that answered comes back with the elements,
10742        // because the client cannot work out which one it was.
10743        assert_eq!(
10744            f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
10745            "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
10746        );
10747        assert_eq!(
10748            f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
10749            "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
10750        );
10751        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
10752        // A null array and not a null, even though what it stands in for is an
10753        // array holding a key name and then another array.
10754        assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
10755    }
10756
10757    #[test]
10758    fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
10759        let mut f = Fixture::new();
10760        f.run(&[b"RPUSH", b"k", b"a"]);
10761        assert_eq!(
10762            f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
10763            "-ERR numkeys should be greater than 0\r\n"
10764        );
10765        assert_eq!(
10766            f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
10767            "-ERR numkeys should be greater than 0\r\n"
10768        );
10769        assert_eq!(
10770            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
10771            "-ERR count should be greater than 0\r\n"
10772        );
10773        // A key count that eats the direction is a syntax error and not a
10774        // sentence about key counts, because the direction is simply not there.
10775        assert_eq!(
10776            f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
10777            "-ERR syntax error\r\n"
10778        );
10779        assert_eq!(
10780            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
10781            "-ERR syntax error\r\n"
10782        );
10783        assert_eq!(
10784            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
10785            "-ERR syntax error\r\n"
10786        );
10787        assert_eq!(
10788            f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
10789            "-ERR syntax error\r\n"
10790        );
10791        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10792    }
10793
10794    #[test]
10795    fn every_list_command_says_wrongtype_and_writes_nothing() {
10796        let mut f = Fixture::new();
10797        f.run(&[b"SET", b"str", b"v"]);
10798        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10799        for cmd in [
10800            &[b"LPUSH".as_slice(), b"str", b"a"][..],
10801            &[b"RPUSH", b"str", b"a"],
10802            &[b"LPUSHX", b"str", b"a"],
10803            &[b"RPUSHX", b"str", b"a"],
10804            &[b"LPOP", b"str"],
10805            &[b"LPOP", b"str", b"2"],
10806            &[b"RPOP", b"str"],
10807            &[b"LLEN", b"str"],
10808            &[b"LRANGE", b"str", b"0", b"-1"],
10809            &[b"LINDEX", b"str", b"0"],
10810            &[b"LSET", b"str", b"0", b"a"],
10811            &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
10812            &[b"LREM", b"str", b"0", b"a"],
10813            &[b"LTRIM", b"str", b"0", b"-1"],
10814            &[b"LPOS", b"str", b"a"],
10815            &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
10816            &[b"RPOPLPUSH", b"str", b"d"],
10817            &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
10818            &[b"LMPOP", b"1", b"str", b"LEFT"],
10819        ] {
10820            assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
10821        }
10822        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
10823        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10824    }
10825
10826    /// A timeout is not an integer and it is not an ordinary float either: the
10827    /// three sentences it can answer with are its own, and which one a given
10828    /// argument gets is not what reading the code would suggest.
10829    #[test]
10830    fn a_timeout_has_three_ways_of_being_wrong() {
10831        let mut f = Fixture::new();
10832        let not_float = "-ERR timeout is not a float or out of range\r\n";
10833        let range = "-ERR timeout is out of range\r\n";
10834        for (bad, want) in [
10835            (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
10836            (&[b"BLPOP", b"k", b"nan"], not_float),
10837            (&[b"BLPOP", b"k", b""], not_float),
10838            // Whitespace on either side, which `strtold` would take and Redis
10839            // does not.
10840            (&[b"BLPOP", b"k", b" 1"], not_float),
10841            (&[b"BLPOP", b"k", b"1 "], not_float),
10842            (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
10843            (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
10844            // These three parse, so they are not the not-a-float error, and all
10845            // three are further off than an i64 of milliseconds reaches.
10846            (&[b"BLPOP", b"k", b"1e400"], range),
10847            (&[b"BLPOP", b"k", b"inf"], range),
10848            (&[b"BLPOP", b"k", b"9999999999999999"], range),
10849            (&[b"BRPOP", b"k", b"abc"], not_float),
10850            (
10851                &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
10852                not_float,
10853            ),
10854            (
10855                &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
10856                "-ERR timeout is negative\r\n",
10857            ),
10858            (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
10859        ] {
10860            assert_eq!(f.run(bad), want, "for {bad:?}");
10861        }
10862    }
10863
10864    /// A timeout of exactly zero means no timeout, and there are two ways of
10865    /// writing exactly zero.
10866    #[test]
10867    fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
10868        let mut f = Fixture::new();
10869        for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
10870            let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
10871            assert_eq!(flow, Flow::Block, "for {timeout:?}");
10872            assert!(out.is_empty(), "for {timeout:?}");
10873        }
10874        // Positive, so it is a real deadline, and the deadline is this
10875        // millisecond. Nothing is written here either: the reply comes from the
10876        // sweep, which is the engine's and not this layer's.
10877        let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
10878        assert_eq!(flow, Flow::Block);
10879        assert!(out.is_empty());
10880    }
10881
10882    #[test]
10883    fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
10884        let mut f = Fixture::new();
10885        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10886
10887        // The one difference from LPOP: the reply names the key that answered,
10888        // which is what makes BLPOP over several keys usable.
10889        assert_eq!(
10890            f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
10891            (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
10892        );
10893        assert_eq!(
10894            f.run(&[b"BRPOP", b"L", b"0"]),
10895            "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
10896        );
10897        assert_eq!(
10898            f.run(&[
10899                b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
10900            ]),
10901            "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10902        );
10903        assert_eq!(
10904            f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10905            "$1\r\nd\r\n"
10906        );
10907        assert_eq!(
10908            f.run(&[b"EXISTS", b"L"]),
10909            ":0\r\n",
10910            "and the key went with it"
10911        );
10912        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
10913        // Onto itself, which is how a list is rotated and is a real thing to ask
10914        // a blocking move for.
10915        f.run(&[b"RPUSH", b"D", b"x"]);
10916        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
10917        assert_eq!(
10918            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10919            "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
10920        );
10921    }
10922
10923    #[test]
10924    fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
10925        let mut f = Fixture::new();
10926        f.run(&[b"RPUSH", b"k", b"a"]);
10927        for (bad, want) in [
10928            (
10929                &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
10930                "-ERR numkeys should be greater than 0\r\n",
10931            ),
10932            (
10933                &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
10934                "-ERR numkeys should be greater than 0\r\n",
10935            ),
10936            // Two keys named and one given, so the word that should have been
10937            // the direction is a key and there is no direction left.
10938            (
10939                &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
10940                "-ERR syntax error\r\n",
10941            ),
10942            (
10943                &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
10944                "-ERR syntax error\r\n",
10945            ),
10946            (
10947                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
10948                "-ERR syntax error\r\n",
10949            ),
10950            (
10951                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
10952                "-ERR syntax error\r\n",
10953            ),
10954            // A count that is not a number at all gets the same sentence a zero
10955            // or a negative one gets, rather than the usual one about integers.
10956            (
10957                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
10958                "-ERR count should be greater than 0\r\n",
10959            ),
10960            (
10961                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
10962                "-ERR count should be greater than 0\r\n",
10963            ),
10964        ] {
10965            assert_eq!(f.run(bad), want, "for {bad:?}");
10966        }
10967        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
10968    }
10969
10970    #[test]
10971    fn a_blocking_move_reads_its_directions_before_its_timeout() {
10972        let mut f = Fixture::new();
10973        // Both are wrong. Redis checks the directions first, so this is the
10974        // syntax error and not a complaint about the timeout.
10975        assert_eq!(
10976            f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
10977            "-ERR syntax error\r\n"
10978        );
10979        assert_eq!(
10980            f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
10981            "-ERR syntax error\r\n"
10982        );
10983    }
10984
10985    /// `BLMOVEM` answers exactly what `LMOVEM` answers when it does not have to
10986    /// wait, which is the same relationship every other command in this file has
10987    /// with the one it wraps.
10988    #[test]
10989    fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
10990        let mut f = Fixture::new();
10991        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10992        assert_eq!(
10993            f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10994            (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
10995        );
10996        assert_eq!(
10997            f.run(&[
10998                b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
10999            ]),
11000            bulks(&["e", "d"])
11001        );
11002        assert_eq!(
11003            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11004            bulks(&["a", "e", "d"])
11005        );
11006        // `EXACTLY` with enough there does not wait either.
11007        assert_eq!(
11008            f.run(&[
11009                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
11010            ]),
11011            bulks(&["b", "c"])
11012        );
11013        assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
11014    }
11015
11016    /// The one thing `BLMOVEM` decides differently from the other five: `COUNT`
11017    /// is ready as soon as there is anything and `EXACTLY` is not ready until the
11018    /// whole block has arrived.
11019    #[test]
11020    fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
11021        let mut f = Fixture::new();
11022        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11023        // Two there and three asked for. `COUNT` takes the two.
11024        assert_eq!(
11025            f.flow(&[
11026                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
11027            ]),
11028            (Flow::Continue, bulks(&["a", "b"]))
11029        );
11030
11031        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11032        // The same line with `EXACTLY` parks instead, and takes nothing on the
11033        // way past.
11034        assert_eq!(
11035            f.flow(&[
11036                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
11037            ])
11038            .0,
11039            Flow::Block
11040        );
11041        assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
11042    }
11043
11044    #[test]
11045    fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
11046        let mut f = Fixture::new();
11047        let syntax = "-ERR syntax error\r\n";
11048        // All three are wrong and the directions are read first.
11049        assert_eq!(
11050            f.run(&[
11051                b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
11052            ]),
11053            syntax
11054        );
11055        // Directions fine, timeout and count both wrong, so the timeout wins.
11056        assert_eq!(
11057            f.run(&[
11058                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
11059            ]),
11060            "-ERR timeout is not a float or out of range\r\n"
11061        );
11062        assert_eq!(
11063            f.run(&[
11064                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
11065            ]),
11066            "-ERR timeout is negative\r\n"
11067        );
11068        // And with the timeout fine, the count before the ordering word.
11069        assert_eq!(
11070            f.run(&[
11071                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
11072            ]),
11073            "-ERR count should be greater than 0\r\n"
11074        );
11075        assert_eq!(
11076            f.run(&[
11077                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
11078            ]),
11079            syntax
11080        );
11081        // Seven and eight arguments are neither of the two forms, the same way
11082        // six and seven are for `LMOVEM`.
11083        assert_eq!(
11084            f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
11085            syntax
11086        );
11087        assert_eq!(
11088            f.run(&[
11089                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
11090            ]),
11091            syntax
11092        );
11093    }
11094
11095    /// The four ways a blocking command sees a key of another type, and the one
11096    /// way it does not.
11097    #[test]
11098    fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
11099        let mut f = Fixture::new();
11100        f.run(&[b"SET", b"S", b"v"]);
11101        f.run(&[b"RPUSH", b"D", b"x"]);
11102        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11103
11104        assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
11105        // Every key is checked even when an earlier one would have blocked, so
11106        // an empty key in front of a string does not hide it.
11107        assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
11108        assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
11109        assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
11110        assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
11111        // The destination, which is only reached because the source has
11112        // something in it.
11113        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
11114        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
11115        assert_eq!(
11116            f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
11117            wrong
11118        );
11119        assert_eq!(
11120            f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
11121            wrong
11122        );
11123
11124        // And the one that does not: an empty source means the destination is
11125        // never looked at, so this waits rather than erroring, and on a real
11126        // server it times out.
11127        assert_eq!(
11128            f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11129                .0,
11130            Flow::Block
11131        );
11132        // `BLMOVEM` has a second way of not being ready, and it hides the
11133        // destination just as well: the source is a list with two elements in it
11134        // and `EXACTLY` wants three, so the string never gets looked at.
11135        assert_eq!(
11136            f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11137                .0,
11138            Flow::Block
11139        );
11140        f.run(&[b"RPUSH", b"E", b"1", b"2"]);
11141        assert_eq!(
11142            f.flow(&[
11143                b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
11144            ])
11145            .0,
11146            Flow::Block
11147        );
11148    }
11149
11150    /// The same churn the set and the string get, because a list that leaks a
11151    /// chunk per push looks exactly like one that does not until it has run for
11152    /// an afternoon.
11153    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
11154    #[cfg_attr(miri, ignore = "the volume is the claim")]
11155    #[test]
11156    fn churning_lists_does_not_grow_the_server() {
11157        let mut f = Fixture::new();
11158        let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
11159        let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
11160            .into_iter()
11161            .chain(vals.iter().map(Vec::as_slice))
11162            .collect();
11163
11164        f.run(&args);
11165        f.run(&[b"DEL", b"k"]);
11166        f.server.compact_step();
11167        let after_first = f.server.memory_bytes();
11168
11169        for _ in 0..200 {
11170            f.run(&args);
11171            f.run(&[b"LTRIM", b"k", b"1", b"0"]);
11172            f.server.compact_step();
11173        }
11174        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11175        assert!(
11176            f.server.memory_bytes() <= after_first * 2,
11177            "held {} after two hundred passes against {after_first} after one",
11178            f.server.memory_bytes()
11179        );
11180    }
11181
11182    // ------------------------------------------------------------ sorted set
11183
11184    #[test]
11185    fn a_sorted_set_takes_scores_and_gives_them_back() {
11186        let mut f = Fixture::new();
11187        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
11188        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
11189        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
11190        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
11191        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
11192        assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
11193        assert_eq!(
11194            f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
11195            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
11196        );
11197        assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
11198        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
11199        // The key goes when the last member does.
11200        assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
11201        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11202    }
11203
11204    #[test]
11205    fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
11206        let mut f = Fixture::new();
11207        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
11208        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
11209        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
11210        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
11211
11212        f.out = Out::new(Proto::Resp3);
11213        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
11214        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
11215        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
11216        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
11217    }
11218
11219    #[test]
11220    fn the_zadd_options_gate_what_gets_written() {
11221        let mut f = Fixture::new();
11222        f.run(&[b"ZADD", b"z", b"5", b"a"]);
11223        // NX leaves a member that is there alone, XX will not create one.
11224        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
11225        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
11226        assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
11227        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
11228        // GT and LT only move a score one way.
11229        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
11230        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
11231        assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
11232        // CH counts a moved score and plain ZADD does not.
11233        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
11234        assert_eq!(
11235            f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
11236            ":2\r\n"
11237        );
11238    }
11239
11240    #[test]
11241    fn zadd_incr_answers_a_score_or_nothing_at_all() {
11242        let mut f = Fixture::new();
11243        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
11244        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
11245        // A gate that refuses is the string nil, because the reply it stands in
11246        // for is a score.
11247        assert_eq!(
11248            f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
11249            "$-1\r\n"
11250        );
11251        assert_eq!(
11252            f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
11253            "$-1\r\n"
11254        );
11255        assert_eq!(
11256            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
11257            "$-1\r\n"
11258        );
11259        assert_eq!(
11260            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
11261            "$1\r\n8\r\n"
11262        );
11263        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
11264        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
11265    }
11266
11267    #[test]
11268    fn the_two_infinities_will_not_be_added_together() {
11269        let mut f = Fixture::new();
11270        f.run(&[b"ZADD", b"z", b"inf", b"m"]);
11271        let nan = "-ERR resulting score is not a number (NaN)\r\n";
11272        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
11273        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
11274        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
11275        // And a key made for an increment that then fails does not stay behind.
11276        assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
11277    }
11278
11279    #[test]
11280    fn zadd_says_its_mistakes_the_way_redis_says_them() {
11281        let mut f = Fixture::new();
11282        // The pairs are counted before the options are looked at, so this is a
11283        // syntax error about having none and not a complaint about NX and XX.
11284        assert_eq!(
11285            f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
11286            "-ERR syntax error\r\n"
11287        );
11288        assert_eq!(
11289            f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
11290            "-ERR XX and NX options at the same time are not compatible\r\n"
11291        );
11292        let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
11293        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
11294        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
11295        assert_eq!(
11296            f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
11297            "-ERR INCR option supports a single increment-element pair\r\n"
11298        );
11299        // An odd number of arguments after the options.
11300        assert_eq!(
11301            f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
11302            "-ERR syntax error\r\n"
11303        );
11304        // Every score is read before the first is stored.
11305        assert_eq!(
11306            f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
11307            "-ERR value is not a valid float\r\n"
11308        );
11309        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11310    }
11311
11312    #[test]
11313    fn a_rank_says_where_a_member_sits_from_either_end() {
11314        let mut f = Fixture::new();
11315        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11316        assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
11317        assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
11318        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
11319        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
11320        // WITHSCORE changes both shapes: the answer and the nothing.
11321        assert_eq!(
11322            f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
11323            "*2\r\n:1\r\n$1\r\n2\r\n"
11324        );
11325        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
11326        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
11327        assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
11328        // A bad option is a syntax error and one argument too many is an arity
11329        // error, which is Redis's split.
11330        assert_eq!(
11331            f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
11332            "-ERR syntax error\r\n"
11333        );
11334        assert_eq!(
11335            f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
11336            "-ERR wrong number of arguments for 'zrevrank' command\r\n"
11337        );
11338    }
11339
11340    #[test]
11341    fn the_two_counts_read_their_two_kinds_of_bound() {
11342        let mut f = Fixture::new();
11343        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11344        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
11345        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
11346        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
11347        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
11348        assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
11349        assert_eq!(
11350            f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
11351            "-ERR min or max is not a float\r\n"
11352        );
11353
11354        f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
11355        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
11356        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
11357        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
11358        // A bare member is not a bound, because a member can start with any
11359        // byte and there would be no way to say the bracket if it were optional.
11360        assert_eq!(
11361            f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
11362            "-ERR min or max not valid string range item\r\n"
11363        );
11364    }
11365
11366    /// The three ways `ZRANGE` can be asked for a window, forwards and back.
11367    ///
11368    /// Every byte in here was read off a real 8.10.1 rather than worked out,
11369    /// because the interesting part of this command is not what it selects, it
11370    /// is which of the two ends the client is expected to name first.
11371    #[test]
11372    fn one_range_command_selects_by_rank_or_score_or_name() {
11373        let mut f = Fixture::new();
11374        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11375        assert_eq!(
11376            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11377            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11378        );
11379        assert_eq!(
11380            f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
11381            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11382        );
11383        assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
11384        assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
11385        // REV over ranks reverses the walk and leaves the two arguments alone,
11386        // because a rank counts from the end the walk starts at.
11387        assert_eq!(
11388            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
11389            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11390        );
11391        assert_eq!(
11392            f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
11393            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11394        );
11395        // And REV over scores does swap them, since a bound does not count from
11396        // anywhere. This is the one line of the parse that tells the two apart.
11397        assert_eq!(
11398            f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
11399            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11400        );
11401        assert_eq!(
11402            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
11403            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11404        );
11405        assert_eq!(
11406            f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
11407            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11408        );
11409    }
11410
11411    /// The older spellings, which are the same six windows with the mode in the
11412    /// name and the high end named first on the three that go backwards.
11413    #[test]
11414    fn the_older_range_spellings_name_their_high_end_first() {
11415        let mut f = Fixture::new();
11416        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11417        assert_eq!(
11418            f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
11419            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11420        );
11421        assert_eq!(
11422            f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
11423            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11424        );
11425        assert_eq!(
11426            f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
11427            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11428        );
11429        assert_eq!(
11430            f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
11431            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11432        );
11433        // The two arguments the wrong way round is an empty answer and not an
11434        // error, which is what the swap being in the parse rather than in the
11435        // window buys.
11436        assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
11437        assert_eq!(
11438            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
11439            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
11440        );
11441        assert_eq!(
11442            f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
11443            "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
11444        );
11445        // BYSCORE, BYLEX and REV mean nothing to these, so they are not another
11446        // way of spelling the mode, they are a syntax error.
11447        for cmd in [
11448            &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
11449            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
11450            &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
11451        ] {
11452            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
11453        }
11454    }
11455
11456    /// `LIMIT` and `WITHSCORES`, which every one of these commands reads and
11457    /// only some of them accept.
11458    #[test]
11459    fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
11460        let mut f = Fixture::new();
11461        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11462        assert_eq!(
11463            f.run(&[
11464                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
11465            ]),
11466            "*1\r\n$1\r\nb\r\n"
11467        );
11468        // A negative offset skips past everything, a negative count is no bound.
11469        assert_eq!(
11470            f.run(&[
11471                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
11472            ]),
11473            "*0\r\n"
11474        );
11475        assert_eq!(
11476            f.run(&[
11477                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
11478            ]),
11479            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11480        );
11481        // The two options in either order, which falls out of the parse loop.
11482        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";
11483        assert_eq!(
11484            f.run(&[
11485                b"ZRANGEBYSCORE",
11486                b"z",
11487                b"1",
11488                b"3",
11489                b"WITHSCORES",
11490                b"LIMIT",
11491                b"0",
11492                b"2"
11493            ]),
11494            both
11495        );
11496        assert_eq!(
11497            f.run(&[
11498                b"ZRANGEBYSCORE",
11499                b"z",
11500                b"1",
11501                b"3",
11502                b"LIMIT",
11503                b"0",
11504                b"2",
11505                b"WITHSCORES"
11506            ]),
11507            both
11508        );
11509        // LIMIT on a range by rank is refused after the whole option list has
11510        // been read, so this complains about LIMIT and not about WITHSCORES.
11511        let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
11512        assert_eq!(
11513            f.run(&[
11514                b"ZREVRANGE",
11515                b"z",
11516                b"0",
11517                b"-1",
11518                b"WITHSCORES",
11519                b"LIMIT",
11520                b"0",
11521                b"1"
11522            ]),
11523            needs_by
11524        );
11525        assert_eq!(
11526            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
11527            needs_by
11528        );
11529        let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
11530        assert_eq!(
11531            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
11532            not_bylex
11533        );
11534        assert_eq!(
11535            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
11536            not_bylex
11537        );
11538        // Two modes at once, an option nobody knows, a LIMIT missing its count,
11539        // and the three number errors, which are three different sentences.
11540        for cmd in [
11541            &[
11542                b"ZRANGE".as_slice(),
11543                b"z",
11544                b"0",
11545                b"-1",
11546                b"BYSCORE",
11547                b"BYLEX",
11548            ][..],
11549            &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
11550            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
11551        ] {
11552            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11553        }
11554        assert_eq!(
11555            f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
11556            "-ERR min or max is not a float\r\n"
11557        );
11558        assert_eq!(
11559            f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
11560            "-ERR min or max not valid string range item\r\n"
11561        );
11562        assert_eq!(
11563            f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
11564            "-ERR value is not an integer or out of range\r\n"
11565        );
11566    }
11567
11568    /// `WITHSCORES` is the one place in this group where the two protocols
11569    /// disagree about the shape of the reply and not just the type of a value.
11570    #[test]
11571    fn withscores_nests_on_resp3_and_flattens_on_resp2() {
11572        let mut f = Fixture::new();
11573        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11574        assert_eq!(
11575            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11576            "*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"
11577        );
11578        f.out = Out::new(Proto::Resp3);
11579        assert_eq!(
11580            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11581            "*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"
11582        );
11583        assert_eq!(
11584            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11585            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11586        );
11587    }
11588
11589    /// The store form, which is the same parse with the destination in front.
11590    #[test]
11591    fn a_range_store_writes_the_window_into_another_key() {
11592        let mut f = Fixture::new();
11593        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11594        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
11595        // A window that selects nothing deletes the destination rather than
11596        // leaving an empty sorted set, because an empty one does not exist.
11597        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
11598        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11599        assert_eq!(
11600            f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
11601            ":2\r\n"
11602        );
11603        assert_eq!(
11604            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11605            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11606        );
11607        // The destination is allowed to be the source, because the result is
11608        // built whole before anything is written over.
11609        assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
11610        assert_eq!(
11611            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11612            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11613        );
11614        // It takes every option ZRANGE takes except WITHSCORES, which is a
11615        // plain syntax error here and not the sentence about BYLEX.
11616        assert_eq!(
11617            f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
11618            "-ERR syntax error\r\n"
11619        );
11620    }
11621
11622    /// The three removals, which are the read side's window with the walk
11623    /// turned into a removal and no options at all.
11624    #[test]
11625    fn the_three_removals_share_their_window_with_the_reads() {
11626        let mut f = Fixture::new();
11627        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11628        assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
11629        assert_eq!(
11630            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11631            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11632        );
11633        assert_eq!(
11634            f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
11635            ":1\r\n"
11636        );
11637        assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
11638        // The last member going takes the key with it.
11639        assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
11640        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11641        assert_eq!(
11642            f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
11643            ":0\r\n"
11644        );
11645        assert_eq!(
11646            f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
11647            "-ERR value is not an integer or out of range\r\n"
11648        );
11649    }
11650
11651    /// The algebra, which is one gather and three names for it.
11652    #[test]
11653    fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
11654        let mut f = Fixture::new();
11655        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11656        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11657        assert_eq!(
11658            f.run(&[b"ZUNION", b"2", b"z", b"y"]),
11659            "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
11660        );
11661        // The scores are added where a member is in both, and the answer comes
11662        // out in the order those combined scores put it in.
11663        assert_eq!(
11664            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
11665            "*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"
11666        );
11667        assert_eq!(
11668            f.run(&[
11669                b"ZUNION",
11670                b"2",
11671                b"z",
11672                b"y",
11673                b"WEIGHTS",
11674                b"2",
11675                b"3",
11676                b"WITHSCORES"
11677            ]),
11678            "*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"
11679        );
11680        assert_eq!(
11681            f.run(&[
11682                b"ZUNION",
11683                b"2",
11684                b"z",
11685                b"y",
11686                b"AGGREGATE",
11687                b"MIN",
11688                b"WITHSCORES"
11689            ]),
11690            "*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"
11691        );
11692        assert_eq!(
11693            f.run(&[
11694                b"ZUNION",
11695                b"2",
11696                b"z",
11697                b"y",
11698                b"AGGREGATE",
11699                b"MAX",
11700                b"WITHSCORES"
11701            ]),
11702            "*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"
11703        );
11704        assert_eq!(
11705            f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
11706            "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
11707        );
11708        assert_eq!(
11709            f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
11710            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
11711        );
11712        assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
11713        // A plain set is an input, and it behaves as a sorted set in which
11714        // every member scores one.
11715        f.run(&[b"SADD", b"p", b"a", b"d"]);
11716        assert_eq!(
11717            f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
11718            "*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"
11719        );
11720        // A difference never combines two scores, so it has nothing for either
11721        // of the two options to do and refuses both.
11722        for cmd in [
11723            &[
11724                b"ZDIFF".as_slice(),
11725                b"2",
11726                b"z",
11727                b"y",
11728                b"WEIGHTS",
11729                b"1",
11730                b"1",
11731            ][..],
11732            &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
11733        ] {
11734            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11735        }
11736    }
11737
11738    /// The count of keys, which is what lets a key be named `WEIGHTS`.
11739    #[test]
11740    fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
11741        let mut f = Fixture::new();
11742        f.run(&[b"ZADD", b"z", b"1", b"a"]);
11743        f.run(&[b"ZADD", b"y", b"2", b"b"]);
11744        // Redis names the command in this one, so each spelling says its own.
11745        assert_eq!(
11746            f.run(&[b"ZUNION", b"0", b"z"]),
11747            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11748        );
11749        assert_eq!(
11750            f.run(&[b"ZUNION", b"-1", b"z"]),
11751            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11752        );
11753        assert_eq!(
11754            f.run(&[b"ZINTERCARD", b"0", b"z"]),
11755            "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
11756        );
11757        // A count bigger than the line is a plain syntax error, which reads
11758        // oddly and is what Redis says.
11759        assert_eq!(
11760            f.run(&[b"ZUNION", b"3", b"z", b"y"]),
11761            "-ERR syntax error\r\n"
11762        );
11763        assert_eq!(
11764            f.run(&[b"ZUNION", b"x", b"z"]),
11765            "-ERR value is not an integer or out of range\r\n"
11766        );
11767        // A WEIGHTS list that is not one per key is a syntax error, and a
11768        // weight that is not a number gets a sentence of its own.
11769        assert_eq!(
11770            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
11771            "-ERR syntax error\r\n"
11772        );
11773        assert_eq!(
11774            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
11775            "-ERR weight value is not a float\r\n"
11776        );
11777        assert_eq!(
11778            f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
11779            "-ERR syntax error\r\n"
11780        );
11781    }
11782
11783    /// The three store forms, which answer a count and take no WITHSCORES.
11784    #[test]
11785    fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
11786        let mut f = Fixture::new();
11787        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11788        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11789        assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
11790        assert_eq!(
11791            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11792            "*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"
11793        );
11794        assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
11795        assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
11796        // An empty result deletes the destination rather than leaving an empty
11797        // sorted set, because an empty one does not exist.
11798        assert_eq!(
11799            f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
11800            ":0\r\n"
11801        );
11802        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11803        // The destination is allowed to name its own source.
11804        assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
11805        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
11806        for cmd in [
11807            &[
11808                b"ZUNIONSTORE".as_slice(),
11809                b"d",
11810                b"2",
11811                b"z",
11812                b"y",
11813                b"WITHSCORES",
11814            ][..],
11815            &[
11816                b"ZDIFFSTORE",
11817                b"d",
11818                b"2",
11819                b"z",
11820                b"y",
11821                b"WEIGHTS",
11822                b"1",
11823                b"1",
11824            ],
11825        ] {
11826            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11827        }
11828    }
11829
11830    /// `ZINTERCARD`, which counts without building anything.
11831    #[test]
11832    fn intercard_counts_and_stops_at_its_limit() {
11833        let mut f = Fixture::new();
11834        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11835        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
11836        assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
11837        // A limit of zero is no limit, which is Redis's reading of it.
11838        assert_eq!(
11839            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
11840            ":2\r\n"
11841        );
11842        assert_eq!(
11843            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
11844            ":1\r\n"
11845        );
11846        // A negative limit and a limit that is not a number at all get the same
11847        // sentence, which looks like a mistake in Redis and is copied as one.
11848        let bad = "-ERR LIMIT can't be negative\r\n";
11849        assert_eq!(
11850            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
11851            bad
11852        );
11853        assert_eq!(
11854            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
11855            bad
11856        );
11857        for cmd in [
11858            &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
11859            &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
11860            &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
11861        ] {
11862            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11863        }
11864    }
11865
11866    /// `ZRANDMEMBER`, which answers two different shapes out of one name.
11867    #[test]
11868    fn a_draw_answers_one_member_or_an_array_of_them() {
11869        let mut f = Fixture::new();
11870        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11871        // No count is one member or a nil, a count is an array that may be
11872        // empty, and those are two reply types the client has to tell apart.
11873        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
11874        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
11875        assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
11876        assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
11877        // A positive count draws without replacement, so a count over the size
11878        // answers the whole set and never a member twice.
11879        let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
11880        assert!(all.starts_with("*3\r\n"), "{all}");
11881        for m in ["a", "b", "c"] {
11882            assert!(all.contains(m), "{all}");
11883        }
11884        // A negative one draws with replacement and answers exactly as many as
11885        // it was asked for, whatever the size of the set.
11886        assert!(
11887            f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
11888            "five draws with replacement"
11889        );
11890        assert!(
11891            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
11892                .starts_with("*4\r\n"),
11893            "two pairs, flat on RESP2"
11894        );
11895        f.out = Out::new(Proto::Resp3);
11896        let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
11897        assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
11898        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
11899        f.out = Out::new(Proto::Resp2);
11900        assert_eq!(
11901            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
11902            "-ERR syntax error\r\n"
11903        );
11904        assert_eq!(
11905            f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
11906            "-ERR value is not an integer or out of range\r\n"
11907        );
11908    }
11909
11910    /// `ZSCAN`, and the one sorted set reply where a score is not a double.
11911    #[test]
11912    fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
11913        let mut f = Fixture::new();
11914        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11915        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";
11916        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11917        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
11918        assert_eq!(
11919            f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
11920            "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11921        );
11922        assert_eq!(
11923            f.run(&[b"ZSCAN", b"nokey", b"0"]),
11924            "*2\r\n$1\r\n0\r\n*0\r\n"
11925        );
11926        // A score stays a bulk string on RESP3, which is the one place the two
11927        // protocols agree about a score and everywhere else they do not.
11928        f.out = Out::new(Proto::Resp3);
11929        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11930        f.out = Out::new(Proto::Resp2);
11931        assert_eq!(
11932            f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
11933            "-ERR NOVALUES option can only be used in HSCAN\r\n"
11934        );
11935        assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
11936        assert_eq!(
11937            f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
11938            "-ERR syntax error\r\n"
11939        );
11940    }
11941
11942    /// The count is what decides the shape, and its value is not.
11943    #[test]
11944    fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
11945        let mut f = Fixture::new();
11946        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11947        // No count, so one flat pair, and the score is a bulk string on RESP2.
11948        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11949        assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
11950        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
11951        // A count, so pairs, and on RESP2 they are flattened into one run.
11952        assert_eq!(
11953            f.run(&[b"ZPOPMIN", b"z", b"2"]),
11954            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
11955        );
11956        // An empty array rather than a null, which is where a sorted set pop and
11957        // a list pop part company, and the same answer a count of zero gives.
11958        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
11959        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
11960        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
11961        // The last member takes the key with it.
11962        assert_eq!(
11963            f.run(&[b"ZPOPMIN", b"z", b"9"]),
11964            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11965        );
11966        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11967
11968        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
11969        f.out = Out::new(Proto::Resp3);
11970        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
11971        assert_eq!(
11972            f.run(&[b"ZPOPMIN", b"z", b"1"]),
11973            "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
11974        );
11975        f.out = Out::new(Proto::Resp2);
11976        // Both of these are the range error rather than the usual sentence about
11977        // integers, which is the odd answer and so the one worth copying.
11978        let bad = "-ERR value is out of range, must be positive\r\n";
11979        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
11980        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
11981        assert_eq!(
11982            f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
11983            "-ERR syntax error\r\n"
11984        );
11985    }
11986
11987    /// `ZMPOP`, which is `LMPOP` with scores and the same parse.
11988    #[test]
11989    fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
11990        let mut f = Fixture::new();
11991        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11992        assert_eq!(
11993            f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
11994            "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11995        );
11996        // Nested on RESP2 as well, because the key name is already in front of
11997        // the pairs and there is nothing left to flatten into.
11998        assert_eq!(
11999            f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
12000            "*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"
12001        );
12002        // A null array and not a null, the same as LMPOP.
12003        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
12004        f.out = Out::new(Proto::Resp3);
12005        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
12006        f.out = Out::new(Proto::Resp2);
12007        let numkeys = "-ERR numkeys should be greater than 0\r\n";
12008        for bad in [
12009            &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
12010            &[b"ZMPOP", b"-1", b"z", b"MIN"],
12011            &[b"ZMPOP", b"x", b"z", b"MIN"],
12012        ] {
12013            assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
12014        }
12015        let count = "-ERR count should be greater than 0\r\n";
12016        for bad in [
12017            &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
12018            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
12019            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
12020        ] {
12021            assert_eq!(f.run(bad), count, "{:?}", bad[5]);
12022        }
12023        let syntax = "-ERR syntax error\r\n";
12024        for bad in [
12025            // Two keys named and one given, so the word that should have been
12026            // the direction is a key and there is no direction left.
12027            &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
12028            &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
12029            &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
12030            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
12031        ] {
12032            assert_eq!(f.run(bad), syntax, "{bad:?}");
12033        }
12034    }
12035
12036    /// The three that wait, when there is something there and they do not have
12037    /// to. `BZPOPMIN` is the one reply in the group that is three flat elements.
12038    #[test]
12039    fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
12040        let mut f = Fixture::new();
12041        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12042        assert_eq!(
12043            f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
12044            (
12045                Flow::Continue,
12046                "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
12047            )
12048        );
12049        assert_eq!(
12050            f.run(&[b"BZPOPMAX", b"z", b"0"]),
12051            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
12052        );
12053        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12054        assert_eq!(
12055            f.run(&[
12056                b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
12057            ]),
12058            "*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"
12059        );
12060        f.out = Out::new(Proto::Resp3);
12061        assert_eq!(
12062            f.run(&[b"BZPOPMIN", b"z", b"0"]),
12063            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
12064        );
12065        f.out = Out::new(Proto::Resp2);
12066        // Nothing to take, so the client is parked and nothing was written.
12067        assert_eq!(
12068            f.flow(&[b"BZPOPMIN", b"z", b"0"]),
12069            (Flow::Block, String::new())
12070        );
12071        assert_eq!(
12072            f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
12073            (Flow::Block, String::new())
12074        );
12075        // The timeout is read before the key count, so this complains about the
12076        // timeout and not about the count.
12077        assert_eq!(
12078            f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
12079            "-ERR timeout is not a float or out of range\r\n"
12080        );
12081        assert_eq!(
12082            f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
12083            "-ERR numkeys should be greater than 0\r\n"
12084        );
12085        assert_eq!(
12086            f.run(&[b"BZPOPMIN", b"z", b"-1"]),
12087            "-ERR timeout is negative\r\n"
12088        );
12089    }
12090
12091    /// A parked sorted set client is served by whatever puts a member under one
12092    /// of its keys, and is not served by something of another type landing
12093    /// there.
12094    #[test]
12095    fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
12096        let mut f = Fixture::new();
12097        assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
12098        assert_eq!(f.server.parked(), 1);
12099        // A string under the key is not what it asked for, so it stays parked
12100        // rather than being handed a WRONGTYPE on a command that was accepted.
12101        f.run(&[b"SET", b"z", b"v"]);
12102        let mut out = Out::new(Proto::Resp2);
12103        assert!(!f.server.serve_waiter(7, 0, &mut out));
12104        assert!(out.as_slice().is_empty());
12105        f.run(&[b"DEL", b"z"]);
12106        f.run(&[b"ZADD", b"z", b"5", b"m"]);
12107        assert!(f.server.serve_waiter(7, 0, &mut out));
12108        assert_eq!(
12109            core::str::from_utf8(out.as_slice()).expect("ascii"),
12110            "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
12111        );
12112        // And the member is gone, which is what makes a queue of workers on a
12113        // sorted set work at all.
12114        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12115    }
12116
12117    #[test]
12118    fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
12119        let mut f = Fixture::new();
12120        f.run(&[b"SET", b"s", b"v"]);
12121        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12122        for cmd in [
12123            &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
12124            &[b"ZINCRBY", b"s", b"1", b"a"],
12125            &[b"ZCARD", b"s"],
12126            &[b"ZSCORE", b"s", b"a"],
12127            &[b"ZMSCORE", b"s", b"a"],
12128            &[b"ZREM", b"s", b"a"],
12129            &[b"ZRANK", b"s", b"a"],
12130            &[b"ZREVRANK", b"s", b"a"],
12131            &[b"ZCOUNT", b"s", b"1", b"2"],
12132            &[b"ZLEXCOUNT", b"s", b"-", b"+"],
12133            &[b"ZRANGE", b"s", b"0", b"-1"],
12134            &[b"ZREVRANGE", b"s", b"0", b"-1"],
12135            &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
12136            &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
12137            &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
12138            &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
12139            &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
12140            &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
12141            &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
12142            &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
12143            &[b"ZUNION", b"1", b"s"],
12144            &[b"ZINTER", b"1", b"s"],
12145            &[b"ZDIFF", b"1", b"s"],
12146            &[b"ZUNIONSTORE", b"d", b"1", b"s"],
12147            &[b"ZINTERSTORE", b"d", b"1", b"s"],
12148            &[b"ZDIFFSTORE", b"d", b"1", b"s"],
12149            &[b"ZINTERCARD", b"1", b"s"],
12150            &[b"ZRANDMEMBER", b"s"],
12151            &[b"ZSCAN", b"s", b"0"],
12152            &[b"ZPOPMIN", b"s"],
12153            &[b"ZPOPMAX", b"s", b"2"],
12154            &[b"ZMPOP", b"1", b"s", b"MIN"],
12155            &[b"BZPOPMIN", b"s", b"0"],
12156            &[b"BZPOPMAX", b"s", b"0"],
12157            &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
12158        ] {
12159            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12160        }
12161        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
12162    }
12163
12164    /// The same churn the set, the string and the list get, because a sorted
12165    /// set that leaks a tree node per add looks exactly like one that does not
12166    /// until it has run for an afternoon.
12167    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
12168    #[cfg_attr(miri, ignore = "the volume is the claim")]
12169    #[test]
12170    fn churning_sorted_sets_does_not_grow_the_server() {
12171        let mut f = Fixture::new();
12172        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
12173        let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
12174        let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
12175        for i in 0..200 {
12176            args.push(&scores[i]);
12177            args.push(&members[i]);
12178        }
12179
12180        f.run(&args);
12181        f.run(&[b"DEL", b"z"]);
12182        f.server.compact_step();
12183        let after_first = f.server.memory_bytes();
12184
12185        for _ in 0..200 {
12186            f.run(&args);
12187            f.run(&[b"DEL", b"z"]);
12188            f.server.compact_step();
12189        }
12190        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
12191        assert!(
12192            f.server.memory_bytes() <= after_first * 2,
12193            "held {} after two hundred passes against {after_first} after one",
12194            f.server.memory_bytes()
12195        );
12196    }
12197
12198    // ------------------------------------------------------------------- geo
12199
12200    /// The three places every Redis geo example uses, and one more.
12201    ///
12202    /// Every reply this section asserts on came off a running 8.10.1 with these
12203    /// three loaded, byte for byte, including the number of digits in a
12204    /// coordinate and the four places on a distance.
12205    fn sicily(f: &mut Fixture) {
12206        f.run(&[
12207            b"GEOADD",
12208            b"Sicily",
12209            b"13.361389",
12210            b"38.115556",
12211            b"Palermo",
12212            b"15.087269",
12213            b"37.502669",
12214            b"Catania",
12215        ]);
12216        f.run(&[
12217            b"GEOADD",
12218            b"Sicily",
12219            b"13.583333",
12220            b"37.316667",
12221            b"Agrigento",
12222        ]);
12223    }
12224
12225    #[test]
12226    fn places_go_in_as_scores_and_come_back_as_positions() {
12227        let mut f = Fixture::new();
12228        assert_eq!(
12229            f.run(&[
12230                b"GEOADD",
12231                b"Sicily",
12232                b"13.361389",
12233                b"38.115556",
12234                b"Palermo",
12235                b"15.087269",
12236                b"37.502669",
12237                b"Catania"
12238            ]),
12239            ":2\r\n"
12240        );
12241        // A geo key is a sorted set and says so, which is not an implementation
12242        // detail either: a client removes a place with ZREM and counts them
12243        // with ZCARD, and the score is the number a real server stores.
12244        assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
12245        assert_eq!(
12246            f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
12247            "$16\r\n3479099956230698\r\n"
12248        );
12249        assert_eq!(
12250            f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
12251            "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
12252        );
12253        assert_eq!(
12254            f.run(&[
12255                b"GEOHASH",
12256                b"Sicily",
12257                b"Palermo",
12258                b"Catania",
12259                b"NonExisting"
12260            ]),
12261            "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
12262        );
12263        // A key that is not there is an empty one, and the two nulls are not
12264        // the same null: GEOPOS answers the array one and GEOHASH the string
12265        // one, which a RESP2 client can tell apart.
12266        assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
12267        assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
12268    }
12269
12270    #[test]
12271    fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
12272        let mut f = Fixture::new();
12273        sicily(&mut f);
12274        assert_eq!(
12275            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
12276            "$11\r\n166274.1516\r\n"
12277        );
12278        assert_eq!(
12279            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
12280            "$8\r\n166.2742\r\n"
12281        );
12282        assert_eq!(
12283            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
12284            "$8\r\n103.3182\r\n"
12285        );
12286        // A member that is not there and a key that is not there are the same
12287        // nil, and the unit is read before the key is looked up, so a bad unit
12288        // on a missing key is still an error.
12289        assert_eq!(
12290            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
12291            "$-1\r\n"
12292        );
12293        assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
12294        assert_eq!(
12295            f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
12296            "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
12297        );
12298        assert_eq!(
12299            f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
12300            "-ERR syntax error\r\n"
12301        );
12302    }
12303
12304    #[test]
12305    fn a_search_finds_what_is_inside_it_nearest_first() {
12306        let mut f = Fixture::new();
12307        sicily(&mut f);
12308        let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
12309        assert_eq!(
12310            f.run(&[
12311                b"GEOSEARCH",
12312                b"Sicily",
12313                b"FROMLONLAT",
12314                b"15",
12315                b"37",
12316                b"BYRADIUS",
12317                b"200",
12318                b"km",
12319                b"ASC"
12320            ]),
12321            all
12322        );
12323        // The older spelling of the same search, which is the same nine boxes
12324        // and the same order.
12325        assert_eq!(
12326            f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
12327            all
12328        );
12329        assert_eq!(
12330            f.run(&[
12331                b"GEORADIUS_RO",
12332                b"Sicily",
12333                b"15",
12334                b"37",
12335                b"200",
12336                b"km",
12337                b"ASC"
12338            ]),
12339            all
12340        );
12341        // A count with no ordering means the nearest ones, so DESC has to be
12342        // asked for to get the far end.
12343        assert_eq!(
12344            f.run(&[
12345                b"GEORADIUS",
12346                b"Sicily",
12347                b"15",
12348                b"37",
12349                b"200",
12350                b"km",
12351                b"DESC",
12352                b"COUNT",
12353                b"1"
12354            ]),
12355            "*1\r\n$7\r\nPalermo\r\n"
12356        );
12357        assert_eq!(
12358            f.run(&[
12359                b"GEORADIUS",
12360                b"Sicily",
12361                b"15",
12362                b"37",
12363                b"200",
12364                b"km",
12365                b"COUNT",
12366                b"1"
12367            ]),
12368            "*1\r\n$7\r\nCatania\r\n"
12369        );
12370        // Nothing inside a kilometre of that point, and nothing in a key that
12371        // is not there, and both are the empty array rather than an error.
12372        let empty = "*0\r\n";
12373        assert_eq!(
12374            f.run(&[
12375                b"GEOSEARCH",
12376                b"Sicily",
12377                b"FROMLONLAT",
12378                b"15",
12379                b"37",
12380                b"BYRADIUS",
12381                b"1",
12382                b"km"
12383            ]),
12384            empty
12385        );
12386        assert_eq!(
12387            f.run(&[
12388                b"GEOSEARCH",
12389                b"nokey",
12390                b"FROMLONLAT",
12391                b"15",
12392                b"37",
12393                b"BYRADIUS",
12394                b"1",
12395                b"km"
12396            ]),
12397            empty
12398        );
12399        assert_eq!(
12400            f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
12401            empty
12402        );
12403    }
12404
12405    #[test]
12406    fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
12407        let mut f = Fixture::new();
12408        sicily(&mut f);
12409        assert_eq!(
12410            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
12411            "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12412        );
12413        // The member itself is nothing away from itself, which is where the
12414        // fixed point writer's zero shows up on the wire.
12415        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";
12416        assert_eq!(
12417            f.run(&[
12418                b"GEORADIUSBYMEMBER_RO",
12419                b"Sicily",
12420                b"Agrigento",
12421                b"100",
12422                b"km",
12423                b"WITHDIST"
12424            ]),
12425            with_dist
12426        );
12427        assert_eq!(
12428            f.run(&[
12429                b"GEOSEARCH",
12430                b"Sicily",
12431                b"FROMMEMBER",
12432                b"Agrigento",
12433                b"BYRADIUS",
12434                b"100",
12435                b"km",
12436                b"ASC",
12437                b"WITHDIST"
12438            ]),
12439            with_dist
12440        );
12441        assert_eq!(
12442            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
12443            "-ERR could not decode requested zset member\r\n"
12444        );
12445    }
12446
12447    #[test]
12448    fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
12449        let mut f = Fixture::new();
12450        sicily(&mut f);
12451        // Three options asked for, so each result is a four element array of
12452        // the member, the distance, the hash and a pair. The order of the three
12453        // is Redis's and not the order they were written in the command.
12454        assert_eq!(
12455            f.run(&[
12456                b"GEOSEARCH",
12457                b"Sicily",
12458                b"FROMLONLAT",
12459                b"15",
12460                b"37",
12461                b"BYBOX",
12462                b"400",
12463                b"400",
12464                b"km",
12465                b"ASC",
12466                b"WITHCOORD",
12467                b"WITHDIST",
12468                b"WITHHASH"
12469            ]),
12470            "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
12471             $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
12472             *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
12473             $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
12474             *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
12475             $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
12476        );
12477    }
12478
12479    #[test]
12480    fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
12481        let mut f = Fixture::new();
12482        sicily(&mut f);
12483        let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
12484                      $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
12485                      $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
12486        assert_eq!(
12487            f.run(&[
12488                b"GEOSEARCHSTORE",
12489                b"dst",
12490                b"Sicily",
12491                b"FROMLONLAT",
12492                b"15",
12493                b"37",
12494                b"BYRADIUS",
12495                b"200",
12496                b"km",
12497                b"ASC"
12498            ]),
12499            ":3\r\n"
12500        );
12501        assert_eq!(
12502            f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
12503            hashes
12504        );
12505        // The same again through the older spelling, which stores the same
12506        // scores, so a key written by either is a geo key.
12507        assert_eq!(
12508            f.run(&[
12509                b"GEORADIUS",
12510                b"Sicily",
12511                b"15",
12512                b"37",
12513                b"200",
12514                b"km",
12515                b"STORE",
12516                b"dst3"
12517            ]),
12518            ":3\r\n"
12519        );
12520        assert_eq!(
12521            f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
12522            hashes
12523        );
12524        // STOREDIST stores the distance in the search unit instead, and those
12525        // are full doubles rather than the four places WITHDIST writes. The
12526        // numbers on the right are what 8.10.1 stored for this search, and they
12527        // are compared with a tolerance rather than byte for byte because the
12528        // last bit of a haversine is the platform's sin, cos and asin: this
12529        // machine and that one disagree in the sixteenth digit, and so do two
12530        // Redis builds. Everything a client actually reads back is four places
12531        // and is asserted exactly above.
12532        assert_eq!(
12533            f.run(&[
12534                b"GEOSEARCHSTORE",
12535                b"dst2",
12536                b"Sicily",
12537                b"FROMLONLAT",
12538                b"15",
12539                b"37",
12540                b"BYRADIUS",
12541                b"200",
12542                b"km",
12543                b"ASC",
12544                b"STOREDIST"
12545            ]),
12546            ":3\r\n"
12547        );
12548        for (member, want) in [
12549            ("Catania", 56.441_257_870_158_19),
12550            ("Agrigento", 130.423_487_067_147_14),
12551            ("Palermo", 190.442_429_847_757_92),
12552        ] {
12553            let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
12554            let got: f64 = reply
12555                .trim_start_matches(|c: char| c != '\n')
12556                .trim()
12557                .parse()
12558                .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
12559            assert!(
12560                (got - want).abs() < 1e-9,
12561                "{member} scored {got} not {want}"
12562            );
12563        }
12564        // The order they went in is the order the scores put them in, which is
12565        // the point of storing the distance rather than the hash.
12566        assert_eq!(
12567            f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
12568            "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12569        );
12570        // A search that finds nothing takes the destination with it rather than
12571        // leaving what was there, and a source key that is not there is a
12572        // search that finds nothing.
12573        assert_eq!(
12574            f.run(&[
12575                b"GEOSEARCHSTORE",
12576                b"dst",
12577                b"nokey",
12578                b"FROMLONLAT",
12579                b"15",
12580                b"37",
12581                b"BYRADIUS",
12582                b"200",
12583                b"km"
12584            ]),
12585            ":0\r\n"
12586        );
12587        assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
12588    }
12589
12590    #[test]
12591    fn the_gates_on_geoadd_are_the_ones_zadd_has() {
12592        let mut f = Fixture::new();
12593        sicily(&mut f);
12594        // XX on a member that is already where it is changes nothing, and NX on
12595        // one that is there refuses to move it.
12596        assert_eq!(
12597            f.run(&[
12598                b"GEOADD",
12599                b"Sicily",
12600                b"XX",
12601                b"CH",
12602                b"13.361389",
12603                b"38.115556",
12604                b"Palermo"
12605            ]),
12606            ":0\r\n"
12607        );
12608        assert_eq!(
12609            f.run(&[
12610                b"GEOADD",
12611                b"Sicily",
12612                b"NX",
12613                b"13.361389",
12614                b"38.9",
12615                b"Palermo"
12616            ]),
12617            ":0\r\n"
12618        );
12619        assert_eq!(
12620            f.run(&[
12621                b"GEOADD",
12622                b"Sicily",
12623                b"CH",
12624                b"13.361389",
12625                b"38.9",
12626                b"Palermo"
12627            ]),
12628            ":1\r\n"
12629        );
12630        // Out of range, and nothing is stored: the whole call is refused rather
12631        // than the good pairs going in and the bad one stopping it.
12632        assert_eq!(
12633            f.run(&[
12634                b"GEOADD",
12635                b"new",
12636                b"13.361389",
12637                b"38.115556",
12638                b"here",
12639                b"181",
12640                b"38",
12641                b"there"
12642            ]),
12643            "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
12644        );
12645        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
12646        assert_eq!(
12647            f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
12648            "-ERR value is not a valid float\r\n"
12649        );
12650        // The count of triples is checked before the two gates are, and a call
12651        // with no triples at all reaches the same sentence.
12652        assert_eq!(
12653            f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
12654            "-ERR syntax error\r\n"
12655        );
12656        assert_eq!(
12657            f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
12658            "-ERR syntax error\r\n"
12659        );
12660        assert_eq!(
12661            f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
12662            "-ERR syntax error\r\n"
12663        );
12664        assert_eq!(
12665            f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
12666            "-ERR wrong number of arguments for 'geoadd' command\r\n"
12667        );
12668    }
12669
12670    /// The sentences a search answers, which are its contract as much as the
12671    /// results are.
12672    #[test]
12673    fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
12674        let mut f = Fixture::new();
12675        sicily(&mut f);
12676        let cases: &[(&[&[u8]], &str)] = &[
12677            (
12678                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
12679                "-ERR need numeric radius\r\n",
12680            ),
12681            (
12682                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
12683                "-ERR radius cannot be negative\r\n",
12684            ),
12685            (
12686                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
12687                "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
12688            ),
12689            (
12690                &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
12691                "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
12692            ),
12693            (
12694                &[
12695                    b"GEOSEARCH",
12696                    b"Sicily",
12697                    b"FROMLONLAT",
12698                    b"15",
12699                    b"37",
12700                    b"BYBOX",
12701                    b"x",
12702                    b"1",
12703                    b"km",
12704                ],
12705                "-ERR need numeric width\r\n",
12706            ),
12707            (
12708                &[
12709                    b"GEOSEARCH",
12710                    b"Sicily",
12711                    b"FROMLONLAT",
12712                    b"15",
12713                    b"37",
12714                    b"BYBOX",
12715                    b"1",
12716                    b"y",
12717                    b"km",
12718                ],
12719                "-ERR need numeric height\r\n",
12720            ),
12721            (
12722                &[
12723                    b"GEOSEARCH",
12724                    b"Sicily",
12725                    b"FROMLONLAT",
12726                    b"15",
12727                    b"37",
12728                    b"BYBOX",
12729                    b"-1",
12730                    b"1",
12731                    b"km",
12732                ],
12733                "-ERR height or width cannot be negative\r\n",
12734            ),
12735            (
12736                &[
12737                    b"GEOSEARCH",
12738                    b"Sicily",
12739                    b"FROMLONLAT",
12740                    b"15",
12741                    b"37",
12742                    b"BYRADIUS",
12743                    b"1",
12744                    b"km",
12745                    b"ANY",
12746                ],
12747                "-ERR the ANY argument requires COUNT argument\r\n",
12748            ),
12749            (
12750                &[
12751                    b"GEOSEARCH",
12752                    b"Sicily",
12753                    b"FROMLONLAT",
12754                    b"15",
12755                    b"37",
12756                    b"BYRADIUS",
12757                    b"1",
12758                    b"km",
12759                    b"COUNT",
12760                    b"0",
12761                ],
12762                "-ERR COUNT must be > 0\r\n",
12763            ),
12764            (
12765                &[
12766                    b"GEOSEARCH",
12767                    b"Sicily",
12768                    b"BYRADIUS",
12769                    b"1",
12770                    b"km",
12771                    b"BYBOX",
12772                    b"1",
12773                    b"1",
12774                    b"km",
12775                ],
12776                "-ERR syntax error\r\n",
12777            ),
12778            (
12779                &[
12780                    b"GEOSEARCH",
12781                    b"Sicily",
12782                    b"FROMMEMBER",
12783                    b"Palermo",
12784                    b"FROMLONLAT",
12785                    b"1",
12786                    b"2",
12787                    b"BYRADIUS",
12788                    b"1",
12789                    b"km",
12790                ],
12791                "-ERR syntax error\r\n",
12792            ),
12793            // The two options a GEOSEARCH cannot leave out, each with its own
12794            // sentence, and the command quoted the way the client spelled it.
12795            (
12796                &[
12797                    b"geosearch",
12798                    b"Sicily",
12799                    b"BYRADIUS",
12800                    b"1",
12801                    b"km",
12802                    b"ASC",
12803                    b"WITHDIST",
12804                ],
12805                "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
12806            ),
12807            (
12808                &[
12809                    b"GEOSEARCH",
12810                    b"Sicily",
12811                    b"FROMLONLAT",
12812                    b"15",
12813                    b"37",
12814                    b"ASC",
12815                    b"WITHDIST",
12816                ],
12817                "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
12818            ),
12819            // A store cannot also be asked for the distance, and the two
12820            // families name themselves differently in the same sentence.
12821            (
12822                &[
12823                    b"GEOSEARCHSTORE",
12824                    b"d",
12825                    b"Sicily",
12826                    b"FROMLONLAT",
12827                    b"15",
12828                    b"37",
12829                    b"BYRADIUS",
12830                    b"1",
12831                    b"km",
12832                    b"WITHCOORD",
12833                ],
12834                "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12835            ),
12836            (
12837                &[
12838                    b"GEORADIUS",
12839                    b"Sicily",
12840                    b"15",
12841                    b"37",
12842                    b"1",
12843                    b"km",
12844                    b"WITHDIST",
12845                    b"STORE",
12846                    b"d",
12847                ],
12848                "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12849            ),
12850            // The read only forms have no store at all, so the word is a stray
12851            // one, and GEOSEARCH's STOREDIST is only a GEOSEARCHSTORE option.
12852            (
12853                &[
12854                    b"GEORADIUS_RO",
12855                    b"Sicily",
12856                    b"15",
12857                    b"37",
12858                    b"1",
12859                    b"km",
12860                    b"STORE",
12861                    b"d",
12862                ],
12863                "-ERR syntax error\r\n",
12864            ),
12865            (
12866                &[
12867                    b"GEOSEARCH",
12868                    b"Sicily",
12869                    b"FROMLONLAT",
12870                    b"15",
12871                    b"37",
12872                    b"BYRADIUS",
12873                    b"1",
12874                    b"km",
12875                    b"STOREDIST",
12876                ],
12877                "-ERR syntax error\r\n",
12878            ),
12879        ];
12880        for (parts, want) in cases {
12881            assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
12882        }
12883    }
12884
12885    /// A wrong type wins over a bad argument, because the key is looked up
12886    /// first, and every one of the ten says the same thing about it.
12887    #[test]
12888    fn every_geo_command_says_wrongtype() {
12889        let mut f = Fixture::new();
12890        f.run(&[b"SET", b"s", b"v"]);
12891        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12892        let cases: &[&[&[u8]]] = &[
12893            &[b"GEOADD", b"s", b"13", b"38", b"m"],
12894            &[b"GEOPOS", b"s", b"m"],
12895            &[b"GEOHASH", b"s", b"m"],
12896            &[b"GEODIST", b"s", b"a", b"b"],
12897            &[
12898                b"GEOSEARCH",
12899                b"s",
12900                b"FROMLONLAT",
12901                b"15",
12902                b"37",
12903                b"BYRADIUS",
12904                b"1",
12905                b"km",
12906            ],
12907            &[
12908                b"GEOSEARCHSTORE",
12909                b"d",
12910                b"s",
12911                b"FROMLONLAT",
12912                b"15",
12913                b"37",
12914                b"BYRADIUS",
12915                b"1",
12916                b"km",
12917            ],
12918            &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
12919            &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
12920            &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
12921            &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
12922        ];
12923        for case in cases {
12924            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
12925        }
12926        // And it wins over an argument that will not parse, which is the whole
12927        // reason the lookup comes first.
12928        assert_eq!(
12929            f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
12930            wrong
12931        );
12932    }
12933
12934    // ----------------------------------------------------------------- array
12935
12936    #[test]
12937    fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
12938        let mut f = Fixture::new();
12939        // Three consecutive positions from a high index, and the reply is how
12940        // many of them were empty before rather than how many were written.
12941        assert_eq!(
12942            f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
12943            ":3\r\n"
12944        );
12945        assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
12946        assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
12947        assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
12948        // A hole and a key that is not there are the same answer.
12949        assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
12950        assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
12951        assert_eq!(
12952            f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
12953            "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
12954        );
12955        // Scattered pairs in one command, last write wins within it.
12956        assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
12957        assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
12958    }
12959
12960    /// The two numbers an array reports are not the same number, and one of
12961    /// them does not fit a signed integer.
12962    #[test]
12963    fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
12964        let mut f = Fixture::new();
12965        assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
12966        assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
12967        f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
12968        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
12969        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
12970        // Deleting in the middle leaves the high water mark where it was.
12971        assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
12972        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
12973        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
12974
12975        // The top of the space is addressable, and its length is a number with
12976        // bit sixty three set, so the reply has to be unsigned or it comes back
12977        // negative.
12978        f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
12979        assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
12980        assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
12981        // And one past it does not exist, so a write that would reach it fails
12982        // before any of it lands.
12983        assert_eq!(
12984            f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
12985            "-ERR array index overflow\r\n"
12986        );
12987        assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
12988    }
12989
12990    /// One reply per position and not one per element, which is the whole
12991    /// reason the range is capped.
12992    #[test]
12993    fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
12994        let mut f = Fixture::new();
12995        f.run(&[b"ARSET", b"a", b"1", b"x"]);
12996        assert_eq!(
12997            f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
12998            "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
12999        );
13000        // The two ends may come in either order, and the answer is reversed
13001        // rather than empty.
13002        assert_eq!(
13003            f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
13004            "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
13005        );
13006        // A key that is not there reads like an array of nothing but holes.
13007        assert_eq!(
13008            f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
13009            "*2\r\n$-1\r\n$-1\r\n"
13010        );
13011        // A range wider than a million positions is refused and not trimmed,
13012        // because against a missing key it is a request for as many nulls as
13013        // the range is wide.
13014        assert_eq!(
13015            f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
13016            "-ERR range exceeds maximum of 1000000 items\r\n"
13017        );
13018    }
13019
13020    /// Every index in the argument list is read before the key is touched, so
13021    /// a bad one at the end leaves nothing half written.
13022    #[test]
13023    fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
13024        let mut f = Fixture::new();
13025        assert_eq!(
13026            f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
13027            "-ERR invalid array index\r\n"
13028        );
13029        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13030        f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
13031        assert_eq!(
13032            f.run(&[b"ARDEL", b"a", b"0", b"01"]),
13033            "-ERR invalid array index\r\n"
13034        );
13035        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
13036        // An index is unsigned here, so the numbers a list would take are not
13037        // the last element, they are errors.
13038        assert_eq!(
13039            f.run(&[b"ARGET", b"a", b"-1"]),
13040            "-ERR invalid array index\r\n"
13041        );
13042        // And a pair list with an odd tail is an arity error rather than a
13043        // syntax one.
13044        assert_eq!(
13045            f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
13046            "-ERR wrong number of arguments for 'armset' command\r\n"
13047        );
13048        assert_eq!(
13049            f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
13050            "-ERR wrong number of arguments for 'ardelrange' command\r\n"
13051        );
13052    }
13053
13054    #[test]
13055    fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
13056        let mut f = Fixture::new();
13057        f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
13058        assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
13059        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13060        // Two ranges in one command, and the second one covers the whole space
13061        // without walking it.
13062        assert_eq!(
13063            f.run(&[
13064                b"ARDELRANGE",
13065                b"a",
13066                b"100",
13067                b"200",
13068                b"0",
13069                b"18446744073709551614"
13070            ]),
13071            ":2\r\n"
13072        );
13073        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13074        assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
13075        assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
13076    }
13077
13078    /// A value goes out as the bytes it came in as, whichever of the three ways
13079    /// the array found to store it.
13080    #[test]
13081    fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
13082        let mut f = Fixture::new();
13083        let long = vec![b'v'; 200];
13084        f.run(&[
13085            b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
13086            b"short", b"5", &long, b"6", b"-0",
13087        ]);
13088        // 42 is an integer, 007 is not one because it does not print back the
13089        // same, 3.5 survives a double and 3.14 does not, and the last two are a
13090        // word packed string and a blob.
13091        assert_eq!(
13092            f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
13093            format!(
13094                "*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",
13095                String::from_utf8_lossy(&long)
13096            )
13097        );
13098    }
13099
13100    #[test]
13101    fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
13102        let mut f = Fixture::new();
13103        f.run(&[b"ARSET", b"a", b"0", b"x"]);
13104        assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
13105        assert_eq!(
13106            f.run(&[b"OBJECT", b"ENCODING", b"a"]),
13107            "$12\r\nsliced-array\r\n"
13108        );
13109        // And it is a body like any other, so the key commands work on it.
13110        assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
13111        assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
13112        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
13113        assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
13114        assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
13115        assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
13116    }
13117
13118    #[test]
13119    fn every_array_command_refuses_a_key_holding_something_else() {
13120        let mut f = Fixture::new();
13121        f.run(&[b"SET", b"s", b"v"]);
13122        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13123        for cmd in [
13124            &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
13125            &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
13126            &[b"ARGET".as_ref(), b"s", b"0"][..],
13127            &[b"ARMGET".as_ref(), b"s", b"0"][..],
13128            &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
13129            &[b"ARLEN".as_ref(), b"s"][..],
13130            &[b"ARCOUNT".as_ref(), b"s"][..],
13131            &[b"ARDEL".as_ref(), b"s", b"0"][..],
13132            &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
13133            &[b"ARINSERT".as_ref(), b"s", b"x"][..],
13134            &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
13135            &[b"ARNEXT".as_ref(), b"s"][..],
13136            &[b"ARSEEK".as_ref(), b"s", b"1"][..],
13137            &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
13138            &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
13139            &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
13140            &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
13141            &[b"ARINFO".as_ref(), b"s"][..],
13142        ] {
13143            assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
13144        }
13145    }
13146
13147    /// Two of the array commands look the key up before they read the index and
13148    /// the rest read the index first, so the same broken argument gets two
13149    /// different errors depending on which command it went to.
13150    #[test]
13151    fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
13152        let mut f = Fixture::new();
13153        f.run(&[b"SET", b"s", b"v"]);
13154        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13155        let bad = "-ERR invalid array index\r\n";
13156        assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
13157        assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
13158        assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
13159        assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
13160        assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
13161        assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
13162        // And on a key that is an array the index is just an index.
13163        f.run(&[b"ARSET", b"a", b"0", b"x"]);
13164        assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
13165        assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
13166    }
13167
13168    #[test]
13169    fn an_append_follows_a_cursor_the_client_can_move() {
13170        let mut f = Fixture::new();
13171        assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
13172        assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
13173        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
13174        assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
13175        assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
13176
13177        // A seek says where the next one goes, and a missing key has no cursor
13178        // to move and is not created by the asking.
13179        assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
13180        assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
13181        assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
13182        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
13183        assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
13184        assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
13185        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
13186
13187        // The top of the space is the one index only ARSEEK will take, and it
13188        // leaves the cursor with nowhere to go.
13189        assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
13190        assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
13191        assert_eq!(
13192            f.run(&[b"ARINSERT", b"a", b"x"]),
13193            "-ERR insert index overflow\r\n"
13194        );
13195        assert_eq!(
13196            f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
13197            "-ERR invalid array index\r\n"
13198        );
13199    }
13200
13201    #[test]
13202    fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
13203        let mut f = Fixture::new();
13204        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
13205        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
13206        assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
13207        assert_eq!(
13208            f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
13209            "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
13210        );
13211        // Growing it after it has wrapped puts the survivors back in the order
13212        // they arrived, which is the whole point of paying for the rebuild.
13213        assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
13214        assert_eq!(
13215            f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
13216            "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
13217        );
13218        // The size is read before the key, so a bad one is a bad size wherever
13219        // it is sent.
13220        assert_eq!(
13221            f.run(&[b"ARRING", b"r", b"0", b"x"]),
13222            "-ERR size must be positive\r\n"
13223        );
13224        assert_eq!(
13225            f.run(&[b"ARRING", b"r", b"big", b"x"]),
13226            "-ERR invalid size\r\n"
13227        );
13228    }
13229
13230    #[test]
13231    fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
13232        let mut f = Fixture::new();
13233        assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
13234        f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
13235        assert_eq!(
13236            f.run(&[b"ARLASTITEMS", b"r", b"3"]),
13237            "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
13238        );
13239        assert_eq!(
13240            f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
13241            "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
13242        );
13243        assert_eq!(
13244            f.run(&[b"ARLASTITEMS", b"r", b"99"]),
13245            "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
13246            "more than there is gets what there is"
13247        );
13248        // Nothing asked for is an empty reply, and Redis answers that before it
13249        // has read the option or looked at the key.
13250        assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
13251        assert_eq!(
13252            f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
13253            "-ERR syntax error\r\n"
13254        );
13255        assert_eq!(
13256            f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
13257            "-ERR invalid COUNT\r\n"
13258        );
13259
13260        // With no cursor the tail of the array is the anchor, and a hole inside
13261        // the window is reported as one.
13262        f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
13263        assert_eq!(
13264            f.run(&[b"ARLASTITEMS", b"h", b"5"]),
13265            "*2\r\n$-1\r\n$1\r\nz\r\n"
13266        );
13267    }
13268
13269    #[test]
13270    fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
13271        let mut f = Fixture::new();
13272        assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
13273        f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
13274        // The whole index space, which ARGETRANGE refuses and this one answers
13275        // in three visits because holes cost nothing.
13276        assert_eq!(
13277            f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
13278            "*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"
13279        );
13280        assert_eq!(
13281            f.run(&[
13282                b"ARSCAN",
13283                b"a",
13284                b"18446744073709551614",
13285                b"0",
13286                b"LIMIT",
13287                b"1"
13288            ]),
13289            "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
13290        );
13291        assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
13292        assert_eq!(
13293            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
13294            "-ERR LIMIT must be positive\r\n"
13295        );
13296        assert_eq!(
13297            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
13298            "-ERR syntax error\r\n"
13299        );
13300        assert_eq!(
13301            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
13302            "-ERR wrong number of arguments for 'arscan' command\r\n"
13303        );
13304    }
13305
13306    #[test]
13307    fn a_grep_answers_the_indexes_whose_elements_match() {
13308        let mut f = Fixture::new();
13309        assert_eq!(
13310            f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
13311            "*0\r\n"
13312        );
13313        f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
13314
13315        // The two bounds take the ends of the array as well as an index, and a
13316        // reversed range is walked backwards the way ARSCAN walks one.
13317        assert_eq!(
13318            f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
13319            "*3\r\n:0\r\n:1\r\n:2\r\n"
13320        );
13321        assert_eq!(
13322            f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
13323            "*3\r\n:2\r\n:1\r\n:0\r\n"
13324        );
13325        assert_eq!(
13326            f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
13327            "*2\r\n:1\r\n:2\r\n"
13328        );
13329
13330        // One test each. NOCASE reaches all four of them and it may be written
13331        // after the pattern it applies to.
13332        assert_eq!(
13333            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
13334            "*1\r\n:0\r\n"
13335        );
13336        assert_eq!(
13337            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
13338            "*2\r\n:0\r\n:3\r\n"
13339        );
13340        assert_eq!(
13341            f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
13342            "*1\r\n:2\r\n"
13343        );
13344        assert_eq!(
13345            f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
13346            "*2\r\n:1\r\n:2\r\n"
13347        );
13348
13349        // OR is the default and AND has to be asked for, and either way the
13350        // last of a repeated option wins.
13351        let both: &[&[u8]] = &[
13352            b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
13353        ];
13354        assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
13355        assert_eq!(
13356            f.run(&[
13357                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
13358            ]),
13359            "*0\r\n"
13360        );
13361        assert_eq!(
13362            f.run(&[
13363                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
13364            ]),
13365            "*2\r\n:0\r\n:1\r\n"
13366        );
13367
13368        // WITHVALUES turns each hit into a pair, and LIMIT counts the hits and
13369        // not the positions it had to look at.
13370        assert_eq!(
13371            f.run(&[
13372                b"ARGREP",
13373                b"a",
13374                b"-",
13375                b"+",
13376                b"MATCH",
13377                b"a",
13378                b"WITHVALUES",
13379                b"LIMIT",
13380                b"2"
13381            ]),
13382            "*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"
13383        );
13384        assert_eq!(
13385            f.run(&[
13386                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
13387            ]),
13388            "*1\r\n:3\r\n"
13389        );
13390    }
13391
13392    /// Everything ARGREP refuses, in the order it refuses it.
13393    #[test]
13394    fn a_grep_reports_a_broken_command_the_way_redis_does() {
13395        let mut f = Fixture::new();
13396        f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
13397        let syntax = "-ERR syntax error\r\n";
13398
13399        // The bounds are read before the plan, so a bad index beats a bad
13400        // predicate whichever way round the two are written.
13401        assert_eq!(
13402            f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
13403            "-ERR invalid array index\r\n"
13404        );
13405        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
13406        // A keyword with nothing after it, and a command that asks for nothing.
13407        assert_eq!(
13408            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
13409            syntax
13410        );
13411        assert_eq!(
13412            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
13413            syntax
13414        );
13415        assert_eq!(
13416            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
13417            syntax,
13418            "a command with no predicate in it at all"
13419        );
13420        assert_eq!(
13421            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
13422            "-ERR LIMIT must be positive\r\n"
13423        );
13424        assert_eq!(
13425            f.run(&[
13426                b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
13427            ]),
13428            "-ERR value is not an integer or out of range\r\n"
13429        );
13430        assert_eq!(
13431            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
13432            "-ERR regular expression is empty\r\n"
13433        );
13434        assert_eq!(
13435            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
13436            "-ERR invalid regular expression: Missing ')'\r\n"
13437        );
13438        assert_eq!(
13439            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
13440            "-ERR regular expression backreferences are not supported\r\n"
13441        );
13442        // The arity is minus six, so a predicate keyword with no pattern after
13443        // it is short by one and never reaches the parser.
13444        let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
13445        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
13446        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
13447    }
13448
13449    #[test]
13450    fn an_op_reduces_a_range_to_one_number() {
13451        let mut f = Fixture::new();
13452        f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
13453        assert_eq!(
13454            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
13455            "$4\r\n-0.5\r\n"
13456        );
13457        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
13458        assert_eq!(
13459            f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
13460            "$3\r\n2.5\r\n"
13461        );
13462        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
13463        assert_eq!(
13464            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
13465            ":1\r\n"
13466        );
13467        // An aggregate is written with seventeen significant digits, which is
13468        // Redis's own choice and not what a score comes back as.
13469        f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
13470        assert_eq!(
13471            f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
13472            "$19\r\n0.30000000000000004\r\n"
13473        );
13474        assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
13475        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
13476
13477        // Nothing to work with is a null, and a missing key is a null for the
13478        // aggregates and a zero for the two that count.
13479        f.run(&[b"ARSET", b"w", b"0", b"word"]);
13480        assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
13481        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
13482        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
13483
13484        assert_eq!(
13485            f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
13486            "-ERR unknown operation\r\n"
13487        );
13488        assert_eq!(
13489            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
13490            "-ERR MATCH requires a value argument\r\n"
13491        );
13492        assert_eq!(
13493            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
13494            "-ERR wrong number of arguments for 'arop' command\r\n"
13495        );
13496    }
13497
13498    #[test]
13499    fn the_info_is_a_map_and_a_missing_key_is_an_error() {
13500        let mut f = Fixture::new();
13501        assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
13502        f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
13503        let short = f.run(&[b"ARINFO", b"a"]);
13504        assert!(
13505            short.starts_with("*14\r\n"),
13506            "seven pairs on RESP2: {short}"
13507        );
13508        assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
13509        assert!(
13510            short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
13511            "{short}"
13512        );
13513        assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
13514        let full = f.run(&[b"ARINFO", b"a", b"full"]);
13515        assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
13516        // Two values one apart are held sparsely, so the dense count is zero and
13517        // the two dense averages have nothing to average.
13518        assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
13519        assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
13520        assert!(
13521            full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
13522            "{full}"
13523        );
13524        assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
13525
13526        // On RESP3 the same reply is a map and the averages are doubles.
13527        let mut g = Fixture::new();
13528        g.run(&[b"HELLO", b"3"]);
13529        g.run(&[b"ARINSERT", b"a", b"x"]);
13530        let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
13531        assert!(map.starts_with("%12\r\n"), "{map}");
13532        assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
13533        assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
13534    }
13535
13536    #[test]
13537    fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
13538        let mut f = Fixture::new();
13539        // Whole numbers up to two to the sixty second come back as integers,
13540        // and past that the digit generator takes over and uses an exponent.
13541        for (score, want) in [
13542            ("3", "3"),
13543            ("3.5", "3.5"),
13544            ("0.3", "0.3"),
13545            ("1e30", "1e+30"),
13546            ("1e19", "1e+19"),
13547            ("1e-7", "1e-7"),
13548            ("0.000001", "0.000001"),
13549            ("4611686018427387904", "4611686018427387904"),
13550            ("-0", "-0"),
13551        ] {
13552            f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
13553            assert_eq!(
13554                f.run(&[b"ZSCORE", b"z", b"m"]),
13555                format!("${}\r\n{want}\r\n", want.len()),
13556                "score {score}"
13557            );
13558        }
13559
13560        // The same bytes on RESP3, where the reply is a double rather than a
13561        // bulk string.
13562        let mut g = Fixture::new();
13563        g.run(&[b"HELLO", b"3"]);
13564        g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
13565        assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
13566        // The two float increments are not this printer. They go through
13567        // ld2string in its human mode, which is a fixed point conversion with
13568        // the trailing zeros taken off, so they never write an exponent, and
13569        // they reply with a bulk string on both protocols.
13570        assert_eq!(
13571            g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
13572            "$31\r\n1000000000000000000000000000000\r\n"
13573        );
13574        assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
13575        assert_eq!(
13576            g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
13577            "$20\r\n10000000000000000000\r\n"
13578        );
13579    }
13580
13581    // ----------------------------------------------------------------- graph
13582
13583    #[test]
13584    fn a_node_comes_back_with_the_fields_it_went_in_with() {
13585        let mut f = Fixture::new();
13586        assert_eq!(
13587            f.run(&[
13588                b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
13589            ]),
13590            ":1\r\n"
13591        );
13592        // The year comes back as the four bytes that were sent and not as a
13593        // number, because every property is text and there is nothing on the
13594        // wire that says which of `1815` and `"1815"` the client meant. The
13595        // fields are in the document's order, which is sorted by name, because
13596        // that is what makes a field lookup a binary search.
13597        assert_eq!(
13598            f.run(&[b"G.NGET", b"social", b"ada"]),
13599            "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13600        );
13601        // A second write to the same id replaces the document and says so with
13602        // a zero, so an ingest can count what it created.
13603        assert_eq!(
13604            f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
13605            ":0\r\n"
13606        );
13607        assert_eq!(
13608            f.run(&[b"G.NGET", b"social", b"ada"]),
13609            "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
13610        );
13611        // A node with no properties is an empty map and not a null, which is
13612        // how a client tells an isolated node from one that is not there.
13613        assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
13614        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13615        assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
13616        assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
13617
13618        // A field with no value creates nothing, because the pairs are checked
13619        // before the key is touched.
13620        assert_eq!(
13621            f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
13622            "-ERR syntax error\r\n"
13623        );
13624        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
13625
13626        // On RESP3 the same reply is a map.
13627        let mut g = Fixture::new();
13628        g.run(&[b"HELLO", b"3"]);
13629        g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
13630        assert_eq!(
13631            g.run(&[b"G.NGET", b"social", b"ada"]),
13632            "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13633        );
13634    }
13635
13636    #[test]
13637    fn an_edge_creates_the_ends_it_needs() {
13638        let mut f = Fixture::new();
13639        assert_eq!(
13640            f.run(&[
13641                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
13642            ]),
13643            ":1\r\n"
13644        );
13645        // Neither end was written first and both are there, as empty nodes.
13646        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
13647        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13648        assert_eq!(
13649            f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
13650            "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
13651        );
13652        assert_eq!(
13653            f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
13654            "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
13655        );
13656        // The same pair under the same label again updates the edge rather than
13657        // making a second one.
13658        assert_eq!(
13659            f.run(&[
13660                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
13661            ]),
13662            ":0\r\n"
13663        );
13664        assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
13665        // A different label between the same pair is a different edge.
13666        assert_eq!(
13667            f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
13668            ":1\r\n"
13669        );
13670        assert_eq!(
13671            f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
13672            ":1\r\n"
13673        );
13674
13675        assert_eq!(
13676            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13677            ":1\r\n"
13678        );
13679        assert_eq!(
13680            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13681            ":0\r\n"
13682        );
13683        // A label nothing has used, an end that is not there, and a key that is
13684        // not there are all a zero rather than an error.
13685        assert_eq!(
13686            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
13687            ":0\r\n"
13688        );
13689        assert_eq!(
13690            f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
13691            ":0\r\n"
13692        );
13693        assert_eq!(
13694            f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
13695            ":0\r\n"
13696        );
13697    }
13698
13699    /// A run is paged the way `SCAN` is paged, so a client that can walk one
13700    /// can walk the other.
13701    #[test]
13702    fn a_hop_answers_a_cursor_and_a_page() {
13703        let mut f = Fixture::new();
13704        for i in 0..25u32 {
13705            let dst = format!("n{i}");
13706            f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
13707        }
13708        // Ten without being asked, and the cursor is where to carry on from.
13709        let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
13710        assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
13711
13712        let mut seen = 0;
13713        let mut cursor = String::from("0");
13714        loop {
13715            let page = f.run(&[
13716                b"G.OUT",
13717                b"social",
13718                b"hub",
13719                b"FOLLOWS",
13720                b"COUNT",
13721                b"7",
13722                b"CURSOR",
13723                cursor.as_bytes(),
13724            ]);
13725            let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
13726            cursor = head
13727                .rsplit("\r\n")
13728                .next()
13729                .expect("the cursor line")
13730                .to_string();
13731            seen += rest
13732                .split_once("\r\n")
13733                .expect("the page length")
13734                .0
13735                .parse::<usize>()
13736                .expect("a length");
13737            if cursor == "0" {
13738                break;
13739            }
13740        }
13741        assert_eq!(seen, 25, "every neighbour once across the pages");
13742
13743        // A cursor past the end is an empty page and not an error, and so is a
13744        // key or a label that is not there.
13745        assert_eq!(
13746            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
13747            "*2\r\n$1\r\n0\r\n*0\r\n"
13748        );
13749        assert_eq!(
13750            f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
13751            "*2\r\n$1\r\n0\r\n*0\r\n"
13752        );
13753        assert_eq!(
13754            f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
13755            "*2\r\n$1\r\n0\r\n*0\r\n"
13756        );
13757        assert_eq!(
13758            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
13759            "-ERR COUNT must be a positive integer\r\n"
13760        );
13761        assert_eq!(
13762            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
13763            "-ERR syntax error\r\n"
13764        );
13765    }
13766
13767    #[test]
13768    fn a_degree_counts_one_way_or_both() {
13769        let mut f = Fixture::new();
13770        f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
13771        f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
13772        f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
13773        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
13774        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
13775        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
13776        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
13777        assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
13778        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
13779        assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
13780        assert_eq!(
13781            f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
13782            "-ERR syntax error\r\n"
13783        );
13784    }
13785
13786    /// A walk answers which nodes it can reach and not by how many routes, so a
13787    /// node two ways out is in the frontier once.
13788    #[test]
13789    fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
13790        let mut f = Fixture::new();
13791        for (src, dst) in [
13792            ("ada", "grace"),
13793            ("ada", "alan"),
13794            ("grace", "edsger"),
13795            ("alan", "edsger"),
13796            ("edsger", "barbara"),
13797        ] {
13798            f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
13799        }
13800        // Two hops without being asked, the start left out, and edsger once
13801        // even though both of the first hop's nodes point at it.
13802        assert_eq!(
13803            f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
13804            "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
13805        );
13806        assert_eq!(
13807            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
13808            "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
13809        );
13810        let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
13811        assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
13812        assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
13813        // COUNT stops the walk rather than trimming what it found.
13814        assert_eq!(
13815            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
13816            "*1\r\n$5\r\ngrace\r\n"
13817        );
13818        // A node nothing leaves is an empty array and not an error.
13819        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
13820        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
13821        assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
13822        assert_eq!(
13823            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
13824            "-ERR DEPTH must be a positive integer\r\n"
13825        );
13826        assert_eq!(
13827            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
13828            "-ERR syntax error\r\n"
13829        );
13830    }
13831
13832    /// The two sided search, which is the whole reason `G.PATH` is a command
13833    /// and not something a client builds out of `G.OUT`.
13834    #[test]
13835    fn a_path_is_the_shortest_one_and_goes_over_any_label() {
13836        let mut f = Fixture::new();
13837        // A chain of six, and a shortcut that makes a shorter way round under a
13838        // second label so the search has to take either kind of hop.
13839        for i in 0..6u32 {
13840            let src = format!("n{i}");
13841            let dst = format!("n{}", i + 1);
13842            f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
13843        }
13844        assert_eq!(
13845            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13846            "*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"
13847        );
13848        f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
13849        assert_eq!(
13850            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13851            "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
13852        );
13853        // A node to itself is a path of one, and a depth too short to reach is
13854        // no path at all.
13855        assert_eq!(
13856            f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
13857            "*1\r\n$2\r\nn2\r\n"
13858        );
13859        assert_eq!(
13860            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
13861            "*0\r\n"
13862        );
13863        // Direction counts: the chain only goes one way.
13864        assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
13865        // An unreachable node, a node that is not there, and a key that is not
13866        // there are the same empty answer.
13867        f.run(&[b"G.NADD", b"road", b"island"]);
13868        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
13869        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
13870        assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
13871        assert_eq!(
13872            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
13873            "-ERR syntax error\r\n"
13874        );
13875    }
13876
13877    /// The point of the escape in the record tag: the keyspace owns a graph key
13878    /// the way it owns every other key, and none of these commands know a graph
13879    /// exists.
13880    #[test]
13881    fn the_keyspace_sees_a_graph_key_like_any_other() {
13882        let mut f = Fixture::new();
13883        f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
13884        assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
13885        assert_eq!(
13886            f.run(&[b"OBJECT", b"ENCODING", b"social"]),
13887            "$9\r\nadjacency\r\n"
13888        );
13889        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13890        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13891        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
13892        // A graph is counted against the server the way every other body is,
13893        // which is what `maxmemory` will read when this key is a million nodes.
13894        // There is no `MEMORY USAGE` command yet, so this asks the server.
13895        let held = f.server.memory_bytes();
13896        for i in 0..200u32 {
13897            let dst = format!("n{i}");
13898            f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
13899        }
13900        assert!(
13901            f.server.memory_bytes() > held,
13902            "two hundred edges cost something: {held} then {}",
13903            f.server.memory_bytes()
13904        );
13905        f.run(&[b"DEL", b"big"]);
13906
13907        // An expiry, then a rename, then a move to another database, all of
13908        // which are the keyspace moving a record it cannot look inside.
13909        assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
13910        assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
13911        assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
13912        assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
13913        assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
13914        f.run(&[b"SELECT", b"1"]);
13915        assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
13916
13917        assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
13918        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13919        f.run(&[b"G.NADD", b"g", b"n"]);
13920        assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
13921        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13922    }
13923
13924    /// Neither `COPY` nor `DUMP` has a byte shape for a graph, so both say so
13925    /// rather than answering the way they answer for a key that is not there.
13926    #[test]
13927    fn a_graph_cannot_be_copied_or_dumped() {
13928        let mut f = Fixture::new();
13929        f.run(&[b"G.NADD", b"social", b"ada"]);
13930        assert_eq!(
13931            f.run(&[b"COPY", b"social", b"other"]),
13932            "-ERR COPY is not supported for a graph\r\n"
13933        );
13934        assert_eq!(
13935            f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
13936            "-ERR COPY is not supported for a graph\r\n"
13937        );
13938        assert_eq!(
13939            f.run(&[b"DUMP", b"social"]),
13940            "-ERR DUMP is not supported for a graph\r\n"
13941        );
13942        // A refused copy leaves both keys exactly as they were.
13943        assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
13944    }
13945
13946    /// A graph key is a key, so the commands for the other types refuse it and
13947    /// the graph commands refuse theirs.
13948    #[test]
13949    fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
13950        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13951        let mut f = Fixture::new();
13952        f.run(&[b"G.NADD", b"social", b"ada"]);
13953        assert_eq!(f.run(&[b"GET", b"social"]), wrong);
13954        assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
13955        assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
13956
13957        f.run(&[b"SET", b"str", b"v"]);
13958        for cmd in [
13959            vec![b"G.NADD".as_ref(), b"str", b"n"],
13960            vec![b"G.NGET".as_ref(), b"str", b"n"],
13961            vec![b"G.NDEL".as_ref(), b"str", b"n"],
13962            vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
13963            vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
13964            vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
13965            vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
13966            vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
13967            vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
13968            vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
13969        ] {
13970            assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
13971        }
13972    }
13973
13974    /// Every other collection here takes its key with it when its last member
13975    /// goes, and a graph is no different.
13976    #[test]
13977    fn a_graph_goes_when_its_last_node_does() {
13978        let mut f = Fixture::new();
13979        f.run(&[
13980            b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
13981        ]);
13982        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
13983        // The node and the edges that hung off it are both gone.
13984        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
13985        assert_eq!(
13986            f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
13987            ":0\r\n"
13988        );
13989        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
13990        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13991
13992        assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
13993        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
13994        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13995        assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
13996
13997        // The id the removed node had is not handed out again, so a client
13998        // holding an id from an earlier reply cannot have it mean another node.
13999        f.run(&[b"G.NADD", b"social", b"first"]);
14000        f.run(&[b"G.NADD", b"social", b"second"]);
14001        f.run(&[b"G.NDEL", b"social", b"first"]);
14002        f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
14003        assert_eq!(
14004            f.run(&[b"G.OUT", b"social", b"third", b"F"]),
14005            "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
14006        );
14007    }
14008
14009    // ------------------------------------------------------------------ json
14010
14011    /// The two path syntaxes answer different shapes, which is the thing a
14012    /// client is most likely to be broken by and so the thing to pin first.
14013    #[test]
14014    fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
14015        let mut f = Fixture::new();
14016        let doc = br#"{"a":1,"b":{"c":true}}"#;
14017        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
14018        // No path at all is the legacy root and not `$`, so the document comes
14019        // back as itself rather than wrapped.
14020        assert_eq!(
14021            f.run(&[b"JSON.GET", b"doc"]),
14022            bulk(r#"{"a":1,"b":{"c":true}}"#)
14023        );
14024        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
14025        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
14026        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
14027        // A path that matched nothing is an empty set on one syntax and an
14028        // error on the other, and the error does not quote the path.
14029        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
14030        assert_eq!(
14031            f.run(&[b"JSON.GET", b"doc", b".nope"]),
14032            "-ERR Path does not exist\r\n"
14033        );
14034        assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
14035        // The key is a document to the rest of the keyspace, under the name
14036        // RedisJSON registers, and every generic command works on it.
14037        assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
14038        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
14039        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
14040        assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
14041        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14042    }
14043
14044    /// The two error lines RedisJSON sends without a prefix in front of them.
14045    ///
14046    /// Every other error this server writes starts `ERR` or `WRONGTYPE`. These
14047    /// two do not, on a real server, and a differential harness compares the
14048    /// whole line.
14049    #[test]
14050    fn the_two_json_errors_that_carry_no_prefix() {
14051        let mut f = Fixture::new();
14052        f.run(&[b"SET", b"plain", b"x"]);
14053        let wrong = "-Existing key has wrong Redis type\r\n";
14054        assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
14055        assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
14056        assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
14057        assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
14058        assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
14059
14060        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
14061        // A wildcard that matched something writes to all of it. A wildcard
14062        // that matched nothing would have to invent a place, and that is the
14063        // other unprefixed line.
14064        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
14065        assert_eq!(
14066            f.run(&[b"JSON.GET", b"doc"]),
14067            bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
14068        );
14069        assert_eq!(
14070            f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
14071            "-Err wrong static path\r\n"
14072        );
14073    }
14074
14075    /// What `JSON.SET` does with a path that named nowhere.
14076    #[test]
14077    fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
14078        let mut f = Fixture::new();
14079        // A key that is not there can only be written whole.
14080        assert_eq!(
14081            f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
14082            "-ERR new objects must be created at the root\r\n"
14083        );
14084        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14085        // The root check comes before NX and XX, which is the order a real
14086        // server checks them in.
14087        assert_eq!(
14088            f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
14089            "-ERR new objects must be created at the root\r\n"
14090        );
14091        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
14092        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
14093
14094        f.run(&[
14095            b"JSON.SET",
14096            b"doc",
14097            b"$",
14098            br#"{"o":{},"arr":[1,2],"s":"x"}"#,
14099        ]);
14100        // One step past a container that is there is a place to write.
14101        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
14102        // One step past something that is not, or past something that is not an
14103        // object, is not an error and is not a write either.
14104        assert_eq!(
14105            f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
14106            "$-1\r\n"
14107        );
14108        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
14109        // An index past the end does not append. JSON.ARRAPPEND appends.
14110        assert_eq!(
14111            f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
14112            "-ERR array index out of range\r\n"
14113        );
14114        assert_eq!(
14115            f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
14116            "-ERR array index out of range\r\n"
14117        );
14118        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
14119        // NX on a path that is there and XX on a path that is not are both a
14120        // nil and neither changes anything.
14121        assert_eq!(
14122            f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
14123            "$-1\r\n"
14124        );
14125        assert_eq!(
14126            f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
14127            "$-1\r\n"
14128        );
14129        assert_eq!(
14130            f.run(&[b"JSON.GET", b"doc"]),
14131            bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
14132        );
14133        // Text that is not JSON is refused before the key is touched. The
14134        // line has no `ERR` in front of it, which is this command's and not
14135        // every command's, and is in D-37.
14136        assert!(
14137            f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
14138                .starts_with("-this is not the start of a value")
14139        );
14140        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
14141    }
14142
14143    /// `JSON.DEL`, `JSON.TYPE`, `JSON.TOGGLE` and `JSON.CLEAR`, each of which
14144    /// answers a count or a word rather than text.
14145    #[test]
14146    fn the_json_commands_that_do_not_answer_text() {
14147        let mut f = Fixture::new();
14148        let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
14149        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14150
14151        assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
14152        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
14153        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
14154        assert_eq!(
14155            f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
14156            format!("*1\r\n{}", bulk("integer"))
14157        );
14158        // The one place a legacy path that matched nothing is a nil rather than
14159        // an error, which lines up with a key that is not there.
14160        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
14161        assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
14162
14163        // A boolean flips and answers the value it now has, as an integer on
14164        // one syntax and as the word on the other.
14165        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
14166        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
14167        // Something that is not a boolean is a hole on one syntax and one
14168        // sentence covering both cases on the other.
14169        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
14170        assert_eq!(
14171            f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
14172            "-ERR Path does not exist or not a bool\r\n"
14173        );
14174        assert_eq!(
14175            f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
14176            "-ERR Path does not exist or not a bool\r\n"
14177        );
14178        assert_eq!(
14179            f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
14180            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14181        );
14182
14183        // Clearing empties containers and zeroes numbers and leaves everything
14184        // else alone, and counts only what it changed.
14185        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
14186        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
14187        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
14188        assert_eq!(
14189            f.run(&[b"JSON.GET", b"doc"]),
14190            bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
14191        );
14192
14193        // Deleting counts what it removed, and deleting the root is deleting
14194        // the key.
14195        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
14196        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
14197        // Deleting the last member of the root container deletes the key, the
14198        // same way popping the last element off a list does. It is a rule about
14199        // deleting and not about shape: a document written as an empty object
14200        // by JSON.SET stays, because nothing was removed from it.
14201        assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
14202        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
14203        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14204        assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
14205        assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
14206        assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
14207        assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
14208        assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
14209    }
14210
14211    /// `JSON.GET` with more than one path, and with a layout.
14212    ///
14213    /// The wrapper the reply is built in is laid out too, so what a path
14214    /// matched starts one level in for a single JSONPath and two for one of
14215    /// several, and getting that wrong is the kind of thing only a byte for
14216    /// byte comparison catches.
14217    #[test]
14218    fn json_get_lays_out_the_wrapper_it_builds() {
14219        let mut f = Fixture::new();
14220        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
14221
14222        assert_eq!(
14223            f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
14224            bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
14225        );
14226        // Legacy paths are not wrapped, even when there are several of them.
14227        assert_eq!(
14228            f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
14229            bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
14230        );
14231        let fmt: &[&[u8]] = &[b"INDENT", b"  ", b"NEWLINE", b"\n", b"SPACE", b" "];
14232        let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
14233        one.extend_from_slice(fmt);
14234        one.push(b"$.b");
14235        assert_eq!(
14236            f.run(&one),
14237            bulk("[\n  [\n    1,\n    {\n      \"c\": 2\n    }\n  ]\n]")
14238        );
14239        let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
14240        two.extend_from_slice(fmt);
14241        two.push(b"$.a");
14242        two.push(b"$.nope");
14243        assert_eq!(
14244            f.run(&two),
14245            bulk("{\n  \"$.a\": [\n    1\n  ],\n  \"$.nope\": []\n}")
14246        );
14247        // The options are read before the paths and in any order, and a
14248        // document with nothing to lay out is the same either way.
14249        let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
14250        root.push(b".a");
14251        assert_eq!(f.run(&root), bulk("1"));
14252    }
14253
14254    /// `JSON.MGET`, which is the only command here that reads more than one key
14255    /// and so the only one whose answer has holes in it.
14256    #[test]
14257    fn json_mget_answers_once_per_key_whatever_is_under_them() {
14258        let mut f = Fixture::new();
14259        f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
14260        f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
14261        f.run(&[b"SET", b"plain", b"x"]);
14262        assert_eq!(
14263            f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
14264            format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
14265        );
14266        // A key that is not there and a key holding something else are both a
14267        // hole rather than an error, the way MGET treats a hash.
14268        assert_eq!(
14269            f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
14270            format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
14271        );
14272        // A legacy path that matched nothing is a hole too, because one bad
14273        // answer should not lose the others.
14274        assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
14275    }
14276
14277    /// The four commands that ask how big something is, and the four different
14278    /// sets of answers they give for the same three failures.
14279    ///
14280    /// There is no pattern in this and there is no reading it off the
14281    /// documentation either. It was read off a running RedisJSON one line at a
14282    /// time, and it is written down here because the error text is what a client
14283    /// library branches on.
14284    #[test]
14285    fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
14286        let mut f = Fixture::new();
14287        let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
14288        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14289
14290        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
14291        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
14292        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
14293        assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
14294        assert_eq!(
14295            f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
14296            format!("*2\r\n{}{}", bulk("x"), bulk("y"))
14297        );
14298        // A JSONPath answers one entry per match and a hole for a match of the
14299        // wrong kind, which is the one shape all four agree on.
14300        assert_eq!(
14301            f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
14302            "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
14303        );
14304
14305        // A legacy path that matched nothing. Two of them are an error and two
14306        // of them are a nil, and the two errors do not use the same sentence.
14307        assert_eq!(
14308            f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
14309            "-ERR Path does not exist\r\n"
14310        );
14311        assert_eq!(
14312            f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
14313            "-ERR Path does not exist\r\n"
14314        );
14315        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
14316        // A nil bulk and not an empty array, even though the answer would have
14317        // been an array, which is what RedisJSON sends here too.
14318        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
14319        // The JSONPath spelling of the same question is an empty array, since
14320        // no match is not a failure on that syntax.
14321        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
14322
14323        // A legacy path that matched the wrong kind of value. Now two of them
14324        // are an ERR and two of them are a WRONGTYPE, and it is not the same
14325        // two.
14326        assert_eq!(
14327            f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
14328            "-ERR Path does not exist or not an array\r\n"
14329        );
14330        assert_eq!(
14331            f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
14332            "-ERR Path does not exist or not an object\r\n"
14333        );
14334        assert_eq!(
14335            f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
14336            "-WRONGTYPE wrong type of path value - expected object\r\n"
14337        );
14338        assert_eq!(
14339            f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
14340            "-WRONGTYPE wrong type of path value - expected string\r\n"
14341        );
14342
14343        // A key that is not there, where the two syntaxes swap over: the legacy
14344        // path is the quiet answer and the JSONPath is the error.
14345        assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
14346        assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
14347        assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
14348        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
14349        assert_eq!(
14350            f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
14351            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14352        );
14353        // Except this one, which answers about the path instead.
14354        assert_eq!(
14355            f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
14356            "-ERR Path does not exist or not an object\r\n"
14357        );
14358    }
14359
14360    /// `JSON.ARRAPPEND`, `JSON.ARRINSERT`, `JSON.ARRTRIM` and `JSON.ARRPOP`.
14361    ///
14362    /// The four of them share one error line for a path that named something
14363    /// that is not an array, and they disagree about what an index outside the
14364    /// array means: insert refuses it and the other two clamp.
14365    #[test]
14366    fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
14367        let mut f = Fixture::new();
14368        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
14369
14370        assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
14371        assert_eq!(
14372            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
14373            "*1\r\n:6\r\n"
14374        );
14375        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
14376
14377        // A negative index counts back from the end, and the end itself is a
14378        // place to insert at, so an insert at the length is an append.
14379        assert_eq!(
14380            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
14381            ":7\r\n"
14382        );
14383        assert_eq!(
14384            f.run(&[b"JSON.GET", b"doc", b".a"]),
14385            bulk("[1,2,3,4,5,0,6]")
14386        );
14387        assert_eq!(
14388            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
14389            ":8\r\n"
14390        );
14391        // One past the end is not, and neither is one before the front.
14392        assert_eq!(
14393            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
14394            "-ERR index out of bounds\r\n"
14395        );
14396        assert_eq!(
14397            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
14398            "-ERR index out of bounds\r\n"
14399        );
14400
14401        // Trim takes both ends inclusive and clamps both of them, so a start
14402        // past the end leaves an empty array rather than an error.
14403        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
14404        assert_eq!(
14405            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
14406            ":3\r\n"
14407        );
14408        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
14409        assert_eq!(
14410            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
14411            ":2\r\n"
14412        );
14413        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
14414        assert_eq!(
14415            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
14416            ":0\r\n"
14417        );
14418        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14419
14420        // Pop clamps as well, its default is the last element, and an empty
14421        // array pops a nil rather than failing.
14422        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
14423        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
14424        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
14425        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
14426        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
14427
14428        // One sentence covers a path that matched nothing and a path that
14429        // matched the wrong kind of value, for all four of them.
14430        for call in [
14431            &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
14432            &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
14433            &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
14434            &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
14435        ] {
14436            for path in [&b".n"[..], &b".nope"[..]] {
14437                let args: Vec<&[u8]> = call
14438                    .iter()
14439                    .map(|a| if *a == b"PATH" { path } else { *a })
14440                    .collect();
14441                assert_eq!(
14442                    f.run(&args),
14443                    "-ERR Path does not exist or not an array\r\n",
14444                    "{} {}",
14445                    String::from_utf8_lossy(call[0]),
14446                    String::from_utf8_lossy(path)
14447                );
14448            }
14449        }
14450
14451        // A key that is not there is the same sentence for all four, on either
14452        // syntax, and it is about the key and not about the path.
14453        assert_eq!(
14454            f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
14455            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14456        );
14457        assert_eq!(
14458            f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
14459            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14460        );
14461
14462        // The values are parsed before the key is touched, so text that is not
14463        // JSON leaves the document alone.
14464        // Text that is not JSON is refused before the key is touched, and
14465        // the line has no `ERR` in front of it, which is D-37.
14466        assert!(
14467            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
14468                .starts_with("-this is not the start of a value")
14469        );
14470        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14471    }
14472
14473    /// `JSON.ARRINSERT` refuses the whole command when any one of the arrays a
14474    /// path matched cannot take the index, which is D-36.
14475    ///
14476    /// RedisJSON walks the matches, inserts into each one it can, and returns
14477    /// the error on the first one it cannot, leaving the earlier inserts in the
14478    /// document. A write here is one list of edits applied together, so either
14479    /// all of them happen or none of them do.
14480    #[test]
14481    fn json_arrinsert_is_all_or_nothing_across_the_matches() {
14482        let mut f = Fixture::new();
14483        let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
14484        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14485        assert_eq!(
14486            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
14487            "-ERR index out of bounds\r\n"
14488        );
14489        assert_eq!(
14490            f.run(&[b"JSON.GET", b"doc"]),
14491            bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
14492        );
14493        // Every match can take the index, so every match gets it.
14494        assert_eq!(
14495            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
14496            "*3\r\n:4\r\n:3\r\n:2\r\n"
14497        );
14498        assert_eq!(
14499            f.run(&[b"JSON.GET", b"doc"]),
14500            bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
14501        );
14502    }
14503
14504    /// `JSON.ARRINDEX`, whose stop is exclusive and whose start clamps to the
14505    /// last element rather than to one past it.
14506    ///
14507    /// Both of those read like mistakes and both are what RedisJSON does. The
14508    /// start is the one that bites: a start of five into an array of four still
14509    /// looks at the fourth, so a search that should have run out of array comes
14510    /// back with an answer.
14511    #[test]
14512    fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
14513        let mut f = Fixture::new();
14514        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
14515
14516        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
14517        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
14518        assert_eq!(
14519            f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
14520            "*1\r\n:1\r\n"
14521        );
14522
14523        // Zero as the stop means the end rather than the front, so leaving it
14524        // off and passing it are the same thing.
14525        assert_eq!(
14526            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
14527            ":3\r\n"
14528        );
14529        // The stop is exclusive, so a stop of three does not look at index
14530        // three.
14531        assert_eq!(
14532            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
14533            ":-1\r\n"
14534        );
14535
14536        // The start clamps to the last element in both directions, which is why
14537        // a start of four, five or minus one all find the 1 at index three.
14538        for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
14539            assert_eq!(
14540                f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
14541                ":3\r\n",
14542                "{}",
14543                String::from_utf8_lossy(start)
14544            );
14545        }
14546        assert_eq!(
14547            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
14548            ":0\r\n"
14549        );
14550        // An empty array is the one case that comes back with nothing, since
14551        // the stop is zero and the loop never starts.
14552        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
14553        assert_eq!(
14554            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
14555            ":-1\r\n"
14556        );
14557
14558        // The comparison is structural rather than one of the encoded bytes,
14559        // because an object in a stored document holds its keys as intern table
14560        // ids where one parsed off the wire holds them as bytes.
14561        f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
14562        assert_eq!(
14563            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
14564            ":0\r\n"
14565        );
14566        assert_eq!(
14567            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
14568            ":1\r\n"
14569        );
14570        assert_eq!(
14571            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
14572            ":-1\r\n"
14573        );
14574
14575        // Its errors are a third set again: a missing legacy path is the short
14576        // sentence, the wrong kind of value is a WRONGTYPE, and a key that is
14577        // not there is about the path on either syntax.
14578        assert_eq!(
14579            f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
14580            "-ERR Path does not exist\r\n"
14581        );
14582        assert_eq!(
14583            f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
14584            "-WRONGTYPE wrong type of path value - expected array\r\n"
14585        );
14586        assert_eq!(
14587            f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
14588            "-ERR Path does not exist\r\n"
14589        );
14590        assert_eq!(
14591            f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
14592            "-ERR Path does not exist\r\n"
14593        );
14594    }
14595
14596    /// The number family answers text and keeps an integer an integer until
14597    /// something in the sum is not one.
14598    #[test]
14599    fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
14600        let mut f = Fixture::new();
14601        let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
14602        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14603
14604        // A legacy path answers the new value as JSON text in a bulk string,
14605        // not as a number, which is the shape all three of them use.
14606        assert_eq!(
14607            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
14608            bulk("9").as_str()
14609        );
14610        // A JSONPath answers a bulk string holding a JSON array.
14611        assert_eq!(
14612            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
14613            bulk("[11]").as_str()
14614        );
14615        // Two integers stay an integer and a double anywhere in it makes the
14616        // answer a double, which the document then holds.
14617        assert_eq!(
14618            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
14619            bulk("13.0").as_str()
14620        );
14621        assert_eq!(
14622            f.run(&[b"JSON.TYPE", b"doc", b".i"]),
14623            bulk("number").as_str()
14624        );
14625        assert_eq!(
14626            f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
14627            bulk("3.0").as_str()
14628        );
14629        assert_eq!(
14630            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
14631            bulk("-8").as_str()
14632        );
14633        // A power of a half is a square root, and the square root of a negative
14634        // number is the error that says the answer is not a number.
14635        f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
14636        assert_eq!(
14637            f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
14638            bulk("1.224744871391589").as_str()
14639        );
14640        assert_eq!(
14641            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
14642            "-ERR result is not a number\r\n"
14643        );
14644        // An integer answer that does not fit is refused rather than promoted,
14645        // and a negative exponent lands in the same error because there is no
14646        // integer answer to two to the minus one.
14647        f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
14648        assert_eq!(
14649            f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
14650            "-ERR numeric overflow\r\n"
14651        );
14652        f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
14653        assert_eq!(
14654            f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
14655            "-ERR numeric overflow\r\n"
14656        );
14657        // A double that leaves the finite numbers is the other error.
14658        f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
14659        assert_eq!(
14660            f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
14661            "-ERR result is not a number\r\n"
14662        );
14663
14664        // A match that is not a number is a null inside the array on a
14665        // JSONPath, and a legacy path that found no number at all is the error
14666        // with the module's own typo in it.
14667        assert_eq!(
14668            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
14669            bulk("[null]").as_str()
14670        );
14671        assert_eq!(
14672            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
14673            bulk("[]").as_str()
14674        );
14675        assert_eq!(
14676            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
14677            "-ERR Path does not exist or does not contains a number\r\n"
14678        );
14679        assert_eq!(
14680            f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
14681            "-ERR Path does not exist or does not contains a number\r\n"
14682        );
14683        // The operand is JSON and has to be a number. Valid JSON that is not
14684        // one is a line of its own, and it goes out without a prefix.
14685        assert_eq!(
14686            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
14687            "-bad input number\r\n"
14688        );
14689        assert_eq!(
14690            f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
14691            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14692        );
14693        assert_eq!(
14694            f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
14695            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14696        );
14697    }
14698
14699    /// `JSON.STRAPPEND` puts its path in the middle and makes it optional,
14700    /// which nothing else in the group does.
14701    #[test]
14702    fn json_strappend_reads_its_shape_off_the_argument_count() {
14703        let mut f = Fixture::new();
14704        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
14705
14706        assert_eq!(
14707            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
14708            ":3\r\n"
14709        );
14710        assert_eq!(
14711            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
14712            "*1\r\n:4\r\n"
14713        );
14714        // The length is in bytes and not in characters, so one two byte letter
14715        // takes it up by two.
14716        assert_eq!(
14717            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
14718            ":6\r\n"
14719        );
14720        // Three arguments means the value is the last one and the path is the
14721        // root, so this appends to a document that is a string on its own.
14722        f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
14723        assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
14724        assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
14725
14726        // The value is JSON and has to be a JSON string. A number is a
14727        // WRONGTYPE about a path value even though it was the value that was
14728        // wrong, which is the module's wording and not a slip here.
14729        assert_eq!(
14730            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
14731            "-WRONGTYPE wrong type of path value - expected string\r\n"
14732        );
14733        assert_eq!(
14734            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
14735            "*1\r\n$-1\r\n"
14736        );
14737        assert_eq!(
14738            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
14739            "-ERR Path does not exist or not a string\r\n"
14740        );
14741        assert_eq!(
14742            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
14743            "*0\r\n"
14744        );
14745        assert_eq!(
14746            f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
14747            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14748        );
14749    }
14750
14751    /// A legacy path can match more than one value, and which of them the one
14752    /// answer comes from is not the same choice twice.
14753    #[test]
14754    fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
14755        let mut f = Fixture::new();
14756        // Three arrays of one, two and three elements, which tells the first
14757        // match and the last match apart in a single command.
14758        let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
14759
14760        f.run(&[b"JSON.SET", b"doc", b"$", three]);
14761        assert_eq!(
14762            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14763            ":4\r\n"
14764        );
14765        f.run(&[b"JSON.SET", b"doc", b"$", three]);
14766        assert_eq!(
14767            f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
14768            ":2\r\n"
14769        );
14770        f.run(&[b"JSON.SET", b"doc", b"$", three]);
14771        assert_eq!(
14772            f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
14773            ":1\r\n"
14774        );
14775        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
14776        assert_eq!(
14777            f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
14778            bulk("1").as_str()
14779        );
14780        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
14781        assert_eq!(
14782            f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
14783            bulk("13").as_str()
14784        );
14785        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
14786        assert_eq!(
14787            f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
14788            ":4\r\n"
14789        );
14790        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
14791        assert_eq!(
14792            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14793            bulk("false").as_str()
14794        );
14795        // Every one of them wrote to all three matches, whichever one it chose
14796        // to answer about.
14797        assert_eq!(
14798            f.run(&[b"JSON.GET", b"doc", b".a"]),
14799            bulk("[false,true,false]").as_str()
14800        );
14801
14802        // A match of the wrong kind is skipped rather than being the answer, so
14803        // a path that found a string and then two arrays still answers.
14804        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
14805        assert_eq!(
14806            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14807            ":3\r\n"
14808        );
14809        assert_eq!(
14810            f.run(&[b"JSON.GET", b"doc", b".a"]),
14811            bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
14812        );
14813        // Nothing of the right kind anywhere is the error, and that is the only
14814        // case that is.
14815        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
14816        assert_eq!(
14817            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14818            "-ERR Path does not exist or not an array\r\n"
14819        );
14820        assert_eq!(
14821            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14822            "-ERR Path does not exist or not a bool\r\n"
14823        );
14824        // The one array that was there and had nothing in it is an answer and
14825        // not a skip, so the pop answers about it rather than about the array
14826        // after it.
14827        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
14828        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
14829        assert_eq!(
14830            f.run(&[b"JSON.GET", b"doc", b".a"]),
14831            bulk("[[],[2]]").as_str()
14832        );
14833    }
14834
14835    /// A path that matched a value and something inside that value writes to
14836    /// both, which is what `$..` and a nested wildcard are for.
14837    #[test]
14838    fn a_write_reaches_a_match_that_sits_inside_another_match() {
14839        let mut f = Fixture::new();
14840        let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
14841
14842        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14843        assert_eq!(
14844            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
14845            "*3\r\n:3\r\n:2\r\n:3\r\n"
14846        );
14847        assert_eq!(
14848            f.run(&[b"JSON.GET", b"doc", b"$"]),
14849            bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
14850        );
14851
14852        // The same for a trim, where the outer array keeps the two elements the
14853        // inner writes landed in.
14854        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14855        assert_eq!(
14856            f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
14857            "*3\r\n:1\r\n:1\r\n:1\r\n"
14858        );
14859        assert_eq!(
14860            f.run(&[b"JSON.GET", b"doc", b"$"]),
14861            bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
14862        );
14863
14864        // And for a number, where the first match is the object the outer array
14865        // holds and only the two inside it are numbers.
14866        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14867        assert_eq!(
14868            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
14869            bulk("[null,8,8]").as_str()
14870        );
14871    }
14872
14873    /// The value a write is given is looked at only once the path has found
14874    /// something of the right kind to use it on.
14875    #[test]
14876    fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
14877        let mut f = Fixture::new();
14878        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
14879
14880        // A string is not a number, so the path answers first and the `"x"` is
14881        // never looked at. Same for the value that is not JSON at all.
14882        assert_eq!(
14883            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
14884            bulk("[null]").as_str()
14885        );
14886        assert_eq!(
14887            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
14888            bulk("[null]").as_str()
14889        );
14890        assert_eq!(
14891            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
14892            bulk("[]").as_str()
14893        );
14894        assert_eq!(
14895            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
14896            "-ERR Path does not exist or does not contains a number\r\n"
14897        );
14898        // A number match anywhere and the value is looked at after all.
14899        assert_eq!(
14900            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
14901            "-bad input number\r\n"
14902        );
14903
14904        // JSON.STRAPPEND follows the same order with its own two answers.
14905        assert_eq!(
14906            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
14907            "*1\r\n$-1\r\n"
14908        );
14909        assert_eq!(
14910            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
14911            "-ERR Path does not exist or not a string\r\n"
14912        );
14913        assert_eq!(
14914            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
14915            "-WRONGTYPE wrong type of path value - expected string\r\n"
14916        );
14917
14918        // A key that is not there still comes before either of them.
14919        assert_eq!(
14920            f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
14921            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14922        );
14923        assert_eq!(
14924            f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
14925            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14926        );
14927    }
14928
14929    /// RFC 7386 in one test: a null deletes, everything else merges, and a
14930    /// patch that is not an object replaces what it lands on.
14931    #[test]
14932    fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
14933        let mut f = Fixture::new();
14934
14935        // A key that is not there is created at the root, nulls and all,
14936        // because a deletion with nothing to delete is still what the client
14937        // sent.
14938        assert_eq!(
14939            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
14940            "+OK\r\n"
14941        );
14942        assert_eq!(
14943            f.run(&[b"JSON.GET", b"doc", b"$"]),
14944            bulk(r#"[{"x":null,"y":1}]"#).as_str()
14945        );
14946
14947        // Onto something that is there, a null deletes the member of that name
14948        // and the rest is merged one level at a time.
14949        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
14950        assert_eq!(
14951            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
14952            "+OK\r\n"
14953        );
14954        assert_eq!(
14955            f.run(&[b"JSON.GET", b"doc", b"$"]),
14956            bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
14957        );
14958
14959        // A patch that is not an object replaces what it is merged onto.
14960        assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
14961        assert_eq!(
14962            f.run(&[b"JSON.GET", b"doc", b"$"]),
14963            bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
14964        );
14965
14966        // A patch object onto a value that is not an object starts from an
14967        // empty object, so this time the null has nothing to delete and is
14968        // dropped rather than stored.
14969        assert_eq!(
14970            f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
14971            "+OK\r\n"
14972        );
14973        assert_eq!(
14974            f.run(&[b"JSON.GET", b"doc", b"$"]),
14975            bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
14976        );
14977
14978        // A member one level past the end of the document is created and keeps
14979        // its nulls, two levels past it is a write that did not happen, and a
14980        // path that would have to invent where it goes is the unprefixed line.
14981        assert_eq!(
14982            f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
14983            "+OK\r\n"
14984        );
14985        assert_eq!(
14986            f.run(&[b"JSON.GET", b"doc", b"$.new"]),
14987            bulk(r#"[{"z":null}]"#).as_str()
14988        );
14989        assert_eq!(
14990            f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
14991            "$-1\r\n"
14992        );
14993        assert_eq!(
14994            f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
14995            "-Err wrong static path\r\n"
14996        );
14997
14998        // A wildcard merges every match.
14999        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
15000        assert_eq!(
15001            f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
15002            "+OK\r\n"
15003        );
15004        assert_eq!(
15005            f.run(&[b"JSON.GET", b"doc", b"$"]),
15006            bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
15007        );
15008
15009        // The three ways to get it wrong.
15010        assert_eq!(
15011            f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
15012            "-ERR syntax error\r\n"
15013        );
15014        assert_eq!(
15015            f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
15016            "-ERR new objects must be created at the root\r\n"
15017        );
15018        f.run(&[b"SET", b"str", b"x"]);
15019        assert_eq!(
15020            f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
15021            "-Existing key has wrong Redis type\r\n"
15022        );
15023    }
15024
15025    /// A descent is the one path that matches a value and something inside that
15026    /// same value, and the inner merge has to survive the outer one.
15027    #[test]
15028    fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
15029        let mut f = Fixture::new();
15030        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15031        assert_eq!(
15032            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
15033            "+OK\r\n"
15034        );
15035        // `a`, `a.b`, `c` and `c[0]` all match. `a.b` is merged first and `a` is
15036        // merged onto the result, so the `{"m":1}` written into `a.b` is still
15037        // there. Doing it the other way round would leave `{"a":{"b":1,"m":1}}`.
15038        assert_eq!(
15039            f.run(&[b"JSON.GET", b"doc", b"$"]),
15040            bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
15041        );
15042
15043        // A deletion down the same path, which is the case where the inner
15044        // merge empties the object the outer one then copies.
15045        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15046        assert_eq!(
15047            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
15048            "+OK\r\n"
15049        );
15050        assert_eq!(
15051            f.run(&[b"JSON.GET", b"doc", b"$"]),
15052            bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
15053        );
15054    }
15055
15056    /// A filter is a selector like any other, so every command that takes a path
15057    /// takes one, reads and writes alike.
15058    #[test]
15059    fn a_filter_path_reads_and_writes_the_members_it_keeps() {
15060        let mut f = Fixture::new();
15061        let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
15062        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15063
15064        assert_eq!(
15065            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
15066            bulk(r#"["a","c"]"#).as_str()
15067        );
15068        // `$` inside the expression is the document, so a member can be measured
15069        // against something that is not inside it.
15070        assert_eq!(
15071            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
15072            bulk(r#"["a","c"]"#).as_str()
15073        );
15074        // The legacy syntax takes one too, and answers the first match.
15075        assert_eq!(
15076            f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
15077            bulk(r#""a""#).as_str()
15078        );
15079        assert_eq!(
15080            f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
15081            "*1\r\n$6\r\nobject\r\n"
15082        );
15083
15084        // A write goes through it as far as a value that is already there. A
15085        // field that is not there yet has nowhere definite to go, which is the
15086        // same refusal a wildcard gets.
15087        assert_eq!(
15088            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
15089            bulk("[9,10]").as_str()
15090        );
15091        assert_eq!(
15092            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
15093            "+OK\r\n"
15094        );
15095        assert_eq!(
15096            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
15097            "-Err wrong static path\r\n"
15098        );
15099        assert_eq!(
15100            f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
15101            ":2\r\n"
15102        );
15103        assert_eq!(
15104            f.run(&[b"JSON.GET", b"doc", b"$"]),
15105            bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
15106        );
15107
15108        // A path that does not parse is refused before the document is read, so
15109        // a key that is not there answers the same way.
15110        assert!(
15111            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
15112                .starts_with("-ERR")
15113        );
15114        assert!(
15115            f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
15116                .starts_with("-ERR")
15117        );
15118    }
15119
15120    /// The operators past the comparisons, over the wire rather than in the
15121    /// parser's own tests, so that a client can reach all of them.
15122    #[test]
15123    fn a_filter_takes_the_membership_operators_and_the_methods_too() {
15124        let mut f = Fixture::new();
15125        let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
15126        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15127
15128        for (path, want) in [
15129            (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
15130            (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
15131            (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
15132            (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
15133            (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
15134            (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
15135            (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
15136            (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
15137            (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
15138            (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
15139            (b"$.box[?(@.n~)].t", "[]"),
15140            (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
15141            (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
15142            (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
15143            (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
15144        ] {
15145            assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
15146        }
15147
15148        // A write goes through one of these the same way it goes through a
15149        // comparison.
15150        assert_eq!(
15151            f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
15152            "+OK\r\n"
15153        );
15154        assert_eq!(
15155            f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
15156            bulk(r#"["b"]"#).as_str()
15157        );
15158    }
15159
15160    /// D-41. RedisJSON refuses this one, and which document it refuses is
15161    /// decided by how it happens to hold an array of numbers.
15162    #[test]
15163    fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
15164        let mut f = Fixture::new();
15165        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
15166        assert_eq!(
15167            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15168            "+OK\r\n"
15169        );
15170        assert_eq!(
15171            f.run(&[b"JSON.GET", b"doc", b"$"]),
15172            bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
15173        );
15174        // The same document with one element that is not an integer is the one
15175        // RedisJSON is happy with, and it goes the same way here.
15176        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
15177        assert_eq!(
15178            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15179            "+OK\r\n"
15180        );
15181        assert_eq!(
15182            f.run(&[b"JSON.GET", b"doc", b"$"]),
15183            bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
15184        );
15185    }
15186
15187    /// `JSON.MSET` checks what it can before it writes anything and skips the
15188    /// one thing it cannot, which is a path with nowhere to put its value.
15189    #[test]
15190    fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
15191        let mut f = Fixture::new();
15192        assert_eq!(
15193            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
15194            "+OK\r\n"
15195        );
15196        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
15197        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
15198
15199        // A repeated key takes the last write.
15200        assert_eq!(
15201            f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
15202            "+OK\r\n"
15203        );
15204        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
15205
15206        // A triple whose path names nowhere is skipped, the others are still
15207        // written and the reply turns into a nil. Both ways round, because a
15208        // loop that gave up at the first skip would agree with this on one
15209        // order and not on the other.
15210        f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
15211        assert_eq!(
15212            f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
15213            "$-1\r\n"
15214        );
15215        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
15216        assert_eq!(
15217            f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
15218            "$-1\r\n"
15219        );
15220        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15221
15222        // A value that is not JSON, a key holding something else and a path
15223        // that would have to create a document below its own root are all
15224        // checked before anything is written, so the good triple next to them
15225        // does not happen either.
15226        f.run(&[b"SET", b"str", b"x"]);
15227        assert_eq!(
15228            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
15229            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
15230        );
15231        assert_eq!(
15232            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
15233            "-Existing key has wrong Redis type\r\n"
15234        );
15235        assert_eq!(
15236            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
15237            "-ERR new objects must be created at the root\r\n"
15238        );
15239        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
15240
15241        // The two errors a path can be are checked up front as well, so the
15242        // triple before them is not written either. A wildcard that matched
15243        // nothing has nowhere to invent, and an index that is not in the array
15244        // is out of range, and both of them stop the whole command.
15245        assert_eq!(
15246            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
15247            "-Err wrong static path\r\n"
15248        );
15249        assert_eq!(
15250            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
15251            "-ERR array index out of range\r\n"
15252        );
15253        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15254
15255        // Every triple is worked out against the keyspace as the command found
15256        // it, so a second triple on the same key does not see the first one and
15257        // the last write is the one that stays.
15258        f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
15259        assert_eq!(
15260            f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
15261            "+OK\r\n"
15262        );
15263        assert_eq!(
15264            f.run(&[b"JSON.GET", b"c", b"$"]),
15265            bulk(r#"[{"n":3}]"#).as_str()
15266        );
15267
15268        // An argument count that is not a run of key, path and value is the
15269        // arity error rather than a syntax one.
15270        assert_eq!(
15271            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
15272            "-ERR wrong number of arguments for 'json.mset' command\r\n"
15273        );
15274    }
15275
15276    /// `JSON.RESP` hands back RESP types, and the marker element is what tells
15277    /// an empty array and an empty object apart.
15278    #[test]
15279    fn json_resp_answers_the_document_as_resp_types() {
15280        let mut f = Fixture::new();
15281        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
15282        assert_eq!(
15283            f.run(&[b"JSON.RESP", b"doc"]),
15284            "*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"
15285        );
15286        // A JSONPath wraps the same answer in one more array.
15287        assert_eq!(
15288            f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
15289            "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
15290        );
15291
15292        f.run(&[
15293            b"JSON.SET",
15294            b"doc",
15295            b"$",
15296            br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
15297        ]);
15298        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
15299        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
15300        // A double goes out as its text, so a client reads the same digits
15301        // `JSON.GET` would have given it.
15302        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
15303        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
15304        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
15305
15306        // A missing legacy path is an error, a missing JSONPath is an empty
15307        // array, and a key that is not there is a nil on either.
15308        assert_eq!(
15309            f.run(&[b"JSON.RESP", b"doc", b".nope"]),
15310            "-ERR Path does not exist\r\n"
15311        );
15312        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
15313        assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
15314        assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
15315    }
15316
15317    /// `JSON.DEBUG` answers a byte count that is this encoding's, so the test
15318    /// pins the shapes and that the two syntaxes agree rather than a number
15319    /// read off another server. That is D-42.
15320    #[test]
15321    fn json_debug_answers_a_byte_count_and_its_own_help() {
15322        let mut f = Fixture::new();
15323        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
15324        let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
15325        assert!(one.starts_with(':'), "{one}");
15326        assert_eq!(
15327            f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
15328            format!("*1\r\n{one}")
15329        );
15330        let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
15331        assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
15332
15333        // A key that is not there is a zero on a legacy path and an empty set
15334        // on a JSONPath, which is the one reader here that does not answer nil
15335        // for it.
15336        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
15337        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
15338        assert_eq!(
15339            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
15340            "-ERR Path does not exist\r\n"
15341        );
15342        assert_eq!(
15343            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
15344            "*0\r\n"
15345        );
15346
15347        assert_eq!(
15348            f.run(&[b"JSON.DEBUG", b"HELP"]),
15349            "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
15350             $34\r\nHELP                - this message\r\n"
15351        );
15352        assert_eq!(
15353            f.run(&[b"JSON.DEBUG", b"NOPE"]),
15354            "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
15355        );
15356        assert_eq!(
15357            f.run(&[b"JSON.DEBUG", b"MEMORY"]),
15358            "-ERR wrong number of arguments for 'json.debug' command\r\n"
15359        );
15360    }
15361
15362    // ---------------------------------------------------------------- vector
15363
15364    /// The first `VADD` fixes the dimension and every one after it has to
15365    /// agree, because there is no create command to say it earlier.
15366    #[test]
15367    fn the_first_vadd_decides_how_wide_the_set_is() {
15368        let mut f = Fixture::new();
15369        assert_eq!(
15370            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
15371            ":1\r\n"
15372        );
15373        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15374        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15375        // A second vector under the same name replaces it and says so with a
15376        // zero, so an ingest can count what it created.
15377        assert_eq!(
15378            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
15379            ":0\r\n"
15380        );
15381        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15382        // Three dimensions into a two dimensional set names both numbers, since
15383        // a client that gets this wrong needs to know which end is which.
15384        assert_eq!(
15385            f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
15386            "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
15387        );
15388        // A vector of zeros has no direction, and it is taken anyway and comes
15389        // back as the origin, because that is what a real server does with it.
15390        assert_eq!(
15391            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
15392            ":1\r\n"
15393        );
15394        assert_eq!(
15395            f.run(&[b"VEMB", b"v", b"nowhere"]),
15396            "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
15397        );
15398        // A set is made with one quantisation and keeps it, and a `VADD` that
15399        // names another is refused. Naming none names `Q8`, which is why this
15400        // set is a `Q8` one.
15401        assert_eq!(
15402            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
15403            "-ERR asked quantization mismatch with existing vector set\r\n"
15404        );
15405        // Nothing above created a key, and a set that never took a vector has
15406        // no dimension to report.
15407        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
15408        assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
15409        assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
15410    }
15411
15412    /// What a client sent comes back out, and what a client asked for is a
15413    /// similarity and not the distance underneath it.
15414    #[test]
15415    fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
15416        let mut f = Fixture::new();
15417        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
15418        // The set stored the direction and the length is multiplied back on the
15419        // way out, so this is `3 4` and not `0.6 0.8`. It is not quite `3 4`
15420        // either, because nobody named a quantisation and that means `Q8`: the
15421        // wider coordinate lands on a code exactly and the other one does not.
15422        // Both numbers are a real server's answers for the same input.
15423        assert_eq!(
15424            f.run(&[b"VEMB", b"v", b"a"]),
15425            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15426        );
15427        // NOQUANT is the way to ask for what went in to come back out.
15428        f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
15429        assert_eq!(
15430            f.run(&[b"VEMB", b"n", b"a"]),
15431            "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
15432        );
15433        // BIN keeps the signs and nothing else, and does not multiply the
15434        // length back on, since a sign has no length in it to scale.
15435        f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
15436        assert_eq!(
15437            f.run(&[b"VEMB", b"b", b"a"]),
15438            "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
15439        );
15440        assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
15441        assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
15442
15443        // On the axes, where the unit vector is exact and so is the dot
15444        // product, both ends of the scale come out exact: the same direction is
15445        // 1 and the opposite one is 0, with a right angle at a half.
15446        let mut f = Fixture::new();
15447        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
15448        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
15449        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
15450        assert_eq!(
15451            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
15452            "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
15453             $8\r\nopposite\r\n$1\r\n0\r\n"
15454        );
15455        // A search from an element leaves that element out, since it is always
15456        // its own nearest neighbour.
15457        assert_eq!(
15458            f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
15459            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15460        );
15461        // An element that is not there is an empty answer and not an error,
15462        // which is what a missing key gives too.
15463        assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
15464        assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
15465        // COUNT bounds it and TRUTH reads every vector rather than the codes,
15466        // which has to agree with the index on a set this small.
15467        assert_eq!(
15468            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
15469            "*1\r\n$6\r\nacross\r\n"
15470        );
15471        assert_eq!(
15472            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
15473            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15474        );
15475        // EF widens how much of the index is read and does not change how many
15476        // answers come back, so a wide search still returns what COUNT asked
15477        // for.
15478        assert_eq!(
15479            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
15480            "*1\r\n$6\r\nacross\r\n"
15481        );
15482
15483        // On RESP3 a scored search is a map, which is what the vector set
15484        // module replies and is not what ZRANGE does here.
15485        let mut g = Fixture::new();
15486        g.run(&[b"HELLO", b"3"]);
15487        g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15488        assert_eq!(
15489            g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
15490            "%1\r\n$4\r\neast\r\n,1\r\n"
15491        );
15492    }
15493
15494    /// The attribute pair, and the one reply that means two things.
15495    #[test]
15496    fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
15497        let mut f = Fixture::new();
15498        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15499        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15500        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
15501        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
15502        // Not parsed as JSON, because nothing reads into it yet and refusing a
15503        // write for a rule nothing enforces would be the wrong trade.
15504        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
15505        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
15506        // An empty string clears it, which is Redis's spelling of the removal.
15507        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
15508        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15509        // An element that is not there answers zero rather than being created,
15510        // since an attribute with no vector under it is not a thing this holds.
15511        assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
15512        assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
15513        assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
15514        // A null for an element with no attribute and a null for one that is
15515        // not there. VISMEMBER is how a client tells the two apart.
15516        assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
15517        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
15518        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
15519        assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
15520
15521        // WITHATTRIBS carries it alongside the answers.
15522        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15523        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15524        assert_eq!(
15525            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
15526            "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
15527        );
15528    }
15529
15530    /// The slot a removed element had is reused, and nothing that was beside it
15531    /// comes back with the next element to get it.
15532    #[test]
15533    fn vrem_takes_the_attribute_with_it() {
15534        let mut f = Fixture::new();
15535        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15536        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15537        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
15538        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
15539        assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
15540        // The key went with the last element, the way every other collection
15541        // here works.
15542        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15543
15544        // The next element is given the slot the removed one had, and it comes
15545        // with no attribute on it.
15546        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15547        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15548        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15549        f.run(&[b"VREM", b"v", b"east"]);
15550        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
15551        assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
15552    }
15553
15554    /// `VINFO` says what the index is before it says anything a client could
15555    /// mistake for a graph.
15556    #[test]
15557    fn vinfo_says_partition_first() {
15558        let mut f = Fixture::new();
15559        f.run(&[
15560            b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
15561        ]);
15562        f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
15563        let info = f.run(&[b"VINFO", b"v"]);
15564        assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
15565        // What the client asked for and not what happened to the tuning, which
15566        // is `10` section 7: M is recorded and changes nothing.
15567        assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
15568        assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
15569        assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
15570        // Nobody named a quantisation, so this set is a `Q8` one and every
15571        // element in it is stored that way.
15572        assert!(
15573            info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
15574            "{info}"
15575        );
15576        let mut f = Fixture::new();
15577        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
15578        assert!(
15579            f.run(&[b"VINFO", b"v"])
15580                .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
15581        );
15582        assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
15583    }
15584
15585    /// A set to read ranges of names out of.
15586    fn named() -> Fixture {
15587        let mut f = Fixture::new();
15588        for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
15589            .iter()
15590            .enumerate()
15591        {
15592            let x = (i + 1).to_string();
15593            f.run(&[
15594                b"VADD",
15595                b"r",
15596                b"VALUES",
15597                b"2",
15598                x.as_bytes(),
15599                b"1",
15600                name.as_bytes(),
15601            ]);
15602        }
15603        f
15604    }
15605
15606    /// `VRANGE` reads the names in the order bytes come in and pays no
15607    /// attention to where the vectors point.
15608    #[test]
15609    fn vrange_walks_the_names_and_not_the_vectors() {
15610        let mut f = named();
15611        assert_eq!(
15612            f.run(&[b"VRANGE", b"r", b"-", b"+"]),
15613            "*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"
15614        );
15615        assert_eq!(
15616            f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
15617            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
15618            "the high end is a name and not a prefix, so delta is past it"
15619        );
15620        assert_eq!(
15621            f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
15622            "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
15623        );
15624        assert_eq!(
15625            f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
15626            "*1\r\n$4\r\nbeta\r\n"
15627        );
15628        assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
15629        // Bytes and not letters, so an upper case name sorts before every lower
15630        // case one rather than beside its own spelling.
15631        f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
15632        assert_eq!(
15633            f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
15634            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15635        );
15636        assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
15637    }
15638
15639    /// The count cuts the answer after the range is decided, and zero is not
15640    /// the same as leaving it out.
15641    #[test]
15642    fn a_vrange_count_of_zero_asks_for_nothing() {
15643        let mut f = named();
15644        assert_eq!(
15645            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
15646            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15647        );
15648        assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
15649        assert!(
15650            f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
15651                .starts_with("*5\r\n"),
15652            "a negative count is no limit at all"
15653        );
15654    }
15655
15656    /// Both ends are read before either is placed, and the count is read before
15657    /// either end.
15658    #[test]
15659    fn vrange_says_which_end_it_could_not_read() {
15660        let mut f = named();
15661        assert_eq!(
15662            f.run(&[b"VRANGE", b"r", b"x", b"y"]),
15663            "-ERR invalid start range format\r\n"
15664        );
15665        assert_eq!(
15666            f.run(&[b"VRANGE", b"r", b"+", b"x"]),
15667            "-ERR invalid end range format\r\n",
15668            "the high end is spelled wrong, which is worth saying before the \
15669             low end being on the wrong side"
15670        );
15671        assert_eq!(
15672            f.run(&[b"VRANGE", b"r", b"+", b"-"]),
15673            "-ERR '-' can only be used as first argument, '+' only as second\r\n"
15674        );
15675        // A bracket with nothing after it is not the empty name here, though an
15676        // element really can be called that.
15677        assert_eq!(
15678            f.run(&[b"VRANGE", b"r", b"[", b"+"]),
15679            "-ERR invalid start range format\r\n"
15680        );
15681        assert_eq!(
15682            f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
15683            "-ERR invalid COUNT value\r\n"
15684        );
15685        assert_eq!(
15686            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
15687            "-ERR wrong number of arguments for 'VRANGE' command\r\n"
15688        );
15689        f.run(&[b"SET", b"s", b"x"]);
15690        assert!(
15691            f.run(&[b"VRANGE", b"s", b"-", b"+"])
15692                .starts_with("-WRONGTYPE")
15693        );
15694    }
15695
15696    /// The option that asks for something this index does not have says so
15697    /// rather than doing something else quietly.
15698    #[test]
15699    fn reduce_is_refused_and_not_ignored() {
15700        let mut f = Fixture::new();
15701        let reduce = f.run(&[
15702            b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
15703        ]);
15704        assert!(
15705            reduce.starts_with("-ERR REDUCE is not supported."),
15706            "{reduce}"
15707        );
15708        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15709    }
15710
15711    /// A filtered search answers with the nearest elements that match, and an
15712    /// expression that is not one is an error before the key is looked at.
15713    #[test]
15714    fn vsim_filter_reads_the_attributes() {
15715        let mut f = Fixture::new();
15716        for (name, x, y, attr) in [
15717            ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
15718            ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
15719            ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
15720            ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
15721        ] {
15722            f.run(&[
15723                b"VADD",
15724                b"v",
15725                b"VALUES",
15726                b"2",
15727                x.as_bytes(),
15728                y.as_bytes(),
15729                name.as_bytes(),
15730                b"SETATTR",
15731                attr.as_bytes(),
15732            ]);
15733        }
15734        // `b` is the nearest to the query and is the one the filter drops, so
15735        // this is the answer a filter applied afterwards would have got wrong.
15736        assert_eq!(
15737            f.run(&[
15738                b"VSIM",
15739                b"v",
15740                b"VALUES",
15741                b"2",
15742                b"9",
15743                b"1",
15744                b"COUNT",
15745                b"2",
15746                b"FILTER",
15747                b".lang == \"en\"",
15748            ]),
15749            "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
15750        );
15751        // A number is compared as a number, and the two halves of an `and` both
15752        // have to hold.
15753        assert_eq!(
15754            f.run(&[
15755                b"VSIM",
15756                b"v",
15757                b"VALUES",
15758                b"2",
15759                b"9",
15760                b"1",
15761                b"FILTER",
15762                b".lang == 'en' and .year > 1980",
15763            ]),
15764            "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
15765        );
15766        // A list, and a field an element does not have.
15767        assert_eq!(
15768            f.run(&[
15769                b"VSIM",
15770                b"v",
15771                b"VALUES",
15772                b"2",
15773                b"9",
15774                b"1",
15775                b"FILTER",
15776                b".lang in ['fr', 'de']",
15777            ]),
15778            "*1\r\n$1\r\nb\r\n"
15779        );
15780        assert_eq!(
15781            f.run(&[
15782                b"VSIM",
15783                b"v",
15784                b"VALUES",
15785                b"2",
15786                b"9",
15787                b"1",
15788                b"FILTER",
15789                b".rating > 3"
15790            ]),
15791            "*0\r\n"
15792        );
15793        // TRUTH measures every vector, and the filter still decides which ones
15794        // are measured.
15795        assert_eq!(
15796            f.run(&[
15797                b"VSIM",
15798                b"v",
15799                b"VALUES",
15800                b"2",
15801                b"9",
15802                b"1",
15803                b"TRUTH",
15804                b"FILTER",
15805                b".year < 1980",
15806            ]),
15807            "*1\r\n$1\r\nc\r\n"
15808        );
15809        // VSETATTR moves an element in and out of a filter, which means the tag
15810        // beside its code was rewritten and not just the string.
15811        f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
15812        assert_eq!(
15813            f.run(&[
15814                b"VSIM",
15815                b"v",
15816                b"VALUES",
15817                b"2",
15818                b"9",
15819                b"1",
15820                b"COUNT",
15821                b"1",
15822                b"FILTER",
15823                b".lang == \"en\"",
15824            ]),
15825            "*1\r\n$1\r\nb\r\n"
15826        );
15827        // And a VADD that replaces the vector keeps the attribute and the tag,
15828        // which is the same rewrite from the other end.
15829        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
15830        assert_eq!(
15831            f.run(&[
15832                b"VSIM",
15833                b"v",
15834                b"VALUES",
15835                b"2",
15836                b"9",
15837                b"1",
15838                b"COUNT",
15839                b"1",
15840                b"FILTER",
15841                b".lang == \"en\"",
15842            ]),
15843            "*1\r\n$1\r\nb\r\n"
15844        );
15845
15846        // The expression is parsed before the key is read, so a bad one is an
15847        // error whether or not the key is there.
15848        let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
15849        assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
15850        assert_eq!(
15851            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
15852            "-ERR invalid FILTER expression\r\n"
15853        );
15854        // FILTER-EF raises the effort rather than capping it, and zero is
15855        // Redis's word for no limit, so neither is an error.
15856        assert_eq!(
15857            f.run(&[
15858                b"VSIM",
15859                b"v",
15860                b"VALUES",
15861                b"2",
15862                b"9",
15863                b"1",
15864                b"COUNT",
15865                b"1",
15866                b"FILTER-EF",
15867                b"500",
15868                b"FILTER",
15869                b".lang == 'en'",
15870            ]),
15871            "*1\r\n$1\r\nb\r\n"
15872        );
15873        assert_eq!(
15874            f.run(&[
15875                b"VSIM",
15876                b"v",
15877                b"VALUES",
15878                b"2",
15879                b"9",
15880                b"1",
15881                b"COUNT",
15882                b"1",
15883                b"FILTER-EF",
15884                b"0"
15885            ]),
15886            "*1\r\n$1\r\nb\r\n"
15887        );
15888        assert_eq!(
15889            f.run(&[
15890                b"VSIM",
15891                b"v",
15892                b"VALUES",
15893                b"2",
15894                b"9",
15895                b"1",
15896                b"FILTER-EF",
15897                b"lots"
15898            ]),
15899            "-ERR EF must be a positive integer\r\n"
15900        );
15901    }
15902
15903    /// A vector set key is a key, so the keyspace owns it the way it owns every
15904    /// other one and none of those commands know what is inside it.
15905    #[test]
15906    fn the_keyspace_sees_a_vector_set_key_like_any_other() {
15907        let mut f = Fixture::new();
15908        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15909        assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
15910        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
15911        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
15912        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
15913        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
15914        assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
15915        assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
15916        assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
15917        assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
15918        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15919
15920        // And the wrong type is the wrong type in both directions.
15921        f.run(&[b"SET", b"s", b"1"]);
15922        assert_eq!(
15923            f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
15924            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15925        );
15926        assert_eq!(
15927            f.run(&[b"VCARD", b"s"]),
15928            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15929        );
15930        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15931        assert_eq!(
15932            f.run(&[b"GET", b"v"]),
15933            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15934        );
15935        // A graph and a vector set share the escape in the record tag and are
15936        // still two different types, which is the case the tag alone cannot
15937        // decide.
15938        f.run(&[b"G.NADD", b"social", b"ada"]);
15939        assert_eq!(
15940            f.run(&[b"VCARD", b"social"]),
15941            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15942        );
15943        assert_eq!(
15944            f.run(&[b"G.NGET", b"v", b"ada"]),
15945            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15946        );
15947    }
15948
15949    /// `VRANDMEMBER` is `SRANDMEMBER` over the element names, in both of its
15950    /// shapes, off the database's own generator.
15951    #[test]
15952    fn vrandmember_has_the_two_shapes_srandmember_has() {
15953        let mut f = Fixture::new();
15954        for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
15955            let x = (i + 1).to_string();
15956            f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
15957        }
15958        // One element is a bulk string and not an array of one.
15959        let one = f.run(&[b"VRANDMEMBER", b"v"]);
15960        assert!(one.starts_with("$1\r\n"), "{one}");
15961        // A positive count is distinct and stops at the size of the set.
15962        let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
15963        assert!(all.starts_with("*3\r\n"), "{all}");
15964        for name in ["a", "b", "c"] {
15965            assert!(all.contains(name), "{all} is missing {name}");
15966        }
15967        all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
15968        assert!(all.starts_with("*2\r\n"), "{all}");
15969        // A negative one draws that many and allows repeats.
15970        let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
15971        assert!(many.starts_with("*5\r\n"), "{many}");
15972        // A key that is not there answers the shape that was asked for.
15973        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
15974        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
15975    }
15976
15977    /// `VLINKS` answers about the index that is here rather than the graph that
15978    /// is not, which is D-2.
15979    #[test]
15980    fn vlinks_reports_one_layer_of_partition_neighbours() {
15981        let mut f = Fixture::new();
15982        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15983        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15984        // One layer deep, because the index is one layer deep, so a client
15985        // walking layers gets a short list and not a shape it cannot parse.
15986        assert_eq!(
15987            f.run(&[b"VLINKS", b"v", b"east"]),
15988            "*1\r\n*1\r\n$5\r\nnorth\r\n"
15989        );
15990        assert_eq!(
15991            f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
15992            "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
15993        );
15994        assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
15995        assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
15996    }
15997
15998    /// A vector arrives either as digits or as bytes, and the two have to mean
15999    /// the same thing.
16000    #[test]
16001    fn fp32_and_values_are_the_same_vector() {
16002        let mut f = Fixture::new();
16003        let mut blob = Vec::new();
16004        for x in [3.0f32, 4.0] {
16005            blob.extend_from_slice(&x.to_le_bytes());
16006        }
16007        assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
16008        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
16009        assert_eq!(
16010            f.run(&[b"VEMB", b"v", b"a"]),
16011            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
16012        );
16013        // RAW is the stored bytes and the numbers that turn them back into the
16014        // client's vector, which for `Q8` is a code a coordinate, the length the
16015        // vector arrived with and the scale the codes are measured against. The
16016        // name of the form is a simple string, which is a real server's shape,
16017        // and all four of these are a real server's answers.
16018        assert_eq!(
16019            f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
16020            "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
16021        );
16022        // A blob that is not a whole number of floats is not a vector.
16023        assert_eq!(
16024            f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
16025            "-ERR invalid vector specification\r\n"
16026        );
16027        // Neither is a count that promises more than arrived.
16028        assert_eq!(
16029            f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
16030            "-ERR syntax error\r\n"
16031        );
16032        assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
16033    }
16034
16035    // ----------------------------------------------------------------- bloom
16036
16037    /// The filter a client gets when it does not describe one, and the two
16038    /// answers an add can give.
16039    #[test]
16040    fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
16041        let mut f = Fixture::new();
16042        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
16043        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
16044        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
16045        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
16046        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
16047        // The defaults are the module's configs and not anything the command
16048        // said, which is 100 entries at a hundredth and a growth of 2.
16049        assert_eq!(
16050            f.run(&[b"BF.INFO", b"b"]),
16051            "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16052             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16053             +Expansion rate\r\n:2\r\n"
16054        );
16055        assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
16056        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
16057        // A key that is not there has no filter to report on, and answers two
16058        // different ways about it depending on which command asked.
16059        assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
16060        assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
16061    }
16062
16063    /// `BF.EXISTS` on a key holding something else answers a miss, and
16064    /// everything else in the family answers `WRONGTYPE`.
16065    ///
16066    /// The two halves of a check and set disagree about what that key is, which
16067    /// is the module's behaviour and not a decision taken here.
16068    #[test]
16069    fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
16070        let mut f = Fixture::new();
16071        f.run(&[b"SET", b"s", b"text"]);
16072        assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
16073        assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16074        for cmd in [
16075            vec![&b"BF.ADD"[..], b"s", b"x"],
16076            vec![&b"BF.MADD"[..], b"s", b"x"],
16077            vec![&b"BF.CARD"[..], b"s"],
16078            vec![&b"BF.INFO"[..], b"s"],
16079            vec![&b"BF.DEBUG"[..], b"s"],
16080            vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
16081        ] {
16082            let name = String::from_utf8_lossy(cmd[0]).into_owned();
16083            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16084        }
16085        // The arguments are read before the key is, so a reserve with a bad
16086        // error rate complains about the rate and never learns about the string.
16087        assert_eq!(
16088            f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
16089            "-ERR bad error rate\r\n"
16090        );
16091        assert!(
16092            f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
16093                .starts_with("-WRONGTYPE")
16094        );
16095    }
16096
16097    /// A chain grows by its expansion factor and each link is half as wrong as
16098    /// the one before, which is what makes the whole filter hold its rate.
16099    #[test]
16100    fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
16101        let mut f = Fixture::new();
16102        assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
16103        for i in 0..10u32 {
16104            assert_eq!(
16105                f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
16106                ":1\r\n"
16107            );
16108        }
16109        assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
16110        assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
16111        assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
16112        // Capacity is the sum of every link and not the number that was asked
16113        // for, so it is 10 and then 10 plus 20.
16114        assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
16115        assert_eq!(
16116            f.run(&[b"BF.DEBUG", b"g"]),
16117            "*3\r\n$7\r\nsize:11\r\n\
16118             $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
16119             $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
16120        );
16121
16122        // The same filter told not to grow fills instead.
16123        assert_eq!(
16124            f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
16125            "+OK\r\n"
16126        );
16127        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
16128        assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
16129        assert_eq!(
16130            f.run(&[b"BF.ADD", b"n", b"c"]),
16131            "-ERR non scaling filter is full\r\n"
16132        );
16133        // And an item that is already in it still answers, because membership
16134        // is checked before fullness.
16135        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
16136        // A filter that will not grow has no expansion rate to report, in
16137        // either of the two spellings that make one.
16138        assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
16139        f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
16140        assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
16141        // Asking for both at once is refused, which is one of the module's
16142        // errors that carries no prefix at all.
16143        assert_eq!(
16144            f.run(&[
16145                b"BF.RESERVE",
16146                b"q",
16147                b"0.01",
16148                b"2",
16149                b"NONSCALING",
16150                b"EXPANSION",
16151                b"2"
16152            ]),
16153            "-Nonscaling filters cannot expand\r\n"
16154        );
16155    }
16156
16157    /// A multi add stops where the filter did, so the reply can be shorter than
16158    /// the argument list.
16159    #[test]
16160    fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
16161        let mut f = Fixture::new();
16162        f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
16163        assert_eq!(
16164            f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
16165            "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
16166        );
16167        assert_eq!(
16168            f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
16169            "*2\r\n:1\r\n:0\r\n"
16170        );
16171    }
16172
16173    /// `BF.INSERT` describes a filter and fills it in one command, with its own
16174    /// spelling of every complaint.
16175    #[test]
16176    fn insert_is_a_reserve_and_a_madd_with_different_errors() {
16177        let mut f = Fixture::new();
16178        assert_eq!(
16179            f.run(&[
16180                b"BF.INSERT",
16181                b"i",
16182                b"CAPACITY",
16183                b"50",
16184                b"ERROR",
16185                b"0.001",
16186                b"ITEMS",
16187                b"a",
16188                b"b"
16189            ]),
16190            "*2\r\n:1\r\n:1\r\n"
16191        );
16192        assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
16193        // NOCREATE is the only way to add without making the key.
16194        assert_eq!(
16195            f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16196            "-ERR not found\r\n"
16197        );
16198        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16199        // The same mistakes as BF.RESERVE, in the sentences this command uses
16200        // for them, and one sentence where BF.RESERVE has two.
16201        assert_eq!(
16202            f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16203            "-Bad capacity\r\n"
16204        );
16205        assert_eq!(
16206            f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
16207            "-Bad error rate\r\n"
16208        );
16209        assert_eq!(
16210            f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
16211            "-Bad expansion\r\n"
16212        );
16213        // An option is matched on its first letter and not on the word, so a
16214        // token nobody meant as an option is one anyway if it starts with the
16215        // right letter. NOSUCHTHING is NONSCALING here, and the filter it
16216        // builds says so.
16217        assert_eq!(
16218            f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
16219            "*1\r\n:1\r\n"
16220        );
16221        assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
16222        // Only E and N need a second look, one for ERROR against EXPANSION and
16223        // the other for NOCREATE against NONSCALING, and both stop as soon as
16224        // they can tell the two apart.
16225        assert_eq!(
16226            f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
16227            "*1\r\n:1\r\n"
16228        );
16229        assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
16230        assert_eq!(
16231            f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
16232            "*1\r\n:1\r\n"
16233        );
16234        assert_eq!(
16235            f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
16236            "-ERR not found\r\n"
16237        );
16238        // A letter that starts nothing is the one case that is refused.
16239        assert_eq!(
16240            f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16241            "-Unknown argument received\r\n"
16242        );
16243        // Everything after ITEMS is an item, even when it spells an option.
16244        assert_eq!(
16245            f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16246            "*1\r\n:1\r\n"
16247        );
16248        // And ITEMS with nothing after it is the same as leaving it out.
16249        assert!(
16250            f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
16251                .contains("wrong number of arguments")
16252        );
16253    }
16254
16255    /// A filter dumped a chunk at a time and put back into another key is the
16256    /// same filter.
16257    #[test]
16258    fn a_dump_replays_into_a_filter_that_answers_the_same() {
16259        let mut f = Fixture::new();
16260        f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
16261        for i in 0..25u32 {
16262            f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
16263        }
16264        assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
16265
16266        // Iterator zero asks for the header and every one after it is a running
16267        // byte offset, and a chunk never spans two links.
16268        let mut iter = b"0".to_vec();
16269        let mut chunks = 0;
16270        loop {
16271            let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
16272            let text = String::from_utf8_lossy(&raw).into_owned();
16273            let next = text
16274                .split("\r\n")
16275                .nth(1)
16276                .and_then(|n| n.strip_prefix(':'))
16277                .expect("a two element reply of an iterator and a chunk")
16278                .to_owned();
16279            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16280            let data = &body[body
16281                .windows(2)
16282                .position(|w| w == b"\r\n")
16283                .expect("a length line")
16284                + 2..body.len() - 2];
16285            if next == "0" {
16286                assert!(data.is_empty(), "the last chunk is empty");
16287                break;
16288            }
16289            let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
16290            assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
16291            iter = next.into_bytes();
16292            chunks += 1;
16293        }
16294        assert_eq!(chunks, 3, "a header and one chunk per link");
16295
16296        assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
16297        assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
16298        for i in 0..25u32 {
16299            assert_eq!(
16300                f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
16301                ":1\r\n"
16302            );
16303        }
16304
16305        // A header on top of a filter is refused rather than merged, and so is
16306        // one that no filter wrote.
16307        assert_eq!(
16308            f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
16309            "-ERR received bad data\r\n"
16310        );
16311        assert_eq!(
16312            f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
16313            "-ERR received bad data\r\n"
16314        );
16315        // An offset past the end of the filter names itself.
16316        assert_eq!(
16317            f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
16318            "-ERR invalid offset - no link found\r\n"
16319        );
16320        assert_eq!(
16321            f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
16322            "-ERR Second argument must be numeric\r\n"
16323        );
16324        // The same complaint without the prefix on the way out, which is the
16325        // module's inconsistency and not a slip here.
16326        assert_eq!(
16327            f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
16328            "-Second argument must be numeric\r\n"
16329        );
16330    }
16331
16332    /// The argument checks, which have a sentence each and read numbers the way
16333    /// Redis reads them everywhere else.
16334    #[test]
16335    fn reserve_reads_its_numbers_the_way_string2ll_does() {
16336        let mut f = Fixture::new();
16337        for (args, want) in [
16338            (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
16339            (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
16340            (
16341                vec![&b"0"[..], b"10"],
16342                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16343            ),
16344            (
16345                vec![&b"1"[..], b"10"],
16346                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16347            ),
16348            (
16349                vec![&b"inf"[..], b"10"],
16350                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16351            ),
16352            (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
16353            (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
16354            (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
16355            (
16356                vec![&b"0.01"[..], b"0"],
16357                "-ERR capacity must be in the range [1, 1073741824]\r\n",
16358            ),
16359            (
16360                vec![&b"0.01"[..], b"1073741825"],
16361                "-ERR capacity must be in the range [1, 1073741824]\r\n",
16362            ),
16363        ] {
16364            let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
16365            cmd.extend(args.iter().copied());
16366            assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
16367        }
16368        assert_eq!(
16369            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
16370            "-ERR no expansion\r\n"
16371        );
16372        assert_eq!(
16373            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
16374            "-ERR bad expansion\r\n"
16375        );
16376        assert_eq!(
16377            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
16378            "-ERR expansion must be in the range [0, 32768]\r\n"
16379        );
16380        // Trailing rubbish after the capacity is ignored rather than refused.
16381        assert_eq!(
16382            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
16383            "+OK\r\n"
16384        );
16385        assert_eq!(
16386            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
16387            "-ERR item exists\r\n"
16388        );
16389        assert_eq!(
16390            f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
16391            "-Invalid information value\r\n"
16392        );
16393        assert!(
16394            f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
16395                .contains("wrong number of arguments")
16396        );
16397    }
16398
16399    /// The RESP3 shapes, which are where this family differs most from RESP2.
16400    #[test]
16401    fn the_bloom_family_answers_in_resp3_spelling_too() {
16402        let mut f = Fixture::new();
16403        f.out.set_proto(Proto::Resp3);
16404        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
16405        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
16406        assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
16407        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
16408        assert_eq!(
16409            f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
16410            "*2\r\n#t\r\n#f\r\n"
16411        );
16412        // The count stays an integer, because it counts rather than answers.
16413        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
16414        assert_eq!(
16415            f.run(&[b"BF.INFO", b"b"]),
16416            "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16417             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
16418             +Expansion rate\r\n:2\r\n"
16419        );
16420        // One field is a map of one here and a bare array of one on RESP2, so
16421        // this is the reply where the two protocols carry different facts.
16422        assert_eq!(
16423            f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
16424            "%1\r\n+Capacity\r\n:100\r\n"
16425        );
16426    }
16427
16428    // ---------------------------------------------------------------- cuckoo
16429
16430    /// A dump header, which is the four counts and the three widths a filter
16431    /// writes in front of its fingerprints.
16432    ///
16433    /// Written by hand rather than taken from a `CF.SCANDUMP`, because what the
16434    /// tests below want out of it is the states a filter cannot be put into
16435    /// from the wire.
16436    fn cf_header(
16437        items: u64,
16438        buckets: u64,
16439        deletes: u64,
16440        filters: u64,
16441        geometry: [u16; 3],
16442    ) -> Vec<u8> {
16443        let mut out = Vec::with_capacity(38);
16444        for n in [items, buckets, deletes, filters] {
16445            out.extend_from_slice(&n.to_le_bytes());
16446        }
16447        for n in geometry {
16448            out.extend_from_slice(&n.to_le_bytes());
16449        }
16450        out
16451    }
16452
16453    /// The filter a client gets when it does not describe one, and the thing a
16454    /// cuckoo filter does that a Bloom filter cannot, which is count copies and
16455    /// take them out again.
16456    #[test]
16457    fn cf_add_makes_the_filter_and_counts_the_copies() {
16458        let mut f = Fixture::new();
16459        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16460        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16461        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
16462        // The NX form is the one that looks first, which is why it is a command
16463        // of its own rather than an option.
16464        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
16465        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
16466        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
16467        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
16468        assert_eq!(
16469            f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
16470            "*2\r\n:1\r\n:0\r\n"
16471        );
16472        // The defaults are the module's configs: 1024 entries over buckets of
16473        // two, twenty kicks and a chain that grows by one.
16474        assert_eq!(
16475            f.run(&[b"CF.INFO", b"d"]),
16476            "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16477             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
16478             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
16479             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16480        );
16481        assert_eq!(
16482            f.run(&[b"CF.DEBUG", b"d"]),
16483            "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
16484             max_iterations:20 expansion:1\r\n"
16485        );
16486        assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
16487        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
16488
16489        // A delete takes one copy, so the same item goes twice and then stops.
16490        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16491        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
16492        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16493        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
16494        assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
16495
16496        // A key with no filter under it gets three different sentences and one
16497        // plain miss, depending on which command asked.
16498        assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
16499        assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
16500        assert_eq!(
16501            f.run(&[b"CF.COMPACT", b"gone"]),
16502            "-Cuckoo filter was not found\r\n"
16503        );
16504        assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
16505        // And `CF.COMPACT` is declared as taking any number of keys and takes
16506        // exactly one, which is the module's own arity being wrong rather than
16507        // this table's.
16508        assert!(
16509            f.run(&[b"CF.COMPACT", b"a", b"b"])
16510                .contains("wrong number of arguments")
16511        );
16512    }
16513
16514    /// The four that only read fingerprints treat a key holding something else
16515    /// as a key with no filter, and everything else answers `WRONGTYPE`.
16516    #[test]
16517    fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
16518        let mut f = Fixture::new();
16519        f.run(&[b"SET", b"s", b"text"]);
16520        assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
16521        assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16522        assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
16523        // `CF.DEL` writes and is still in that group, and `CF.COMPACT` writes
16524        // and is declared read only, so neither of the two halves of the family
16525        // is the same set as the flags say.
16526        assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
16527        assert_eq!(
16528            f.run(&[b"CF.COMPACT", b"s"]),
16529            "-Cuckoo filter was not found\r\n"
16530        );
16531        for cmd in [
16532            vec![&b"CF.ADD"[..], b"s", b"x"],
16533            vec![&b"CF.ADDNX"[..], b"s", b"x"],
16534            vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
16535            vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
16536            vec![&b"CF.INFO"[..], b"s"],
16537            vec![&b"CF.DEBUG"[..], b"s"],
16538            vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
16539            vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
16540            vec![&b"CF.RESERVE"[..], b"s", b"64"],
16541        ] {
16542            let name = String::from_utf8_lossy(cmd[0]).into_owned();
16543            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16544        }
16545    }
16546
16547    /// `CF.RESERVE` reads its options by name in an order of its own, and the
16548    /// first pair with a given name is the only one it looks at.
16549    #[test]
16550    fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
16551        let mut f = Fixture::new();
16552        assert_eq!(
16553            f.run(&[
16554                b"CF.RESERVE",
16555                b"r",
16556                b"64",
16557                b"BUCKETSIZE",
16558                b"1",
16559                b"MAXITERATIONS",
16560                b"7",
16561                b"EXPANSION",
16562                b"4"
16563            ]),
16564            "+OK\r\n"
16565        );
16566        assert_eq!(
16567            f.run(&[b"CF.DEBUG", b"r"]),
16568            "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
16569             max_iterations:7 expansion:4\r\n"
16570        );
16571        assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
16572
16573        assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
16574        assert_eq!(
16575            f.run(&[b"CF.RESERVE", b"q", b"1"]),
16576            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16577        );
16578        // The range is the bucket size's and not a constant, so a capacity that
16579        // was fine at two slots a bucket is not at four.
16580        assert_eq!(
16581            f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
16582            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16583        );
16584        assert_eq!(
16585            f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
16586            "+OK\r\n"
16587        );
16588
16589        // The capacity is checked last, so a command that is wrong twice
16590        // answers about the option. Which option it answers about is the order
16591        // the module looks for them in and not the order they were written, so
16592        // a bad kick budget wins over a bad bucket size wherever the two sit.
16593        assert_eq!(
16594            f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
16595            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16596        );
16597        assert_eq!(
16598            f.run(&[
16599                b"CF.RESERVE",
16600                b"q2",
16601                b"64",
16602                b"EXPANSION",
16603                b"xx",
16604                b"BUCKETSIZE",
16605                b"0"
16606            ]),
16607            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16608        );
16609        assert_eq!(
16610            f.run(&[
16611                b"CF.RESERVE",
16612                b"q2",
16613                b"64",
16614                b"MAXITERATIONS",
16615                b"0",
16616                b"BUCKETSIZE",
16617                b"0"
16618            ]),
16619            "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
16620        );
16621        // A second pair with a name that has already been read is not looked at
16622        // at all, so this one is a filter with buckets of one rather than an
16623        // error about a bucket size of zero.
16624        assert_eq!(
16625            f.run(&[
16626                b"CF.RESERVE",
16627                b"q3",
16628                b"64",
16629                b"BUCKETSIZE",
16630                b"1",
16631                b"BUCKETSIZE",
16632                b"0"
16633            ]),
16634            "+OK\r\n"
16635        );
16636        // A pair nobody knows is dropped, which is the opposite of what
16637        // `CF.INSERT` does with the same mistake.
16638        assert_eq!(
16639            f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
16640            "+OK\r\n"
16641        );
16642        assert_eq!(
16643            f.run(&[b"CF.DEBUG", b"q4"]),
16644            "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
16645             max_iterations:20 expansion:1\r\n"
16646        );
16647        // And an option with nothing after it leaves an odd number of them,
16648        // which is an arity error rather than a complaint about the option.
16649        assert!(
16650            f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
16651                .contains("wrong number of arguments")
16652        );
16653    }
16654
16655    /// `CF.INSERT` is a reserve and a multi add, with a grammar that agrees
16656    /// with `CF.RESERVE` about nothing.
16657    #[test]
16658    fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
16659        let mut f = Fixture::new();
16660        assert_eq!(
16661            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
16662            "*2\r\n:1\r\n:1\r\n"
16663        );
16664        assert_eq!(
16665            f.run(&[b"CF.DEBUG", b"i"]),
16666            "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
16667             max_iterations:20 expansion:1\r\n"
16668        );
16669        // The NX form has three answers rather than two, which is why it stays
16670        // integers on both protocols.
16671        assert_eq!(
16672            f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
16673            "*2\r\n:0\r\n:1\r\n"
16674        );
16675        assert_eq!(
16676            f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16677            "-ERR not found\r\n"
16678        );
16679        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16680
16681        assert_eq!(
16682            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16683            "-Bad capacity\r\n"
16684        );
16685        // The bucket size cannot be given here, so the range names the config
16686        // that holds it instead of the option `CF.RESERVE` names.
16687        assert_eq!(
16688            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
16689            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16690        );
16691        // Every occurrence is checked, which is where this differs from
16692        // `CF.RESERVE`: the second `CAPACITY` is an error even though the first
16693        // one is the one that would have been used.
16694        assert_eq!(
16695            f.run(&[
16696                b"CF.INSERT",
16697                b"i",
16698                b"CAPACITY",
16699                b"8",
16700                b"CAPACITY",
16701                b"2",
16702                b"ITEMS",
16703                b"a"
16704            ]),
16705            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16706        );
16707        // An option is one letter and not a word, so `NOSUCH` is `NOCREATE` and
16708        // `ITEMSXYZ` is `ITEMS`, and only a letter that starts nothing is
16709        // refused.
16710        assert_eq!(
16711            f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
16712            "*1\r\n:1\r\n"
16713        );
16714        assert_eq!(
16715            f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
16716            "*1\r\n:1\r\n"
16717        );
16718        assert_eq!(
16719            f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16720            "-Unknown argument received\r\n"
16721        );
16722        // Everything after ITEMS is an item, even when it spells an option.
16723        assert_eq!(
16724            f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16725            "*1\r\n:1\r\n"
16726        );
16727        // And the two ways of sending no items at all are the same complaint.
16728        assert!(
16729            f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
16730                .contains("wrong number of arguments")
16731        );
16732        assert!(
16733            f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
16734                .contains("wrong number of arguments")
16735        );
16736    }
16737
16738    /// The two walls a filter can hit, which say different things and are not
16739    /// the same wall.
16740    #[test]
16741    fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
16742        let mut f = Fixture::new();
16743        f.run(&[
16744            b"CF.RESERVE",
16745            b"s",
16746            b"4",
16747            b"BUCKETSIZE",
16748            b"1",
16749            b"EXPANSION",
16750            b"0",
16751        ]);
16752        for i in 0..4u32 {
16753            assert_eq!(
16754                f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
16755                ":1\r\n"
16756            );
16757        }
16758        assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
16759        assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
16760        // The add commands say it in a sentence and the insert commands say it
16761        // in the array, one value per item, and the array is never short.
16762        assert_eq!(
16763            f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
16764            "*2\r\n:-1\r\n:-1\r\n"
16765        );
16766        assert_eq!(
16767            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
16768            "*2\r\n:0\r\n:-1\r\n"
16769        );
16770
16771        // A chain that is allowed to grow stops for a different reason, and the
16772        // count it stops at is the filter limit rather than the room: this one
16773        // gives up with three slots free. Loading a chain that already has
16774        // every filter it is allowed shows why, since it refuses an item
16775        // straight into an empty one.
16776        let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
16777        assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
16778        assert_eq!(
16779            f.run(&[b"CF.ADD", b"g", b"q"]),
16780            "-Maximum expansions reached\r\n"
16781        );
16782        assert_eq!(
16783            f.run(&[b"CF.INFO", b"g"]),
16784            "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
16785             +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
16786             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
16787             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16788        );
16789    }
16790
16791    /// A filter dumped a chunk at a time and put back under another key is the
16792    /// same filter, and the headers that describe one nobody could build are
16793    /// refused on the way in.
16794    #[test]
16795    fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
16796        let mut f = Fixture::new();
16797        f.run(&[
16798            b"CF.RESERVE",
16799            b"src",
16800            b"8",
16801            b"BUCKETSIZE",
16802            b"2",
16803            b"EXPANSION",
16804            b"2",
16805        ]);
16806        for i in 0..40u32 {
16807            f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
16808        }
16809        // Position zero asks for the header and every one after it is a byte
16810        // offset across every filter laid end to end, and the walk ends on a
16811        // zero and a nil rather than an empty chunk.
16812        let mut pos = b"0".to_vec();
16813        let mut chunks = 0;
16814        loop {
16815            let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
16816            let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
16817            let next = head
16818                .split("\r\n")
16819                .nth(1)
16820                .and_then(|n| n.strip_prefix(':'))
16821                .expect("a two element reply of a position and a chunk")
16822                .to_owned();
16823            if next == "0" {
16824                assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
16825                break;
16826            }
16827            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16828            let at = body
16829                .windows(2)
16830                .position(|w| w == b"\r\n")
16831                .expect("a length line")
16832                + 2;
16833            let data = &body[at..body.len() - 2];
16834            assert_eq!(
16835                f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
16836                "+OK\r\n",
16837                "loading chunk {chunks}"
16838            );
16839            pos = next.into_bytes();
16840            chunks += 1;
16841        }
16842        assert!(chunks >= 2, "a header and at least one chunk");
16843
16844        assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
16845        assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
16846        for i in 0..40u32 {
16847            assert_eq!(
16848                f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
16849                ":1\r\n"
16850            );
16851        }
16852
16853        // A filter with nothing in it hands out no header at all, so a client
16854        // that dumps one has nothing to load back.
16855        f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
16856        assert_eq!(
16857            f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
16858            "*2\r\n:0\r\n$-1\r\n"
16859        );
16860
16861        // The positions this end will not take, which are not the same set at
16862        // both ends: a dump refuses a negative one and a load takes it as an
16863        // offset and fails to find anything there.
16864        assert_eq!(
16865            f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
16866            "-Invalid position\r\n"
16867        );
16868        assert_eq!(
16869            f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
16870            "-Invalid position\r\n"
16871        );
16872        assert_eq!(
16873            f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
16874            "-Invalid position\r\n"
16875        );
16876        assert_eq!(
16877            f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
16878            "-Couldn't load chunk!\r\n"
16879        );
16880        // A header on top of a filter is refused rather than merged.
16881        let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
16882        assert_eq!(
16883            f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
16884            "-ERR item exists\r\n"
16885        );
16886        // A chunk that is not the size of a header where a header should have
16887        // been is one sentence, and one that is the size of a header and
16888        // describes a filter nobody could build is another.
16889        assert_eq!(
16890            f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
16891            "-Invalid header\r\n"
16892        );
16893        for (why, bad) in [
16894            ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
16895            ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
16896            (
16897                "a bucket count that is not a power of two",
16898                cf_header(0, 3, 0, 1, [2, 20, 1]),
16899            ),
16900            ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
16901            ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
16902            (
16903                "a growth nobody could reach",
16904                cf_header(0, 8, 0, 1, [2, 20, 32769]),
16905            ),
16906            (
16907                "a chain that cannot grow and did",
16908                cf_header(0, 8, 0, 2, [2, 20, 0]),
16909            ),
16910            // The count is written in eight bytes and read into two, so a
16911            // number that is a multiple of the second arrives as none.
16912            (
16913                "a filter count that wraps",
16914                cf_header(0, 8, 0, 65_536, [2, 20, 1]),
16915            ),
16916        ] {
16917            assert_eq!(
16918                f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
16919                "-Couldn't create filter!\r\n",
16920                "{why}"
16921            );
16922        }
16923    }
16924
16925    /// The RESP3 shapes, which are where this family differs most from RESP2
16926    /// and where one of its answers stops being readable.
16927    #[test]
16928    fn the_cuckoo_family_answers_in_resp3_spelling_too() {
16929        let mut f = Fixture::new();
16930        f.out.set_proto(Proto::Resp3);
16931        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16932        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16933        assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
16934        assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
16935        assert_eq!(
16936            f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
16937            "*2\r\n#t\r\n#f\r\n"
16938        );
16939        assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
16940        assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
16941        // The count stays an integer, because it counts rather than answers.
16942        assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
16943        assert_eq!(
16944            f.run(&[b"CF.INFO", b"c"]),
16945            "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16946             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16947             +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
16948             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16949        );
16950
16951        // `CF.INSERT` writes a boolean per item here and an integer per item on
16952        // RESP2, and minus one has nowhere to go in a boolean, so a RESP3
16953        // client cannot tell an item that did not fit from one that is already
16954        // there. `CF.INSERTNX` keeps its integers for exactly that reason.
16955        f.run(&[
16956            b"CF.RESERVE",
16957            b"s",
16958            b"4",
16959            b"BUCKETSIZE",
16960            b"1",
16961            b"EXPANSION",
16962            b"0",
16963        ]);
16964        assert_eq!(
16965            f.run(&[
16966                b"CF.INSERT",
16967                b"s",
16968                b"ITEMS",
16969                b"a",
16970                b"b",
16971                b"c",
16972                b"d",
16973                b"e",
16974                b"f"
16975            ]),
16976            "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
16977        );
16978        assert_eq!(
16979            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
16980            "*2\r\n:0\r\n:-1\r\n"
16981        );
16982        assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
16983        // The end of a dump is a nil and not an empty chunk, which is one
16984        // underscore here and a negative length on RESP2.
16985        assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
16986    }
16987
16988    // ------------------------------------------------------------------- cms
16989
16990    /// A sketch is made from either end, and both constructors look at the key
16991    /// before they look at their arguments.
16992    #[test]
16993    fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
16994        let mut f = Fixture::new();
16995        assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
16996        assert_eq!(
16997            f.run(&[b"CMS.INFO", b"d"]),
16998            "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
16999        );
17000        assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
17001        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17002        // Two over the error rounded up, and the log of the probability over the
17003        // log of a half rounded up, which for these two is 200 by 6.
17004        assert_eq!(
17005            f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
17006            "+OK\r\n"
17007        );
17008        assert_eq!(
17009            f.run(&[b"CMS.INFO", b"p"]),
17010            "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
17011        );
17012        // The key is checked first, so a width of zero at a key that is already
17013        // there is about the key and not about the width.
17014        assert_eq!(
17015            f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
17016            "-CMS: key already exists\r\n"
17017        );
17018        assert_eq!(
17019            f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
17020            "-CMS: invalid width\r\n"
17021        );
17022        assert_eq!(
17023            f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
17024            "-CMS: invalid depth\r\n"
17025        );
17026        assert_eq!(
17027            f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
17028            "-CMS: invalid overestimation value\r\n"
17029        );
17030        assert_eq!(
17031            f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
17032            "-CMS: invalid prob value\r\n"
17033        );
17034        // A probability whose float conversion is zero has no depth, and a width
17035        // past a signed sixty four bit integer has no width, and both are the
17036        // same sentence.
17037        assert_eq!(
17038            f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
17039            "-CMS: invalid init arguments\r\n"
17040        );
17041        // And a sketch bigger than a gibibyte of counters is refused here where
17042        // the reference reserves address space nobody has touched, which is
17043        // D-47.
17044        assert_eq!(
17045            f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
17046            "-CMS: Insufficient memory to create the key\r\n"
17047        );
17048        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17049    }
17050
17051    /// Every pair is parsed before any of them lands, the counters saturate,
17052    /// and the count is a signed total of what was asked for.
17053    #[test]
17054    fn increments_are_parsed_whole_and_the_counters_saturate() {
17055        let mut f = Fixture::new();
17056        f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
17057        assert_eq!(
17058            f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
17059            "*2\r\n:3\r\n:4\r\n"
17060        );
17061        // An item that is incremented twice in one call sees its own first
17062        // increment in the reply to the second.
17063        assert_eq!(
17064            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
17065            "*2\r\n:4\r\n:5\r\n"
17066        );
17067        // A bad number anywhere means nothing at all is applied.
17068        assert_eq!(
17069            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
17070            "-CMS: Cannot parse number\r\n"
17071        );
17072        assert_eq!(
17073            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
17074            "-CMS: Number cannot be negative\r\n"
17075        );
17076        assert_eq!(
17077            f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
17078            "*2\r\n:5\r\n:4\r\n"
17079        );
17080        // The counters stop at four billion and the item that stopped says so in
17081        // its own slot while the one beside it answers a number.
17082        f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
17083        assert_eq!(
17084            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
17085            "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
17086        );
17087        assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
17088        // The count is what was asked for rather than what landed, and it is
17089        // signed, so a big enough total comes back negative.
17090        f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
17091        f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
17092        f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
17093        assert_eq!(
17094            f.run(&[b"CMS.INFO", b"w"]),
17095            "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
17096        );
17097        // An odd number of arguments after the key is an arity error and not a
17098        // syntax one.
17099        assert!(
17100            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
17101                .contains("wrong number of arguments")
17102        );
17103        assert_eq!(
17104            f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
17105            "-CMS: key does not exist\r\n"
17106        );
17107        assert_eq!(
17108            f.run(&[b"CMS.QUERY", b"nope", b"a"]),
17109            "-CMS: key does not exist\r\n"
17110        );
17111    }
17112
17113    /// A merge overwrites its destination, and it is worked out in full before
17114    /// any of it is written.
17115    #[test]
17116    fn a_merge_lands_whole_or_not_at_all() {
17117        let mut f = Fixture::new();
17118        for name in [&b"m1"[..], b"m2", b"dst"] {
17119            f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
17120        }
17121        f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
17122        f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
17123        assert_eq!(
17124            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17125            "+OK\r\n"
17126        );
17127        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17128        // Overwritten and not added to, so the same merge twice is the same
17129        // answer twice.
17130        assert_eq!(
17131            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17132            "+OK\r\n"
17133        );
17134        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17135        assert_eq!(
17136            f.run(&[
17137                b"CMS.MERGE",
17138                b"dst",
17139                b"2",
17140                b"m1",
17141                b"m2",
17142                b"WEIGHTS",
17143                b"2",
17144                b"3"
17145            ]),
17146            "+OK\r\n"
17147        );
17148        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17149        // A cell times a weight is checked wide rather than wrapped, so this is
17150        // a refusal and the destination is left exactly as it was.
17151        assert_eq!(
17152            f.run(&[
17153                b"CMS.MERGE",
17154                b"dst",
17155                b"1",
17156                b"m1",
17157                b"WEIGHTS",
17158                b"4611686018427387904"
17159            ]),
17160            "-CMS: MERGE overflow\r\n"
17161        );
17162        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17163        // The destination comes first, then the count, then the layout, then the
17164        // weights, then the sources one at a time.
17165        f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
17166        assert_eq!(
17167            f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
17168            "-CMS: key does not exist\r\n"
17169        );
17170        assert_eq!(
17171            f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
17172            "-CMS: Number of keys must be positive\r\n"
17173        );
17174        assert_eq!(
17175            f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
17176            "-CMS: wrong number of keys\r\n"
17177        );
17178        assert_eq!(
17179            f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
17180            "-CMS: wrong number of keys/weights\r\n"
17181        );
17182        assert_eq!(
17183            f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
17184            "-CMS: width/depth is not equal\r\n"
17185        );
17186        assert_eq!(
17187            f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
17188            "-CMS: key does not exist\r\n"
17189        );
17190    }
17191
17192    /// A key holding anything else is `WRONGTYPE` to all six, and a key holding
17193    /// a sketch is refused by the two commands that would have to serialise it.
17194    #[test]
17195    fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17196        let mut f = Fixture::new();
17197        f.run(&[b"SET", b"s", b"text"]);
17198        for cmd in [
17199            vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
17200            vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
17201            vec![&b"CMS.QUERY"[..], b"s", b"a"],
17202            vec![&b"CMS.INFO"[..], b"s"],
17203            vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
17204        ] {
17205            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17206            let reply = f.run(&cmd);
17207            // The two constructors see the key before anything else and say so
17208            // in the module's own words, and the rest are `WRONGTYPE`.
17209            assert!(
17210                reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
17211                "{name}: {reply}"
17212            );
17213        }
17214        f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
17215        // Redis refuses to copy a module key that has no copy callback, and
17216        // these are its words rather than ours. `DUMP` is the other half of
17217        // D-48: the reference has a payload for one of these and we do not.
17218        assert_eq!(
17219            f.run(&[b"COPY", b"c", b"c2"]),
17220            "-ERR not supported for this module key\r\n"
17221        );
17222        assert_eq!(
17223            f.run(&[b"DUMP", b"c"]),
17224            "-ERR DUMP is not supported for this module key\r\n"
17225        );
17226        // A graph is nobody's module and keeps its own sentence.
17227        f.run(&[b"G.NADD", b"g", b"a"]);
17228        assert_eq!(
17229            f.run(&[b"COPY", b"g", b"g2"]),
17230            "-ERR COPY is not supported for a graph\r\n"
17231        );
17232        assert_eq!(
17233            f.run(&[b"DUMP", b"g"]),
17234            "-ERR DUMP is not supported for a graph\r\n"
17235        );
17236        // Everything that does not need a byte shape works on a sketch key the
17237        // way it works on any other.
17238        assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
17239        assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
17240        assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
17241        assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
17242        assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
17243    }
17244
17245    // ------------------------------------------------------------------ topk
17246
17247    /// `TOPK.RESERVE` takes three arguments or six, and looks at the key before
17248    /// it looks at any of them.
17249    #[test]
17250    fn a_reserve_takes_three_arguments_or_six() {
17251        let mut f = Fixture::new();
17252        assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
17253        assert_eq!(
17254            f.run(&[b"TOPK.INFO", b"t"]),
17255            "*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"
17256        );
17257        // Four arguments and five are an arity error rather than a defaulted
17258        // depth or decay.
17259        for cmd in [
17260            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
17261            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
17262        ] {
17263            assert!(f.run(&cmd).contains("wrong number of arguments"));
17264        }
17265        assert_eq!(
17266            f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
17267            "+OK\r\n"
17268        );
17269        // The key is checked first, so a reserve with nothing else right at a
17270        // key that is taken still says the key is taken.
17271        assert_eq!(
17272            f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
17273            "-TopK: key already exists\r\n"
17274        );
17275        assert_eq!(
17276            f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
17277            "-TopK: invalid k\r\n"
17278        );
17279        assert_eq!(
17280            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
17281            "-TopK: invalid width\r\n"
17282        );
17283        assert_eq!(
17284            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
17285            "-TopK: invalid depth\r\n"
17286        );
17287        // Zero is out and one is in, which is the module's `> 0` and `<= 1`.
17288        assert_eq!(
17289            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
17290            "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
17291        );
17292        assert_eq!(
17293            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
17294            "+OK\r\n"
17295        );
17296        // Past the cap, with the one sentence in the family that has a prefix.
17297        assert_eq!(
17298            f.run(&[
17299                b"TOPK.RESERVE",
17300                b"w",
17301                b"1",
17302                b"4294967295",
17303                b"4294967295",
17304                b"0.9"
17305            ]),
17306            "-ERR Insufficient memory to create topk data structure\r\n"
17307        );
17308    }
17309
17310    /// What the sketch keeps, and the three ways of asking about it.
17311    #[test]
17312    fn the_kept_set_is_what_query_and_list_answer_from() {
17313        let mut f = Fixture::new();
17314        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
17315        // A null an item while there is room, then the name of whatever was
17316        // pushed out.
17317        assert_eq!(
17318            f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
17319            "*2\r\n$-1\r\n$-1\r\n"
17320        );
17321        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
17322        // Two slots are full and `c` arrives with a count of one, which is not
17323        // under the smallest kept count, so it takes that slot straight away.
17324        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
17325        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
17326        assert_eq!(
17327            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
17328            "*3\r\n:1\r\n:0\r\n:1\r\n"
17329        );
17330        // The table still counts what the kept set let go of.
17331        assert_eq!(
17332            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17333            "*3\r\n:11\r\n:1\r\n:6\r\n"
17334        );
17335        assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
17336        assert_eq!(
17337            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
17338            "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
17339        );
17340        // Any prefix of the keyword turns the counts on, the empty string
17341        // included, and only a longer word or a different one is refused.
17342        assert_eq!(
17343            f.run(&[b"TOPK.LIST", b"t", b"w"]),
17344            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17345        );
17346        assert_eq!(
17347            f.run(&[b"TOPK.LIST", b"t", b""]),
17348            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17349        );
17350        assert_eq!(
17351            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
17352            "-WITHCOUNT keyword expected\r\n"
17353        );
17354        // And the keyword is looked at before the key, so a missing key with a
17355        // bad keyword complains about the keyword.
17356        assert_eq!(
17357            f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
17358            "-WITHCOUNT keyword expected\r\n"
17359        );
17360        assert_eq!(
17361            f.run(&[b"TOPK.LIST", b"missing"]),
17362            "-TopK: key does not exist\r\n"
17363        );
17364        // An item counted zero times is kept and not listed.
17365        f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
17366        assert_eq!(
17367            f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
17368            "*1\r\n$-1\r\n"
17369        );
17370        assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
17371        assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
17372    }
17373
17374    /// `TOPK.INCRBY` applies as it goes, so a bad increment leaves everything
17375    /// before it counted, and the reply counts what it wrote.
17376    #[test]
17377    fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
17378        let mut f = Fixture::new();
17379        f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
17380        // Three pairs, the middle one bad: two elements come back, one of them
17381        // the error, and the array header says two rather than three. That last
17382        // part is D-51 and it is why a client here stays in step.
17383        assert_eq!(
17384            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
17385            format!(
17386                "*2\r\n$-1\r\n-{}\r\n",
17387                "TopK: increment must be an integer greater or equal to 0                            and smaller or equal to 100,000"
17388            )
17389        );
17390        assert_eq!(
17391            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17392            "*3\r\n:3\r\n:0\r\n:0\r\n"
17393        );
17394        // A hundred thousand is in and one more is out.
17395        assert_eq!(
17396            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
17397            "*1\r\n$-1\r\n"
17398        );
17399        assert!(
17400            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
17401                .contains("smaller or equal to 100,000")
17402        );
17403        // Pairs have to be pairs.
17404        assert!(
17405            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
17406                .contains("wrong number of arguments")
17407        );
17408        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
17409    }
17410
17411    /// The RESP3 shapes, which are the two the protocols disagree about.
17412    #[test]
17413    fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
17414        let mut f = Fixture::new();
17415        f.run(&[b"HELLO", b"3"]);
17416        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
17417        f.run(&[b"TOPK.ADD", b"t", b"a"]);
17418        assert_eq!(
17419            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
17420            "*2\r\n#t\r\n#f\r\n"
17421        );
17422        // The count stays an integer on both protocols.
17423        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
17424        assert_eq!(
17425            f.run(&[b"TOPK.INFO", b"t"]),
17426            "%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"
17427        );
17428        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
17429    }
17430
17431    /// A top k key answers the module sentences the other sketch families
17432    /// answer, and its own word for its type.
17433    #[test]
17434    fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17435        let mut f = Fixture::new();
17436        f.run(&[b"SET", b"s", b"text"]);
17437        for cmd in [
17438            vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
17439            vec![&b"TOPK.ADD"[..], b"s", b"a"],
17440            vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
17441            vec![&b"TOPK.QUERY"[..], b"s", b"a"],
17442            vec![&b"TOPK.COUNT"[..], b"s", b"a"],
17443            vec![&b"TOPK.LIST"[..], b"s"],
17444            vec![&b"TOPK.INFO"[..], b"s"],
17445        ] {
17446            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17447            let reply = f.run(&cmd);
17448            assert!(
17449                reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
17450                "{name}: {reply}"
17451            );
17452        }
17453        f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
17454        assert_eq!(
17455            f.run(&[b"COPY", b"t", b"t2"]),
17456            "-ERR not supported for this module key\r\n"
17457        );
17458        assert_eq!(
17459            f.run(&[b"DUMP", b"t"]),
17460            "-ERR DUMP is not supported for this module key\r\n"
17461        );
17462        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17463        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17464        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17465        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
17466        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17467        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17468        // Every one of the six that is not the constructor says the same thing
17469        // about a key that is not there.
17470        assert_eq!(
17471            f.run(&[b"TOPK.INFO", b"t3"]),
17472            "-TopK: key does not exist\r\n"
17473        );
17474    }
17475
17476    // --------------------------------------------------------------- tdigest
17477
17478    /// `TDIGEST.CREATE` takes two arguments or four, and the keyword search is a
17479    /// search rather than a lookup.
17480    #[test]
17481    fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
17482        let mut f = Fixture::new();
17483        assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
17484        // A hundred is the default and the capacity is six times it plus ten.
17485        assert_eq!(
17486            f.run(&[b"TDIGEST.INFO", b"t"]),
17487            "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
17488             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
17489             +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
17490        );
17491        assert_eq!(
17492            f.run(&[b"TDIGEST.CREATE", b"t"]),
17493            "-ERR T-Digest: key already exists\r\n"
17494        );
17495        // Three arguments is an arity error and not a missing keyword.
17496        assert!(
17497            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
17498                .contains("wrong number of arguments")
17499        );
17500        assert_eq!(
17501            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
17502            "+OK\r\n"
17503        );
17504        assert_eq!(
17505            f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
17506            "+OK\r\n"
17507        );
17508        // The word is looked for across both trailing arguments and the number
17509        // is then read out of the last one whatever was found, so this looks for
17510        // a number inside the word `COMPRESSION` and does not find one.
17511        assert_eq!(
17512            f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
17513            "-ERR T-Digest: error parsing compression parameter\r\n"
17514        );
17515        assert_eq!(
17516            f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
17517            "-ERR T-Digest: wrong keyword\r\n"
17518        );
17519        assert_eq!(
17520            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
17521            "-ERR T-Digest: error parsing compression parameter\r\n"
17522        );
17523        assert_eq!(
17524            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
17525            "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
17526        );
17527        // The reference's own ceiling, which is where the capacity stops fitting
17528        // in an int, and one past it.
17529        assert_eq!(
17530            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
17531            "-ERR T-Digest: allocation failed\r\n"
17532        );
17533        // And ours, which is a gibibyte of centroids and is D-52.
17534        assert_eq!(
17535            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
17536            "-ERR T-Digest: allocation failed\r\n"
17537        );
17538        // The key is checked before the arguments, so a bad compression at a key
17539        // that is already a digest still says the key is taken.
17540        assert_eq!(
17541            f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
17542            "-ERR T-Digest: key already exists\r\n"
17543        );
17544    }
17545
17546    /// The four samples every note about this family is written against, and the
17547    /// answers a real 8.10.1 gives for them.
17548    #[test]
17549    fn the_quantile_family_answers_what_the_module_answers() {
17550        let mut f = Fixture::new();
17551        f.run(&[b"TDIGEST.CREATE", b"s"]);
17552        assert_eq!(
17553            f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
17554            "+OK\r\n"
17555        );
17556        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
17557        assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
17558        // The cdf of a sample is the weight below it plus half its own.
17559        assert_eq!(
17560            f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
17561            "*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"
17562        );
17563        assert_eq!(
17564            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
17565            "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
17566        );
17567        // Out of order, the walk restarts, and 0.5 answers 3 either way while
17568        // the two after it are read from the front again.
17569        assert_eq!(
17570            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
17571            "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
17572        );
17573        assert_eq!(
17574            f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17575            "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
17576        );
17577        assert_eq!(
17578            f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17579            "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
17580        );
17581        assert_eq!(
17582            f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
17583            "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
17584        );
17585        assert_eq!(
17586            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
17587            "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
17588        );
17589        assert_eq!(
17590            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
17591            "$3\r\n2.5\r\n"
17592        );
17593        assert_eq!(
17594            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
17595            "$3\r\n2.5\r\n"
17596        );
17597        // The ranges, which are separate sentences from the parse failures.
17598        assert_eq!(
17599            f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
17600            "-ERR T-Digest: quantile should be in [0,1]\r\n"
17601        );
17602        assert_eq!(
17603            f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
17604            "-ERR T-Digest: error parsing quantile\r\n"
17605        );
17606        assert_eq!(
17607            f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
17608            "-ERR T-Digest: error parsing cdf\r\n"
17609        );
17610        assert_eq!(
17611            f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
17612            "-ERR T-Digest: error parsing value\r\n"
17613        );
17614        assert_eq!(
17615            f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
17616            "-ERR T-Digest: rank needs to be non negative\r\n"
17617        );
17618        assert_eq!(
17619            f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
17620            "-ERR T-Digest: error parsing rank\r\n"
17621        );
17622        // Both cuts have their own parse sentence and share the range one, and
17623        // equal cuts are refused rather than answering nothing.
17624        assert_eq!(
17625            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
17626            "-ERR T-Digest: error parsing low_cut_percentile\r\n"
17627        );
17628        assert_eq!(
17629            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
17630            "-ERR T-Digest: error parsing high_cut_percentile\r\n"
17631        );
17632        assert_eq!(
17633            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
17634            "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
17635        );
17636        assert_eq!(
17637            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
17638            "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
17639        );
17640    }
17641
17642    /// An empty digest answers every question, and answers most of them with
17643    /// something that is not a number.
17644    #[test]
17645    fn an_empty_digest_has_an_answer_for_everything() {
17646        let mut f = Fixture::new();
17647        f.run(&[b"TDIGEST.CREATE", b"e"]);
17648        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17649        assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
17650        assert_eq!(
17651            f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
17652            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17653        );
17654        assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
17655        assert_eq!(
17656            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
17657            "$3\r\nnan\r\n"
17658        );
17659        // Minus two, which is a number no rank on a digest with samples in it
17660        // can ever be.
17661        assert_eq!(
17662            f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
17663            "*2\r\n:-2\r\n:-2\r\n"
17664        );
17665        assert_eq!(
17666            f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
17667            "*2\r\n:-2\r\n:-2\r\n"
17668        );
17669        assert_eq!(
17670            f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
17671            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17672        );
17673        // A reset puts a digest with samples back into exactly this state.
17674        f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
17675        assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
17676        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17677        // Down to the compression count, so a reset digest and a fresh one of
17678        // the same compression report the same nine numbers.
17679        f.run(&[b"TDIGEST.CREATE", b"e2"]);
17680        assert_eq!(
17681            f.run(&[b"TDIGEST.INFO", b"e"]),
17682            f.run(&[b"TDIGEST.INFO", b"e2"])
17683        );
17684    }
17685
17686    /// The double parser is Redis's and not this engine's, and the two disagree
17687    /// at both ends of the range.
17688    #[test]
17689    fn a_sample_is_read_the_way_redis_reads_a_double() {
17690        let mut f = Fixture::new();
17691        f.run(&[b"TDIGEST.CREATE", b"a"]);
17692        // Overflow and underflow are parse failures rather than an infinity and
17693        // a zero, which is where this parts company with the rest of the engine.
17694        for bad in [
17695            &b"nan"[..],
17696            b"1e400",
17697            b"-1e400",
17698            b"1e309",
17699            b"1e-400",
17700            b"",
17701            b" 1",
17702            b"1 ",
17703            b"1e",
17704            b"--1",
17705        ] {
17706            assert_eq!(
17707                f.run(&[b"TDIGEST.ADD", b"a", bad]),
17708                "-ERR T-Digest: error parsing val parameter\r\n",
17709                "{}",
17710                String::from_utf8_lossy(bad)
17711            );
17712        }
17713        // An infinity spelled out parses and is then refused for being one, with
17714        // a different sentence.
17715        for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
17716            assert_eq!(
17717                f.run(&[b"TDIGEST.ADD", b"a", word]),
17718                "-ERR T-Digest: val parameter needs to be a finite number\r\n",
17719                "{}",
17720                String::from_utf8_lossy(word)
17721            );
17722        }
17723        // These all parse: hex, a bare point either side, and the smallest
17724        // subnormal the reference will take.
17725        for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
17726            assert_eq!(
17727                f.run(&[b"TDIGEST.ADD", b"a", good]),
17728                "+OK\r\n",
17729                "{}",
17730                String::from_utf8_lossy(good)
17731            );
17732        }
17733        // Nothing landed from the failures, so six samples is what there is.
17734        assert!(
17735            f.run(&[b"TDIGEST.INFO", b"a"])
17736                .contains("Observations\r\n:6\r\n")
17737        );
17738        // Every value is parsed before any is added, so this whole command is a
17739        // no op.
17740        assert_eq!(
17741            f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
17742            "-ERR T-Digest: error parsing val parameter\r\n"
17743        );
17744        assert!(
17745            f.run(&[b"TDIGEST.INFO", b"a"])
17746                .contains("Observations\r\n:6\r\n")
17747        );
17748    }
17749
17750    /// What a merge does to its destination, to its inputs and to the buffer
17751    /// split `TDIGEST.INFO` reports.
17752    #[test]
17753    fn a_merge_sweeps_the_destination_between_its_inputs() {
17754        let mut f = Fixture::new();
17755        f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
17756        f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
17757        f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
17758        f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
17759        assert_eq!(
17760            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
17761            "+OK\r\n"
17762        );
17763        // The destination did not exist, so the compression is the largest of
17764        // the inputs. The three from the first input were swept in before the
17765        // three from the second arrived, which is the one visible effect of the
17766        // reference folding one input at a time.
17767        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17768        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17769        assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
17770        assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
17771        assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
17772        assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
17773        assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
17774        // Reading a source sweeps it too, so a merge writes to keys it only
17775        // reads from.
17776        assert!(
17777            f.run(&[b"TDIGEST.INFO", b"m1"])
17778                .contains("Merged nodes\r\n:3\r\n")
17779        );
17780        // Without OVERRIDE the destination joins its own inputs, so this takes
17781        // it to nine observations and keeps its own compression.
17782        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
17783        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17784        assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
17785        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17786        // With OVERRIDE the old destination is dropped and the compression goes
17787        // back to the largest of the inputs.
17788        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
17789        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17790        assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
17791        assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
17792        // And COMPRESSION beats both.
17793        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
17794        assert!(
17795            f.run(&[b"TDIGEST.INFO", b"d"])
17796                .contains("Compression\r\n:500\r\n")
17797        );
17798        // Naming the destination as a source folds it in twice.
17799        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
17800        assert!(
17801            f.run(&[b"TDIGEST.INFO", b"d"])
17802                .contains("Observations\r\n:12\r\n")
17803        );
17804        // The arguments, in the order the reference checks them.
17805        assert_eq!(
17806            f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
17807            "-ERR T-Digest: error parsing numkeys\r\n"
17808        );
17809        assert_eq!(
17810            f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
17811            "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
17812        );
17813        assert!(
17814            f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
17815                .contains("wrong number of arguments")
17816        );
17817        assert!(
17818            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
17819                .contains("wrong number of arguments")
17820        );
17821        assert_eq!(
17822            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
17823            "-ERR T-Digest: wrong keyword\r\n"
17824        );
17825        // A source that is not there stops the whole thing, and the destination
17826        // is left as it was.
17827        assert_eq!(
17828            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
17829            "-ERR T-Digest: key does not exist\r\n"
17830        );
17831        assert!(
17832            f.run(&[b"TDIGEST.INFO", b"d"])
17833                .contains("Observations\r\n:12\r\n")
17834        );
17835        // A destination that is not there and is also named as a source is the
17836        // same sentence rather than an empty merge.
17837        assert_eq!(
17838            f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
17839            "-ERR T-Digest: key does not exist\r\n"
17840        );
17841    }
17842
17843    /// The RESP3 shapes, which are the two the protocols disagree about.
17844    #[test]
17845    fn a_digest_answers_doubles_and_a_map_on_resp3() {
17846        let mut f = Fixture::new();
17847        f.run(&[b"HELLO", b"3"]);
17848        f.run(&[b"TDIGEST.CREATE", b"s"]);
17849        f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
17850        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
17851        assert_eq!(
17852            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
17853            "*2\r\n,1\r\n,4\r\n"
17854        );
17855        assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
17856        // The two infinities and the NaN go out as the bare words.
17857        assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
17858        assert_eq!(
17859            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
17860            "*1\r\n,-inf\r\n"
17861        );
17862        f.run(&[b"TDIGEST.CREATE", b"e"]);
17863        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
17864        // The ranks stay integers on both protocols.
17865        assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
17866        // Every question above swept the buffer in, so the four samples are all
17867        // merged by now and the compression count says it happened once.
17868        assert_eq!(
17869            f.run(&[b"TDIGEST.INFO", b"s"]),
17870            "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
17871             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
17872             +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
17873        );
17874    }
17875
17876    /// A t digest key answers the module sentences the other sketch families
17877    /// answer, and its own word for its type.
17878    #[test]
17879    fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
17880        let mut f = Fixture::new();
17881        f.run(&[b"SET", b"s", b"text"]);
17882        for cmd in [
17883            vec![&b"TDIGEST.CREATE"[..], b"s"],
17884            vec![&b"TDIGEST.RESET"[..], b"s"],
17885            vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
17886            vec![&b"TDIGEST.MIN"[..], b"s"],
17887            vec![&b"TDIGEST.MAX"[..], b"s"],
17888            vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
17889            vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
17890            vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
17891            vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
17892            vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
17893            vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
17894            vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
17895            vec![&b"TDIGEST.INFO"[..], b"s"],
17896        ] {
17897            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17898            let reply = f.run(&cmd);
17899            assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
17900        }
17901        // The merge checks its destination the same way, and its sources too.
17902        f.run(&[b"TDIGEST.CREATE", b"t"]);
17903        assert!(
17904            f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
17905                .starts_with("-WRONGTYPE")
17906        );
17907        assert!(
17908            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
17909                .starts_with("-WRONGTYPE")
17910        );
17911        assert_eq!(
17912            f.run(&[b"COPY", b"t", b"t2"]),
17913            "-ERR not supported for this module key\r\n"
17914        );
17915        assert_eq!(
17916            f.run(&[b"DUMP", b"t"]),
17917            "-ERR DUMP is not supported for this module key\r\n"
17918        );
17919        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17920        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17921        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17922        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
17923        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17924        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17925        // An empty digest is still a key, so the twelve that are not the
17926        // constructor all say the same thing once it is gone.
17927        assert_eq!(
17928            f.run(&[b"TDIGEST.INFO", b"t3"]),
17929            "-ERR T-Digest: key does not exist\r\n"
17930        );
17931        // The key is looked at before the arguments, so a bad argument at a key
17932        // that is not there still says the key is not there.
17933        assert_eq!(
17934            f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
17935            "-ERR T-Digest: key does not exist\r\n"
17936        );
17937    }
17938
17939    // -------------------------------------------------------------------- ts
17940
17941    /// A `TS.INFO` reply with the memory usage taken out of it.
17942    ///
17943    /// That number is what a series costs here rather than what one costs in the
17944    /// module, which is D-53, and it moves whenever the layout of a chunk does.
17945    /// Everything either side of it is the wire contract and is worth pinning
17946    /// down exactly, so the tests below check the whole reply with the one
17947    /// number lifted out.
17948    fn without_memory(reply: &str) -> String {
17949        let head = "+memoryUsage\r\n:";
17950        let at = reply.find(head).expect("every TS.INFO reports memory");
17951        let rest = &reply[at + head.len()..];
17952        let end = rest.find("\r\n").expect("and it is a whole number");
17953        format!("{}{}", &reply[..at + head.len()], &rest[end..])
17954    }
17955
17956    /// A series is made empty and still says it has a chunk, and the options are
17957    /// read before the key is looked at.
17958    #[test]
17959    fn a_series_is_made_empty_and_reports_on_itself() {
17960        let mut f = Fixture::new();
17961        assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
17962        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
17963        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
17964        // Fourteen fields, so twenty eight elements. An empty series reports one
17965        // chunk and zero at both ends, and neither the chunk type nor the
17966        // duplicate policy is ever a nil.
17967        assert_eq!(
17968            without_memory(&f.run(&[b"TS.INFO", b"t"])),
17969            "*28\r\n\
17970             +totalSamples\r\n:0\r\n\
17971             +memoryUsage\r\n:\r\n\
17972             +firstTimestamp\r\n:0\r\n\
17973             +lastTimestamp\r\n:0\r\n\
17974             +retentionTime\r\n:0\r\n\
17975             +chunkCount\r\n:1\r\n\
17976             +chunkSize\r\n:4096\r\n\
17977             +chunkType\r\n+compressed\r\n\
17978             +duplicatePolicy\r\n+block\r\n\
17979             +labels\r\n*0\r\n\
17980             +sourceKey\r\n$-1\r\n\
17981             +rules\r\n*0\r\n\
17982             +ignoreMaxTimeDiff\r\n:0\r\n\
17983             +ignoreMaxValDiff\r\n$1\r\n0\r\n"
17984        );
17985        // A key that is already there is about the key whatever it holds, and
17986        // the existence is what is checked rather than the type.
17987        assert_eq!(
17988            f.run(&[b"TS.CREATE", b"t"]),
17989            "-ERR TSDB: key already exists\r\n"
17990        );
17991        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17992        assert_eq!(
17993            f.run(&[b"TS.CREATE", b"str"]),
17994            "-ERR TSDB: key already exists\r\n"
17995        );
17996        // But the arguments are read first, so a bad one at a key that is there
17997        // answers about the argument.
17998        assert_eq!(
17999            f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
18000            "-ERR TSDB: Couldn't parse RETENTION\r\n"
18001        );
18002        // The seven that will not make a series say WRONGTYPE about a key
18003        // holding something else, where the two that would say a sentence.
18004        // The word is inside the sentence and not in front of it, because the
18005        // module writes its own error text and Redis puts ERR on the front of
18006        // anything a module writes.
18007        let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
18008        assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
18009        assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
18010        assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
18011        assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
18012        assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
18013        assert_eq!(
18014            f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
18015            "-ERR TSDB: the key is not a TSDB key\r\n"
18016        );
18017        // And the ones that will not make one say so about a key that is gone.
18018        assert_eq!(
18019            f.run(&[b"TS.INFO", b"nope"]),
18020            "-ERR TSDB: the key does not exist\r\n"
18021        );
18022        assert_eq!(
18023            f.run(&[b"TS.GET", b"nope"]),
18024            "-ERR TSDB: the key does not exist\r\n"
18025        );
18026        assert_eq!(
18027            f.run(&[b"TS.ALTER", b"nope"]),
18028            "-ERR TSDB: the key does not exist\r\n"
18029        );
18030        assert_eq!(
18031            f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
18032            "-ERR TSDB: the key does not exist\r\n"
18033        );
18034    }
18035
18036    /// Every option word, including the ones that are wrong, and the scan that
18037    /// finds them.
18038    #[test]
18039    fn the_options_are_a_keyword_scan_and_not_a_grammar() {
18040        let mut f = Fixture::new();
18041        assert_eq!(
18042            f.run(&[
18043                b"TS.CREATE",
18044                b"t",
18045                b"RETENTION",
18046                b"5000",
18047                b"ENCODING",
18048                b"UNCOMPRESSED",
18049                b"CHUNK_SIZE",
18050                b"128",
18051                b"DUPLICATE_POLICY",
18052                b"LAST",
18053                b"IGNORE",
18054                b"10",
18055                b"0.5",
18056                b"LABELS",
18057                b"room",
18058                b"kitchen"
18059            ]),
18060            "+OK\r\n"
18061        );
18062        let info = f.run(&[b"TS.INFO", b"t"]);
18063        assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
18064        assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
18065        assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
18066        assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
18067        assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
18068        // A plain double here, where a sample value out of TS.GET is the
18069        // shortest digits that read back as the same number.
18070        assert!(
18071            info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
18072            "{info}"
18073        );
18074        assert!(
18075            info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
18076            "{info}"
18077        );
18078
18079        // A word that is not an option is read past rather than refused.
18080        assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
18081        // LABELS eats everything after it in pairs, and the later scans still
18082        // look inside what it ate, so this sets a retention and stores a label
18083        // called RETENTION at the same time.
18084        assert_eq!(
18085            f.run(&[
18086                b"TS.CREATE",
18087                b"g",
18088                b"LABELS",
18089                b"a",
18090                b"b",
18091                b"RETENTION",
18092                b"5"
18093            ]),
18094            "+OK\r\n"
18095        );
18096        let greedy = f.run(&[b"TS.INFO", b"g"]);
18097        assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
18098        assert!(
18099            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"),
18100            "{greedy}"
18101        );
18102
18103        // Every way an option can be wrong, in the order the module reads them.
18104        assert_eq!(
18105            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
18106            "-ERR TSDB: Couldn't parse LABELS\r\n"
18107        );
18108        assert_eq!(
18109            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
18110            "-ERR TSDB: Couldn't parse LABELS\r\n"
18111        );
18112        assert_eq!(
18113            f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
18114            "-ERR TSDB: Couldn't parse RETENTION\r\n"
18115        );
18116        // A retention below zero is one of the two the module writes with no
18117        // ERR in front of it, where one that is not a number gets one.
18118        assert_eq!(
18119            f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
18120            "-TSDB: Couldn't parse RETENTION\r\n"
18121        );
18122        assert_eq!(
18123            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
18124            "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
18125        );
18126        assert_eq!(
18127            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
18128            "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
18129        );
18130        assert_eq!(
18131            f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
18132            "-ERR TSDB: unknown ENCODING parameter\r\n"
18133        );
18134        // And an ENCODING with nothing behind it is an arity error where every
18135        // other keyword in the same spot is a sentence.
18136        assert!(
18137            f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
18138                .contains("wrong number of arguments for 'ts.create' command")
18139        );
18140        assert_eq!(
18141            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
18142            "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
18143        );
18144        assert_eq!(
18145            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
18146            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18147        );
18148        assert_eq!(
18149            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
18150            "-ERR TSDB: Couldn't parse IGNORE\r\n"
18151        );
18152        assert_eq!(
18153            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
18154            "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
18155        );
18156        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
18157
18158        // An alter changes what was named and leaves the rest alone, and reads
18159        // an encoding only far enough to refuse a bad one.
18160        assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
18161        let after = f.run(&[b"TS.INFO", b"t"]);
18162        assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
18163        assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
18164        assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
18165        assert_eq!(
18166            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
18167            "-ERR TSDB: unknown ENCODING parameter\r\n"
18168        );
18169        // An encoding it does take is still not applied.
18170        assert_eq!(
18171            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
18172            "+OK\r\n"
18173        );
18174        assert!(
18175            f.run(&[b"TS.INFO", b"t"])
18176                .contains("+chunkType\r\n+uncompressed\r\n")
18177        );
18178    }
18179
18180    /// Samples go in, come back out and are refused for the reasons the module
18181    /// refuses them.
18182    #[test]
18183    fn samples_land_where_they_are_put_and_the_newest_comes_back() {
18184        let mut f = Fixture::new();
18185        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
18186        // The series was made on the way in.
18187        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18188        assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
18189        // A sample value goes out as a simple string of the shortest digits
18190        // that read back as the same number.
18191        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
18192        assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
18193        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
18194        // An empty series has no newest sample and answers an empty array
18195        // rather than a nil.
18196        assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
18197        assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
18198
18199        // The value is read before the key, so a bad one against a key holding
18200        // a string is about the value.
18201        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18202        assert_eq!(
18203            f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
18204            "-ERR TSDB: invalid value\r\n"
18205        );
18206        // The grammar is tighter than the one a number argument usually gets:
18207        // no leading plus, no bare fraction, no infinity and nothing that does
18208        // not fit.
18209        for bad in [
18210            &b".5"[..],
18211            b"1.",
18212            b"+1",
18213            b" 1",
18214            b"0x10",
18215            b"inf",
18216            b"1e400",
18217            b"--1",
18218            b"1e",
18219        ] {
18220            assert_eq!(
18221                f.run(&[b"TS.ADD", b"v", b"1", bad]),
18222                "-ERR TSDB: invalid value\r\n",
18223                "{}",
18224                String::from_utf8_lossy(bad)
18225            );
18226        }
18227        // And a reading that is not a number is one of three words.
18228        assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
18229
18230        // A timestamp that is not a number, and one that is and is below zero,
18231        // are two different sentences.
18232        assert_eq!(
18233            f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
18234            "-ERR TSDB: invalid timestamp\r\n"
18235        );
18236        assert_eq!(
18237            f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
18238            "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
18239        );
18240
18241        // A repeated timestamp is blocked by default, and ON_DUPLICATE on the
18242        // command beats what the series was told.
18243        assert_eq!(
18244            f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
18245            "-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"
18246        );
18247        assert_eq!(
18248            f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
18249            ":300\r\n"
18250        );
18251        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
18252        // ON_DUPLICATE is only read when the key was already there, which is
18253        // why a policy word that is not a policy passes on a fresh key.
18254        assert_eq!(
18255            f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
18256            ":1\r\n"
18257        );
18258        assert_eq!(
18259            f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
18260            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18261        );
18262
18263        // Retention is exact and it is checked before anything else happens, so
18264        // a sample landing behind the window is refused rather than trimmed.
18265        assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
18266        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
18267        assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
18268        assert_eq!(
18269            f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
18270            "-ERR TSDB: Timestamp is older than retention\r\n"
18271        );
18272        // And the window trims as it moves.
18273        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
18274        assert!(
18275            f.run(&[b"TS.INFO", b"r"])
18276                .contains("+totalSamples\r\n:1\r\n")
18277        );
18278
18279        // An ignore window drops a sample close enough to the newest one to be
18280        // uninteresting, and answers the newest timestamp so a client can tell.
18281        assert_eq!(
18282            f.run(&[
18283                b"TS.CREATE",
18284                b"i",
18285                b"DUPLICATE_POLICY",
18286                b"LAST",
18287                b"IGNORE",
18288                b"10",
18289                b"0.5"
18290            ]),
18291            "+OK\r\n"
18292        );
18293        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
18294        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
18295        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
18296    }
18297
18298    /// Every triple in a `TS.MADD` is answered on its own, and none of them
18299    /// makes a series.
18300    #[test]
18301    fn a_madd_answers_each_triple_and_creates_nothing() {
18302        let mut f = Fixture::new();
18303        assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
18304        assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
18305        assert_eq!(
18306            f.run(&[
18307                b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
18308            ]),
18309            "*3\r\n:100\r\n:100\r\n:200\r\n"
18310        );
18311        // A key that is not a series is an error in its own slot and the ones
18312        // after it still land.
18313        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18314        assert_eq!(
18315            f.run(&[
18316                b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
18317            ]),
18318            "*3\r\n\
18319             -ERR TSDB: the key is not a TSDB key\r\n\
18320             -ERR TSDB: the key is not a TSDB key\r\n\
18321             :300\r\n"
18322        );
18323        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
18324        // A bad value and a bad timestamp are answered in their slots too.
18325        assert_eq!(
18326            f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
18327            "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
18328        );
18329        // And a list that is not made of triples is an arity error.
18330        assert!(
18331            f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
18332                .contains("wrong number of arguments for 'ts.madd' command")
18333        );
18334    }
18335
18336    /// The two increments, which only ever write forwards.
18337    #[test]
18338    fn an_increment_walks_the_newest_value_up_and_down() {
18339        let mut f = Fixture::new();
18340        assert_eq!(
18341            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18342            ":100\r\n"
18343        );
18344        assert_eq!(
18345            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18346            ":100\r\n"
18347        );
18348        // Two on one timestamp add up rather than collide, because the sample
18349        // goes in under the last policy whatever the series says.
18350        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
18351        assert_eq!(
18352            f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
18353            ":200\r\n"
18354        );
18355        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
18356        // A timestamp behind the newest sample is the other of the two errors
18357        // the module writes with no ERR in front of it.
18358        assert_eq!(
18359            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
18360            "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
18361        );
18362        // The increment goes through the ordinary number reader, so it takes
18363        // what a sample value will not and refuses a NaN that a sample value
18364        // takes.
18365        assert_eq!(
18366            f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
18367            ":1\r\n"
18368        );
18369        assert_eq!(
18370            f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
18371            ":1\r\n"
18372        );
18373        assert_eq!(
18374            f.run(&[b"TS.INCRBY", b"t", b"nan"]),
18375            "-ERR TSDB: invalid increase/decrease value\r\n"
18376        );
18377        assert_eq!(
18378            f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
18379            "-ERR TSDB: invalid increase/decrease value\r\n"
18380        );
18381        // A key holding something else is WRONGTYPE and is answered before the
18382        // number is looked at.
18383        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18384        assert_eq!(
18385            f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
18386            "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18387        );
18388        // A TIMESTAMP keyword with nothing behind it is about the timestamp.
18389        // The reference reads one past the end of its own arguments here and
18390        // answers whatever was in that memory, so there is nothing to copy and
18391        // this answers the same thing every time.
18392        assert_eq!(
18393            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
18394            "-ERR TSDB: invalid timestamp\r\n"
18395        );
18396        // And one behind a LABELS is a label name rather than the keyword, so
18397        // this lands at the clock rather than at 5.
18398        assert_eq!(
18399            f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
18400            format!(":{}\r\n", f.server.now_ms())
18401        );
18402        // Adding to a series whose newest value is not a number has no answer.
18403        assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
18404        assert_eq!(
18405            f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
18406            "-ERR TSDB: cannot increment/decrement NaN value\r\n"
18407        );
18408    }
18409
18410    /// Deleting a span, both ends included.
18411    #[test]
18412    fn deleting_takes_out_a_span_and_answers_how_many_went() {
18413        let mut f = Fixture::new();
18414        for at in [b"100".as_slice(), b"200", b"300", b"400"] {
18415            f.run(&[b"TS.ADD", b"t", at, b"1"]);
18416        }
18417        assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
18418        assert!(
18419            f.run(&[b"TS.INFO", b"t"])
18420                .contains("+totalSamples\r\n:2\r\n")
18421        );
18422        // Ends the wrong way round take nothing out rather than being an error.
18423        assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
18424        // The two open ends.
18425        assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
18426        // A series everything has been deleted from keeps its chunk and reports
18427        // zero at both ends again.
18428        let empty = f.run(&[b"TS.INFO", b"t"]);
18429        assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
18430        assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
18431        assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
18432        assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
18433        assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
18434        // The two ends have their own sentences.
18435        assert_eq!(
18436            f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
18437            "-ERR TSDB: wrong fromTimestamp\r\n"
18438        );
18439        assert_eq!(
18440            f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
18441            "-ERR TSDB: wrong toTimestamp\r\n"
18442        );
18443        assert_eq!(
18444            f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
18445            "-ERR TSDB: wrong fromTimestamp\r\n"
18446        );
18447    }
18448
18449    /// What RESP3 changes, which is the two places a number is written and the
18450    /// shape of `TS.INFO`.
18451    #[test]
18452    fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
18453        let mut f = Fixture::new();
18454        f.out = Out::new(Proto::Resp3);
18455        assert_eq!(
18456            f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
18457            "+OK\r\n"
18458        );
18459        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
18460        // A double rather than the simple string RESP2 gets.
18461        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
18462        assert_eq!(
18463            without_memory(&f.run(&[b"TS.INFO", b"t"])),
18464            "%14\r\n\
18465             +totalSamples\r\n:1\r\n\
18466             +memoryUsage\r\n:\r\n\
18467             +firstTimestamp\r\n:100\r\n\
18468             +lastTimestamp\r\n:100\r\n\
18469             +retentionTime\r\n:0\r\n\
18470             +chunkCount\r\n:1\r\n\
18471             +chunkSize\r\n:4096\r\n\
18472             +chunkType\r\n+compressed\r\n\
18473             +duplicatePolicy\r\n+block\r\n\
18474             +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18475             +sourceKey\r\n_\r\n\
18476             +rules\r\n%0\r\n\
18477             +ignoreMaxTimeDiff\r\n:0\r\n\
18478             +ignoreMaxValDiff\r\n,0\r\n"
18479        );
18480    }
18481
18482    /// Reading a span back, both ways round, with the two ends and the three
18483    /// things that trim what comes out.
18484    #[test]
18485    fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
18486        let mut f = Fixture::new();
18487        for (at, v) in [
18488            (b"100".as_slice(), b"1".as_slice()),
18489            (b"200", b"2"),
18490            (b"300", b"3"),
18491            (b"400", b"4"),
18492        ] {
18493            f.run(&[b"TS.ADD", b"t", at, v]);
18494        }
18495        assert_eq!(
18496            f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
18497            "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
18498             *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
18499        );
18500        // Both ends are included.
18501        assert_eq!(
18502            f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
18503            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18504        );
18505        // Backwards, and the count takes from the front of what comes out, so
18506        // backwards it takes the newest.
18507        assert_eq!(
18508            f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
18509            "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
18510        );
18511        // Ends the wrong way round are empty rather than an error.
18512        assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
18513        // The two filters.
18514        assert_eq!(
18515            f.run(&[
18516                b"TS.RANGE",
18517                b"t",
18518                b"-",
18519                b"+",
18520                b"FILTER_BY_VALUE",
18521                b"2",
18522                b"3"
18523            ]),
18524            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18525        );
18526        assert_eq!(
18527            f.run(&[
18528                b"TS.RANGE",
18529                b"t",
18530                b"-",
18531                b"+",
18532                b"FILTER_BY_TS",
18533                b"100",
18534                b"400"
18535            ]),
18536            "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
18537        );
18538        // A word that is not an option is ignored wherever it sits.
18539        assert_eq!(
18540            f.run(&[
18541                b"TS.RANGE",
18542                b"t",
18543                b"-",
18544                b"+",
18545                b"ZZZ",
18546                b"FILTER_BY_TS",
18547                b"400"
18548            ]),
18549            "*1\r\n*2\r\n:400\r\n+4\r\n"
18550        );
18551        // `LATEST` means nothing until there is a compaction rule to follow.
18552        assert_eq!(
18553            f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
18554            "*1\r\n*2\r\n:100\r\n+1\r\n"
18555        );
18556    }
18557
18558    /// The bucketing, which is one column a reduction and a flat row.
18559    #[test]
18560    fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
18561        let mut f = Fixture::new();
18562        for (at, v) in [
18563            (b"100".as_slice(), b"1".as_slice()),
18564            (b"200", b"2"),
18565            (b"300", b"3"),
18566            (b"400", b"4"),
18567        ] {
18568            f.run(&[b"TS.ADD", b"t", at, v]);
18569        }
18570        assert_eq!(
18571            f.run(&[
18572                b"TS.RANGE",
18573                b"t",
18574                b"-",
18575                b"+",
18576                b"AGGREGATION",
18577                b"avg",
18578                b"200"
18579            ]),
18580            "*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"
18581        );
18582        // Three reductions is a row of four and not a row of two with a nested
18583        // three in it.
18584        assert_eq!(
18585            f.run(&[
18586                b"TS.RANGE",
18587                b"t",
18588                b"-",
18589                b"+",
18590                b"AGGREGATION",
18591                b"min,max,count",
18592                b"200"
18593            ]),
18594            "*3\r\n\
18595             *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
18596             *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
18597             *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
18598        );
18599        // The timestamp a bucket is reported under.
18600        assert_eq!(
18601            f.run(&[
18602                b"TS.RANGE",
18603                b"t",
18604                b"-",
18605                b"+",
18606                b"AGGREGATION",
18607                b"avg",
18608                b"200",
18609                b"BUCKETTIMESTAMP",
18610                b"+"
18611            ]),
18612            "*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"
18613        );
18614        // An alignment moves where the bucket edges land.
18615        assert_eq!(
18616            f.run(&[
18617                b"TS.RANGE",
18618                b"t",
18619                b"100",
18620                b"400",
18621                b"ALIGN",
18622                b"100",
18623                b"AGGREGATION",
18624                b"sum",
18625                b"200"
18626            ]),
18627            "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
18628        );
18629        // A `COUNT` sitting where the reduction name belongs is that name, and
18630        // the scan for a real one starts again two words later.
18631        assert_eq!(
18632            f.run(&[
18633                b"TS.RANGE",
18634                b"t",
18635                b"-",
18636                b"+",
18637                b"AGGREGATION",
18638                b"count",
18639                b"200"
18640            ]),
18641            "*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"
18642        );
18643        assert_eq!(
18644            f.run(&[
18645                b"TS.RANGE",
18646                b"t",
18647                b"-",
18648                b"+",
18649                b"AGGREGATION",
18650                b"count",
18651                b"200",
18652                b"COUNT",
18653                b"1"
18654            ]),
18655            "*1\r\n*2\r\n:0\r\n+1\r\n"
18656        );
18657    }
18658
18659    /// `EMPTY` fills the gaps between readings and nothing else, and `last`
18660    /// carries two different things depending on which kind of empty it is.
18661    #[test]
18662    fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
18663        let mut f = Fixture::new();
18664        for (at, v) in [
18665            (b"0".as_slice(), b"1".as_slice()),
18666            (b"100", b"2"),
18667            (b"500", b"nan"),
18668            (b"600", b"3"),
18669        ] {
18670            f.run(&[b"TS.ADD", b"g", at, v]);
18671        }
18672        // Without `EMPTY` the buckets with nothing in them are not there at all,
18673        // and neither is the one holding only a reading that is not a number.
18674        assert_eq!(
18675            f.run(&[
18676                b"TS.RANGE",
18677                b"g",
18678                b"-",
18679                b"+",
18680                b"AGGREGATION",
18681                b"avg",
18682                b"100"
18683            ]),
18684            "*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"
18685        );
18686        // The sum of nothing is zero rather than not a number.
18687        assert_eq!(
18688            f.run(&[
18689                b"TS.RANGE",
18690                b"g",
18691                b"-",
18692                b"+",
18693                b"AGGREGATION",
18694                b"sum",
18695                b"100",
18696                b"EMPTY"
18697            ]),
18698            "*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\
18699             *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\
18700             *2\r\n:600\r\n+3\r\n"
18701        );
18702        // Buckets 200 through 400 have no readings at all and carry the reading
18703        // before the gap either way round. Bucket 500 has a reading that is not
18704        // a number, so it carries whatever the bucket before it in the reading
18705        // direction answered, which is 2 forwards and 3 backwards.
18706        assert_eq!(
18707            f.run(&[
18708                b"TS.RANGE",
18709                b"g",
18710                b"-",
18711                b"+",
18712                b"AGGREGATION",
18713                b"last",
18714                b"100",
18715                b"EMPTY"
18716            ]),
18717            "*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\
18718             *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\
18719             *2\r\n:600\r\n+3\r\n"
18720        );
18721        assert_eq!(
18722            f.run(&[
18723                b"TS.REVRANGE",
18724                b"g",
18725                b"-",
18726                b"+",
18727                b"AGGREGATION",
18728                b"last",
18729                b"100",
18730                b"EMPTY"
18731            ]),
18732            "*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\
18733             *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\
18734             *2\r\n:0\r\n+1\r\n"
18735        );
18736        // And a window that opens on that bucket has nothing in range before it
18737        // to carry, so it answers not a number.
18738        assert_eq!(
18739            f.run(&[
18740                b"TS.RANGE",
18741                b"g",
18742                b"500",
18743                b"600",
18744                b"AGGREGATION",
18745                b"last",
18746                b"100",
18747                b"EMPTY"
18748            ]),
18749            "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
18750        );
18751    }
18752
18753    /// The sentences a read answers when its options do not add up, which are
18754    /// the module's own word for word.
18755    #[test]
18756    fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
18757        let mut f = Fixture::new();
18758        f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
18759        f.run(&[b"SET", b"str", b"x"]);
18760        let cases: &[(&[&[u8]], &str)] = &[
18761            (
18762                &[b"TS.RANGE", b"t"],
18763                "-ERR wrong number of arguments for 'ts.range' command\r\n",
18764            ),
18765            // The key is resolved before a single option is read.
18766            (
18767                &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
18768                "-ERR TSDB: the key does not exist\r\n",
18769            ),
18770            (
18771                &[b"TS.RANGE", b"str", b"-", b"+"],
18772                "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
18773            ),
18774            (
18775                &[b"TS.RANGE", b"t", b"abc", b"+"],
18776                "-ERR TSDB: wrong fromTimestamp\r\n",
18777            ),
18778            (
18779                &[b"TS.RANGE", b"t", b"-", b"abc"],
18780                "-ERR TSDB: wrong toTimestamp\r\n",
18781            ),
18782            (
18783                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
18784                "-ERR TSDB: COUNT argument is missing\r\n",
18785            ),
18786            (
18787                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
18788                "-ERR TSDB: Couldn't parse COUNT\r\n",
18789            ),
18790            (
18791                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
18792                "-ERR TSDB: Invalid COUNT value\r\n",
18793            ),
18794            (
18795                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
18796                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18797            ),
18798            (
18799                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
18800                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18801            ),
18802            (
18803                &[
18804                    b"TS.RANGE",
18805                    b"t",
18806                    b"-",
18807                    b"+",
18808                    b"AGGREGATION",
18809                    b"nope",
18810                    b"100",
18811                ],
18812                "-ERR TSDB: Unknown aggregation type\r\n",
18813            ),
18814            (
18815                &[
18816                    b"TS.RANGE",
18817                    b"t",
18818                    b"-",
18819                    b"+",
18820                    b"AGGREGATION",
18821                    b"avg,,min",
18822                    b"100",
18823                ],
18824                "-ERR TSDB: Empty aggregation type in list\r\n",
18825            ),
18826            // The list of names is read before the width is looked at.
18827            (
18828                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
18829                "-ERR TSDB: Unknown aggregation type\r\n",
18830            ),
18831            (
18832                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
18833                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18834            ),
18835            (
18836                &[
18837                    b"TS.RANGE",
18838                    b"t",
18839                    b"-",
18840                    b"+",
18841                    b"AGGREGATION",
18842                    b"avg",
18843                    b"100",
18844                    b"X",
18845                    b"EMPTY",
18846                ],
18847                "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
18848            ),
18849            (
18850                &[
18851                    b"TS.RANGE",
18852                    b"t",
18853                    b"-",
18854                    b"+",
18855                    b"AGGREGATION",
18856                    b"avg",
18857                    b"100",
18858                    b"BUCKETTIMESTAMP",
18859                    b"z",
18860                ],
18861                "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
18862            ),
18863            (
18864                &[
18865                    b"TS.RANGE",
18866                    b"t",
18867                    b"-",
18868                    b"+",
18869                    b"AGGREGATION",
18870                    b"avg",
18871                    b"100",
18872                    b"X",
18873                    b"Y",
18874                    b"BUCKETTIMESTAMP",
18875                    b"-",
18876                ],
18877                "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
18878                 AGGREGATION flag\r\n",
18879            ),
18880            (
18881                &[
18882                    b"TS.RANGE",
18883                    b"t",
18884                    b"-",
18885                    b"+",
18886                    b"ALIGN",
18887                    b"z",
18888                    b"AGGREGATION",
18889                    b"avg",
18890                    b"100",
18891                ],
18892                "-ERR TSDB: unknown ALIGN parameter\r\n",
18893            ),
18894            (
18895                &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
18896                "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
18897            ),
18898            (
18899                &[
18900                    b"TS.RANGE",
18901                    b"t",
18902                    b"-",
18903                    b"+",
18904                    b"ALIGN",
18905                    b"-",
18906                    b"AGGREGATION",
18907                    b"avg",
18908                    b"100",
18909                ],
18910                "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
18911            ),
18912            (
18913                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
18914                "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
18915            ),
18916            (
18917                &[
18918                    b"TS.RANGE",
18919                    b"t",
18920                    b"-",
18921                    b"+",
18922                    b"FILTER_BY_VALUE",
18923                    b"x",
18924                    b"2",
18925                ],
18926                "-ERR TSDB: Couldn't parse MIN\r\n",
18927            ),
18928            (
18929                &[
18930                    b"TS.RANGE",
18931                    b"t",
18932                    b"-",
18933                    b"+",
18934                    b"FILTER_BY_VALUE",
18935                    b"1",
18936                    b"y",
18937                ],
18938                "-ERR TSDB: Couldn't parse MAX\r\n",
18939            ),
18940            (
18941                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
18942                "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
18943            ),
18944        ];
18945        for (argv, want) in cases {
18946            let got = f.run(argv);
18947            assert_eq!(&got, want, "{:?}", argv.last());
18948        }
18949        // The one sentence here that is yo's own rather than the module's, which
18950        // is D-54. A read that would build more rows than yo will build is
18951        // refused instead of attempted.
18952        f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
18953        f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
18954        assert_eq!(
18955            f.run(&[
18956                b"TS.RANGE",
18957                b"wide",
18958                b"-",
18959                b"+",
18960                b"AGGREGATION",
18961                b"avg",
18962                b"1",
18963                b"EMPTY"
18964            ]),
18965            "-ERR TSDB: the requested range holds too many empty buckets\r\n"
18966        );
18967    }
18968
18969    /// What RESP3 changes on a read, which is only how a number is written.
18970    #[test]
18971    fn resp3_writes_a_read_value_as_a_double() {
18972        let mut f = Fixture::new();
18973        f.out = Out::new(Proto::Resp3);
18974        for (at, v) in [
18975            (b"0".as_slice(), b"1".as_slice()),
18976            (b"100", b"2"),
18977            (b"500", b"nan"),
18978            (b"600", b"3"),
18979        ] {
18980            f.run(&[b"TS.ADD", b"g", at, v]);
18981        }
18982        assert_eq!(
18983            f.run(&[
18984                b"TS.RANGE",
18985                b"g",
18986                b"0",
18987                b"100",
18988                b"AGGREGATION",
18989                b"avg,min",
18990                b"200"
18991            ]),
18992            "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
18993        );
18994        assert_eq!(
18995            f.run(&[
18996                b"TS.RANGE",
18997                b"g",
18998                b"500",
18999                b"600",
19000                b"AGGREGATION",
19001                b"last",
19002                b"100",
19003                b"EMPTY"
19004            ]),
19005            "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
19006        );
19007    }
19008
19009    /// Two series with an overlap and a gap each, plus a third holding nothing,
19010    /// which is what the joined reads are measured against.
19011    fn joined() -> Fixture {
19012        let mut f = Fixture::new();
19013        f.run(&[b"TS.CREATE", b"z"]);
19014        for (at, v) in [
19015            (b"10".as_slice(), b"1".as_slice()),
19016            (b"20", b"2"),
19017            (b"40", b"4"),
19018            (b"50", b"5"),
19019        ] {
19020            f.run(&[b"TS.ADD", b"x", at, v]);
19021        }
19022        for (at, v) in [
19023            (b"20".as_slice(), b"20".as_slice()),
19024            (b"30", b"30"),
19025            (b"50", b"50"),
19026            (b"60", b"60"),
19027        ] {
19028            f.run(&[b"TS.ADD", b"y", at, v]);
19029        }
19030        f
19031    }
19032
19033    /// The joined read lines its keys up on the timestamp and writes a row as
19034    /// the timestamp and then a nested array of the columns, which is the one
19035    /// shape in the family that is not the flat pair.
19036    #[test]
19037    fn an_nrange_joins_its_keys_on_the_timestamp() {
19038        let mut f = joined();
19039        // One key still nests, so the shape does not depend on the count.
19040        assert_eq!(
19041            f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
19042            "*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\
19043             *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
19044        );
19045        // A key with no reading where another key has one writes NaN there.
19046        assert_eq!(
19047            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
19048            "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
19049             *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19050             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19051             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19052             *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
19053             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19054        );
19055        // A series holding nothing is a column of NaN and never a row of its
19056        // own, and the same key twice answers twice.
19057        assert_eq!(
19058            f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
19059            "*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"
19060        );
19061        assert_eq!(
19062            f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
19063            "*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"
19064        );
19065        // COUNT is applied to the joined rows and not to each key, so backwards
19066        // it gives the newest joined row rather than the newest of each.
19067        assert_eq!(
19068            f.run(&[
19069                b"TS.NREVRANGE",
19070                b"2",
19071                b"x",
19072                b"y",
19073                b"-",
19074                b"+",
19075                b"COUNT",
19076                b"1"
19077            ]),
19078            "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19079        );
19080        assert_eq!(
19081            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
19082            "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
19083        );
19084        // The two sample filters are settled a key at a time, before the join.
19085        assert_eq!(
19086            f.run(&[
19087                b"TS.NRANGE",
19088                b"2",
19089                b"x",
19090                b"y",
19091                b"-",
19092                b"+",
19093                b"FILTER_BY_VALUE",
19094                b"2",
19095                b"30"
19096            ]),
19097            "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19098             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19099             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19100             *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
19101        );
19102    }
19103
19104    /// The aggregation on a joined read names one reduction a key and then the
19105    /// one bucket width, and each name may be a comma list, so a row can be
19106    /// wider than the key count.
19107    #[test]
19108    fn an_nrange_aggregation_names_one_reduction_a_key() {
19109        let mut f = joined();
19110        assert_eq!(
19111            f.run(&[
19112                b"TS.NRANGE",
19113                b"2",
19114                b"x",
19115                b"y",
19116                b"-",
19117                b"+",
19118                b"AGGREGATION",
19119                b"sum",
19120                b"sum",
19121                b"20"
19122            ]),
19123            "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
19124             *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
19125             *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
19126             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19127        );
19128        // A comma list on the first key widens the row to three columns.
19129        assert_eq!(
19130            f.run(&[
19131                b"TS.NRANGE",
19132                b"2",
19133                b"x",
19134                b"y",
19135                b"-",
19136                b"+",
19137                b"AGGREGATION",
19138                b"sum,count",
19139                b"avg",
19140                b"20"
19141            ]),
19142            "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
19143             *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
19144             *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
19145             *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
19146        );
19147        // Everything behind the width moves along with it, so BUCKETTIMESTAMP
19148        // sits one or two past the width whatever the key count is.
19149        assert_eq!(
19150            f.run(&[
19151                b"TS.NRANGE",
19152                b"2",
19153                b"x",
19154                b"y",
19155                b"-",
19156                b"+",
19157                b"AGGREGATION",
19158                b"avg",
19159                b"sum",
19160                b"100",
19161                b"EMPTY",
19162                b"BUCKETTIMESTAMP",
19163                b"end"
19164            ]),
19165            "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
19166        );
19167        // A COUNT landing in one of the name slots is a reduction name and not
19168        // the keyword, and the read then has no count at all.
19169        assert_eq!(
19170            f.run(&[
19171                b"TS.NRANGE",
19172                b"2",
19173                b"x",
19174                b"y",
19175                b"-",
19176                b"+",
19177                b"AGGREGATION",
19178                b"avg",
19179                b"COUNT",
19180                b"100"
19181            ]),
19182            "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
19183        );
19184    }
19185
19186    /// The sentences a joined read answers when it does not add up, which are
19187    /// the module's own and come out in the module's own order.
19188    #[test]
19189    fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
19190        let mut f = joined();
19191        f.run(&[b"SET", b"str", b"hi"]);
19192        let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
19193        let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
19194                       must be equal to numkeys\r\n";
19195        let cases: &[(&[&[u8]], &str)] = &[
19196            (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
19197            (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
19198            (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
19199            // Not enough words behind the count for the keys and both ends of
19200            // the span, which is an arity error however many keys were named.
19201            (
19202                &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
19203                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19204            ),
19205            (
19206                &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
19207                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19208            ),
19209            // The reduction names are read before the two ends of the span,
19210            // which no other option is.
19211            (
19212                &[
19213                    b"TS.NRANGE",
19214                    b"2",
19215                    b"x",
19216                    b"y",
19217                    b"abc",
19218                    b"+",
19219                    b"AGGREGATION",
19220                    b"nope",
19221                    b"sum",
19222                    b"100",
19223                ],
19224                "-ERR TSDB: Unknown aggregation type\r\n",
19225            ),
19226            (
19227                &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
19228                "-ERR TSDB: wrong fromTimestamp\r\n",
19229            ),
19230            (
19231                &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
19232                "-ERR TSDB: wrong toTimestamp\r\n",
19233            ),
19234            // A name slot that is missing or holds a number is the count
19235            // sentence, and a width slot that is itself a reduction name is
19236            // that sentence as well.
19237            (
19238                &[
19239                    b"TS.NRANGE",
19240                    b"2",
19241                    b"x",
19242                    b"y",
19243                    b"-",
19244                    b"+",
19245                    b"AGGREGATION",
19246                    b"avg",
19247                ],
19248                numkeys,
19249            ),
19250            (
19251                &[
19252                    b"TS.NRANGE",
19253                    b"2",
19254                    b"x",
19255                    b"y",
19256                    b"-",
19257                    b"+",
19258                    b"AGGREGATION",
19259                    b"100",
19260                    b"sum",
19261                    b"100",
19262                ],
19263                numkeys,
19264            ),
19265            (
19266                &[
19267                    b"TS.NRANGE",
19268                    b"2",
19269                    b"x",
19270                    b"y",
19271                    b"-",
19272                    b"+",
19273                    b"AGGREGATION",
19274                    b"avg",
19275                    b"sum",
19276                    b"sum",
19277                    b"100",
19278                ],
19279                numkeys,
19280            ),
19281            (
19282                &[
19283                    b"TS.NRANGE",
19284                    b"2",
19285                    b"x",
19286                    b"y",
19287                    b"-",
19288                    b"+",
19289                    b"AGGREGATION",
19290                    b"avg",
19291                    b"sum",
19292                    b"abc",
19293                ],
19294                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19295            ),
19296            (
19297                &[
19298                    b"TS.NRANGE",
19299                    b"2",
19300                    b"x",
19301                    b"y",
19302                    b"-",
19303                    b"+",
19304                    b"AGGREGATION",
19305                    b"avg",
19306                    b"sum",
19307                    b"0",
19308                ],
19309                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19310            ),
19311            // With one key none of that applies and the plain parser runs, so a
19312            // lone width is a missing width rather than a count mismatch.
19313            (
19314                &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
19315                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19316            ),
19317            (
19318                &[
19319                    b"TS.NRANGE",
19320                    b"1",
19321                    b"x",
19322                    b"-",
19323                    b"+",
19324                    b"AGGREGATION",
19325                    b"100",
19326                    b"200",
19327                ],
19328                "-ERR TSDB: Unknown aggregation type\r\n",
19329            ),
19330            // The keys come last and in the order they were named.
19331            (
19332                &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
19333                "-ERR TSDB: the key does not exist\r\n",
19334            ),
19335            (
19336                &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
19337                "-ERR WRONGTYPE Operation against a key \
19338                 holding the wrong kind of value\r\n",
19339            ),
19340        ];
19341        for (argv, want) in cases {
19342            let got = f.run(argv);
19343            assert_eq!(&got, want, "{argv:?}");
19344        }
19345    }
19346
19347    /// `TS.READ`, which is a key, one timestamp and everything from there on.
19348    #[test]
19349    fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
19350        let mut f = joined();
19351        assert_eq!(
19352            f.run(&[b"TS.READ", b"x", b"-"]),
19353            "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
19354             *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19355        );
19356        // A plus is the last sample on its own, and a timestamp between two
19357        // samples starts at the one behind it.
19358        assert_eq!(
19359            f.run(&[b"TS.READ", b"x", b"+"]),
19360            "*1\r\n*2\r\n:50\r\n+5\r\n"
19361        );
19362        assert_eq!(
19363            f.run(&[b"TS.READ", b"x", b"25"]),
19364            "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19365        );
19366        // Past the end, a series holding nothing and a key that is not there
19367        // are all the empty array rather than an error.
19368        assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
19369        assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
19370        assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
19371        assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
19372        // The timestamp refusal goes out with nothing in front of it, and a key
19373        // holding something else answers the bare WRONGTYPE rather than the
19374        // module's prefixed one, both unlike the rest of the family.
19375        assert_eq!(
19376            f.run(&[b"TS.READ", b"x", b"abc"]),
19377            "-TSDB: invalid timestamp\r\n"
19378        );
19379        assert_eq!(
19380            f.run(&[b"TS.READ", b"x", b"-1"]),
19381            "-TSDB: invalid timestamp\r\n"
19382        );
19383        f.run(&[b"SET", b"str", b"hi"]);
19384        assert_eq!(
19385            f.run(&[b"TS.READ", b"str", b"-"]),
19386            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19387        );
19388        // Anything other than exactly three words is an arity error, so there
19389        // is nowhere to put an option even though the table says minus three.
19390        assert_eq!(
19391            f.run(&[b"TS.READ", b"x"]),
19392            "-ERR wrong number of arguments for 'ts.read' command\r\n"
19393        );
19394        assert_eq!(
19395            f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
19396            "-ERR wrong number of arguments for 'ts.read' command\r\n"
19397        );
19398    }
19399
19400    /// The keys of a joined read sit behind a count, so `COMMAND GETKEYS` has
19401    /// to read the count to find them.
19402    #[test]
19403    fn getkeys_reads_the_count_of_a_joined_read() {
19404        let mut f = Fixture::new();
19405        assert_eq!(
19406            f.run(&[
19407                b"COMMAND",
19408                b"GETKEYS",
19409                b"TS.NRANGE",
19410                b"2",
19411                b"a",
19412                b"b",
19413                b"-",
19414                b"+"
19415            ]),
19416            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
19417        );
19418        assert_eq!(
19419            f.run(&[
19420                b"COMMAND",
19421                b"GETKEYS",
19422                b"TS.NREVRANGE",
19423                b"1",
19424                b"a",
19425                b"-",
19426                b"+"
19427            ]),
19428            "*1\r\n$1\r\na\r\n"
19429        );
19430        // A count of zero, or one too large for the words that follow it, is
19431        // the server's own refusal and not the module's.
19432        for n in [b"0".as_slice(), b"9", b"abc"] {
19433            assert_eq!(
19434                f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
19435                "-ERR Invalid arguments specified for command\r\n"
19436            );
19437        }
19438    }
19439
19440    /// The five series every test of the label surface works against.
19441    fn labelled() -> Fixture {
19442        let mut f = Fixture::new();
19443        f.run(&[
19444            b"TS.CREATE",
19445            b"a",
19446            b"LABELS",
19447            b"room",
19448            b"kitchen",
19449            b"x",
19450            b"1",
19451        ]);
19452        f.run(&[
19453            b"TS.CREATE",
19454            b"b",
19455            b"LABELS",
19456            b"room",
19457            b"bedroom",
19458            b"x",
19459            b"2",
19460        ]);
19461        f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
19462        f.run(&[b"TS.CREATE", b"d"]);
19463        f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
19464        f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
19465        f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
19466        f
19467    }
19468
19469    /// The filter grammar, which is four steps and a `strtok` rather than a
19470    /// grammar, and which every command that searches on labels shares.
19471    #[test]
19472    fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
19473        let mut f = labelled();
19474        let cases: &[(&[&[u8]], &str)] = &[
19475            // The plain forms, and the order the answer comes back in, which is
19476            // by key name and not by anything the series remembers.
19477            (
19478                &[b"TS.QUERYINDEX", b"room=kitchen"],
19479                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19480            ),
19481            (
19482                &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
19483                "*1\r\n$1\r\na\r\n",
19484            ),
19485            (
19486                &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
19487                "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
19488            ),
19489            // An empty list still counts as something that says which series to
19490            // take, it just never takes any.
19491            (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
19492            // Absent and present, neither of which stands on its own.
19493            (
19494                &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
19495                "*1\r\n$1\r\nc\r\n",
19496            ),
19497            (
19498                &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
19499                "*1\r\n$1\r\na\r\n",
19500            ),
19501            (
19502                &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
19503                "-ERR TSDB: please provide at least one matcher\r\n",
19504            ),
19505            // A run of separators is one separator and everything past the
19506            // second field is dropped, so all three of these ask one question.
19507            (
19508                &[b"TS.QUERYINDEX", b"room==kitchen"],
19509                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19510            ),
19511            (
19512                &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
19513                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19514            ),
19515            (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
19516            // A bracket is only a list when it sits straight behind the
19517            // separator, and then the label in front of it has to be there.
19518            (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
19519            (
19520                &[b"TS.QUERYINDEX", b"=(1)"],
19521                "-ERR TSDB: failed parsing labels\r\n",
19522            ),
19523            (
19524                &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
19525                "-ERR TSDB: failed parsing labels\r\n",
19526            ),
19527            (
19528                &[b"TS.QUERYINDEX", b"room=(kitchen"],
19529                "-ERR TSDB: failed parsing labels\r\n",
19530            ),
19531            (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
19532            (
19533                &[b"TS.QUERYINDEX", b"nonsense"],
19534                "-ERR TSDB: failed parsing labels\r\n",
19535            ),
19536            // Nothing here says which series to take.
19537            (
19538                &[b"TS.QUERYINDEX", b"room!=kitchen"],
19539                "-ERR TSDB: please provide at least one matcher\r\n",
19540            ),
19541            // Names and values are both compared byte for byte.
19542            (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
19543            (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
19544            (
19545                &[b"TS.QUERYINDEX"],
19546                "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
19547            ),
19548        ];
19549        for (argv, want) in cases {
19550            let got = f.run(argv);
19551            assert_eq!(&got, want, "{:?}", argv.last());
19552        }
19553    }
19554
19555    /// `TS.QUERYLABELS`, whose filter is the one that is allowed to be missing.
19556    #[test]
19557    fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
19558        let mut f = labelled();
19559        let cases: &[(&[&[u8]], &str)] = &[
19560            (
19561                &[b"TS.QUERYLABELS", b"LABELS"],
19562                "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19563            ),
19564            (
19565                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
19566                "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19567            ),
19568            (
19569                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19570                "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19571            ),
19572            // The series wearing `r` twice contributes the smaller of the two
19573            // here, which is not the one it was written down as first.
19574            (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
19575            (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
19576            (
19577                &[b"TS.QUERYLABELS", b"VALUES"],
19578                "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
19579            ),
19580            (
19581                &[b"TS.QUERYLABELS", b"ZZZ"],
19582                "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
19583            ),
19584            (
19585                &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
19586                "-ERR TSDB: unknown argument, expected FILTER\r\n",
19587            ),
19588            (
19589                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
19590                "-ERR TSDB: FILTER given with no filter expressions\r\n",
19591            ),
19592            // With no filter at all every series is taken, which is why the
19593            // first case here answers about `r` as well. A filter that is there
19594            // still has to say which series to take.
19595            (
19596                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
19597                "-ERR TSDB: please provide at least one matcher\r\n",
19598            ),
19599            (
19600                &[
19601                    b"TS.QUERYLABELS",
19602                    b"LABELS",
19603                    b"FILTER",
19604                    b"room=kitchen",
19605                    b"x=",
19606                ],
19607                "*1\r\n$4\r\nroom\r\n",
19608            ),
19609        ];
19610        for (argv, want) in cases {
19611            let got = f.run(argv);
19612            assert_eq!(&got, want, "{:?}", argv.last());
19613        }
19614    }
19615
19616    /// `TS.MGET`, the newest sample of every series a filter takes, and the two
19617    /// ways of asking for the labels back alongside it.
19618    #[test]
19619    fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
19620        let mut f = labelled();
19621        let cases: &[(&[&[u8]], &str)] = &[
19622            // A series with no samples writes an empty array where the sample
19623            // goes rather than dropping out of the reply.
19624            (
19625                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19626                "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
19627                 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
19628            ),
19629            (
19630                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19631                "*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\
19632                 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
19633                 *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",
19634            ),
19635            // A selected label the series does not wear is a nil, not a gap.
19636            (
19637                &[
19638                    b"TS.MGET",
19639                    b"SELECTED_LABELS",
19640                    b"x",
19641                    b"FILTER",
19642                    b"room=kitchen",
19643                ],
19644                "*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\
19645                 *2\r\n:100\r\n+1.5\r\n\
19646                 *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",
19647            ),
19648            // The other half of the duplicated name rule. This one takes the
19649            // first written down where `TS.QUERYLABELS` takes the smallest.
19650            (
19651                &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
19652                "*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",
19653            ),
19654            (
19655                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19656                "*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\
19657                 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19658            ),
19659            // A word that is not an option is ignored, but a missing `FILTER`
19660            // is an arity error whatever else was written.
19661            (
19662                &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
19663                "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
19664            ),
19665            (
19666                &[b"TS.MGET", b"a", b"b", b"c"],
19667                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19668            ),
19669            (
19670                &[b"TS.MGET", b"FILTER"],
19671                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19672            ),
19673            // Both keyword checks happen before the filter is read, and the two
19674            // sentences spell the second keyword without its `ED`.
19675            (
19676                &[
19677                    b"TS.MGET",
19678                    b"WITHLABELS",
19679                    b"SELECTED_LABELS",
19680                    b"x",
19681                    b"FILTER",
19682                    b"bad",
19683                ],
19684                "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
19685            ),
19686            (
19687                &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
19688                "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
19689            ),
19690        ];
19691        for (argv, want) in cases {
19692            let got = f.run(argv);
19693            assert_eq!(&got, want, "{:?}", argv.last());
19694        }
19695    }
19696
19697    /// What RESP3 changes across the label surface, which is a set where there
19698    /// was an array and a map where there was a pair of them.
19699    #[test]
19700    fn resp3_writes_the_label_surface_as_sets_and_maps() {
19701        let mut f = labelled();
19702        f.out = Out::new(Proto::Resp3);
19703        let cases: &[(&[&[u8]], &str)] = &[
19704            (
19705                &[b"TS.QUERYINDEX", b"room=kitchen"],
19706                "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
19707            ),
19708            (
19709                &[b"TS.QUERYLABELS", b"LABELS"],
19710                "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19711            ),
19712            (
19713                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19714                "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19715            ),
19716            // The key stops being the first of three and becomes the map key,
19717            // and the labels stop being pairs and become a map of their own.
19718            (
19719                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19720                "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
19721                 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
19722            ),
19723            (
19724                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19725                "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19726                 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
19727                 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
19728            ),
19729            (
19730                &[
19731                    b"TS.MGET",
19732                    b"SELECTED_LABELS",
19733                    b"x",
19734                    b"FILTER",
19735                    b"room=kitchen",
19736                ],
19737                "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
19738                 *2\r\n:100\r\n,1.5\r\n\
19739                 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
19740            ),
19741            // A map with a name in it twice, which is what a series wearing one
19742            // label name twice turns into.
19743            (
19744                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19745                "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
19746                 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19747            ),
19748        ];
19749        for (argv, want) in cases {
19750            let got = f.run(argv);
19751            assert_eq!(&got, want, "{:?}", argv.last());
19752        }
19753    }
19754
19755    /// The same five series with enough samples in them for a group to have
19756    /// something to fold.
19757    fn spanned() -> Fixture {
19758        let mut f = labelled();
19759        f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
19760        f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
19761        f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
19762        f
19763    }
19764
19765    /// A span read out of every series a filter takes, with and without a group
19766    /// over the top of it.
19767    #[test]
19768    fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
19769        let mut f = spanned();
19770        let cases: &[(&[&[u8]], &str)] = &[
19771            (
19772                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
19773                "*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\
19774                 *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",
19775            ),
19776            // Newest first is applied to each series before anything else sees
19777            // the rows.
19778            (
19779                &[
19780                    b"TS.MREVRANGE",
19781                    b"-",
19782                    b"+",
19783                    b"WITHLABELS",
19784                    b"FILTER",
19785                    b"room=kitchen",
19786                ],
19787                "*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\
19788                 *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\
19789                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19790                 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
19791            ),
19792            // A label a series does not wear comes back against a nil rather
19793            // than being left out.
19794            (
19795                &[
19796                    b"TS.MRANGE",
19797                    b"-",
19798                    b"+",
19799                    b"SELECTED_LABELS",
19800                    b"x",
19801                    b"FILTER",
19802                    b"room=kitchen",
19803                ],
19804                "*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\
19805                 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
19806                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
19807                 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
19808            ),
19809            // The fold: 100 is in both series and adds up, the other two are in
19810            // one each and are still rows.
19811            (
19812                &[
19813                    b"TS.MRANGE",
19814                    b"-",
19815                    b"+",
19816                    b"FILTER",
19817                    b"room=kitchen",
19818                    b"GROUPBY",
19819                    b"room",
19820                    b"REDUCE",
19821                    b"sum",
19822                ],
19823                "*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\
19824                 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
19825            ),
19826            // RESP2 has nowhere to put the reducer and the member keys, so a
19827            // group wearing labels writes them as two more labels.
19828            (
19829                &[
19830                    b"TS.MRANGE",
19831                    b"-",
19832                    b"+",
19833                    b"WITHLABELS",
19834                    b"FILTER",
19835                    b"room=kitchen",
19836                    b"GROUPBY",
19837                    b"room",
19838                    b"REDUCE",
19839                    b"max",
19840                ],
19841                "*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\
19842                 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
19843                 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
19844                 *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",
19845            ),
19846            // A count is applied to each member and then again to the fold.
19847            (
19848                &[
19849                    b"TS.MREVRANGE",
19850                    b"-",
19851                    b"+",
19852                    b"COUNT",
19853                    b"1",
19854                    b"FILTER",
19855                    b"room=kitchen",
19856                    b"GROUPBY",
19857                    b"room",
19858                    b"REDUCE",
19859                    b"count",
19860                ],
19861                "*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",
19862            ),
19863            // Nothing wears the label, so nothing is in any group.
19864            (
19865                &[
19866                    b"TS.MRANGE",
19867                    b"-",
19868                    b"+",
19869                    b"FILTER",
19870                    b"room=kitchen",
19871                    b"GROUPBY",
19872                    b"nope",
19873                    b"REDUCE",
19874                    b"sum",
19875                ],
19876                "*0\r\n",
19877            ),
19878            (
19879                &[
19880                    b"TS.MRANGE",
19881                    b"-",
19882                    b"+",
19883                    b"AGGREGATION",
19884                    b"sum,avg",
19885                    b"100",
19886                    b"FILTER",
19887                    b"room=bedroom",
19888                ],
19889                "*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",
19890            ),
19891            // The errors, in the order they are looked for.
19892            (
19893                &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
19894                "-ERR TSDB: missing FILTER argument\r\n",
19895            ),
19896            (
19897                &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
19898                "-ERR TSDB: missing labels for filter argument\r\n",
19899            ),
19900            (
19901                &[
19902                    b"TS.MRANGE",
19903                    b"-",
19904                    b"+",
19905                    b"GROUPBY",
19906                    b"room",
19907                    b"REDUCE",
19908                    b"sum",
19909                    b"FILTER",
19910                    b"room=kitchen",
19911                ],
19912                "-ERR TSDB: GROUPBY should always come after filter\r\n",
19913            ),
19914            // The group is four words from the end here, so the length is what
19915            // is wrong with it.
19916            (
19917                &[
19918                    b"TS.MRANGE",
19919                    b"-",
19920                    b"+",
19921                    b"FILTER",
19922                    b"room=kitchen",
19923                    b"GROUPBY",
19924                    b"room",
19925                    b"REDUCE",
19926                    b"sum",
19927                    b"x",
19928                ],
19929                "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
19930            ),
19931            // And here it is not, so its words are filters and answer first.
19932            (
19933                &[
19934                    b"TS.MRANGE",
19935                    b"-",
19936                    b"+",
19937                    b"FILTER",
19938                    b"nope",
19939                    b"GROUPBY",
19940                    b"room",
19941                    b"REDUCE",
19942                    b"sum",
19943                    b"x",
19944                ],
19945                "-ERR TSDB: failed parsing labels\r\n",
19946            ),
19947            (
19948                &[
19949                    b"TS.MRANGE",
19950                    b"-",
19951                    b"+",
19952                    b"FILTER",
19953                    b"room=kitchen",
19954                    b"GROUPBY",
19955                    b"room",
19956                    b"REDUCE",
19957                    b"twa",
19958                ],
19959                "-ERR TSDB: Invalid reducer type\r\n",
19960            ),
19961            (
19962                &[
19963                    b"TS.MRANGE",
19964                    b"-",
19965                    b"+",
19966                    b"AGGREGATION",
19967                    b"sum,avg",
19968                    b"100",
19969                    b"FILTER",
19970                    b"room=kitchen",
19971                    b"GROUPBY",
19972                    b"room",
19973                    b"REDUCE",
19974                    b"sum",
19975                ],
19976                "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
19977            ),
19978            // The label list ends at a keyword, so this is a `COUNT` with a
19979            // `FILTER` where its number should be.
19980            (
19981                &[
19982                    b"TS.MRANGE",
19983                    b"-",
19984                    b"+",
19985                    b"SELECTED_LABELS",
19986                    b"COUNT",
19987                    b"FILTER",
19988                    b"room=kitchen",
19989                ],
19990                "-ERR TSDB: Couldn't parse COUNT\r\n",
19991            ),
19992        ];
19993        for (argv, want) in cases {
19994            let got = f.run(argv);
19995            assert_eq!(&got, want, "{argv:?}");
19996        }
19997    }
19998
19999    /// The multi key reads on RESP3, where the key becomes a map key and the
20000    /// reducer and the member keys become fields of their own.
20001    #[test]
20002    fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
20003        let mut f = spanned();
20004        f.out = Out::new(Proto::Resp3);
20005        let cases: &[(&[&[u8]], &str)] = &[
20006            (
20007                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
20008                "%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\
20009                 *1\r\n*2\r\n:200\r\n,2\r\n",
20010            ),
20011            // The reductions a read asked for, which RESP2 has no room for at
20012            // all and which is empty on a read that asked for none.
20013            (
20014                &[
20015                    b"TS.MRANGE",
20016                    b"-",
20017                    b"+",
20018                    b"AGGREGATION",
20019                    b"sum,avg",
20020                    b"100",
20021                    b"FILTER",
20022                    b"room=bedroom",
20023                ],
20024                "%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\
20025                 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
20026            ),
20027            (
20028                &[
20029                    b"TS.MRANGE",
20030                    b"-",
20031                    b"+",
20032                    b"FILTER",
20033                    b"room=kitchen",
20034                    b"GROUPBY",
20035                    b"room",
20036                    b"REDUCE",
20037                    b"sum",
20038                ],
20039                "%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\
20040                 $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\
20041                 *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",
20042            ),
20043            // The labels hold only the pair the group was made on, because the
20044            // reducer and the sources have somewhere else to go.
20045            (
20046                &[
20047                    b"TS.MRANGE",
20048                    b"-",
20049                    b"+",
20050                    b"WITHLABELS",
20051                    b"FILTER",
20052                    b"room=kitchen",
20053                    b"GROUPBY",
20054                    b"room",
20055                    b"REDUCE",
20056                    b"max",
20057                ],
20058                "%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\
20059                 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
20060                 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
20061                 *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",
20062            ),
20063            (
20064                &[
20065                    b"TS.MRANGE",
20066                    b"-",
20067                    b"+",
20068                    b"FILTER",
20069                    b"room=kitchen",
20070                    b"GROUPBY",
20071                    b"nope",
20072                    b"REDUCE",
20073                    b"sum",
20074                ],
20075                "%0\r\n",
20076            ),
20077        ];
20078        for (argv, want) in cases {
20079            let got = f.run(argv);
20080            assert_eq!(&got, want, "{argv:?}");
20081        }
20082    }
20083
20084    /// `TS.CREATERULE`, whose refusals come in an order of their own.
20085    #[test]
20086    fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
20087        let mut f = Fixture::new();
20088        f.run(&[b"TS.CREATE", b"src"]);
20089        f.run(&[b"TS.CREATE", b"dst"]);
20090        f.run(&[b"SET", b"plain", b"v"]);
20091        let cases: &[(&[&[u8]], &str)] = &[
20092            // The width is read before the reduction, the reduction before the
20093            // width being above zero, and all three before either key is looked
20094            // at, so a command that is wrong twice complains about the first.
20095            (
20096                &[
20097                    b"TS.CREATERULE",
20098                    b"src",
20099                    b"dst",
20100                    b"AGGREGATION",
20101                    b"nope",
20102                    b"x",
20103                ],
20104                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
20105            ),
20106            (
20107                &[
20108                    b"TS.CREATERULE",
20109                    b"src",
20110                    b"dst",
20111                    b"AGGREGATION",
20112                    b"nope",
20113                    b"10",
20114                ],
20115                "-ERR TSDB: Unknown aggregation type\r\n",
20116            ),
20117            (
20118                &[
20119                    b"TS.CREATERULE",
20120                    b"src",
20121                    b"dst",
20122                    b"AGGREGATION",
20123                    b"avg",
20124                    b"0",
20125                ],
20126                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
20127            ),
20128            (
20129                &[
20130                    b"TS.CREATERULE",
20131                    b"src",
20132                    b"dst",
20133                    b"AGGREGATION",
20134                    b"avg",
20135                    b"10",
20136                    b"x",
20137                ],
20138                "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
20139            ),
20140            (
20141                &[
20142                    b"TS.CREATERULE",
20143                    b"src",
20144                    b"src",
20145                    b"AGGREGATION",
20146                    b"avg",
20147                    b"10",
20148                ],
20149                "-ERR TSDB: the source key and destination key should be different\r\n",
20150            ),
20151            // A key holding something else answers the same as a key that is not
20152            // there at all, because the source is looked up first and neither of
20153            // them is a series.
20154            (
20155                &[
20156                    b"TS.CREATERULE",
20157                    b"nope",
20158                    b"plain",
20159                    b"AGGREGATION",
20160                    b"avg",
20161                    b"10",
20162                ],
20163                "-ERR TSDB: the key does not exist\r\n",
20164            ),
20165            (
20166                &[
20167                    b"TS.CREATERULE",
20168                    b"src",
20169                    b"nope",
20170                    b"AGGREGATION",
20171                    b"avg",
20172                    b"10",
20173                ],
20174                "-ERR TSDB: the key does not exist\r\n",
20175            ),
20176            // A keyword other than AGGREGATION is an arity error rather than a
20177            // syntax one, because the arity is all that is checked.
20178            (
20179                &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
20180                "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
20181            ),
20182            (
20183                &[
20184                    b"TS.CREATERULE",
20185                    b"src",
20186                    b"dst",
20187                    b"AGGREGATION",
20188                    b"avg",
20189                    b"10",
20190                ],
20191                "+OK\r\n",
20192            ),
20193            // The link is now in place, so the same rule again is refused from
20194            // the destination's end.
20195            (
20196                &[
20197                    b"TS.CREATERULE",
20198                    b"src",
20199                    b"dst",
20200                    b"AGGREGATION",
20201                    b"avg",
20202                    b"10",
20203                ],
20204                "-ERR TSDB: the destination key already has a src rule\r\n",
20205            ),
20206            // A source that is already someone's destination, and a destination
20207            // that is already someone's source, are two different sentences.
20208            (
20209                &[
20210                    b"TS.CREATERULE",
20211                    b"dst",
20212                    b"src",
20213                    b"AGGREGATION",
20214                    b"avg",
20215                    b"10",
20216                ],
20217                "-ERR TSDB: the source key already has a source rule\r\n",
20218            ),
20219            (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
20220            (
20221                &[b"TS.DELETERULE", b"src", b"dst"],
20222                "-ERR TSDB: compaction rule does not exist\r\n",
20223            ),
20224            // The source is looked up and the destination is not, so a missing
20225            // destination is a missing rule and a missing source is a missing
20226            // key, which is the other way round from `TS.CREATERULE`.
20227            (
20228                &[b"TS.DELETERULE", b"src", b"nope"],
20229                "-ERR TSDB: compaction rule does not exist\r\n",
20230            ),
20231            (
20232                &[b"TS.DELETERULE", b"nope", b"dst"],
20233                "-ERR TSDB: the key does not exist\r\n",
20234            ),
20235        ];
20236        for (argv, want) in cases {
20237            let got = f.run(argv);
20238            assert_eq!(&got, want, "{argv:?}");
20239        }
20240    }
20241
20242    /// What a rule writes, which is every bucket but the one it is filling.
20243    #[test]
20244    fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
20245        let mut f = Fixture::new();
20246        f.run(&[b"TS.CREATE", b"src"]);
20247        f.run(&[b"TS.CREATE", b"dst"]);
20248        // The readings written before the rule was made are not folded, so the
20249        // destination is still empty after the first two.
20250        f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
20251        f.run(&[
20252            b"TS.CREATERULE",
20253            b"src",
20254            b"dst",
20255            b"AGGREGATION",
20256            b"sum",
20257            b"100",
20258        ]);
20259        f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
20260        assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
20261        // The bucket the rule is filling holds only what it was given, so it is
20262        // 2 rather than 3, and it is written when a reading lands past it.
20263        assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
20264        f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
20265        assert_eq!(
20266            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20267            "*1\r\n*2\r\n:0\r\n+2\r\n"
20268        );
20269        // A reading into a bucket that has already been written works that
20270        // bucket out again over everything the source now holds.
20271        f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
20272        assert_eq!(
20273            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20274            "*1\r\n*2\r\n:0\r\n+11\r\n"
20275        );
20276        // Deleting from the source works the buckets it touched out again and
20277        // reopens the newest one, so `LATEST` starts from the whole bucket.
20278        assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
20279        assert_eq!(
20280            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20281            "*1\r\n*2\r\n:0\r\n+8\r\n"
20282        );
20283        assert_eq!(
20284            f.run(&[b"TS.GET", b"dst", b"LATEST"]),
20285            "*2\r\n:100\r\n+4\r\n"
20286        );
20287        // The link shows on both ends, and dropping either key takes it down.
20288        assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
20289        f.run(&[b"DEL", b"dst"]);
20290        assert_eq!(
20291            f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
20292            "-ERR TSDB: compaction rule does not exist\r\n"
20293        );
20294    }
20295
20296    /// The three shapes an `XADD` id can take, and the one rule behind all of
20297    /// them.
20298    #[test]
20299    fn xadd_ids_only_ever_go_up() {
20300        let mut f = Fixture::new();
20301        // A bare millisecond is that millisecond and sequence zero.
20302        assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
20303        // And `5-*` is the next free sequence inside it.
20304        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
20305        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
20306        assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
20307        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20308
20309        assert!(
20310            f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
20311                .contains("equal or smaller")
20312        );
20313        assert!(
20314            f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
20315                .contains("must be greater than 0-0")
20316        );
20317        assert!(
20318            f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
20319                .contains("Invalid stream ID")
20320        );
20321        // The pairs have to be pairs, and Redis calls an odd one an arity error
20322        // rather than a syntax error even though the table has already passed.
20323        assert!(
20324            f.run(&[b"XADD", b"s", b"*", b"a"])
20325                .contains("wrong number of arguments")
20326        );
20327
20328        // `NOMKSTREAM` on a key that is not there is a null and not a zero, so a
20329        // producer can tell nobody is consuming this yet from the write landed.
20330        assert_eq!(
20331            f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
20332            "$-1\r\n"
20333        );
20334        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20335        assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
20336        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
20337    }
20338
20339    /// The trim options, which are three keywords that disagree about how many
20340    /// arguments they take.
20341    #[test]
20342    fn trimming_reads_its_options_the_way_redis_does() {
20343        let mut f = Fixture::new();
20344        for i in 1..=10u32 {
20345            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20346        }
20347        assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
20348        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20349        assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
20350        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20351
20352        // One argument after the keyword and the `~` is read as the threshold,
20353        // which is what a real server does and is the reason this is a number
20354        // complaint and not a syntax one.
20355        assert!(
20356            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
20357                .contains("not an integer")
20358        );
20359        assert!(
20360            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
20361                .contains("MAXLEN argument must be >= 0")
20362        );
20363        // The strategy check runs before the approximation check, so a LIMIT
20364        // with neither is told about the missing strategy.
20365        assert!(
20366            f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
20367                .contains("without specifying a trimming strategy")
20368        );
20369        assert!(
20370            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
20371                .contains("without the special ~ option")
20372        );
20373        assert!(
20374            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
20375                .contains("at the same time are not compatible")
20376        );
20377        // NOMKSTREAM is XADD's and XTRIM does not take it.
20378        assert!(
20379            f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
20380                .contains("syntax error")
20381        );
20382        assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
20383    }
20384
20385    /// `XRANGE`, whose two kinds of nothing are the thing worth pinning.
20386    #[test]
20387    fn xrange_looks_the_key_up_before_it_reads_the_count() {
20388        let mut f = Fixture::new();
20389        f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
20390        f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
20391
20392        assert_eq!(
20393            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20394            "*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\
20395             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20396        );
20397        assert_eq!(
20398            f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
20399            "*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"
20400        );
20401        // The exclusive bound is stepped after the missing sequence is filled
20402        // in, so `(6` is `6-` and the largest sequence there is, minus one, and
20403        // `6-1` is still in the range.
20404        assert_eq!(
20405            f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
20406            "*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\
20407             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20408        );
20409        assert_eq!(
20410            f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
20411            "*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"
20412        );
20413        assert!(
20414            f.run(&[b"XRANGE", b"s", b"(-", b"+"])
20415                .contains("Invalid stream ID")
20416        );
20417
20418        // The two kinds of nothing. A key that is not there is an empty array
20419        // and a key that is there with a count of zero is a null array, because
20420        // the lookup happens first.
20421        assert_eq!(
20422            f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
20423            "*0\r\n"
20424        );
20425        assert_eq!(
20426            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
20427            "*-1\r\n"
20428        );
20429        f.run(&[b"SET", b"str", b"v"]);
20430        assert!(
20431            f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
20432                .starts_with("-WRONGTYPE")
20433        );
20434        // The count is read in a loop, so the last one wins.
20435        assert_eq!(
20436            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
20437            "*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"
20438        );
20439    }
20440
20441    /// `XDEL` and `XACK` check every id before they touch any of them.
20442    #[test]
20443    fn a_bad_id_late_in_the_list_stops_the_whole_command() {
20444        let mut f = Fixture::new();
20445        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20446        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20447        assert!(
20448            f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
20449                .contains("Invalid stream ID")
20450        );
20451        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20452        assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
20453        assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
20454        assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
20455        assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
20456    }
20457
20458    /// `XGROUP`, and the two different complaints it makes about arguments.
20459    #[test]
20460    fn xgroup_has_an_arity_per_subcommand() {
20461        let mut f = Fixture::new();
20462        assert!(
20463            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20464                .contains("requires the key")
20465        );
20466        assert_eq!(
20467            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
20468            "+OK\r\n"
20469        );
20470        // A second CREATE is BUSYGROUP and not an ordinary error, because a
20471        // client racing another one to make a group branches on the prefix.
20472        assert!(
20473            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20474                .starts_with("-BUSYGROUP")
20475        );
20476        assert_eq!(
20477            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20478            ":1\r\n"
20479        );
20480        assert_eq!(
20481            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20482            ":0\r\n"
20483        );
20484        assert_eq!(
20485            f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
20486            ":0\r\n"
20487        );
20488
20489        // Below the subcommand's own arity is an arity error naming the pair.
20490        let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
20491        assert!(
20492            short.contains("wrong number of arguments for 'xgroup|destroy' command"),
20493            "{short}"
20494        );
20495        // At or above it in a shape the handler will not take is the other one.
20496        let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
20497        assert!(
20498            odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
20499            "{odd}"
20500        );
20501        assert!(
20502            f.run(&[b"XGROUP", b"NOSUCH", b"s"])
20503                .contains("Try XGROUP HELP")
20504        );
20505
20506        assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
20507        assert!(
20508            f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
20509                .starts_with("-NOGROUP")
20510        );
20511        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
20512        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
20513        assert!(
20514            f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
20515                .contains("requires the key")
20516        );
20517    }
20518
20519    /// A group read, an acknowledgement, and what is left in between.
20520    #[test]
20521    fn xreadgroup_hands_out_and_xack_takes_back() {
20522        let mut f = Fixture::new();
20523        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20524        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20525        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20526
20527        let first = f.run(&[
20528            b"XREADGROUP",
20529            b"GROUP",
20530            b"g",
20531            b"c1",
20532            b"COUNT",
20533            b"1",
20534            b"STREAMS",
20535            b"s",
20536            b">",
20537        ]);
20538        assert_eq!(
20539            first,
20540            "*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"
20541        );
20542        // A history read names its stream even with nothing to show, which is
20543        // the difference between it and a `>` read that found nothing.
20544        assert_eq!(
20545            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
20546            "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
20547        );
20548        assert_eq!(
20549            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20550            "*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"
20551        );
20552
20553        assert_eq!(
20554            f.run(&[b"XPENDING", b"s", b"g"]),
20555            "*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"
20556        );
20557        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
20558        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
20559        // Empty is four nulls and not a zero with three empty things.
20560        assert_eq!(
20561            f.run(&[b"XPENDING", b"s", b"g"]),
20562            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
20563        );
20564
20565        // A history read of an entry that has since been deleted is the id with
20566        // a null beside it, so the consumer can still acknowledge it.
20567        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20568        f.run(&[b"XDEL", b"s", b"2-1"]);
20569        assert_eq!(
20570            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20571            "*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"
20572        );
20573
20574        // The group lookup runs before the id parse, so a `+` at a stream with
20575        // no such group is told about the group and not about the id.
20576        assert!(
20577            f.run(&[
20578                b"XREADGROUP",
20579                b"GROUP",
20580                b"nope",
20581                b"c",
20582                b"STREAMS",
20583                b"s",
20584                b"+"
20585            ])
20586            .starts_with("-NOGROUP")
20587        );
20588        assert!(
20589            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
20590                .contains("meaningless in the context of XREADGROUP")
20591        );
20592        assert!(
20593            f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
20594                .contains("only supported by XREADGROUP")
20595        );
20596        assert!(
20597            f.run(&[
20598                b"XREADGROUP",
20599                b"GROUP",
20600                b"g",
20601                b"c",
20602                b"STREAMS",
20603                b"s",
20604                b"a",
20605                b"b"
20606            ])
20607            .contains("Unbalanced 'xreadgroup' list of streams")
20608        );
20609    }
20610
20611    /// `XREAD` without `BLOCK`, which answers now and takes nothing for an
20612    /// answer.
20613    #[test]
20614    fn xread_with_no_block_writes_the_null_itself() {
20615        let mut f = Fixture::new();
20616        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20617        assert_eq!(
20618            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
20619            "*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"
20620        );
20621        // Nothing new is a null array and not an empty one, and a stream with
20622        // nothing new is left out rather than sent with an empty list.
20623        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
20624        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
20625        f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
20626        assert_eq!(
20627            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
20628            "*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"
20629        );
20630        // `$` is the last id, so nothing that is already there comes back.
20631        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
20632        // And `+` is the last entry, whatever COUNT says.
20633        assert_eq!(
20634            f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
20635            "*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"
20636        );
20637        // A count of zero means unlimited here, which is the opposite of what it
20638        // means to XRANGE.
20639        assert_eq!(
20640            f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
20641            "*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"
20642        );
20643        // Milliseconds as a whole number, where BLPOP takes seconds as a float.
20644        assert!(
20645            f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
20646                .contains("not an integer")
20647        );
20648        assert!(
20649            f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
20650                .contains("timeout is negative")
20651        );
20652        assert!(
20653            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
20654                .contains("Unbalanced 'xread' list of streams")
20655        );
20656    }
20657
20658    /// A blocked reader, and the two ways it stops being blocked.
20659    #[test]
20660    fn a_blocked_xread_wakes_on_the_next_entry() {
20661        let mut f = Fixture::new();
20662        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20663        let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20664        assert_eq!(flow, Flow::Block);
20665        assert!(reply.is_empty());
20666
20667        // Everybody parked on the stream gets the entry, because a read takes
20668        // nothing away. That is the difference between this and BLPOP. Two
20669        // clients rather than one twice, since a client that is waiting is not
20670        // reading and cannot block again.
20671        f.session = Session::new(8);
20672        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20673        assert_eq!(flow, Flow::Block);
20674        assert_eq!(f.server.parked(), 2);
20675
20676        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20677        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";
20678        for client in [7, 8] {
20679            let mut out = Out::new(Proto::Resp2);
20680            assert!(f.server.serve_waiter(client, 0, &mut out));
20681            assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
20682        }
20683
20684        // And a deadline that runs out is a null array, the same as a plain
20685        // XREAD that found nothing.
20686        f.server.forget_waiters(7);
20687        f.server.forget_waiters(8);
20688        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
20689        assert_eq!(flow, Flow::Block);
20690        let mut out = Out::new(Proto::Resp2);
20691        assert!(!f.server.serve_waiter(8, 0, &mut out));
20692        assert!(out.as_slice().is_empty());
20693        assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
20694        assert_eq!(
20695            core::str::from_utf8(out.as_slice()).expect("ascii"),
20696            "*-1\r\n"
20697        );
20698    }
20699
20700    /// A blocked group reader whose group is destroyed under it.
20701    #[test]
20702    fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
20703        let mut f = Fixture::new();
20704        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20705        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
20706        let (flow, _) = f.flow(&[
20707            b"XREADGROUP",
20708            b"GROUP",
20709            b"g",
20710            b"c",
20711            b"BLOCK",
20712            b"0",
20713            b"STREAMS",
20714            b"s",
20715            b">",
20716        ]);
20717        assert_eq!(flow, Flow::Block);
20718
20719        f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
20720        let mut out = Out::new(Proto::Resp2);
20721        assert!(f.server.serve_waiter(7, 0, &mut out));
20722        // The ordinary sentence and not a special one about having been parked,
20723        // which is what a running 8.10 sends.
20724        assert_eq!(
20725            core::str::from_utf8(out.as_slice()).expect("ascii"),
20726            "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
20727        );
20728    }
20729
20730    /// `XCLAIM`, whose argument shape is the odd one in the group.
20731    #[test]
20732    fn xclaim_reads_ids_until_one_will_not_parse() {
20733        let mut f = Fixture::new();
20734        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20735        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20736        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20737        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20738
20739        // Everything after the first argument that is not an id is an option, so
20740        // a `-` is an unrecognised option and not a bad id.
20741        assert!(
20742            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
20743                .contains("Unrecognized XCLAIM option '-'")
20744        );
20745        assert_eq!(
20746            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
20747            "*1\r\n$3\r\n1-1\r\n"
20748        );
20749        // An id that is pending but whose entry has gone is an empty answer, and
20750        // it leaves the pending list on the way past.
20751        f.run(&[b"XDEL", b"s", b"2-1"]);
20752        assert_eq!(
20753            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
20754            "*0\r\n"
20755        );
20756        assert!(
20757            f.run(&[b"XPENDING", b"s", b"g"])
20758                .starts_with("*4\r\n:1\r\n")
20759        );
20760        assert!(
20761            f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
20762                .starts_with("-NOGROUP")
20763        );
20764        assert!(
20765            f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
20766                .contains("Invalid min-idle-time argument for XCLAIM")
20767        );
20768    }
20769
20770    /// `XAUTOCLAIM`, and the third value nobody expects.
20771    #[test]
20772    fn xautoclaim_reports_what_it_dropped() {
20773        let mut f = Fixture::new();
20774        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20775        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20776        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20777        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20778        f.run(&[b"XDEL", b"s", b"1-1"]);
20779
20780        // The cursor, what was claimed, and what was dropped for no longer being
20781        // in the stream. The third one is what makes a sweep converge.
20782        assert_eq!(
20783            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
20784            "*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"
20785        );
20786        assert!(
20787            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
20788                .contains("COUNT must be > 0")
20789        );
20790        assert!(
20791            f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
20792                .starts_with("-NOGROUP")
20793        );
20794    }
20795
20796    /// `XDELEX`, which is `XDEL` with a say in what the groups keep.
20797    #[test]
20798    fn xdelex_answers_one_integer_an_id() {
20799        let mut f = Fixture::new();
20800        for i in 1..=4 {
20801            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20802        }
20803        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20804        f.run(&[
20805            b"XREADGROUP",
20806            b"GROUP",
20807            b"g",
20808            b"c",
20809            b"COUNT",
20810            b"2",
20811            b"STREAMS",
20812            b"s",
20813            b">",
20814        ]);
20815
20816        // One means gone and minus one means it was not there to start with.
20817        assert_eq!(
20818            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
20819            "*2\r\n:1\r\n:-1\r\n"
20820        );
20821        // `KEEPREF` leaves the pending entry behind, so the group still counts
20822        // the one it was handed even though the entry has gone.
20823        assert!(
20824            f.run(&[b"XPENDING", b"s", b"g"])
20825                .starts_with("*4\r\n:2\r\n")
20826        );
20827        // `DELREF` takes it out of every pending list on the way past.
20828        assert_eq!(
20829            f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
20830            "*1\r\n:1\r\n"
20831        );
20832        // `1-1` is still in the list, because the delete before it said KEEPREF.
20833        assert_eq!(
20834            f.run(&[b"XPENDING", b"s", b"g"]),
20835            "*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"
20836        );
20837
20838        // Two means somebody still wants it, and the question is wider than the
20839        // name: the group's bookmark is at `2-1`, so `4-1` is above it and is
20840        // refused even though no consumer has ever been handed it.
20841        assert_eq!(
20842            f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
20843            "*2\r\n:2\r\n:2\r\n"
20844        );
20845
20846        // A key that is not there answers minus ones without reading the IDs.
20847        assert_eq!(
20848            f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
20849            "*2\r\n:-1\r\n:-1\r\n"
20850        );
20851        // A key that is there validates every ID before deleting any of them.
20852        assert!(
20853            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
20854                .starts_with("-ERR Invalid stream ID")
20855        );
20856        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20857
20858        assert!(
20859            f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
20860                .contains("Number of IDs must be a positive integer")
20861        );
20862        assert!(
20863            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
20864                .contains("The `numids` parameter must match the number of arguments")
20865        );
20866        // The condition is one word, so a second one is a syntax error, and so
20867        // is one ID more than the count promised.
20868        assert!(
20869            f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
20870                .starts_with("-ERR syntax error")
20871        );
20872        assert!(
20873            f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
20874                .starts_with("-ERR syntax error")
20875        );
20876        // The key is looked up first, so the wrong type beats the syntax.
20877        f.run(&[b"SET", b"str", b"v"]);
20878        assert!(
20879            f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
20880                .starts_with("-WRONGTYPE")
20881        );
20882    }
20883
20884    /// `XACKDEL`, whose reply is about the pending list and not about the log.
20885    #[test]
20886    fn xackdel_reports_what_the_group_was_holding() {
20887        let mut f = Fixture::new();
20888        for i in 1..=3 {
20889            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20890        }
20891        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20892        f.run(&[
20893            b"XREADGROUP",
20894            b"GROUP",
20895            b"g",
20896            b"c",
20897            b"COUNT",
20898            b"1",
20899            b"STREAMS",
20900            b"s",
20901            b">",
20902        ]);
20903
20904        // Minus one is not about the stream: `2-1` is sitting there unread and
20905        // still answers minus one, because the group was not holding it. It also
20906        // stays, since only an ID that was acknowledged is deleted.
20907        assert_eq!(
20908            f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
20909            "*2\r\n:1\r\n:-1\r\n"
20910        );
20911        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20912
20913        // A missing group is minus one an ID and not a NOGROUP.
20914        assert_eq!(
20915            f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
20916            "*1\r\n:-1\r\n"
20917        );
20918        assert_eq!(
20919            f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
20920            "*1\r\n:-1\r\n"
20921        );
20922
20923        // The acknowledgement happens whatever the condition says, so an ACKED
20924        // that answers two has still emptied the pending list.
20925        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
20926        f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
20927        assert_eq!(
20928            f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
20929            "*1\r\n:2\r\n"
20930        );
20931        assert_eq!(
20932            f.run(&[b"XPENDING", b"s", b"g"]),
20933            "*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"
20934        );
20935        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20936    }
20937
20938    /// `XNACK`, which hands an entry back to nobody.
20939    #[test]
20940    fn xnack_releases_an_entry_for_the_next_claim() {
20941        let mut f = Fixture::new();
20942        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20943        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20944        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20945        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20946        // Twice, so the delivery count is two and the words have something to
20947        // do with it.
20948        f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
20949
20950        assert_eq!(
20951            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
20952            ":1\r\n"
20953        );
20954        // No owner, no idle time, and the count left where it was. A released
20955        // entry reads as idle for longer than any min-idle-time, which is what
20956        // puts it at the front of the next claim.
20957        assert_eq!(
20958            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
20959            "*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"
20960        );
20961        // The consumer no longer holds it, so a filtered XPENDING skips it.
20962        assert_eq!(
20963            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
20964            "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
20965        );
20966        // The bookmark did not move, so a `>` read will not hand it out again.
20967        assert_eq!(
20968            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
20969            "*-1\r\n"
20970        );
20971        // A claim at any min-idle-time takes it.
20972        assert_eq!(
20973            f.run(&[
20974                b"XAUTOCLAIM",
20975                b"s",
20976                b"g",
20977                b"c2",
20978                b"99999999",
20979                b"-",
20980                b"JUSTID"
20981            ]),
20982            "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
20983        );
20984
20985        // `SILENT` takes one off the count rather than putting it back to zero,
20986        // which only shows on an entry that has been handed out more than once.
20987        // It was delivered and then claimed, so it is on two and goes to one.
20988        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20989        assert!(
20990            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20991                .contains(":-1\r\n:1\r\n")
20992        );
20993        // And it stops at zero rather than wrapping.
20994        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20995        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20996        assert!(
20997            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20998                .contains(":-1\r\n:0\r\n")
20999        );
21000        // `FATAL` puts it at the ceiling, and `RETRYCOUNT` wins over the word.
21001        f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
21002        assert!(
21003            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21004                .contains(":9223372036854775807\r\n")
21005        );
21006        f.run(&[
21007            b"XNACK",
21008            b"s",
21009            b"g",
21010            b"FATAL",
21011            b"IDS",
21012            b"1",
21013            b"1-1",
21014            b"RETRYCOUNT",
21015            b"3",
21016        ]);
21017        assert!(
21018            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21019                .contains(":-1\r\n:3\r\n")
21020        );
21021
21022        // Releasing something the group is not holding is zero, and `FORCE`
21023        // makes the pending entry rather than answering zero. A forced entry
21024        // starts at zero, since there was no earlier count to keep.
21025        f.run(&[b"XACK", b"s", b"g", b"2-1"]);
21026        assert_eq!(
21027            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
21028            ":0\r\n"
21029        );
21030        assert_eq!(
21031            f.run(&[
21032                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
21033            ]),
21034            ":1\r\n"
21035        );
21036        assert!(
21037            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21038                .contains(":-1\r\n:0\r\n")
21039        );
21040        // `FORCE` on an ID the stream does not have is still zero.
21041        assert_eq!(
21042            f.run(&[
21043                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
21044            ]),
21045            ":0\r\n"
21046        );
21047
21048        // The group is looked up before the mode word, and it raises rather
21049        // than answering per ID the way the two delete commands do.
21050        assert_eq!(
21051            f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
21052            "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
21053        );
21054        assert!(
21055            f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
21056                .starts_with("-ERR")
21057        );
21058        // Its own sentences, which are not the ones XDELEX uses.
21059        assert!(
21060            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
21061                .contains("numids must be a positive integer")
21062        );
21063        assert!(
21064            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
21065                .contains("number of IDs doesn't match numids")
21066        );
21067        // Everything past the counted IDs is an option, so one too many is an
21068        // option nobody recognises and not a count that does not add up.
21069        assert!(
21070            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
21071                .contains("Unrecognized XNACK option '2-1'")
21072        );
21073    }
21074
21075    /// `XINFO`, which is where the shape of the storage shows through.
21076    #[test]
21077    fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
21078        let mut f = Fixture::new();
21079        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21080        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21081        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21082        f.run(&[
21083            b"XREADGROUP",
21084            b"GROUP",
21085            b"g",
21086            b"c1",
21087            b"COUNT",
21088            b"1",
21089            b"STREAMS",
21090            b"s",
21091            b">",
21092        ]);
21093
21094        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21095        // Ten pairs, since the six idempotency fields have nothing behind them
21096        // here and a zero would claim they had. That is D-27.
21097        assert!(info.starts_with("*20\r\n"), "{info}");
21098        assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
21099        assert!(
21100            info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
21101            "{info}"
21102        );
21103        assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
21104        assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
21105
21106        let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
21107        assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
21108        assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
21109        assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
21110        assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
21111
21112        // A consumer that has never been given anything reports minus one for
21113        // inactive rather than the moment it turned up, which is what tells a
21114        // worker that is stuck from one that has nothing to do.
21115        f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
21116        let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
21117        assert!(consumers.starts_with("*2\r\n"), "{consumers}");
21118        assert!(
21119            consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
21120            "{consumers}"
21121        );
21122        // And in name order, which the storage does not hold them in.
21123        let c1 = consumers.find("c1").unwrap();
21124        let c2 = consumers.find("c2").unwrap();
21125        assert!(c1 < c2, "{consumers}");
21126
21127        let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
21128        assert!(full.starts_with("*18\r\n"), "{full}");
21129        assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
21130        assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
21131
21132        assert!(
21133            f.run(&[b"XINFO", b"STREAM", b"missing"])
21134                .contains("no such key")
21135        );
21136        assert!(
21137            f.run(&[b"XINFO", b"GROUPS", b"missing"])
21138                .contains("no such key")
21139        );
21140        assert!(
21141            f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
21142                .starts_with("-NOGROUP")
21143        );
21144        assert!(
21145            f.run(&[b"XINFO", b"NOSUCH", b"s"])
21146                .contains("Try XINFO HELP")
21147        );
21148        assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
21149        assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
21150    }
21151
21152    /// `XPENDING`'s long form, which reads its arguments by counting them.
21153    #[test]
21154    fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
21155        let mut f = Fixture::new();
21156        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21157        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21158        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21159
21160        let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
21161        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");
21162        assert_eq!(
21163            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21164            "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
21165        );
21166        // A consumer nobody has heard of holds nothing rather than erroring.
21167        assert_eq!(
21168            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
21169            "*0\r\n"
21170        );
21171        assert_eq!(
21172            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
21173            list
21174        );
21175        // IDLE is only read at position three.
21176        assert!(
21177            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
21178                .contains("syntax error")
21179        );
21180        assert!(
21181            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
21182                .contains("syntax error")
21183        );
21184        assert_eq!(
21185            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
21186            "*0\r\n"
21187        );
21188        assert!(
21189            f.run(&[b"XPENDING", b"missing", b"g"])
21190                .starts_with("-NOGROUP")
21191        );
21192    }
21193
21194    /// `XSETID`, which is three counters and two refusals.
21195    #[test]
21196    fn xsetid_will_not_go_below_what_is_there() {
21197        let mut f = Fixture::new();
21198        f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
21199        assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
21200        assert_eq!(
21201            f.run(&[
21202                b"XSETID",
21203                b"s",
21204                b"10-1",
21205                b"ENTRIESADDED",
21206                b"7",
21207                b"MAXDELETEDID",
21208                b"9-1"
21209            ]),
21210            "+OK\r\n"
21211        );
21212        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21213        assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
21214        assert!(
21215            info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
21216            "{info}"
21217        );
21218
21219        assert!(
21220            f.run(&[b"XSETID", b"s", b"1-1"])
21221                .contains("smaller than the target stream top item")
21222        );
21223        assert!(
21224            f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
21225                .contains("entries_added must be positive")
21226        );
21227        assert!(
21228            f.run(&[b"XSETID", b"missing", b"1-1"])
21229                .contains("no such key")
21230        );
21231    }
21232
21233    /// RESP3, where the two reads answer a map and the entries stay an array.
21234    #[test]
21235    fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
21236        let mut f = Fixture::new();
21237        f.run(&[b"HELLO", b"3"]);
21238        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21239        // A map header and then the key and the entries side by side, with no
21240        // two element array wrapping the pair.
21241        assert_eq!(
21242            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21243            "%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"
21244        );
21245        // The fields are still one flat array and not a map, which is Redis's
21246        // shape and is what every consumer written before RESP3 expects.
21247        assert_eq!(
21248            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
21249            "*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"
21250        );
21251        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
21252    }
21253
21254    /// A store to migrate values into, so a test can watch the inversion.
21255    ///
21256    /// A vector rather than a file for the same reason the tier's own tests use
21257    /// one: the file work has not attached a real store yet, and what this is
21258    /// checking is the policy above the store rather than the store.
21259    struct Mem {
21260        blobs: Vec<Vec<u8>>,
21261    }
21262
21263    impl yo_kv::cold::Blocks for Mem {
21264        fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
21265            self.blobs.push(bytes.to_vec());
21266            Ok(yo_common::Addr::new(
21267                yo_common::Space::Log,
21268                (self.blobs.len() - 1) as u64,
21269            ))
21270        }
21271
21272        fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
21273            self.blobs
21274                .get(at.offset() as usize)
21275                .map(Vec::as_slice)
21276                .ok_or_else(|| {
21277                    yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
21278                })
21279        }
21280
21281        fn bytes(&self) -> u64 {
21282            self.blobs.iter().map(|b| b.len() as u64).sum()
21283        }
21284    }
21285
21286    /// A server holding several segments of strings, with somewhere to put them.
21287    ///
21288    /// Answers the fixture and what it was holding when it stopped filling.
21289    /// The three tests that call this are the ones Miri is not run over.
21290    ///
21291    /// What they are about is the regime a database is in once the arena has
21292    /// several segments, and a segment is two megabytes, so there is no smaller
21293    /// version of the question: twenty four thousand keys is already the least
21294    /// that gets there. Interpreted, each of them sat for over forty minutes
21295    /// and was still going. The arena's own segment handling is interpreted in
21296    /// full in its own crate, and the policy these three check is ordinary
21297    /// bookkeeping with no unsafe block anywhere in it.
21298    fn filled(attach: bool) -> (Fixture, usize) {
21299        let mut f = Fixture::new();
21300        if attach {
21301            f.server
21302                .striped(0)
21303                .hold_stripe(0)
21304                .attach(Box::new(Mem { blobs: Vec::new() }));
21305        }
21306        let val = vec![b'v'; 256];
21307        for i in 0..24000u32 {
21308            let k = format!("key:{i:08}");
21309            f.run(&[b"SET", k.as_bytes(), &val]);
21310        }
21311        let full = f.server.memory_bytes();
21312        assert!(full > 3 * 1024 * 1024, "the arena is several segments");
21313        (f, full)
21314    }
21315
21316    /// Write until the server is under `limit` or the writes run out.
21317    ///
21318    /// The same shape the eviction test uses. A memory limit is enforced in
21319    /// front of a command, so nothing happens until something is written, and
21320    /// the budget means one command does not do the whole job.
21321    fn press(f: &mut Fixture, limit: usize) {
21322        let val = vec![b'v'; 256];
21323        for i in 0..3000u32 {
21324            let k = format!("new:{i:08}");
21325            assert_eq!(
21326                f.run(&[b"SET", k.as_bytes(), &val]),
21327                "+OK\r\n",
21328                "write {i} was refused"
21329            );
21330            f.server.refresh_memory();
21331            if f.server.memory_bytes() <= limit {
21332                return;
21333            }
21334        }
21335        panic!(
21336            "it never got under: {} against {limit}",
21337            f.server.memory_bytes()
21338        );
21339    }
21340
21341    #[test]
21342    fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
21343        let mut f = Fixture::new();
21344        assert_eq!(
21345            f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21346            "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
21347            "no limit is the default"
21348        );
21349        // The same memory value parser `maxmemory` uses, and the same trap in
21350        // it, plus the one spelling that means no limit at all.
21351        for (typed, bytes) in [
21352            (&b"0"[..], "0"),
21353            (b"1024", "1024"),
21354            (b"1k", "1000"),
21355            (b"1gb", "1073741824"),
21356            (b"-1", "-1"),
21357        ] {
21358            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
21359            assert_eq!(
21360                f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21361                format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
21362                "set {}",
21363                String::from_utf8_lossy(typed)
21364            );
21365        }
21366        for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
21367            assert_eq!(
21368                f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
21369                "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
21370                "refused {}",
21371                String::from_utf8_lossy(bad)
21372            );
21373        }
21374        // Nothing is attached, so the answer to a memory limit is still Redis's.
21375        let info = f.run(&[b"INFO", b"memory"]);
21376        assert!(info.contains("maxstore:-1"), "{info}");
21377        assert!(info.contains("yo_memory_regime:evict"), "{info}");
21378        assert!(info.contains("yo_store_bytes:0"), "{info}");
21379    }
21380
21381    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21382    #[test]
21383    fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
21384        // The inversion. The same pressure that makes a Redis server throw keys
21385        // away makes this one move values to the file, and afterwards every key
21386        // is still there and still answers with what was stored in it.
21387        let (mut f, full) = filled(true);
21388        let keys = f.run(&[b"DBSIZE"]);
21389        assert!(
21390            f.run(&[b"INFO", b"memory"])
21391                .contains("yo_memory_regime:migrate"),
21392            "a database with somewhere to put values migrates"
21393        );
21394
21395        let limit = full - 2 * 1024 * 1024;
21396        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21397        f.run(&[
21398            b"CONFIG",
21399            b"SET",
21400            b"maxmemory",
21401            limit.to_string().as_bytes(),
21402        ]);
21403        press(&mut f, limit);
21404
21405        assert!(
21406            f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21407            "nothing was thrown away"
21408        );
21409        let after: usize = f.run(&[b"DBSIZE"])[1..]
21410            .trim_end()
21411            .parse()
21412            .expect("a count");
21413        let before: usize = keys[1..].trim_end().parse().expect("a count");
21414        assert!(after > before, "the keys that came in are all still here");
21415        assert!(
21416            f.server.store_bytes() > 0,
21417            "and what came out of memory went to the file"
21418        );
21419        // And the values read back, which is the part that makes it a migration
21420        // rather than a loss.
21421        let val = format!("$256\r\n{}\r\n", "v".repeat(256));
21422        assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
21423        assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
21424    }
21425
21426    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21427    #[test]
21428    fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
21429        // The documented setting for a drop in cache. A file that may hold
21430        // nothing cannot be migrated to, so eviction is all that is left, and
21431        // the server behaves exactly as it did before any of this existed.
21432        let (mut f, full) = filled(true);
21433        f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
21434        assert!(
21435            f.run(&[b"INFO", b"memory"])
21436                .contains("yo_memory_regime:evict"),
21437            "nothing may go to the file"
21438        );
21439
21440        let limit = full - 2 * 1024 * 1024;
21441        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21442        f.run(&[
21443            b"CONFIG",
21444            b"SET",
21445            b"maxmemory",
21446            limit.to_string().as_bytes(),
21447        ]);
21448        press(&mut f, limit);
21449
21450        assert!(
21451            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21452            "keys were thrown away, which is what was asked for"
21453        );
21454        assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
21455    }
21456
21457    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21458    #[test]
21459    fn a_full_file_goes_back_to_evicting() {
21460        // A storage limit reached is a storage limit, and eviction is the right
21461        // answer to one. The budget here is a few kilobytes, so the first round
21462        // of migration fills it and everything after that is evicted.
21463        let (mut f, full) = filled(true);
21464        f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
21465        let limit = full - 2 * 1024 * 1024;
21466        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21467        f.run(&[
21468            b"CONFIG",
21469            b"SET",
21470            b"maxmemory",
21471            limit.to_string().as_bytes(),
21472        ]);
21473        press(&mut f, limit);
21474
21475        assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
21476        assert!(
21477            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21478            "and then it started evicting"
21479        );
21480        assert!(
21481            f.run(&[b"INFO", b"memory"])
21482                .contains("yo_memory_regime:evict"),
21483            "and it says so"
21484        );
21485    }
21486    // ------------------------------------------------------------- stripes
21487
21488    /// Every string command, run twice: once on a database that is one keyspace
21489    /// and once on a database that is eight, with the same commands in the same
21490    /// order and the replies compared byte for byte.
21491    ///
21492    /// This is the whole claim the striping rests on. A key belongs to one
21493    /// stripe and to no other, so the answer to a command cannot depend on how
21494    /// many stripes there are, and the way to check that is to ask the same
21495    /// question of two servers that differ in nothing else.
21496    ///
21497    /// The keys are chosen to land on different stripes rather than to look
21498    /// tidy. `MSET a 1 b 2 c 3` over eight stripes is only a test of anything if
21499    /// those three keys are not all on the same one, and at eight stripes three
21500    /// keys land together about one time in fifty.
21501    #[test]
21502    fn the_string_group_answers_the_same_however_many_stripes_there_are() {
21503        let script: &[&[&[u8]]] = &[
21504            // The single key commands, which are the ones that get handed one
21505            // stripe at the dispatch site.
21506            &[b"SET", b"k1", b"v1"],
21507            &[b"SET", b"k2", b"v2"],
21508            &[b"GET", b"k1"],
21509            &[b"GET", b"nothing"],
21510            &[b"GETSET", b"k1", b"v1b"],
21511            &[b"SETNX", b"k1", b"no"],
21512            &[b"SETNX", b"k3", b"yes"],
21513            &[b"APPEND", b"k3", b"!"],
21514            &[b"STRLEN", b"k3"],
21515            &[b"SETRANGE", b"k3", b"1", b"XY"],
21516            &[b"GETRANGE", b"k3", b"0", b"-1"],
21517            &[b"INCR", b"n1"],
21518            &[b"INCRBY", b"n1", b"41"],
21519            &[b"DECRBY", b"n1", b"2"],
21520            &[b"INCRBYFLOAT", b"f1", b"1.5"],
21521            &[b"SETEX", b"e1", b"100", b"v"],
21522            &[b"PSETEX", b"e2", b"100000", b"v"],
21523            &[b"GETEX", b"e1", b"PERSIST"],
21524            &[b"GETDEL", b"k2"],
21525            &[b"GET", b"k2"],
21526            &[b"DIGEST", b"k1"],
21527            &[b"DELEX", b"k3"],
21528            // The five that name more than one key, which are the ones that
21529            // cannot be handed one stripe at all.
21530            &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
21531            &[b"MGET", b"a", b"b", b"c", b"missing"],
21532            &[b"MSETNX", b"d", b"4", b"e", b"5"],
21533            &[b"MSETNX", b"e", b"6", b"f", b"7"],
21534            &[b"MGET", b"d", b"e", b"f"],
21535            &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
21536            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
21537            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
21538            &[b"MGET", b"g", b"h"],
21539            &[b"SET", b"s1", b"ohmytext"],
21540            &[b"SET", b"s2", b"mynewtext"],
21541            &[b"LCS", b"s1", b"s2"],
21542            &[b"LCS", b"s1", b"s2", b"LEN"],
21543            &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
21544            &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
21545            &[b"LCS", b"s1", b"gone"],
21546            // And the errors, which have to be the same errors.
21547            &[b"MSET", b"odd"],
21548            &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
21549            &[b"MGET"],
21550        ];
21551
21552        let mut one = Fixture::new();
21553        let mut many = Fixture::striped(8);
21554        for parts in script {
21555            let a = one.run(parts);
21556            let b = many.run(parts);
21557            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21558        }
21559    }
21560
21561    /// The keys of an `MSET` really do end up on different stripes.
21562    ///
21563    /// Without this the test above could pass on a server whose stripe number
21564    /// happened to be a constant, which is a striped database in name only.
21565    #[test]
21566    fn a_striped_database_spreads_the_keys_it_is_given() {
21567        let mut f = Fixture::striped(8);
21568        for i in 0..256 {
21569            let key = format!("key:{i}");
21570            f.run(&[b"SET", key.as_bytes(), b"v"]);
21571        }
21572        assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
21573    }
21574
21575    /// A wrong type stops an `MGET` no more than it does on one stripe: the key
21576    /// that is not a string comes back nil and the rest of the reply is intact.
21577    #[test]
21578    fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
21579        let mut one = Fixture::new();
21580        let mut many = Fixture::striped(8);
21581        for f in [&mut one, &mut many] {
21582            f.run(&[b"SET", b"str", b"v"]);
21583            // Planted rather than pushed. `RPUSH` belongs to the list group,
21584            // which has not been taught about stripes yet and would refuse the
21585            // wide server. What is under test is what `MGET` does when it walks
21586            // onto a key that is not a string, and that does not care how the
21587            // key got there.
21588            f.server
21589                .striped(0)
21590                .hold(b"list")
21591                .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
21592                .expect("a new list");
21593        }
21594        assert_eq!(
21595            one.run(&[b"MGET", b"str", b"list", b"gone"]),
21596            many.run(&[b"MGET", b"str", b"list", b"gone"])
21597        );
21598    }
21599
21600    /// The same claim for the keyspace group, and the same way of checking it.
21601    ///
21602    /// `SORT` is not in the script because it is the one command in that file
21603    /// that has not been taught about stripes, and `SCAN`, `KEYS` and
21604    /// `RANDOMKEY` are not in it either, because those three do not promise an
21605    /// order and comparing two replies byte for byte would be asserting one.
21606    /// They get tests of their own below.
21607    #[test]
21608    fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
21609        let script: &[&[&[u8]]] = &[
21610            &[b"SET", b"k1", b"v1"],
21611            &[b"SET", b"k2", b"v2"],
21612            &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
21613            &[b"TYPE", b"k1"],
21614            &[b"TYPE", b"gone"],
21615            &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
21616            &[b"EXPIRE", b"k1", b"100"],
21617            &[b"TTL", b"k1"],
21618            &[b"EXPIRE", b"k1", b"200", b"NX"],
21619            &[b"PERSIST", b"k1"],
21620            &[b"TTL", b"k1"],
21621            &[b"PEXPIREAT", b"k2", b"1900000000000"],
21622            &[b"EXPIRETIME", b"k2"],
21623            &[b"PEXPIRETIME", b"k2"],
21624            &[b"PERSIST", b"k2"],
21625            &[b"OBJECT", b"ENCODING", b"k1"],
21626            &[b"OBJECT", b"REFCOUNT", b"k1"],
21627            &[b"OBJECT", b"IDLETIME", b"k1"],
21628            &[b"OBJECT", b"FREQ", b"k1"],
21629            &[b"OBJECT", b"ENCODING", b"gone"],
21630            &[b"OBJECT", b"HELP"],
21631            &[b"RENAME", b"k1", b"k9"],
21632            &[b"GET", b"k9"],
21633            &[b"RENAME", b"gone", b"x"],
21634            &[b"RENAMENX", b"k9", b"k2"],
21635            &[b"RENAMENX", b"k9", b"k8"],
21636            &[b"GET", b"k8"],
21637            &[b"COPY", b"k8", b"c1"],
21638            &[b"COPY", b"k8", b"c1"],
21639            &[b"COPY", b"k8", b"c1", b"REPLACE"],
21640            &[b"COPY", b"k8", b"k8"],
21641            &[b"COPY", b"gone", b"c2"],
21642            &[b"COPY", b"k8", b"k8", b"DB", b"1"],
21643            &[b"COPY", b"k8", b"c9", b"DB", b"9"],
21644            &[b"MOVE", b"c1", b"1"],
21645            &[b"MOVE", b"c1", b"1"],
21646            &[b"MOVE", b"k8", b"0"],
21647            &[b"DEL", b"k2", b"gone"],
21648            &[b"UNLINK", b"k8", b"k8"],
21649            &[b"DBSIZE"],
21650        ];
21651
21652        let mut one = Fixture::new();
21653        let mut many = Fixture::striped(8);
21654        for parts in script {
21655            let a = one.run(parts);
21656            let b = many.run(parts);
21657            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21658        }
21659
21660        // `RESTORE` needs bytes a client would have got from a `DUMP`, so the
21661        // payload is taken from the store rather than parsed back out of a
21662        // reply that is not text. Both servers dump the same key and the bytes
21663        // are the same bytes, which is the first half of what is being checked
21664        // here.
21665        for f in [&mut one, &mut many] {
21666            f.run(&[b"SET", b"d1", b"payload"]);
21667            let payload = f
21668                .server
21669                .striped(0)
21670                .hold(b"d1")
21671                .dump(b"d1")
21672                .expect("a key that is there");
21673            assert!(
21674                f.run(&[b"DUMP", b"d1"])
21675                    .starts_with(&format!("${}", payload.len())),
21676                "a payload of the length the store gave"
21677            );
21678            assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
21679            assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
21680            assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
21681            assert_eq!(
21682                f.run(&[b"RESTORE", b"d2", b"0", &payload]),
21683                "-BUSYKEY Target key name already exists.\r\n"
21684            );
21685            assert_eq!(
21686                f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
21687                "-ERR DUMP payload version or checksum are wrong\r\n"
21688            );
21689        }
21690    }
21691
21692    /// A `SCAN` of a database of eight stripes comes back with all of it.
21693    ///
21694    /// The cursor is the thing under test. It has to carry the stripe as well
21695    /// as the place in it, so a client that stops at one stripe and comes back
21696    /// carries on in that stripe and not at the top of the database, and the
21697    /// walk has to end once rather than eight times.
21698    #[test]
21699    fn a_scan_of_a_striped_database_walks_all_of_it() {
21700        // Eight stripes and a COUNT of ten, so eighty keys is already more than
21701        // one page on every stripe and the cursor has to carry which stripe it
21702        // was on, which is the thing being checked.
21703        let n = if cfg!(miri) { 80 } else { 500 };
21704        let mut f = Fixture::striped(8);
21705        for i in 0..n {
21706            let key = format!("key:{i}");
21707            f.run(&[b"SET", key.as_bytes(), b"v"]);
21708        }
21709
21710        let mut seen = Vec::new();
21711        let mut cursor = "0".to_owned();
21712        let mut calls = 0;
21713        loop {
21714            let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
21715            let (next, keys) = scan_reply(&reply);
21716            seen.extend(keys);
21717            cursor = next;
21718            calls += 1;
21719            assert!(calls < 5_000, "a scan that will not finish");
21720            if cursor == "0" {
21721                break;
21722            }
21723        }
21724        seen.sort();
21725        assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
21726        assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
21727
21728        // And the options still work when the walk is over several stripes,
21729        // since a `MATCH` is applied to keys a stripe handed up and a `TYPE` is
21730        // applied by each stripe on the way.
21731        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
21732        let (_, keys) = scan_reply(&reply);
21733        assert_eq!(keys.len(), 10, "key:40 through key:49");
21734        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
21735        let (_, keys) = scan_reply(&reply);
21736        assert!(keys.is_empty(), "nothing here is a list");
21737    }
21738
21739    /// `RANDOMKEY` on a striped database answers a key from any of the stripes.
21740    ///
21741    /// The draw picks the stripe first, so the thing that can go wrong is that
21742    /// it always picks the same one, and two hundred draws over eight stripes
21743    /// would make that obvious.
21744    #[test]
21745    fn a_random_key_can_come_from_any_stripe() {
21746        let mut f = Fixture::striped(8);
21747        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
21748        for i in 0..200 {
21749            let key = format!("key:{i}");
21750            f.run(&[b"SET", key.as_bytes(), b"v"]);
21751        }
21752        let mut homes = std::collections::HashSet::new();
21753        for _ in 0..200 {
21754            let got = f.run(&[b"RANDOMKEY"]);
21755            let key = got.split("\r\n").nth(1).expect("a key").to_owned();
21756            assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
21757            homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
21758        }
21759        assert_eq!(homes.len(), 8, "some stripe was never drawn from");
21760    }
21761
21762    /// Two keys that are not on the same stripe, which is what `RENAME` and
21763    /// `COPY` have to cope with and what a test has to arrange rather than
21764    /// hope for.
21765    fn apart(f: &mut Fixture, src: &str) -> String {
21766        let home = f.server.striped(0).stripe_of(src.as_bytes());
21767        for i in 0..1_000 {
21768            let dst = format!("dst:{i}");
21769            if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
21770                return dst;
21771            }
21772        }
21773        panic!("eight stripes and a thousand keys all landed in one place");
21774    }
21775
21776    /// A rename whose two keys are on two stripes moves the value, the deadline
21777    /// and, for a collection, the body itself.
21778    #[test]
21779    fn a_rename_across_stripes_takes_everything_with_it() {
21780        let mut f = Fixture::striped(8);
21781        let dst = apart(&mut f, "src");
21782        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21783
21784        f.run(&[b"SET", src, b"v"]);
21785        f.run(&[b"EXPIRE", src, b"100"]);
21786        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21787        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
21788        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
21789        assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
21790
21791        // A list, because a string lives in its record and a collection lives
21792        // in a slab, and the second of those is the one that can be left
21793        // behind. Planted through the store, since the list group has not been
21794        // taught about stripes yet.
21795        f.server
21796            .striped(0)
21797            .hold(src)
21798            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21799            .expect("a new list");
21800        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21801        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
21802        assert_eq!(
21803            f.server.striped(0).hold(dst).llen(dst).expect("a list"),
21804            2,
21805            "the members are on the stripe the key moved to"
21806        );
21807
21808        // And `RENAMENX` still refuses a destination that is taken, which is
21809        // the one answer the cross stripe path has to work out for itself.
21810        f.run(&[b"SET", src, b"v"]);
21811        assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
21812        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
21813        assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
21814    }
21815
21816    /// And a copy across two stripes leaves both keys behind it.
21817    #[test]
21818    fn a_copy_across_stripes_leaves_the_source_where_it_was() {
21819        let mut f = Fixture::striped(8);
21820        let dst = apart(&mut f, "src");
21821        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21822
21823        f.run(&[b"SET", src, b"v"]);
21824        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21825        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
21826        assert_eq!(
21827            f.run(&[b"COPY", src, dst]),
21828            ":0\r\n",
21829            "the destination is taken"
21830        );
21831        f.run(&[b"SET", src, b"w"]);
21832        assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
21833        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
21834
21835        // A collection is cloned rather than moved, so both keys have a body of
21836        // their own afterwards and writing to one does not show up in the
21837        // other.
21838        f.run(&[b"DEL", src, dst]);
21839        f.server
21840            .striped(0)
21841            .hold(src)
21842            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21843            .expect("a new list");
21844        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21845        f.server
21846            .striped(0)
21847            .hold(src)
21848            .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
21849            .expect("a list that is there");
21850        assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
21851        assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
21852    }
21853
21854    /// Every bitmap command, on one stripe and on eight, replies compared byte
21855    /// for byte.
21856    ///
21857    /// `BITOP` is the one that names more than one key and it is where the work
21858    /// went. The rest are single key commands that now find their own stripe,
21859    /// and they are here because the cheapest way to be sure the routing is
21860    /// right is to ask.
21861    #[test]
21862    fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
21863        let script: &[&[&[u8]]] = &[
21864            &[b"SET", b"k1", b"foobar"],
21865            &[b"SETBIT", b"b1", b"7", b"1"],
21866            &[b"SETBIT", b"b1", b"7", b"0"],
21867            &[b"GETBIT", b"k1", b"6"],
21868            &[b"GETBIT", b"k1", b"100"],
21869            &[b"BITCOUNT", b"k1"],
21870            &[b"BITCOUNT", b"k1", b"0", b"0"],
21871            &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
21872            &[b"BITPOS", b"k1", b"1"],
21873            &[b"BITPOS", b"k1", b"0", b"2"],
21874            &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
21875            &[
21876                b"BITFIELD",
21877                b"bf",
21878                b"SET",
21879                b"u8",
21880                b"0",
21881                b"255",
21882                b"GET",
21883                b"u8",
21884                b"0",
21885            ],
21886            &[
21887                b"BITFIELD",
21888                b"bf",
21889                b"OVERFLOW",
21890                b"SAT",
21891                b"INCRBY",
21892                b"u8",
21893                b"0",
21894                b"10",
21895            ],
21896            &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
21897            // The multi key one, over sources that are not on one stripe unless
21898            // eight stripes have folded into one.
21899            &[b"SET", b"s1", b"abc"],
21900            &[b"SET", b"s2", b"abd"],
21901            &[b"SET", b"s3", b"a"],
21902            &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
21903            &[b"GET", b"d1"],
21904            &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
21905            &[b"GET", b"d2"],
21906            &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
21907            &[b"STRLEN", b"d3"],
21908            &[b"BITOP", b"NOT", b"d4", b"s1"],
21909            &[b"STRLEN", b"d4"],
21910            &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
21911            &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
21912            &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
21913            &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
21914            // A source that is not there reads as empty, and a result with
21915            // nothing in it deletes the destination rather than writing one.
21916            &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
21917            &[b"EXISTS", b"d1"],
21918            &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
21919            &[b"GET", b"d9"],
21920            // And the errors, which have to be the same errors. The key that
21921            // is not a string is planted below rather than pushed here, since
21922            // the list group has not been taught about stripes yet.
21923            &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
21924            &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
21925            &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
21926            &[b"BITOP", b"DIFF", b"d1", b"s1"],
21927            &[b"BITOP", b"NOPE", b"d1", b"s1"],
21928            &[b"BITCOUNT", b"list"],
21929            &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
21930        ];
21931
21932        let mut one = Fixture::new();
21933        let mut many = Fixture::striped(8);
21934        for f in [&mut one, &mut many] {
21935            plant_list(f, b"list");
21936        }
21937        for parts in script {
21938            let a = one.run(parts);
21939            let b = many.run(parts);
21940            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21941        }
21942    }
21943
21944    /// A list under `key`, put there through the store.
21945    ///
21946    /// What a test does when it wants a key of the wrong type on a striped
21947    /// server, because the command that would make one is in a group that has
21948    /// not been taught about stripes yet.
21949    fn plant_list(f: &mut Fixture, key: &[u8]) {
21950        f.server
21951            .striped(0)
21952            .hold(key)
21953            .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
21954            .expect("a new list");
21955    }
21956
21957    /// A `BITOP` whose keys are on two stripes reads both of them.
21958    ///
21959    /// The test above spreads its keys by hashing and would still pass if one
21960    /// stripe were doing all the work, since the answers would be the same. This
21961    /// one puts the destination and the two sources where they are known not to
21962    /// share a stripe.
21963    #[test]
21964    fn a_bitop_across_stripes_reads_every_source() {
21965        let mut f = Fixture::striped(8);
21966        let other = apart(&mut f, "src");
21967        let (src, far) = (b"src".as_slice(), other.as_bytes());
21968        assert_ne!(
21969            f.server.striped(0).stripe_of(src),
21970            f.server.striped(0).stripe_of(far),
21971            "the two keys are the point of the test"
21972        );
21973
21974        f.run(&[b"SET", src, b"abc"]);
21975        f.run(&[b"SET", far, b"abd"]);
21976        assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
21977        assert_eq!(
21978            f.run(&[b"GET", far]),
21979            "$3\r\nab`\r\n",
21980            "a destination that is also a source"
21981        );
21982        f.run(&[b"SET", far, b"abd"]);
21983        assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
21984        assert_eq!(
21985            f.run(&[b"GET", src]),
21986            "$3\r\n\0\0\x07\r\n",
21987            "and the other way round"
21988        );
21989
21990        // A result of nothing deletes a destination on whatever stripe it is
21991        // on, and a source of the wrong type is refused before anything is
21992        // written.
21993        f.run(&[b"SET", src, b"abc"]);
21994        f.run(&[b"DEL", far]);
21995        assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
21996        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
21997        f.run(&[b"SET", src, b"abc"]);
21998        f.run(&[b"DEL", far]);
21999        plant_list(&mut f, far);
22000        assert_eq!(
22001            f.run(&[b"BITOP", b"OR", b"out", src, far]),
22002            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22003        );
22004        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
22005    }
22006
22007    /// Every HyperLogLog command, on one stripe and on eight.
22008    ///
22009    /// Not under Miri, for the reason on
22010    /// `the_debug_forms_answer_four_different_shapes`, and twice over here
22011    /// because the script is run against both shapes of server.
22012    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
22013    #[test]
22014    fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
22015        let script: &[&[&[u8]]] = &[
22016            &[b"PFADD", b"h1", b"a", b"b", b"c"],
22017            &[b"PFADD", b"h1", b"a"],
22018            &[b"PFADD", b"h2"],
22019            &[b"PFADD", b"h2", b"c", b"d", b"e"],
22020            &[b"PFCOUNT", b"h1"],
22021            &[b"PFCOUNT", b"h2"],
22022            &[b"PFCOUNT", b"missing"],
22023            // The two that name more than one key.
22024            &[b"PFCOUNT", b"h1", b"h2"],
22025            &[b"PFCOUNT", b"h1", b"missing"],
22026            &[b"PFMERGE", b"m", b"h1", b"h2"],
22027            &[b"PFCOUNT", b"m"],
22028            &[b"STRLEN", b"m"],
22029            &[b"PFMERGE", b"m"],
22030            &[b"PFCOUNT", b"m"],
22031            &[b"PFMERGE", b"m2", b"missing"],
22032            &[b"PFCOUNT", b"m2"],
22033            // The debugging ones, which are single key and change what they
22034            // look at.
22035            &[b"PFDEBUG", b"ENCODING", b"h1"],
22036            &[b"PFDEBUG", b"DECODE", b"h1"],
22037            &[b"PFDEBUG", b"TODENSE", b"h1"],
22038            &[b"PFDEBUG", b"ENCODING", b"h1"],
22039            &[b"PFDEBUG", b"TODENSE", b"h1"],
22040            &[b"PFCOUNT", b"h1", b"h2"],
22041            &[b"PFSELFTEST"],
22042            // And the errors.
22043            &[b"SET", b"plain", b"not a sketch at all"],
22044            &[b"PFADD", b"plain", b"a"],
22045            &[b"PFCOUNT", b"plain"],
22046            &[b"PFCOUNT", b"h1", b"plain"],
22047            &[b"PFMERGE", b"plain", b"h1"],
22048            &[b"PFMERGE", b"m", b"plain"],
22049            &[b"PFDEBUG", b"ENCODING", b"gone"],
22050            &[b"PFDEBUG", b"NOPE", b"h1"],
22051        ];
22052
22053        let mut one = Fixture::new();
22054        let mut many = Fixture::striped(8);
22055        for parts in script {
22056            let a = one.run(parts);
22057            let b = many.run(parts);
22058            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22059        }
22060    }
22061
22062    /// Every set command, on one stripe and on eight.
22063    ///
22064    /// The commands that answer members answer them in whatever order the set
22065    /// or the table they were built in holds them, so those replies are
22066    /// compared as sets. Everything else is compared byte for byte. Two servers
22067    /// agreeing on the order would be a fact about the tables and not about the
22068    /// answer, and asserting it would make this test fail for a reason nobody
22069    /// cares about.
22070    #[test]
22071    fn the_set_group_answers_the_same_however_many_stripes_there_are() {
22072        const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
22073        let script: &[&[&[u8]]] = &[
22074            &[b"SADD", b"s1", b"a", b"b", b"c"],
22075            &[b"SADD", b"s1", b"a"],
22076            &[b"SADD", b"s2", b"b", b"c", b"d"],
22077            &[b"SADD", b"ints", b"1", b"2", b"3"],
22078            &[b"SCARD", b"s1"],
22079            &[b"SISMEMBER", b"s1", b"a"],
22080            &[b"SISMEMBER", b"s1", b"z"],
22081            &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
22082            &[b"SMEMBERS", b"s1"],
22083            &[b"SREM", b"s1", b"c"],
22084            &[b"SADD", b"s1", b"c"],
22085            &[b"SSCAN", b"s1", b"0"],
22086            &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
22087            // The two draws, on a set of one member, which is the only shape
22088            // whose answer two servers have to agree on.
22089            &[b"SADD", b"one", b"m"],
22090            &[b"SRANDMEMBER", b"one"],
22091            &[b"SRANDMEMBER", b"one", b"-3"],
22092            &[b"SRANDMEMBER", b"gone"],
22093            &[b"SPOP", b"one"],
22094            &[b"SPOP", b"one"],
22095            &[b"SPOP", b"gone", b"2"],
22096            // The one that names two keys.
22097            &[b"SMOVE", b"s1", b"s2", b"a"],
22098            &[b"SMOVE", b"s1", b"s2", b"zzz"],
22099            &[b"SMOVE", b"gone", b"s2", b"a"],
22100            &[b"SMEMBERS", b"s1"],
22101            &[b"SMEMBERS", b"s2"],
22102            // The algebra.
22103            &[b"SINTER", b"s1", b"s2"],
22104            &[b"SUNION", b"s1", b"s2"],
22105            &[b"SDIFF", b"s2", b"s1"],
22106            &[b"SINTER", b"s1", b"gone"],
22107            &[b"SUNION", b"s1", b"gone"],
22108            &[b"SDIFF", b"gone", b"s1"],
22109            &[b"SINTER", b"ints", b"s1"],
22110            &[b"SINTERCARD", b"2", b"s1", b"s2"],
22111            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
22112            &[b"SUNIONCARD", b"2", b"s1", b"s2"],
22113            &[b"SDIFFCARD", b"2", b"s2", b"s1"],
22114            &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
22115            &[b"SMEMBERS", b"d1"],
22116            &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
22117            &[b"SCARD", b"d2"],
22118            &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
22119            &[b"SCARD", b"d3"],
22120            // An empty result deletes the destination rather than storing a
22121            // set with nothing in it.
22122            &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
22123            &[b"EXISTS", b"d4"],
22124            // And a destination that is also a source.
22125            &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
22126            &[b"SCARD", b"s2"],
22127            // The errors, which have to be the same errors.
22128            &[b"SET", b"str", b"v"],
22129            &[b"SADD", b"str", b"a"],
22130            &[b"SINTER", b"s1", b"str"],
22131            &[b"SINTERSTORE", b"d5", b"s1", b"str"],
22132            &[b"EXISTS", b"d5"],
22133            &[b"SMOVE", b"str", b"s2", b"a"],
22134            &[b"SMOVE", b"s1", b"str", b"b"],
22135            &[b"SMOVE", b"gone", b"str", b"b"],
22136            &[b"SINTERCARD", b"0", b"s1"],
22137            &[b"SINTERCARD", b"3", b"s1", b"s2"],
22138            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
22139            &[b"SPOP", b"s1", b"-1"],
22140        ];
22141
22142        let mut one = Fixture::new();
22143        let mut many = Fixture::striped(8);
22144        for parts in script {
22145            let a = one.run(parts);
22146            let b = many.run(parts);
22147            let name = String::from_utf8_lossy(parts[0]).to_uppercase();
22148            if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
22149                assert_eq!(sorted(&a), sorted(&b), "{name}");
22150            } else {
22151                assert_eq!(a, b, "{name}");
22152            }
22153        }
22154    }
22155
22156    /// The algebra over sets that are known to be on different stripes.
22157    #[test]
22158    fn a_set_operation_across_stripes_reads_every_set() {
22159        let mut f = Fixture::striped(8);
22160        let second = apart(&mut f, "s1");
22161        let third = apart(&mut f, &second);
22162        let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
22163
22164        f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
22165        f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
22166        assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
22167        assert_eq!(
22168            sorted(&f.run(&[b"SUNION", s1, s2])),
22169            ["a", "b", "c", "d"],
22170            "a union of two stripes is both of them"
22171        );
22172        assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
22173        assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
22174        assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
22175        assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
22176
22177        // A destination on a third stripe, and then one that is also a source.
22178        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
22179        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
22180        assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
22181        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
22182        assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
22183        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
22184
22185        // An empty result deletes a destination wherever it is, and a key of
22186        // the wrong type stops the command before the destination is touched.
22187        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
22188        assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
22189        f.run(&[b"SET", s3, b"v"]);
22190        assert_eq!(
22191            f.run(&[b"SINTER", s1, s3]),
22192            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22193        );
22194        assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
22195    }
22196
22197    /// An `SMOVE` whose two keys are on two stripes.
22198    #[test]
22199    fn a_move_across_stripes_takes_the_member_with_it() {
22200        let mut f = Fixture::striped(8);
22201        let other = apart(&mut f, "src");
22202        let (src, dst) = (b"src".as_slice(), other.as_bytes());
22203
22204        f.run(&[b"SADD", src, b"a", b"b"]);
22205        f.run(&[b"SADD", dst, b"c"]);
22206        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
22207        assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
22208        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
22209        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
22210
22211        // A destination that is not there is created on its own stripe, and a
22212        // source that loses its last member is deleted from its own.
22213        f.run(&[b"DEL", dst]);
22214        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
22215        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
22216        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
22217
22218        // And a source that is not there answers zero without ever asking what
22219        // the destination holds, which is Redis's order and not the obvious
22220        // one.
22221        f.run(&[b"SET", dst, b"v"]);
22222        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
22223        f.run(&[b"SADD", src, b"b"]);
22224        assert_eq!(
22225            f.run(&[b"SMOVE", src, dst, b"b"]),
22226            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22227        );
22228    }
22229
22230    /// A count and a merge over sketches that are known to be on two stripes.
22231    #[test]
22232    fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
22233        let mut f = Fixture::striped(8);
22234        let other = apart(&mut f, "src");
22235        let (src, far) = (b"src".as_slice(), other.as_bytes());
22236
22237        for i in 0..150 {
22238            let ele = format!("e:{i}");
22239            f.run(&[b"PFADD", src, ele.as_bytes()]);
22240        }
22241        for i in 150..200 {
22242            let ele = format!("e:{i}");
22243            f.run(&[b"PFADD", far, ele.as_bytes()]);
22244        }
22245        // The three numbers a real server gives for these elements, which are
22246        // the numbers the single stripe tests in the keyspace crate check too.
22247        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
22248        assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
22249        assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
22250
22251        // A merge whose destination is on a third stripe, and then one that
22252        // writes into a source.
22253        let dest = apart(&mut f, &other);
22254        assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
22255        assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
22256        assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
22257        assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
22258        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
22259    }
22260
22261    /// Every sorted set command, on one stripe and on eight.
22262    ///
22263    /// Every reply here is compared byte for byte, unlike the set group, because
22264    /// a sorted set answers in rank order and members sharing a score come out
22265    /// in the order of their bytes. There is nothing left for the table the
22266    /// answer was built in to decide.
22267    #[test]
22268    fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
22269        let script: &[&[&[u8]]] = &[
22270            &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
22271            &[b"ZADD", b"z1", b"NX", b"9", b"a"],
22272            &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
22273            &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
22274            &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
22275            &[b"ZINCRBY", b"z1", b"1.5", b"b"],
22276            &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
22277            &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
22278            &[b"ZADD", b"one", b"1", b"m"],
22279            &[b"ZCARD", b"z1"],
22280            &[b"ZCARD", b"gone"],
22281            &[b"ZSCORE", b"z1", b"a"],
22282            &[b"ZSCORE", b"z1", b"zz"],
22283            &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
22284            &[b"ZRANK", b"z1", b"c"],
22285            &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
22286            &[b"ZREVRANK", b"z1", b"c"],
22287            &[b"ZRANK", b"z1", b"gone"],
22288            &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
22289            &[b"ZCOUNT", b"z1", b"(1", b"3"],
22290            &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
22291            // The range commands, which are one parse and one walk.
22292            &[b"ZRANGE", b"z1", b"0", b"-1"],
22293            &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
22294            &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
22295            &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
22296            &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
22297            &[b"ZREVRANGE", b"z1", b"0", b"-1"],
22298            &[
22299                b"ZRANGEBYSCORE",
22300                b"z1",
22301                b"-inf",
22302                b"+inf",
22303                b"LIMIT",
22304                b"1",
22305                b"1",
22306            ],
22307            &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
22308            &[b"ZSCAN", b"z1", b"0"],
22309            &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
22310            // The draw, on a sorted set of one member, which is the only shape
22311            // whose answer two servers have to agree on.
22312            &[b"ZRANDMEMBER", b"one"],
22313            &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
22314            &[b"ZRANDMEMBER", b"gone"],
22315            // The one that copies a window into another key.
22316            &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
22317            &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
22318            &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
22319            &[b"EXISTS", b"d0"],
22320            // The algebra, in both its shapes.
22321            &[b"ZUNION", b"2", b"z1", b"z2"],
22322            &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
22323            &[
22324                b"ZUNION",
22325                b"2",
22326                b"z1",
22327                b"z2",
22328                b"WEIGHTS",
22329                b"2",
22330                b"3",
22331                b"AGGREGATE",
22332                b"MAX",
22333                b"WITHSCORES",
22334            ],
22335            &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
22336            &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
22337            &[b"ZDIFF", b"2", b"gone", b"z1"],
22338            &[b"ZINTERCARD", b"2", b"z1", b"z2"],
22339            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
22340            &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
22341            &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
22342            &[
22343                b"ZINTERSTORE",
22344                b"d2",
22345                b"2",
22346                b"z1",
22347                b"z2",
22348                b"AGGREGATE",
22349                b"MIN",
22350            ],
22351            &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
22352            &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
22353            &[b"ZCARD", b"d3"],
22354            // An empty result deletes the destination rather than storing a
22355            // sorted set with nothing in it.
22356            &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
22357            &[b"EXISTS", b"d4"],
22358            // A plain set is a sorted set where every score is one, so it is a
22359            // legal input to all of these.
22360            &[b"SADD", b"plain", b"a", b"x"],
22361            &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
22362            &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
22363            // And a destination that is also a source.
22364            &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
22365            &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
22366            // The three removals and the two pops.
22367            &[b"ZREM", b"d5", b"x", b"nothere"],
22368            &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
22369            &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
22370            &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
22371            &[b"ZPOPMIN", b"z1"],
22372            &[b"ZPOPMAX", b"z1", b"2"],
22373            &[b"ZPOPMIN", b"gone"],
22374            &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
22375            &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
22376            // The errors, which have to be the same errors.
22377            &[b"SET", b"str", b"v"],
22378            &[b"ZADD", b"str", b"1", b"a"],
22379            &[b"ZSCORE", b"str", b"a"],
22380            &[b"ZADD", b"z1", b"nan", b"a"],
22381            &[b"ZUNION", b"2", b"z1", b"str"],
22382            &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
22383            &[b"EXISTS", b"d6"],
22384            &[b"ZINTERCARD", b"0", b"z1"],
22385            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
22386            &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
22387            &[b"ZMPOP", b"1", b"str", b"MIN"],
22388            &[b"ZPOPMIN", b"z1", b"-1"],
22389        ];
22390
22391        let mut one = Fixture::new();
22392        let mut many = Fixture::striped(8);
22393        for parts in script {
22394            let a = one.run(parts);
22395            let b = many.run(parts);
22396            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22397        }
22398    }
22399
22400    /// The algebra over sorted sets that are known to be on different stripes.
22401    #[test]
22402    fn a_sorted_set_operation_across_stripes_reads_every_input() {
22403        let mut f = Fixture::striped(8);
22404        let second = apart(&mut f, "z1");
22405        let third = apart(&mut f, &second);
22406        let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
22407
22408        f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
22409        f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
22410        // a is 1, c is 4, b is 2 and 3 added together, which is the order they
22411        // come out in and the answer that says both stripes were read.
22412        assert_eq!(
22413            f.run(&[b"ZUNION", b"2", z1, z2]),
22414            "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
22415        );
22416        assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
22417        assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
22418        assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
22419        assert_eq!(
22420            f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
22421            ":1\r\n"
22422        );
22423
22424        // A destination on a third stripe, and the weights and the aggregate
22425        // reaching every input.
22426        assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
22427        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
22428        assert_eq!(
22429            f.run(&[
22430                b"ZUNIONSTORE",
22431                z3,
22432                b"2",
22433                z1,
22434                z2,
22435                b"WEIGHTS",
22436                b"2",
22437                b"3",
22438                b"AGGREGATE",
22439                b"MAX"
22440            ]),
22441            ":3\r\n"
22442        );
22443        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
22444        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
22445        assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
22446        assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
22447        assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
22448
22449        // A pop over keys on several stripes takes from the first one that has
22450        // anything, which is what makes the order of the keys matter.
22451        let popped = format!(
22452            "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
22453            second.len()
22454        );
22455        assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
22456        f.run(&[b"ZADD", z2, b"3", b"b"]);
22457
22458        // An empty result deletes a destination wherever it is, and an input of
22459        // the wrong type stops the command before the destination is touched.
22460        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
22461        assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
22462        f.run(&[b"SET", z3, b"v"]);
22463        assert_eq!(
22464            f.run(&[b"ZUNION", b"2", z1, z3]),
22465            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22466        );
22467        assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
22468
22469        // And a destination that is also a source works across stripes for the
22470        // reason it works on one: the whole result is built before anything is
22471        // written.
22472        assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
22473        assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
22474        assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
22475    }
22476
22477    /// A `ZRANGESTORE` whose two keys are on two stripes.
22478    #[test]
22479    fn a_range_store_across_stripes_copies_the_window() {
22480        let mut f = Fixture::striped(8);
22481        let other = apart(&mut f, "src");
22482        let third = apart(&mut f, &other);
22483        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22484
22485        f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
22486        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
22487        assert_eq!(
22488            f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
22489            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
22490        );
22491        assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
22492
22493        // A window walked backwards takes the other end of the sorted set and
22494        // still stores what it took in score order.
22495        assert_eq!(
22496            f.run(&[
22497                b"ZRANGESTORE",
22498                dst,
22499                src,
22500                b"+inf",
22501                b"-inf",
22502                b"BYSCORE",
22503                b"REV",
22504                b"LIMIT",
22505                b"0",
22506                b"2"
22507            ]),
22508            ":2\r\n"
22509        );
22510        assert_eq!(
22511            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
22512            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22513        );
22514
22515        // An empty window deletes the destination on its own stripe, and a
22516        // source of the wrong type is refused before the destination is touched.
22517        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
22518        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
22519        f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
22520        f.run(&[b"SET", plain, b"v"]);
22521        assert_eq!(
22522            f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
22523            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22524        );
22525        assert_eq!(
22526            f.run(&[b"ZCARD", dst]),
22527            ":3\r\n",
22528            "and left the destination"
22529        );
22530    }
22531
22532    /// Every list command, on one stripe and on eight.
22533    ///
22534    /// The blocking six are in here too, both when they can be answered on the
22535    /// spot and when they cannot, since a command that parks its client writes
22536    /// nothing at all and two servers have to agree about that as much as they
22537    /// agree about a reply.
22538    #[test]
22539    fn the_list_group_answers_the_same_however_many_stripes_there_are() {
22540        let script: &[&[&[u8]]] = &[
22541            &[b"RPUSH", b"l1", b"a", b"b", b"c"],
22542            &[b"LPUSH", b"l1", b"z"],
22543            &[b"RPUSHX", b"l1", b"d"],
22544            &[b"LPUSHX", b"gone", b"x"],
22545            &[b"RPUSHX", b"gone", b"x"],
22546            &[b"LLEN", b"l1"],
22547            &[b"LLEN", b"gone"],
22548            &[b"LRANGE", b"l1", b"0", b"-1"],
22549            &[b"LRANGE", b"l1", b"1", b"2"],
22550            &[b"LRANGE", b"l1", b"5", b"9"],
22551            &[b"LINDEX", b"l1", b"0"],
22552            &[b"LINDEX", b"l1", b"-1"],
22553            &[b"LINDEX", b"l1", b"99"],
22554            &[b"LSET", b"l1", b"0", b"y"],
22555            &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
22556            &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
22557            &[b"LPOS", b"l1", b"b"],
22558            &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
22559            &[b"LPOS", b"l1", b"nothere"],
22560            &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
22561            &[b"LREM", b"l1", b"1", b"aa"],
22562            &[b"LTRIM", b"l1", b"0", b"3"],
22563            &[b"LRANGE", b"l1", b"0", b"-1"],
22564            &[b"LPOP", b"l1"],
22565            &[b"RPOP", b"l1"],
22566            &[b"LPOP", b"l1", b"2"],
22567            &[b"LPOP", b"gone"],
22568            &[b"LPOP", b"gone", b"2"],
22569            &[b"EXISTS", b"l1"],
22570            // The ones that name two keys, and the one that takes a block of
22571            // elements rather than the one on the end.
22572            &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
22573            &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
22574            &[b"RPOPLPUSH", b"src", b"dst"],
22575            &[b"LRANGE", b"dst", b"0", b"-1"],
22576            &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
22577            &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
22578            &[
22579                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
22580            ],
22581            &[
22582                b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
22583            ],
22584            &[b"LRANGE", b"dst", b"0", b"-1"],
22585            &[
22586                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
22587            ],
22588            &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
22589            &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
22590            &[b"LMPOP", b"1", b"gone", b"LEFT"],
22591            // The blocking ones, first with something there to answer them and
22592            // then with nothing, which parks the client and writes nothing.
22593            &[b"RPUSH", b"q", b"a", b"b", b"c"],
22594            &[b"BLPOP", b"gone", b"q", b"0"],
22595            &[b"BRPOP", b"q", b"0"],
22596            &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
22597            &[b"RPUSH", b"q", b"x", b"y", b"z"],
22598            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22599            &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
22600            &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22601            &[b"BLPOP", b"q", b"0"],
22602            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22603            // The errors, which have to be the same errors.
22604            &[b"SET", b"plain", b"v"],
22605            &[b"LPUSH", b"plain", b"a"],
22606            &[b"LLEN", b"plain"],
22607            &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
22608            &[b"LRANGE", b"dst", b"0", b"-1"],
22609            &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
22610            &[b"LSET", b"gone", b"0", b"v"],
22611            &[b"LSET", b"dst", b"99", b"v"],
22612            &[b"LPOP", b"dst", b"-1"],
22613            &[b"LMPOP", b"0", b"dst", b"LEFT"],
22614            &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
22615        ];
22616
22617        let mut one = Fixture::new();
22618        let mut many = Fixture::striped(8);
22619        for parts in script {
22620            let a = one.run(parts);
22621            let b = many.run(parts);
22622            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22623        }
22624    }
22625
22626    /// An `LMOVE` and an `LMOVEM` whose two keys are on two stripes.
22627    #[test]
22628    fn a_list_move_across_stripes_takes_the_elements_with_it() {
22629        let mut f = Fixture::striped(8);
22630        let other = apart(&mut f, "src");
22631        let third = apart(&mut f, &other);
22632        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22633
22634        f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
22635        assert_eq!(
22636            f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
22637            "$1\r\na\r\n"
22638        );
22639        assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
22640        assert_eq!(
22641            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22642            "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
22643            "one went on each end of the destination"
22644        );
22645        assert_eq!(
22646            f.run(&[b"LRANGE", src, b"0", b"-1"]),
22647            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22648        );
22649
22650        // A block of them, which under BULK arrives in the order it left.
22651        assert_eq!(
22652            f.run(&[
22653                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
22654            ]),
22655            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22656        );
22657        assert_eq!(
22658            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22659            "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
22660        );
22661        assert_eq!(
22662            f.run(&[b"EXISTS", src]),
22663            ":0\r\n",
22664            "and the source is gone with its last element"
22665        );
22666
22667        // An `EXACTLY` the source cannot fill moves nothing, and a source that
22668        // is not there at all is the two kinds of nothing the two commands have.
22669        f.run(&[b"RPUSH", src, b"e", b"f"]);
22670        assert_eq!(
22671            f.run(&[
22672                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
22673            ]),
22674            "*-1\r\n"
22675        );
22676        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
22677        assert_eq!(
22678            f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
22679            "$-1\r\n"
22680        );
22681        assert_eq!(
22682            f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
22683            "*-1\r\n"
22684        );
22685
22686        // A destination of the wrong type is refused before anything is taken,
22687        // which is the order that matters most here, since an element already
22688        // out of the source would have nowhere to go back to.
22689        f.run(&[b"SET", plain, b"v"]);
22690        assert_eq!(
22691            f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
22692            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22693        );
22694        assert_eq!(
22695            f.run(&[b"LLEN", src]),
22696            ":2\r\n",
22697            "and left the source alone"
22698        );
22699        assert_eq!(
22700            f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
22701            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22702        );
22703        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
22704    }
22705
22706    /// A parked client served by a push that landed on another stripe.
22707    ///
22708    /// A waiter remembers the database and not the stripe, which is the point:
22709    /// serving it runs the same attempt the command ran, and the attempt finds
22710    /// the stripe each of its keys is on for itself.
22711    #[test]
22712    fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
22713        let mut f = Fixture::striped(8);
22714        let other = apart(&mut f, "q");
22715        let (q, far) = (b"q".as_slice(), other.as_bytes());
22716
22717        assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
22718        assert_eq!(f.server.parked(), 1);
22719        f.run(&[b"RPUSH", far, b"v"]);
22720        let mut out = Out::new(Proto::Resp2);
22721        assert!(f.server.serve_waiter(7, 0, &mut out));
22722        let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
22723        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22724        assert_eq!(
22725            f.run(&[b"EXISTS", far]),
22726            ":0\r\n",
22727            "and it took the element with it"
22728        );
22729
22730        // And a move across two stripes is served the same way, by the push
22731        // that fills its source.
22732        f.server.forget_waiters(7);
22733        assert_eq!(
22734            f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
22735            Flow::Block
22736        );
22737        f.run(&[b"RPUSH", q, b"w"]);
22738        let mut out = Out::new(Proto::Resp2);
22739        assert!(f.server.serve_waiter(7, 0, &mut out));
22740        assert_eq!(
22741            core::str::from_utf8(out.as_slice()).expect("ascii"),
22742            "$1\r\nw\r\n"
22743        );
22744        assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
22745    }
22746
22747    /// Every stream command, on one stripe and on eight.
22748    ///
22749    /// Every ID is written out rather than left to the clock, so the two servers
22750    /// are being compared on what they store and not on how long the test took
22751    /// to get from one of them to the other.
22752    #[test]
22753    fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
22754        let script: &[&[&[u8]]] = &[
22755            &[b"XADD", b"s", b"1-1", b"a", b"1"],
22756            &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
22757            &[b"XADD", b"s", b"3-1", b"d", b"4"],
22758            &[b"XADD", b"s", b"1-1", b"e", b"5"],
22759            &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
22760            &[b"XLEN", b"s"],
22761            &[b"XLEN", b"gone"],
22762            &[b"XRANGE", b"s", b"-", b"+"],
22763            &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
22764            &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
22765            &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
22766            &[b"XREVRANGE", b"s", b"+", b"-"],
22767            &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
22768            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
22769            &[b"XREAD", b"STREAMS", b"s", b"$"],
22770            // The groups, which is where most of the state is.
22771            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22772            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22773            &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
22774            &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
22775            &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
22776            &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
22777            &[
22778                b"XREADGROUP",
22779                b"GROUP",
22780                b"g",
22781                b"c1",
22782                b"COUNT",
22783                b"1",
22784                b"STREAMS",
22785                b"s",
22786                b"0",
22787            ],
22788            &[
22789                b"XREADGROUP",
22790                b"GROUP",
22791                b"nope",
22792                b"c1",
22793                b"STREAMS",
22794                b"s",
22795                b">",
22796            ],
22797            &[b"XPENDING", b"s", b"g"],
22798            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
22799            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
22800            &[b"XPENDING", b"s", b"nope"],
22801            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
22802            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
22803            &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
22804            &[b"XACK", b"s", b"g", b"1-1"],
22805            &[b"XACK", b"s", b"g", b"1-1"],
22806            &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
22807            &[b"XPENDING", b"s", b"g"],
22808            &[b"XINFO", b"STREAM", b"s"],
22809            &[b"XINFO", b"GROUPS", b"s"],
22810            &[b"XINFO", b"CONSUMERS", b"s", b"g"],
22811            &[b"XINFO", b"STREAM", b"gone"],
22812            // Deleting, trimming and moving the ID on.
22813            &[b"XDEL", b"s", b"3-1"],
22814            &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
22815            &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
22816            &[b"XADD", b"s", b"9-1", b"z", b"9"],
22817            &[b"XTRIM", b"s", b"MAXLEN", b"1"],
22818            &[b"XTRIM", b"s", b"MINID", b"9"],
22819            &[b"XSETID", b"s", b"99-1"],
22820            &[b"XSETID", b"s", b"1-1"],
22821            &[b"XLEN", b"s"],
22822            &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
22823            &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
22824            &[b"XGROUP", b"DESTROY", b"s", b"g"],
22825            &[b"XGROUP", b"DESTROY", b"s", b"g"],
22826            // And the errors.
22827            &[b"SET", b"plain", b"v"],
22828            &[b"XADD", b"plain", b"1-1", b"a", b"1"],
22829            &[b"XLEN", b"plain"],
22830            &[b"XREAD", b"STREAMS", b"plain", b"0"],
22831            &[b"XRANGE", b"s", b"bogus", b"+"],
22832            &[b"XADD", b"s", b"1-1", b"a"],
22833            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
22834            &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
22835        ];
22836
22837        let mut one = Fixture::new();
22838        let mut many = Fixture::striped(8);
22839        for parts in script {
22840            let a = one.run(parts);
22841            let b = many.run(parts);
22842            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22843        }
22844    }
22845
22846    /// An `XREAD` and an `XREADGROUP` naming two keys on two stripes.
22847    ///
22848    /// Nothing is shared between the two streams, so the only thing this can go
22849    /// wrong at is looking both of them up, which is exactly what a read that
22850    /// held one database and walked it would get wrong.
22851    #[test]
22852    fn a_stream_read_across_stripes_reads_every_key() {
22853        let mut f = Fixture::striped(8);
22854        let other = apart(&mut f, "s1");
22855        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22856
22857        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22858        f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
22859        let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
22860        assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
22861        assert!(got.contains("1-1"), "the first one is in there: {got}");
22862        assert!(got.contains("2-1"), "and so is the second: {got}");
22863
22864        // A group read looks its group up on every key before it reads any of
22865        // them, so a group that is missing on the far key stops the near one.
22866        f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
22867        let got = f.run(&[
22868            b"XREADGROUP",
22869            b"GROUP",
22870            b"g",
22871            b"c",
22872            b"STREAMS",
22873            s1,
22874            s2,
22875            b">",
22876            b">",
22877        ]);
22878        assert!(got.starts_with("-NOGROUP"), "{got}");
22879        assert_eq!(
22880            f.run(&[b"XPENDING", s1, b"g"]),
22881            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
22882            "and read nothing from the key that did have the group"
22883        );
22884
22885        f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
22886        let got = f.run(&[
22887            b"XREADGROUP",
22888            b"GROUP",
22889            b"g",
22890            b"c",
22891            b"STREAMS",
22892            s1,
22893            s2,
22894            b">",
22895            b">",
22896        ]);
22897        assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
22898    }
22899
22900    /// A client parked on an `XREAD` woken by an entry on another stripe.
22901    #[test]
22902    fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
22903        let mut f = Fixture::striped(8);
22904        let other = apart(&mut f, "s1");
22905        let (s1, far) = (b"s1".as_slice(), other.as_bytes());
22906        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22907        f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
22908
22909        assert_eq!(
22910            f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
22911                .0,
22912            Flow::Block
22913        );
22914        f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
22915        let mut out = Out::new(Proto::Resp2);
22916        assert!(f.server.serve_waiter(7, 0, &mut out));
22917        let want = format!(
22918            "*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",
22919            other.len()
22920        );
22921        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22922    }
22923
22924    /// Every JSON command, on one stripe and on eight.
22925    #[test]
22926    fn the_json_group_answers_the_same_however_many_stripes_there_are() {
22927        let script: &[&[&[u8]]] = &[
22928            &[
22929                b"JSON.SET",
22930                b"d",
22931                b"$",
22932                br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
22933            ],
22934            &[b"JSON.SET", b"d", b"$.a", b"2"],
22935            &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
22936            &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
22937            &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
22938            &[b"JSON.GET", b"d"],
22939            &[b"JSON.GET", b"d", b"$.b"],
22940            &[b"JSON.GET", b"gone", b"$"],
22941            &[b"JSON.TYPE", b"d", b"$.b"],
22942            &[b"JSON.TYPE", b"d", b"$.s"],
22943            &[b"JSON.TOGGLE", b"d", b"$.t"],
22944            &[b"JSON.ARRLEN", b"d", b"$.b"],
22945            &[b"JSON.OBJLEN", b"d", b"$"],
22946            &[b"JSON.OBJKEYS", b"d", b"$"],
22947            &[b"JSON.STRLEN", b"d", b"$.s"],
22948            &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
22949            &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
22950            &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
22951            &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
22952            &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
22953            &[b"JSON.ARRPOP", b"d", b"$.b"],
22954            &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
22955            &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
22956            &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
22957            &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
22958            &[b"JSON.RESP", b"d", b"$.b"],
22959            &[b"JSON.DEBUG", b"MEMORY", b"d"],
22960            &[b"JSON.CLEAR", b"d", b"$.b"],
22961            &[b"JSON.DEL", b"d", b"$.m"],
22962            &[b"JSON.FORGET", b"d", b"$.nothere"],
22963            // The two that name more than one key.
22964            &[
22965                b"JSON.MSET",
22966                b"m1",
22967                b"$",
22968                b"1",
22969                b"m2",
22970                b"$",
22971                b"2",
22972                b"m3",
22973                b"$",
22974                b"3",
22975            ],
22976            &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
22977            &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
22978            &[b"JSON.GET", b"m1", b"$"],
22979            &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
22980            &[b"JSON.GET", b"m2", b"$"],
22981            // And the errors.
22982            &[b"SET", b"plain", b"v"],
22983            &[b"JSON.GET", b"plain", b"$"],
22984            &[b"JSON.SET", b"plain", b"$", b"1"],
22985            &[b"JSON.MGET", b"m1", b"plain", b"$"],
22986            &[b"JSON.SET", b"d", b"$.b", b"["],
22987            &[b"JSON.DEL", b"plain"],
22988        ];
22989
22990        let mut one = Fixture::new();
22991        let mut many = Fixture::striped(8);
22992        for parts in script {
22993            let a = one.run(parts);
22994            let b = many.run(parts);
22995            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22996        }
22997    }
22998
22999    /// A `JSON.MSET` and a `JSON.MGET` whose keys are on several stripes.
23000    ///
23001    /// `JSON.MSET` works every triple out against the keyspace as it was before
23002    /// the command and writes nothing until all of them are known to work, so
23003    /// the thing to check is that a triple that cannot be written stops the
23004    /// ones on other stripes as well as the ones on its own.
23005    #[test]
23006    fn a_json_multi_write_across_stripes_reaches_every_key() {
23007        let mut f = Fixture::striped(8);
23008        let second = apart(&mut f, "m1");
23009        let third = apart(&mut f, &second);
23010        let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
23011
23012        assert_eq!(
23013            f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
23014            "+OK\r\n"
23015        );
23016        assert_eq!(
23017            f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
23018            "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
23019        );
23020
23021        // A value that is not JSON is refused before anything is written, and
23022        // the key on the far stripe keeps what it had.
23023        assert_eq!(
23024            f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
23025            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
23026        );
23027        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
23028
23029        // A path that names nowhere is not an error. That triple is skipped,
23030        // the ones on the other stripes are still written, and the reply is a
23031        // nil rather than OK.
23032        assert_eq!(
23033            f.run(&[
23034                b"JSON.MSET",
23035                m1,
23036                b"$",
23037                b"9",
23038                m2,
23039                b"$.deep",
23040                b"9",
23041                m3,
23042                b"$",
23043                b"7"
23044            ]),
23045            "$-1\r\n"
23046        );
23047        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
23048        assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
23049        assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
23050    }
23051
23052    /// Every geospatial command, on one stripe and on eight.
23053    #[test]
23054    fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
23055        let script: &[&[&[u8]]] = &[
23056            &[
23057                b"GEOADD",
23058                b"g",
23059                b"13.361389",
23060                b"38.115556",
23061                b"palermo",
23062                b"15.087269",
23063                b"37.502669",
23064                b"catania",
23065            ],
23066            &[
23067                b"GEOADD",
23068                b"g",
23069                b"NX",
23070                b"13.361389",
23071                b"38.115556",
23072                b"palermo",
23073            ],
23074            &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
23075            &[b"GEOPOS", b"g", b"palermo", b"nothere"],
23076            &[b"GEOHASH", b"g", b"palermo", b"catania"],
23077            &[b"GEODIST", b"g", b"palermo", b"catania"],
23078            &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
23079            &[b"GEODIST", b"g", b"palermo", b"nothere"],
23080            &[
23081                b"GEOSEARCH",
23082                b"g",
23083                b"FROMLONLAT",
23084                b"15",
23085                b"37",
23086                b"BYRADIUS",
23087                b"200",
23088                b"KM",
23089                b"ASC",
23090                b"WITHCOORD",
23091                b"WITHDIST",
23092                b"WITHHASH",
23093            ],
23094            &[
23095                b"GEOSEARCH",
23096                b"g",
23097                b"FROMMEMBER",
23098                b"palermo",
23099                b"BYBOX",
23100                b"400",
23101                b"400",
23102                b"KM",
23103                b"DESC",
23104            ],
23105            &[
23106                b"GEORADIUS",
23107                b"g",
23108                b"15",
23109                b"37",
23110                b"200",
23111                b"KM",
23112                b"COUNT",
23113                b"1",
23114            ],
23115            &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
23116            &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
23117            &[
23118                b"GEOSEARCHSTORE",
23119                b"dst",
23120                b"g",
23121                b"FROMLONLAT",
23122                b"15",
23123                b"37",
23124                b"BYRADIUS",
23125                b"200",
23126                b"KM",
23127            ],
23128            &[b"ZRANGE", b"dst", b"0", b"-1"],
23129            &[
23130                b"GEOSEARCHSTORE",
23131                b"dst",
23132                b"g",
23133                b"FROMLONLAT",
23134                b"15",
23135                b"37",
23136                b"BYRADIUS",
23137                b"1",
23138                b"M",
23139                b"STOREDIST",
23140            ],
23141            &[b"EXISTS", b"dst"],
23142            &[
23143                b"GEORADIUS",
23144                b"g",
23145                b"15",
23146                b"37",
23147                b"200",
23148                b"KM",
23149                b"STORE",
23150                b"dst",
23151            ],
23152            &[b"ZCARD", b"dst"],
23153            // And the errors.
23154            &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
23155            &[b"SET", b"plain", b"v"],
23156            &[b"GEOPOS", b"plain", b"a"],
23157            &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
23158            &[
23159                b"GEOSEARCHSTORE",
23160                b"dst",
23161                b"g",
23162                b"FROMLONLAT",
23163                b"15",
23164                b"37",
23165                b"BYRADIUS",
23166                b"200",
23167                b"KM",
23168                b"WITHCOORD",
23169            ],
23170        ];
23171
23172        let mut one = Fixture::new();
23173        let mut many = Fixture::striped(8);
23174        for parts in script {
23175            let a = one.run(parts);
23176            let b = many.run(parts);
23177            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23178        }
23179    }
23180
23181    /// A `GEOSEARCHSTORE` whose two keys are on two stripes.
23182    #[test]
23183    fn a_geo_search_store_across_stripes_writes_what_it_found() {
23184        let mut f = Fixture::striped(8);
23185        let other = apart(&mut f, "g");
23186        let third = apart(&mut f, &other);
23187        let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
23188
23189        f.run(&[
23190            b"GEOADD",
23191            g,
23192            b"13.361389",
23193            b"38.115556",
23194            b"palermo",
23195            b"15.087269",
23196            b"37.502669",
23197            b"catania",
23198        ]);
23199        assert_eq!(
23200            f.run(&[
23201                b"GEOSEARCHSTORE",
23202                dst,
23203                g,
23204                b"FROMLONLAT",
23205                b"15",
23206                b"37",
23207                b"BYRADIUS",
23208                b"200",
23209                b"KM",
23210                b"ASC",
23211            ]),
23212            ":2\r\n"
23213        );
23214        assert_eq!(
23215            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23216            "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
23217            "the geohash is the score, so the order is not the search order"
23218        );
23219        assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
23220
23221        // `STOREDIST` stores the distance in the unit the search was asked in,
23222        // which is the destination stripe's sorted set and not the source's.
23223        assert_eq!(
23224            f.run(&[
23225                b"GEOSEARCHSTORE",
23226                dst,
23227                g,
23228                b"FROMMEMBER",
23229                b"palermo",
23230                b"BYRADIUS",
23231                b"200",
23232                b"KM",
23233                b"STOREDIST",
23234            ]),
23235            ":2\r\n"
23236        );
23237        assert_eq!(
23238            f.run(&[b"ZSCORE", dst, b"palermo"]),
23239            "$1\r\n0\r\n",
23240            "the centre is nought away from itself"
23241        );
23242
23243        // A search that found nothing deletes the destination on its own
23244        // stripe, and a source of the wrong type is refused with the
23245        // destination left alone.
23246        assert_eq!(
23247            f.run(&[
23248                b"GEOSEARCHSTORE",
23249                dst,
23250                g,
23251                b"FROMLONLAT",
23252                b"0",
23253                b"0",
23254                b"BYRADIUS",
23255                b"1",
23256                b"M",
23257            ]),
23258            ":0\r\n"
23259        );
23260        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23261        f.run(&[
23262            b"GEOSEARCHSTORE",
23263            dst,
23264            g,
23265            b"FROMLONLAT",
23266            b"15",
23267            b"37",
23268            b"BYRADIUS",
23269            b"200",
23270            b"KM",
23271        ]);
23272        f.run(&[b"SET", plain, b"v"]);
23273        assert_eq!(
23274            f.run(&[
23275                b"GEOSEARCHSTORE",
23276                dst,
23277                plain,
23278                b"FROMLONLAT",
23279                b"15",
23280                b"37",
23281                b"BYRADIUS",
23282                b"200",
23283                b"KM",
23284            ]),
23285            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23286        );
23287        assert_eq!(
23288            f.run(&[b"ZCARD", dst]),
23289            ":2\r\n",
23290            "and left the destination"
23291        );
23292    }
23293
23294    /// Every time series command, on one stripe and on eight.
23295    ///
23296    /// Every timestamp is written out rather than left to the clock, so the two
23297    /// servers are compared on the samples they hold and not on how long the
23298    /// test took to get from one of them to the other.
23299    #[test]
23300    fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
23301        let script: &[&[&[u8]]] = &[
23302            &[
23303                b"TS.CREATE",
23304                b"ts:a",
23305                b"LABELS",
23306                b"sensor",
23307                b"a",
23308                b"room",
23309                b"1",
23310            ],
23311            &[b"TS.CREATE", b"ts:a"],
23312            &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
23313            &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
23314            &[
23315                b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
23316            ],
23317            &[
23318                b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
23319            ],
23320            &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
23321            &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
23322            &[b"TS.GET", b"ts:a"],
23323            &[b"TS.GET", b"gone"],
23324            &[b"TS.RANGE", b"ts:a", b"-", b"+"],
23325            &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
23326            &[
23327                b"TS.RANGE",
23328                b"ts:a",
23329                b"-",
23330                b"+",
23331                b"AGGREGATION",
23332                b"avg",
23333                b"2000",
23334            ],
23335            &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
23336            &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23337            &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23338            &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
23339            &[b"TS.READ", b"ts:a", b"0"],
23340            &[b"TS.READ", b"ts:a", b"+"],
23341            // The filters, which are the ones that have to walk every stripe.
23342            &[b"TS.QUERYINDEX", b"sensor=a"],
23343            &[b"TS.QUERYINDEX", b"room=1"],
23344            &[b"TS.QUERYINDEX", b"room=9"],
23345            &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
23346            &[
23347                b"TS.QUERYLABELS",
23348                b"VALUES",
23349                b"sensor",
23350                b"FILTER",
23351                b"room=1",
23352            ],
23353            &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
23354            &[
23355                b"TS.MGET",
23356                b"SELECTED_LABELS",
23357                b"sensor",
23358                b"FILTER",
23359                b"sensor=a",
23360            ],
23361            &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
23362            &[
23363                b"TS.MREVRANGE",
23364                b"-",
23365                b"+",
23366                b"WITHLABELS",
23367                b"FILTER",
23368                b"sensor=a",
23369            ],
23370            &[
23371                b"TS.MRANGE",
23372                b"-",
23373                b"+",
23374                b"FILTER",
23375                b"room=1",
23376                b"GROUPBY",
23377                b"room",
23378                b"REDUCE",
23379                b"max",
23380            ],
23381            &[b"TS.INFO", b"ts:a"],
23382            // And a rule, which is the one thing here that names two keys.
23383            &[
23384                b"TS.CREATERULE",
23385                b"ts:a",
23386                b"ts:down",
23387                b"AGGREGATION",
23388                b"avg",
23389                b"1000",
23390            ],
23391            &[b"TS.CREATE", b"ts:down"],
23392            &[
23393                b"TS.CREATERULE",
23394                b"ts:a",
23395                b"ts:down",
23396                b"AGGREGATION",
23397                b"avg",
23398                b"1000",
23399            ],
23400            &[b"TS.ADD", b"ts:a", b"5000", b"4"],
23401            &[b"TS.ADD", b"ts:a", b"6000", b"5"],
23402            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23403            &[b"TS.GET", b"ts:down", b"LATEST"],
23404            &[b"TS.INFO", b"ts:down"],
23405            &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
23406            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23407            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23408            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23409            &[b"TS.DEL", b"ts:a", b"0", b"1000"],
23410            // And the errors.
23411            &[b"SET", b"plain", b"v"],
23412            &[b"TS.ADD", b"plain", b"1", b"1"],
23413            &[b"TS.GET", b"plain"],
23414            &[b"TS.READ", b"plain", b"0"],
23415            &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
23416            &[b"TS.RANGE", b"gone", b"-", b"+"],
23417            &[b"TS.INFO", b"gone"],
23418        ];
23419
23420        let mut one = Fixture::new();
23421        let mut many = Fixture::striped(8);
23422        for parts in script {
23423            let a = one.run(parts);
23424            let b = many.run(parts);
23425            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23426        }
23427    }
23428
23429    /// A compaction rule whose two ends are on two stripes.
23430    ///
23431    /// This is the one thing in the family that walks from a key to another key,
23432    /// and it walks it in both directions: a sample on the source closes a
23433    /// bucket on the destination, a `LATEST` read on the destination folds the
23434    /// bucket the source is still filling, and a delete on the source rewrites
23435    /// what the destination already held. The same script is run against a
23436    /// server one stripe wide, where the two keys share a store, and against one
23437    /// eight stripes wide, where they do not.
23438    #[test]
23439    fn a_compaction_rule_across_stripes_reaches_both_ends() {
23440        let mut many = Fixture::striped(8);
23441        let other = apart(&mut many, "src");
23442        let (src, dst) = (b"src".as_slice(), other.as_bytes());
23443        let mut one = Fixture::new();
23444        let mut both = |parts: &[&[u8]]| {
23445            let a = one.run(parts);
23446            let b = many.run(parts);
23447            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23448            a
23449        };
23450
23451        both(&[b"TS.CREATE", src]);
23452        both(&[b"TS.CREATE", dst]);
23453        assert_eq!(
23454            both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
23455            "+OK\r\n"
23456        );
23457        both(&[b"TS.ADD", src, b"1000", b"1"]);
23458        both(&[b"TS.ADD", src, b"1500", b"3"]);
23459        // The bucket the source is filling is not written down yet, and asking
23460        // for it works it out off the source.
23461        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23462        let open = both(&[b"TS.GET", dst, b"LATEST"]);
23463        assert!(open.contains(":1000"), "the open bucket is folded: {open}");
23464
23465        // A sample past the bucket closes it, which is the write that has to
23466        // land on the other stripe.
23467        both(&[b"TS.ADD", src, b"2000", b"5"]);
23468        let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
23469        assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
23470        assert!(got.contains(":1000"), "{got}");
23471
23472        // And a delete on the source takes it away again.
23473        both(&[b"TS.DEL", src, b"1000", b"1999"]);
23474        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23475
23476        // Both ends still know about each other, and the link comes apart from
23477        // the source.
23478        assert!(
23479            both(&[b"TS.INFO", dst]).contains("src"),
23480            "the source is named"
23481        );
23482        assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
23483        assert_eq!(
23484            both(&[b"TS.DELETERULE", src, dst]),
23485            "-ERR TSDB: compaction rule does not exist\r\n"
23486        );
23487    }
23488
23489    /// A label filter takes the series it names wherever they landed.
23490    #[test]
23491    fn a_label_query_across_stripes_finds_every_series() {
23492        let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
23493        let mut many = Fixture::striped(8);
23494        let mut homes: Vec<usize> = names
23495            .iter()
23496            .map(|name| many.server.striped(0).stripe_of(name))
23497            .collect();
23498        homes.sort_unstable();
23499        homes.dedup();
23500        assert!(homes.len() > 1, "the six keys are not all on one stripe");
23501
23502        let mut one = Fixture::new();
23503        let mut both = |parts: &[&[u8]]| {
23504            let a = one.run(parts);
23505            let b = many.run(parts);
23506            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23507            a
23508        };
23509        for name in &names {
23510            both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
23511            both(&[b"TS.ADD", name, b"1000", b"1"]);
23512        }
23513
23514        let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
23515        assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
23516        assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23517        assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23518        assert_eq!(
23519            both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
23520            "*1\r\n$4\r\nroom\r\n"
23521        );
23522    }
23523
23524    /// Every hash command, and the field import beside it, on one stripe and on
23525    /// eight.
23526    ///
23527    /// `HRANDFIELD` with a count draws from the stripe's own generator and two
23528    /// stripes do not draw the same numbers, so the only draw here is off a hash
23529    /// holding one field, where every generator gives the same answer.
23530    #[test]
23531    fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
23532        let script: &[&[&[u8]]] = &[
23533            &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
23534            &[b"HMSET", b"h", b"c", b"3"],
23535            &[b"HSETNX", b"h", b"a", b"9"],
23536            &[b"HSETNX", b"h", b"d", b"4"],
23537            &[b"HGET", b"h", b"a"],
23538            &[b"HGET", b"h", b"nope"],
23539            &[b"HMGET", b"h", b"a", b"nope"],
23540            &[b"HLEN", b"h"],
23541            &[b"HEXISTS", b"h", b"a"],
23542            &[b"HSTRLEN", b"h", b"a"],
23543            &[b"HGETALL", b"h"],
23544            &[b"HKEYS", b"h"],
23545            &[b"HVALS", b"h"],
23546            &[b"HINCRBY", b"h", b"a", b"5"],
23547            &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
23548            &[b"HSCAN", b"h", b"0"],
23549            &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
23550            &[b"HSCAN", b"h", b"0", b"NOVALUES"],
23551            &[b"HDEL", b"h", b"d"],
23552            &[b"HSET", b"one", b"f", b"v"],
23553            &[b"HRANDFIELD", b"one"],
23554            &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
23555            // The field deadlines.
23556            &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
23557            &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
23558            &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
23559            &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23560            &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23561            &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
23562            &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
23563            &[b"HGET", b"h", b"b"],
23564            // The three that came later and word everything their own way.
23565            &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
23566            &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
23567            &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
23568            &[b"HGET", b"h", b"e"],
23569            // And the import, whose key is the third word.
23570            &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
23571            &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
23572            &[b"HGETALL", b"imp"],
23573            &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
23574            &[b"HIMPORT", b"DISCARD", b"fs"],
23575            // And the errors.
23576            &[b"SET", b"plain", b"v"],
23577            &[b"HSET", b"plain", b"a", b"1"],
23578            &[b"HGETALL", b"plain"],
23579            &[b"HGET", b"gone", b"a"],
23580            &[b"HINCRBY", b"h", b"a", b"nan"],
23581        ];
23582
23583        let mut one = Fixture::new();
23584        let mut many = Fixture::striped(8);
23585        // The field deadlines are absolute milliseconds worked out from the
23586        // clock, so both servers are put on the same one rather than left to
23587        // read the wall a moment apart.
23588        one.server.set_clock_ms(1_700_000_000_000);
23589        many.server.set_clock_ms(1_700_000_000_000);
23590        for parts in script {
23591            let a = one.run(parts);
23592            let b = many.run(parts);
23593            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23594        }
23595    }
23596
23597    /// Every array command, on one stripe and on eight.
23598    #[test]
23599    fn the_array_group_answers_the_same_however_many_stripes_there_are() {
23600        let script: &[&[&[u8]]] = &[
23601            &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
23602            &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
23603            &[b"ARGET", b"a", b"1"],
23604            &[b"ARGET", b"a", b"99"],
23605            &[b"ARMGET", b"a", b"0", b"5", b"99"],
23606            &[b"ARGETRANGE", b"a", b"0", b"7"],
23607            &[b"ARLEN", b"a"],
23608            &[b"ARCOUNT", b"a"],
23609            &[b"ARINSERT", b"a", b"m", b"n"],
23610            &[b"ARSCAN", b"a", b"0", b"20"],
23611            &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
23612            &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
23613            &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
23614            &[b"ARLASTITEMS", b"a", b"2"],
23615            &[b"ARLASTITEMS", b"a", b"2", b"REV"],
23616            &[b"ARNEXT", b"a"],
23617            &[b"ARSEEK", b"a", b"3"],
23618            &[b"AROP", b"a", b"0", b"20", b"USED"],
23619            &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
23620            &[b"ARINFO", b"a"],
23621            &[b"ARINFO", b"a", b"FULL"],
23622            &[b"ARDEL", b"a", b"0"],
23623            &[b"ARDELRANGE", b"a", b"1", b"2"],
23624            &[b"ARCOUNT", b"a"],
23625            &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
23626            &[b"ARGETRANGE", b"r", b"0", b"9"],
23627            // And the errors.
23628            &[b"SET", b"plain", b"v"],
23629            &[b"ARGET", b"plain", b"0"],
23630            &[b"ARSET", b"plain", b"0", b"v"],
23631            &[b"ARGET", b"gone", b"0"],
23632            &[b"ARSET", b"a", b"bad", b"v"],
23633        ];
23634
23635        let mut one = Fixture::new();
23636        let mut many = Fixture::striped(8);
23637        for parts in script {
23638            let a = one.run(parts);
23639            let b = many.run(parts);
23640            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23641        }
23642    }
23643
23644    /// Every graph and vector set command, on one stripe and on eight.
23645    ///
23646    /// `VRANDMEMBER` is not in here for the reason `HRANDFIELD` with a count is
23647    /// not: it draws from the stripe's generator, and the stripes do not share
23648    /// one.
23649    #[test]
23650    fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
23651        let script: &[&[&[u8]]] = &[
23652            &[b"G.NADD", b"g", b"n1", b"name", b"one"],
23653            &[b"G.NADD", b"g", b"n2", b"name", b"two"],
23654            &[b"G.NADD", b"g", b"n3"],
23655            &[b"G.NGET", b"g", b"n1"],
23656            &[b"G.NGET", b"g", b"gone"],
23657            &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
23658            &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
23659            &[b"G.OUT", b"g", b"n1", b"knows"],
23660            &[b"G.IN", b"g", b"n2", b"knows"],
23661            &[b"G.DEG", b"g", b"n1", b"knows"],
23662            &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
23663            &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
23664            &[b"G.PATH", b"g", b"n1", b"n3"],
23665            &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
23666            &[b"G.NDEL", b"g", b"n3"],
23667            &[b"G.NGET", b"g", b"n3"],
23668            // The vector set, which is one index under one key.
23669            &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
23670            &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
23671            &[b"VCARD", b"v"],
23672            &[b"VDIM", b"v"],
23673            &[b"VEMB", b"v", b"e1"],
23674            &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
23675            &[b"VSIM", b"v", b"ELE", b"e1"],
23676            &[b"VISMEMBER", b"v", b"e1"],
23677            &[b"VISMEMBER", b"v", b"gone"],
23678            &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
23679            &[b"VGETATTR", b"v", b"e1"],
23680            &[b"VRANGE", b"v", b"-", b"+"],
23681            &[b"VLINKS", b"v", b"e1"],
23682            &[b"VINFO", b"v"],
23683            &[b"VREM", b"v", b"e2"],
23684            &[b"VCARD", b"v"],
23685            // And the errors.
23686            &[b"SET", b"plain", b"v"],
23687            &[b"G.NGET", b"plain", b"n1"],
23688            &[b"VCARD", b"plain"],
23689            &[b"G.NADD", b"gone2", b"n"],
23690            &[b"VEMB", b"gone3", b"e"],
23691        ];
23692
23693        let mut one = Fixture::new();
23694        let mut many = Fixture::striped(8);
23695        for parts in script {
23696            let a = one.run(parts);
23697            let b = many.run(parts);
23698            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23699        }
23700    }
23701
23702    /// Every bloom filter, cuckoo filter, count min sketch, top k and t digest
23703    /// command, on one stripe and on eight.
23704    #[test]
23705    fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
23706        let script: &[&[&[u8]]] = &[
23707            // The bloom filter.
23708            &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
23709            &[b"BF.ADD", b"bf", b"a"],
23710            &[b"BF.ADD", b"bf", b"a"],
23711            &[b"BF.MADD", b"bf", b"b", b"c"],
23712            &[b"BF.EXISTS", b"bf", b"a"],
23713            &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
23714            &[b"BF.CARD", b"bf"],
23715            &[b"BF.INFO", b"bf"],
23716            &[b"BF.INFO", b"bf", b"CAPACITY"],
23717            &[b"BF.DEBUG", b"bf"],
23718            &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
23719            &[b"BF.EXISTS", b"made", b"x"],
23720            &[b"BF.SCANDUMP", b"bf", b"0"],
23721            // The cuckoo filter.
23722            &[b"CF.RESERVE", b"cf", b"100"],
23723            &[b"CF.ADD", b"cf", b"a"],
23724            &[b"CF.ADDNX", b"cf", b"a"],
23725            &[b"CF.COUNT", b"cf", b"a"],
23726            &[b"CF.EXISTS", b"cf", b"a"],
23727            &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
23728            &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
23729            &[b"CF.DEL", b"cf", b"a"],
23730            &[b"CF.COMPACT", b"cf"],
23731            &[b"CF.INFO", b"cf"],
23732            &[b"CF.DEBUG", b"cf"],
23733            &[b"CF.SCANDUMP", b"cf", b"0"],
23734            // The count min sketch.
23735            &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
23736            &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
23737            &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
23738            &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
23739            &[b"CMS.INFO", b"cms"],
23740            // The top k sketch.
23741            &[b"TOPK.RESERVE", b"tk", b"3"],
23742            &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
23743            &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
23744            &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
23745            &[b"TOPK.COUNT", b"tk", b"a", b"c"],
23746            &[b"TOPK.LIST", b"tk"],
23747            &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
23748            &[b"TOPK.INFO", b"tk"],
23749            // The t digest.
23750            &[b"TDIGEST.CREATE", b"td"],
23751            &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
23752            &[b"TDIGEST.MIN", b"td"],
23753            &[b"TDIGEST.MAX", b"td"],
23754            &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
23755            &[b"TDIGEST.CDF", b"td", b"3"],
23756            &[b"TDIGEST.RANK", b"td", b"3"],
23757            &[b"TDIGEST.REVRANK", b"td", b"3"],
23758            &[b"TDIGEST.BYRANK", b"td", b"0"],
23759            &[b"TDIGEST.BYREVRANK", b"td", b"0"],
23760            &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
23761            &[b"TDIGEST.INFO", b"td"],
23762            &[b"TDIGEST.RESET", b"td"],
23763            &[b"TDIGEST.MIN", b"td"],
23764            // And the errors.
23765            &[b"SET", b"plain", b"v"],
23766            &[b"BF.ADD", b"plain", b"a"],
23767            &[b"CF.ADD", b"plain", b"a"],
23768            &[b"CMS.QUERY", b"plain", b"a"],
23769            &[b"TOPK.ADD", b"plain", b"a"],
23770            &[b"TDIGEST.ADD", b"plain", b"1"],
23771            &[b"CMS.INFO", b"gone"],
23772            &[b"TOPK.INFO", b"gone"],
23773            &[b"TDIGEST.INFO", b"gone"],
23774        ];
23775
23776        let mut one = Fixture::new();
23777        let mut many = Fixture::striped(8);
23778        for parts in script {
23779            let a = one.run(parts);
23780            let b = many.run(parts);
23781            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23782        }
23783    }
23784
23785    /// The two sketch merges, with their sources on stripes of their own.
23786    ///
23787    /// These are the only two commands in the ten groups that name more than one
23788    /// key, and both read a run of sources and write a destination, so both go
23789    /// wrong in the same way if a merge holds one store and looks every source up
23790    /// in it.
23791    #[test]
23792    fn a_sketch_merge_across_stripes_reads_every_source() {
23793        let mut many = Fixture::striped(8);
23794        let other = apart(&mut many, "s1");
23795        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
23796        let mut one = Fixture::new();
23797        let mut both = |parts: &[&[u8]]| {
23798            let a = one.run(parts);
23799            let b = many.run(parts);
23800            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23801            a
23802        };
23803
23804        // The count min sketch. The destination has to be the sources' shape,
23805        // and it is named first, so all three keys are read before anything is
23806        // written.
23807        for key in [b"cd".as_slice(), s1, s2] {
23808            both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
23809        }
23810        both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
23811        both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
23812        assert_eq!(
23813            both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
23814            "+OK\r\n",
23815            "the merge took both sources"
23816        );
23817        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
23818        // And with weights, which are read against the sources in order.
23819        both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
23820        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23821        // A source that is not a sketch is answered before anything is written.
23822        both(&[b"SET", b"plain", b"v"]);
23823        assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
23824        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23825
23826        // The t digest, which builds its destination and then puts it in place.
23827        // The two source keys are used again here, so what they held goes first.
23828        both(&[b"FLUSHALL"]);
23829        both(&[b"TDIGEST.CREATE", b"td"]);
23830        both(&[b"TDIGEST.CREATE", s1]);
23831        both(&[b"TDIGEST.CREATE", s2]);
23832        both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
23833        both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
23834        assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
23835        assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
23836        assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
23837    }
23838
23839    /// Every shape of `SORT`, on one stripe and on eight.
23840    ///
23841    /// The key it sorts, the keys a `BY` names, the keys a `GET` names and the
23842    /// destination are four different names and nothing lines them up, so on
23843    /// eight stripes this script is reading and writing all over the database
23844    /// while on one it is doing what it always did.
23845    #[test]
23846    fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
23847        let script: &[&[&[u8]]] = &[
23848            &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
23849            &[b"SORT", b"l"],
23850            &[b"SORT", b"l", b"DESC"],
23851            &[b"SORT", b"l", b"ALPHA"],
23852            &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
23853            &[b"SORT_RO", b"l"],
23854            // A weight per element, so the order comes off keys the command
23855            // never named.
23856            &[
23857                b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
23858            ],
23859            &[b"SORT", b"l", b"BY", b"w_*"],
23860            &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
23861            &[b"DEL", b"w_2"],
23862            &[b"SORT", b"l", b"BY", b"w_*"],
23863            // And the answer off another set of keys again, with `#` mixed in
23864            // so the rows are not all lookups.
23865            &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
23866            &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
23867            // A pattern that reaches into a hash, which is another key again.
23868            &[b"HSET", b"h_1", b"f", b"9"],
23869            &[b"HSET", b"h_2", b"f", b"8"],
23870            &[b"HSET", b"h_3", b"f", b"7"],
23871            &[b"HSET", b"h_10", b"f", b"6"],
23872            &[b"SORT", b"l", b"BY", b"h_*->f"],
23873            &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
23874            // The destination, which is a fourth place to land.
23875            &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
23876            &[b"LRANGE", b"out", b"0", b"-1"],
23877            &[b"SORT", b"l", b"STORE", b"l"],
23878            &[b"LRANGE", b"l", b"0", b"-1"],
23879            // An empty result takes the destination away rather than leaving a
23880            // list of nothing behind.
23881            &[b"SORT", b"missing", b"STORE", b"out"],
23882            &[b"EXISTS", b"out"],
23883            // A set and a sorted set sort the same way a list does, and a set
23884            // written to a destination is sorted even when nothing asked.
23885            &[b"SADD", b"s", b"c", b"a", b"b"],
23886            &[b"SORT", b"s", b"ALPHA"],
23887            &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
23888            &[b"LRANGE", b"out", b"0", b"-1"],
23889            &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
23890            &[b"SORT", b"z", b"BY", b"nosort"],
23891            &[b"SORT", b"z", b"ALPHA", b"DESC"],
23892            // And the two ways it refuses: a key of the wrong type, and an
23893            // element that is not a number under a numeric sort.
23894            &[b"SET", b"str", b"v"],
23895            &[b"SORT", b"str"],
23896            &[b"RPUSH", b"words", b"one", b"two"],
23897            &[b"SORT", b"words"],
23898            &[b"SORT_RO", b"l", b"STORE", b"out"],
23899        ];
23900
23901        let mut one = Fixture::new();
23902        let mut many = Fixture::striped(8);
23903        for parts in script {
23904            let a = one.run(parts);
23905            let b = many.run(parts);
23906            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23907        }
23908    }
23909
23910    /// One `SORT` whose four kinds of key are on stripes of their own.
23911    ///
23912    /// The script above spreads keys around by writing enough of them, and this
23913    /// one checks the spread rather than trusting it: the list, the weight key
23914    /// for one of its elements and the destination are asserted to be in three
23915    /// places before the command runs.
23916    #[test]
23917    fn a_sort_across_stripes_reads_every_pattern_key() {
23918        let mut f = Fixture::striped(8);
23919        let out = apart(&mut f, "l");
23920        let (list, dest) = (b"l".as_slice(), out.as_bytes());
23921
23922        f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
23923        f.run(&[
23924            b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
23925        ]);
23926        f.run(&[
23927            b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
23928        ]);
23929
23930        // The weights are four keys and they are not all in one place, which is
23931        // the thing that would go unnoticed if the command held a stripe.
23932        let db = f.server.striped(0);
23933        let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
23934            .iter()
23935            .map(|k| db.stripe_of(k.as_slice()))
23936            .collect();
23937        assert!(
23938            weights.iter().any(|s| *s != weights[0]),
23939            "the four weight keys all landed on one stripe, so this proves nothing"
23940        );
23941
23942        assert_eq!(
23943            f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
23944            "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
23945            "the order came off the weights and the answer off the data keys"
23946        );
23947        assert_eq!(
23948            f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
23949            ":4\r\n"
23950        );
23951        assert_eq!(
23952            f.run(&[b"LRANGE", dest, b"0", b"-1"]),
23953            "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
23954            "the destination is on a stripe of its own and got the whole answer"
23955        );
23956    }
23957
23958    /// A `CONFIG SET` reaches every stripe, so where a key landed does not
23959    /// decide what shape it is stored in.
23960    ///
23961    /// This is the setting that would go wrong quietly. A stripe that kept the
23962    /// old ladder would hold the same hash in a different encoding from the
23963    /// stripe next to it, and the only thing that would ever say so is
23964    /// `OBJECT ENCODING`, which is why the check is on that.
23965    #[test]
23966    fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
23967        let mut f = Fixture::striped(8);
23968        let other = apart(&mut f, "h");
23969        let (first, second) = (b"h".as_slice(), other.as_bytes());
23970
23971        assert_eq!(
23972            f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
23973            "+OK\r\n"
23974        );
23975        assert_eq!(
23976            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
23977            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
23978            "the read comes off one stripe and has to answer for all of them"
23979        );
23980        for key in [first, second] {
23981            f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
23982            assert_eq!(
23983                f.run(&[b"OBJECT", b"ENCODING", key]),
23984                "$8\r\nlistpack\r\n",
23985                "two fields is still under the ladder"
23986            );
23987            f.run(&[b"HSET", key, b"c", b"3"]);
23988            assert_eq!(
23989                f.run(&[b"OBJECT", b"ENCODING", key]),
23990                "$9\r\nhashtable\r\n",
23991                "three fields is over it, on whichever stripe the key is on"
23992            );
23993        }
23994
23995        // And the policy, which every stripe has to agree about for the same
23996        // reason: an eviction draws from one stripe at a time.
23997        assert_eq!(
23998            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
23999            "+OK\r\n"
24000        );
24001        let db = f.server.striped(0);
24002        assert!(
24003            (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
24004            "a stripe kept the old policy"
24005        );
24006    }
24007
24008    /// What an index holds, as the two numbers `FT.INFO` reports about it.
24009    ///
24010    /// Read off the registry rather than parsed back out of an `FT.INFO` reply,
24011    /// because the reply is thirty odd fields and these two are the ones the
24012    /// keyspace hook moves.
24013    fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
24014        let search = f.server.search.lock();
24015        let index = search.named(name).expect("the index is there");
24016        (index.held.docs.len(), index.held.docs.last())
24017    }
24018
24019    /// A hash written under an index's prefix reaches it, and one written
24020    /// outside the prefix does not.
24021    #[test]
24022    fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
24023        let mut f = Fixture::new();
24024        f.run(&[
24025            b"FT.CREATE",
24026            b"ix",
24027            b"PREFIX",
24028            b"1",
24029            b"p:",
24030            b"SCHEMA",
24031            b"t",
24032            b"TEXT",
24033        ]);
24034        f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
24035        assert_eq!(held(&f, b"ix"), (1, 1));
24036        f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
24037        assert_eq!(held(&f, b"ix"), (1, 1));
24038
24039        // Every field of the key and not the one the command named, since a
24040        // document is read from nothing every time.
24041        f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
24042        f.run(&[b"HDEL", b"p:1", b"u"]);
24043        assert_eq!(held(&f, b"ix"), (1, 3));
24044        let search = f.server.search.lock();
24045        let index = search.named(b"ix").expect("there");
24046        assert_eq!(index.held.docs.id(b"p:1"), Some(3));
24047    }
24048
24049    /// A fresh index reads the keys that were already there, and walks past a
24050    /// key of the wrong type without counting a failure.
24051    #[test]
24052    fn a_fresh_index_reads_the_keys_that_were_already_there() {
24053        let mut f = Fixture::new();
24054        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24055        f.run(&[b"SET", b"p:str", b"not a hash"]);
24056        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24057        f.run(&[
24058            b"FT.CREATE",
24059            b"ix",
24060            b"PREFIX",
24061            b"1",
24062            b"p:",
24063            b"SCHEMA",
24064            b"t",
24065            b"TEXT",
24066        ]);
24067
24068        assert_eq!(held(&f, b"ix"), (1, 1));
24069        let search = f.server.search.lock();
24070        let index = search.named(b"ix").expect("there");
24071        assert_eq!(index.trouble.whole().failures(), 0);
24072    }
24073
24074    /// `SKIPINITIALSCAN` leaves what was there alone, and a later write to one
24075    /// of those keys still lands.
24076    #[test]
24077    fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
24078        let mut f = Fixture::new();
24079        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24080        f.run(&[
24081            b"FT.CREATE",
24082            b"ix",
24083            b"PREFIX",
24084            b"1",
24085            b"p:",
24086            b"SKIPINITIALSCAN",
24087            b"SCHEMA",
24088            b"t",
24089            b"TEXT",
24090        ]);
24091        assert_eq!(held(&f, b"ix"), (0, 0));
24092        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24093        assert_eq!(held(&f, b"ix"), (1, 1));
24094    }
24095
24096    /// A command that changed nothing leaves the document where it was, which
24097    /// is not the same as a command that was not a write.
24098    ///
24099    /// All five of these were measured against 8.10.1. Writing the same value
24100    /// again moves the number and a deadline set for later does not, which is
24101    /// the pair that makes the rule "the fields are not what they were" rather
24102    /// than "this was a write".
24103    #[test]
24104    fn only_a_real_change_gives_the_document_a_new_number() {
24105        let mut f = Fixture::new();
24106        f.run(&[
24107            b"FT.CREATE",
24108            b"ix",
24109            b"PREFIX",
24110            b"1",
24111            b"p:",
24112            b"SCHEMA",
24113            b"t",
24114            b"TEXT",
24115        ]);
24116        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24117        assert_eq!(held(&f, b"ix"), (1, 1));
24118
24119        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24120        assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
24121
24122        for quiet in [
24123            vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
24124            vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
24125            vec![b"HGET".as_slice(), b"p:1", b"t"],
24126            vec![b"HGETALL".as_slice(), b"p:1"],
24127            vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
24128            vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
24129            vec![
24130                b"HGETEX".as_slice(),
24131                b"p:1",
24132                b"EX",
24133                b"100",
24134                b"FIELDS",
24135                b"1",
24136                b"t",
24137            ],
24138            vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
24139        ] {
24140            f.run(&quiet);
24141            assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
24142        }
24143
24144        // And the ones that do change something.
24145        f.run(&[b"HSET", b"p:2", b"n", b"1"]);
24146        f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
24147        assert_eq!(held(&f, b"ix"), (2, 4));
24148        // A deadline that has already passed takes the field away, and taking
24149        // the last field away takes the key and the document with it. The
24150        // number still moves on the way past, because the field going and the
24151        // key going are two separate pieces of news and the first of them
24152        // writes the document one last time.
24153        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
24154        assert_eq!(held(&f, b"ix"), (1, 5));
24155    }
24156
24157    /// The two ways of emptying a hash, which do not leave the same thing
24158    /// behind. `HDEL` of the last field spends no number and is counted as a
24159    /// refusal, and a deadline that has already passed spends one on a document
24160    /// nobody sees and is counted as nothing. Measured against 8.10.1 and not
24161    /// something anyone would guess.
24162    #[test]
24163    fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
24164        /// The index's own failure count.
24165        fn refused(f: &Fixture, name: &[u8]) -> u64 {
24166            let search = f.server.search.lock();
24167            let index = search.named(name).expect("the index is there");
24168            index.trouble.whole().failures()
24169        }
24170
24171        let mut f = Fixture::new();
24172        f.run(&[
24173            b"FT.CREATE",
24174            b"ix",
24175            b"PREFIX",
24176            b"1",
24177            b"p:",
24178            b"SCHEMA",
24179            b"t",
24180            b"TEXT",
24181        ]);
24182        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24183        assert_eq!(held(&f, b"ix"), (1, 1));
24184        f.run(&[b"HDEL", b"p:1", b"t"]);
24185        assert_eq!(
24186            held(&f, b"ix"),
24187            (0, 1),
24188            "HDEL of the last field spends none"
24189        );
24190        assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
24191
24192        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24193        assert_eq!(held(&f, b"ix"), (1, 2));
24194        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
24195        assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
24196        assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
24197
24198        f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
24199        assert_eq!(held(&f, b"ix"), (1, 4));
24200        f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
24201        assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
24202
24203        // Two fields and one command is one rewrite and not two, whichever way
24204        // the fields go.
24205        f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
24206        assert_eq!(held(&f, b"ix"), (1, 6));
24207        f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
24208        assert_eq!(held(&f, b"ix"), (0, 7));
24209        assert_eq!(refused(&f, b"ix"), 1);
24210    }
24211
24212    /// `HSETEX` with a deadline that has already passed is two pieces of news
24213    /// from one command, so the number moves twice and the value never reaches
24214    /// the index.
24215    #[test]
24216    fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
24217        let mut f = Fixture::new();
24218        f.run(&[
24219            b"FT.CREATE",
24220            b"ix",
24221            b"PREFIX",
24222            b"1",
24223            b"p:",
24224            b"SCHEMA",
24225            b"t",
24226            b"TEXT",
24227            b"u",
24228            b"TEXT",
24229        ]);
24230        f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
24231        assert_eq!(held(&f, b"ix"), (1, 1));
24232        f.run(&[
24233            b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24234        ]);
24235        assert_eq!(
24236            held(&f, b"ix"),
24237            (1, 3),
24238            "the key lived and the field did not"
24239        );
24240
24241        // And the same when the key does not survive it.
24242        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24243        assert_eq!(held(&f, b"ix"), (2, 4));
24244        f.run(&[
24245            b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24246        ]);
24247        assert_eq!(held(&f, b"ix"), (1, 6));
24248    }
24249
24250    /// The number one key is indexed under, or `None` when it holds no
24251    /// document.
24252    fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
24253        let search = f.server.search.lock();
24254        let index = search.named(name).expect("the index is there");
24255        index.held.docs.id(key)
24256    }
24257
24258    /// An index over `p:` with one document under `p:1`, which is where four of
24259    /// the tests below start.
24260    fn indexed() -> Fixture {
24261        let mut f = Fixture::new();
24262        f.run(&[
24263            b"FT.CREATE",
24264            b"ix",
24265            b"PREFIX",
24266            b"1",
24267            b"p:",
24268            b"SCHEMA",
24269            b"t",
24270            b"TEXT",
24271        ]);
24272        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24273        f
24274    }
24275
24276    /// Every way a keyspace command takes a key away leaves no document behind,
24277    /// and none of them spends a number or is counted as a refusal.
24278    #[test]
24279    fn a_key_a_keyspace_command_takes_away_loses_its_document() {
24280        for take in [
24281            vec![b"DEL".as_slice(), b"p:1"],
24282            vec![b"UNLINK".as_slice(), b"p:1"],
24283            vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
24284            vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
24285        ] {
24286            let mut f = indexed();
24287            assert_eq!(held(&f, b"ix"), (1, 1));
24288            f.run(&take);
24289            assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
24290            let search = f.server.search.lock();
24291            let index = search.named(b"ix").expect("the index is there");
24292            assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
24293        }
24294
24295        // A deadline that has not passed yet is not one of them.
24296        let mut f = indexed();
24297        f.run(&[b"EXPIRE", b"p:1", b"1000"]);
24298        assert_eq!(held(&f, b"ix"), (1, 1));
24299        f.run(&[b"PERSIST", b"p:1"]);
24300        assert_eq!(held(&f, b"ix"), (1, 1));
24301    }
24302
24303    /// A rename inside the prefix keeps the number the document had, which is
24304    /// the one write on a followed key that does not spend one. Out of the
24305    /// prefix is an erase and into it is a fresh reading, both measured.
24306    #[test]
24307    fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
24308        let mut f = indexed();
24309        f.run(&[b"RENAME", b"p:1", b"p:2"]);
24310        assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
24311        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24312        assert_eq!(number(&f, b"ix", b"p:1"), None);
24313
24314        f.run(&[b"RENAME", b"p:2", b"q:1"]);
24315        assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
24316
24317        f.run(&[b"RENAME", b"q:1", b"p:3"]);
24318        assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
24319        assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
24320
24321        // `RENAMENX` goes the same way, and the one that answers zero changes
24322        // nothing.
24323        f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
24324        assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
24325        assert_eq!(held(&f, b"ix"), (2, 3));
24326        f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
24327        assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
24328    }
24329
24330    /// A rename over a key that already had a document leaves one document and
24331    /// not two. A real server leaves both, and D-64 is that difference.
24332    #[test]
24333    fn a_rename_over_a_document_leaves_one_of_them() {
24334        let mut f = indexed();
24335        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24336        assert_eq!(held(&f, b"ix"), (2, 2));
24337        f.run(&[b"RENAME", b"p:1", b"p:2"]);
24338        assert_eq!(held(&f, b"ix"), (1, 2));
24339        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24340    }
24341
24342    /// A key that arrives under the prefix by being copied or restored is read
24343    /// as a new document, and one that is written over by something that is not
24344    /// a hash is erased without a word.
24345    #[test]
24346    fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
24347        let mut f = indexed();
24348        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24349        f.run(&[b"COPY", b"q:1", b"p:2"]);
24350        assert_eq!(held(&f, b"ix"), (2, 2));
24351        assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
24352
24353        // Out of the prefix, where the source keeps the document it had.
24354        f.run(&[b"COPY", b"p:1", b"q:2"]);
24355        assert_eq!(held(&f, b"ix"), (2, 2));
24356
24357        // Over a key that has one, which is a new reading and not a rename.
24358        f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
24359        assert_eq!(held(&f, b"ix"), (2, 3));
24360        assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
24361
24362        // And a string landing on top of a document takes it away, spending no
24363        // number and counting no failure.
24364        f.run(&[b"SET", b"s:1", b"plain"]);
24365        f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
24366        assert_eq!(held(&f, b"ix"), (1, 3));
24367        let dump = f.run(&[b"DUMP", b"q:1"]);
24368        assert!(dump.starts_with('$'), "{dump}");
24369    }
24370
24371    /// The keyspace group reads a key back on database zero whatever database
24372    /// the command ran on, which is measured and is not what the hash commands
24373    /// do. A `COPY` into another database indexes nothing and takes away
24374    /// whatever the destination had, and a `RESTORE` anywhere else is invisible.
24375    #[test]
24376    fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
24377        let mut f = indexed();
24378        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24379        assert_eq!(held(&f, b"ix"), (2, 2));
24380        // Into database one, so the indexes look for `p:2` on database zero,
24381        // find the one that is still there and read it again.
24382        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24383        assert_eq!(held(&f, b"ix"), (2, 3));
24384        // And with nothing under that name on database zero, the copy leaves
24385        // the index one document lighter than it found it.
24386        f.run(&[b"DEL", b"p:2"]);
24387        assert_eq!(held(&f, b"ix"), (1, 3));
24388        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24389        assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
24390
24391        // A restore on another database is the same story.
24392        let dump = f.run(&[b"DUMP", b"p:1"]);
24393        assert!(dump.starts_with('$'), "{dump}");
24394        f.run(&[b"SELECT", b"1"]);
24395        f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
24396        f.run(&[b"RENAME", b"q:1", b"p:3"]);
24397        assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
24398    }
24399
24400    /// `MOVE` is not a change at all, because an index follows a key by name
24401    /// and a write on any database still reaches it.
24402    #[test]
24403    fn a_move_leaves_the_document_where_it_is() {
24404        let mut f = indexed();
24405        f.run(&[b"MOVE", b"p:1", b"1"]);
24406        assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
24407        assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
24408
24409        f.run(&[b"SELECT", b"1"]);
24410        f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
24411        assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
24412        f.run(&[b"DEL", b"p:1"]);
24413        assert_eq!(held(&f, b"ix"), (0, 2));
24414    }
24415
24416    /// A flush takes every index with it, whichever database it flushed.
24417    #[test]
24418    fn a_flush_drops_the_indexes() {
24419        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24420            let mut f = indexed();
24421            f.run(&[flush]);
24422            assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
24423            assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
24424        }
24425
24426        // Even on a database no index ever read, which is what a real server
24427        // does and is not what anyone would guess.
24428        let mut f = indexed();
24429        f.run(&[b"SELECT", b"9"]);
24430        f.run(&[b"FLUSHDB"]);
24431        assert!(f.server.search.lock().is_empty());
24432    }
24433
24434    /// An index whose schema has one tag field of each kind, plus a number so
24435    /// there is something for `FT.TAGVALS` to refuse.
24436    fn tagged() -> Fixture {
24437        let mut f = Fixture::new();
24438        f.run(&[
24439            b"FT.CREATE",
24440            b"tv",
24441            b"PREFIX",
24442            b"1",
24443            b"tv:",
24444            b"SCHEMA",
24445            b"g",
24446            b"AS",
24447            b"gg",
24448            b"TAG",
24449            b"h",
24450            b"TAG",
24451            b"SEPARATOR",
24452            b"|",
24453            b"CASESENSITIVE",
24454            b"n",
24455            b"NUMERIC",
24456        ]);
24457        f.run(&[
24458            b"HSET",
24459            b"tv:1",
24460            b"g",
24461            b"Red, BLUE ",
24462            b"h",
24463            b"Aa|bB",
24464            b"n",
24465            b"1",
24466        ]);
24467        f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
24468        f
24469    }
24470
24471    /// The values come back as they are stored, so an ordinary tag field
24472    /// answers them folded and trimmed and a `CASESENSITIVE` one answers what
24473    /// it was given. Byte order either way, which puts the capital first.
24474    #[test]
24475    fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
24476        let mut f = tagged();
24477        assert_eq!(
24478            f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
24479            "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
24480        );
24481        assert_eq!(
24482            f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
24483            "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
24484        );
24485    }
24486
24487    /// The name asked about is the attribute, so the identifier of a field
24488    /// declared `AS` is not a name this knows.
24489    #[test]
24490    fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
24491        let mut f = tagged();
24492        for (name, want) in [
24493            (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
24494            (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
24495            (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
24496        ] {
24497            assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
24498        }
24499        assert_eq!(
24500            f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
24501            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24502        );
24503    }
24504
24505    /// Looking up the index counts as a use of it on the roads that refuse the
24506    /// field as well as on the one that answers, which is measured.
24507    #[test]
24508    fn asking_for_tag_values_counts_a_use_of_the_index() {
24509        let mut f = tagged();
24510        let uses = |f: &mut Fixture| {
24511            let reply = f.run(&[b"FT.INFO", b"tv"]);
24512            let at = reply.find("number_of_uses").expect("the field is reported");
24513            let value = reply[at..].split("\r\n").nth(1).unwrap();
24514            value.trim_start_matches(':').parse::<i64>().unwrap()
24515        };
24516        let before = uses(&mut f);
24517        f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
24518        f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
24519        // Three more than before: two tag lookups and the second `FT.INFO`.
24520        assert_eq!(uses(&mut f), before + 3);
24521    }
24522
24523    /// A tag field nothing was ever written to has no list at all, which
24524    /// answers the same empty set a list that has been emptied does.
24525    #[test]
24526    fn a_tag_field_with_nothing_in_it_answers_empty() {
24527        let mut f = Fixture::new();
24528        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
24529        assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
24530    }
24531
24532    /// A dictionary is module state and not a key, so nothing in the keyspace
24533    /// can see one.
24534    #[test]
24535    fn a_dictionary_is_not_a_key() {
24536        let mut f = Fixture::new();
24537        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
24538        assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
24539        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
24540        assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
24541    }
24542
24543    /// The count is how many terms were new, an empty term is not a term, and
24544    /// the dump is sorted by bytes rather than folded.
24545    #[test]
24546    fn a_dictionary_counts_the_terms_it_had_not_seen() {
24547        let mut f = Fixture::new();
24548        assert_eq!(
24549            f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
24550            ":3\r\n"
24551        );
24552        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
24553        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
24554        assert_eq!(
24555            f.run(&[b"FT.DICTDUMP", b"d"]),
24556            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
24557        );
24558        assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
24559    }
24560
24561    /// A name nobody ever added to is not an error on either of the two
24562    /// commands that will take one, which is the only place in the group where
24563    /// a missing name is forgiven.
24564    #[test]
24565    fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
24566        let mut f = Fixture::new();
24567        assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
24568        assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
24569    }
24570
24571    /// The dictionaries go when the keyspace does, the same way the indexes do.
24572    #[test]
24573    fn a_flush_drops_the_dictionaries() {
24574        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24575            let mut f = Fixture::new();
24576            f.run(&[b"FT.DICTADD", b"d", b"a"]);
24577            f.run(&[flush]);
24578            assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
24579        }
24580    }
24581
24582    // -------------------------------------------------------------- profile
24583
24584    /// A fixture holding one index over three documents, two of which hold the
24585    /// first word and two the second.
24586    fn profiling() -> Fixture {
24587        let mut f = Fixture::new();
24588        f.run(&[
24589            b"FT.CREATE",
24590            b"ix",
24591            b"PREFIX",
24592            b"1",
24593            b"p:",
24594            b"SCHEMA",
24595            b"t",
24596            b"TEXT",
24597            b"n",
24598            b"NUMERIC",
24599        ]);
24600        f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
24601        f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
24602        f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
24603        f
24604    }
24605
24606    /// The reply with every time taken out of it, since no two runs agree on
24607    /// those and everything else about a profile is exact.
24608    fn timeless(reply: &str) -> String {
24609        const KEYS: &[&str] = &[
24610            "+Total profile time",
24611            "+Parsing time",
24612            "+Workers queue time",
24613            "+Pipeline creation time",
24614            "+Time",
24615        ];
24616        let mut out = String::new();
24617        let mut parts = reply.split("\r\n").peekable();
24618        while let Some(part) = parts.next() {
24619            out.push_str(part);
24620            out.push_str("\r\n");
24621            if !KEYS.contains(&part) {
24622                continue;
24623            }
24624            // A double is one line on RESP3 and a bulk header and its digits on
24625            // RESP2, and both of them stand for the same one value.
24626            match parts.next() {
24627                Some(head) if head.starts_with('$') => {
24628                    parts.next();
24629                }
24630                _ => {}
24631            }
24632            out.push_str("<t>\r\n");
24633        }
24634        // The split leaves an empty piece past the last line ending.
24635        out.truncate(out.len() - 2);
24636        out
24637    }
24638
24639    /// The whole envelope on both protocols, which is a two element array on
24640    /// one and a two key map on the other.
24641    #[test]
24642    fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
24643        let mut f = profiling();
24644        assert_eq!(
24645            timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
24646            "*2\r\n\
24647             *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\
24648             $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\
24649             *4\r\n+Shards\r\n*1\r\n*14\r\n\
24650             +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
24651             +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
24652             +Warning\r\n*1\r\n+None\r\n\
24653             +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24654             +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24655             +Estimated number of matches\r\n:2\r\n\
24656             +Result processors profile\r\n*4\r\n\
24657             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24658             *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24659             *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24660             *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24661             +Coordinator\r\n*0\r\n"
24662        );
24663        let mut g = profiling();
24664        g.run(&[b"HELLO", b"3"]);
24665        let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
24666        assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
24667        assert!(
24668            three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
24669            "{three}"
24670        );
24671        assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
24672        assert!(
24673            three.contains(
24674                "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24675                 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24676                 +Estimated number of matches\r\n:2\r\n"
24677            ),
24678            "{three}"
24679        );
24680    }
24681
24682    /// Every kind of step names itself, and the three that hold other steps say
24683    /// so in the singular or the plural depending on how many they hold.
24684    #[test]
24685    fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
24686        let mut f = profiling();
24687        let tree = |f: &mut Fixture, query: &[u8]| {
24688            let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
24689            let at = reply.find("+Iterators profile").expect("a tree");
24690            let end = reply.find("+Result processors").expect("a list of steps");
24691            reply[at..end].to_string()
24692        };
24693        assert_eq!(
24694            tree(&mut f, b"alpha beta"),
24695            "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
24696             +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
24697             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
24698             +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
24699             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
24700             +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
24701        );
24702        assert!(tree(&mut f, b"alpha|beta").starts_with(
24703            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
24704             +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
24705        ));
24706        // One thing under it, named in the singular, which is a different key
24707        // and not a list holding one.
24708        assert!(tree(&mut f, b"-alpha").starts_with(
24709            "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
24710             +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
24711        ));
24712        assert!(tree(&mut f, b"~alpha").starts_with(
24713            "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
24714             +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
24715        ));
24716        // No guess at how many, which is the one leaf that leaves it off.
24717        assert_eq!(
24718            tree(&mut f, b"*"),
24719            "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
24720             +Number of reading operations\r\n:3\r\n"
24721        );
24722        assert!(tree(&mut f, b"@n:[1 2]").starts_with(
24723            "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
24724             $19\r\n1.000000 - 2.000000\r\n"
24725        ));
24726    }
24727
24728    /// A union an expansion made folds into a count of its branches and a union
24729    /// a client wrote with a bar does not.
24730    #[test]
24731    fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
24732        let mut f = profiling();
24733        f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
24734        let tree = |f: &mut Fixture, words: &[&[u8]]| {
24735            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
24736            argv.extend_from_slice(words);
24737            let reply = timeless(&f.run(&argv));
24738            let at = reply.find("+Iterators profile").expect("a tree");
24739            let end = reply.find("+Result processors").expect("a list of steps");
24740            reply[at..end].to_string()
24741        };
24742        assert_eq!(
24743            tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
24744            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
24745             +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
24746             +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
24747             +The number of iterators in the union is 2\r\n"
24748        );
24749        assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
24750        assert!(
24751            tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
24752                .contains("+Child iterators\r\n*2\r\n")
24753        );
24754        // A union that says nothing but its own name says it as a status, and
24755        // one that says what it stood for says that as a string. Measured, and
24756        // it is the one place in this reply where the two are told apart.
24757        assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
24758        assert!(
24759            tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
24760        );
24761    }
24762
24763    /// Which steps a search runs the rows through, which turns on the window,
24764    /// on whether anything asked for the fields and on what the order is.
24765    #[test]
24766    fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
24767        let mut f = profiling();
24768        let steps = |f: &mut Fixture, words: &[&[u8]]| {
24769            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
24770            argv.extend_from_slice(words);
24771            let reply = timeless(&f.run(&argv));
24772            let at = reply.find("+Result processors").expect("a list of steps");
24773            let end = reply.find("+Coordinator").expect("an end");
24774            let mut out = Vec::new();
24775            let mut parts = reply[at..end].split("\r\n").peekable();
24776            while let Some(part) = parts.next() {
24777                if part == "+Type" {
24778                    out.push(parts.next().unwrap_or_default().to_string());
24779                }
24780            }
24781            out
24782        };
24783        assert_eq!(
24784            steps(&mut f, &[]),
24785            ["+Index", "+Scorer", "+Sorter", "+Loader"]
24786        );
24787        assert_eq!(
24788            steps(&mut f, &[b"NOCONTENT"]),
24789            ["+Index", "+Scorer", "+Sorter"]
24790        );
24791        // A window of nothing is a client asking for the total and nothing
24792        // else, so nothing is scored and nothing is sorted.
24793        assert_eq!(
24794            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
24795            ["+Index", "+Counter"]
24796        );
24797        // A sort by a field does not need a score, and asking for the scores
24798        // puts the step back.
24799        assert_eq!(
24800            steps(&mut f, &[b"SORTBY", b"n"]),
24801            ["+Index", "+Sorter", "+Loader"]
24802        );
24803        assert_eq!(
24804            steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
24805            ["+Index", "+Scorer", "+Sorter", "+Loader"]
24806        );
24807        assert_eq!(
24808            steps(&mut f, &[b"HIGHLIGHT"]),
24809            ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
24810        );
24811        assert_eq!(
24812            steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
24813            ["+Index", "+Scorer", "+Sorter"]
24814        );
24815    }
24816
24817    /// A pipeline names each of its steps after the expression it runs, which
24818    /// is what a real server prints beside them.
24819    #[test]
24820    fn a_pipeline_names_every_step_after_what_it_runs() {
24821        let mut f = profiling();
24822        let steps = |f: &mut Fixture, words: &[&[u8]]| {
24823            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
24824            argv.extend_from_slice(words);
24825            let reply = timeless(&f.run(&argv));
24826            let at = reply.find("+Result processors").expect("a list of steps");
24827            let end = reply.find("+Coordinator").expect("an end");
24828            let mut out = Vec::new();
24829            let mut parts = reply[at..end].split("\r\n").peekable();
24830            while let Some(part) = parts.next() {
24831                if part == "+Type" {
24832                    out.push(parts.next().unwrap_or_default().to_string());
24833                }
24834            }
24835            out
24836        };
24837        assert_eq!(steps(&mut f, &[]), ["+Index"]);
24838        assert_eq!(
24839            steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
24840            ["+Index", "+Projector - Literal 1"]
24841        );
24842        assert_eq!(
24843            steps(
24844                &mut f,
24845                &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
24846            ),
24847            ["+Index", "+Loader", "+Projector - Operator *"]
24848        );
24849        assert_eq!(
24850            steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
24851            ["+Index", "+Loader", "+Filter - Predicate >"]
24852        );
24853        assert_eq!(
24854            steps(
24855                &mut f,
24856                &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
24857            ),
24858            ["+Index", "+Loader", "+Grouper"]
24859        );
24860        assert_eq!(
24861            steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
24862            ["+Index", "+Loader", "+Sorter"]
24863        );
24864        assert_eq!(
24865            steps(&mut f, &[b"LIMIT", b"0", b"2"]),
24866            ["+Index", "+Pager/Limiter"]
24867        );
24868        // Asking for the score by name is a step of its own, and it goes in
24869        // front of the read rather than after it.
24870        assert_eq!(
24871            steps(
24872                &mut f,
24873                &[
24874                    b"ADDSCORES",
24875                    b"LOAD",
24876                    b"1",
24877                    b"@n",
24878                    b"APPLY",
24879                    b"@__score",
24880                    b"AS",
24881                    b"s"
24882                ]
24883            ),
24884            [
24885                "+Index",
24886                "+Scorer",
24887                "+Loader",
24888                "+Projector - Property __score"
24889            ]
24890        );
24891    }
24892
24893    /// A field the schema marked sortable is held beside the document number,
24894    /// so a pipeline that only names those never opens a key and never reports
24895    /// a read.
24896    ///
24897    /// Measured: on a schema of `n NUMERIC SORTABLE g TAG`, `LOAD 1 @n` has no
24898    /// `Loader` step and `LOAD 1 @g` has one. So does `LOAD *`, because what a
24899    /// key turns out to hold is not knowable without opening it.
24900    #[test]
24901    fn a_sortable_field_is_read_without_the_key_being_opened() {
24902        let mut f = Fixture::new();
24903        f.run(&[
24904            b"FT.CREATE",
24905            b"sx",
24906            b"PREFIX",
24907            b"1",
24908            b"s:",
24909            b"SCHEMA",
24910            b"n",
24911            b"NUMERIC",
24912            b"SORTABLE",
24913            b"g",
24914            b"TAG",
24915        ]);
24916        f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
24917        f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
24918        let loads = |f: &mut Fixture, words: &[&[u8]]| {
24919            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
24920            argv.extend_from_slice(words);
24921            f.run(&argv).contains("+Loader")
24922        };
24923        assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
24924        assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
24925        assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
24926        assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
24927        assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
24928        assert!(loads(
24929            &mut f,
24930            &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
24931        ));
24932        assert!(loads(&mut f, &[b"LOAD", b"*"]));
24933    }
24934
24935    /// The four ways the words can be wrong, none of which reaches the search
24936    /// underneath.
24937    #[test]
24938    fn a_profile_checks_its_own_words_before_it_runs_anything() {
24939        let mut f = profiling();
24940        assert_eq!(
24941            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
24942            "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
24943        );
24944        assert_eq!(
24945            f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
24946            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24947        );
24948        // The word goes between the two and nowhere else, so one written in
24949        // front of them is not the word at all.
24950        assert_eq!(
24951            f.run(&[
24952                b"FT.PROFILE",
24953                b"ix",
24954                b"LIMITED",
24955                b"SEARCH",
24956                b"QUERY",
24957                b"alpha"
24958            ]),
24959            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24960        );
24961        assert_eq!(
24962            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
24963            "-The QUERY keyword is expected\r\n"
24964        );
24965        assert_eq!(
24966            f.run(&[
24967                b"FT.PROFILE",
24968                b"ix",
24969                b"AGGREGATE",
24970                b"QUERY",
24971                b"alpha",
24972                b"WITHCURSOR"
24973            ]),
24974            "-FT.PROFILE does not support cursor\r\n"
24975        );
24976        // And what the search itself complains about comes back on its own,
24977        // without an envelope around it saying the command worked.
24978        assert_eq!(
24979            f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
24980            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24981        );
24982        assert_eq!(
24983            f.run(&[
24984                b"FT.PROFILE",
24985                b"ix",
24986                b"SEARCH",
24987                b"QUERY",
24988                b"alpha",
24989                b"extra"
24990            ]),
24991            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
24992        );
24993    }
24994
24995    /// Every word of the command's own is read without regard to case.
24996    #[test]
24997    fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
24998        let mut f = profiling();
24999        let one = f.run(&[
25000            b"FT.PROFILE",
25001            b"ix",
25002            b"search",
25003            b"limited",
25004            b"query",
25005            b"alpha",
25006        ]);
25007        let two = f.run(&[
25008            b"FT.PROFILE",
25009            b"ix",
25010            b"SEARCH",
25011            b"LIMITED",
25012            b"QUERY",
25013            b"alpha",
25014        ]);
25015        assert_eq!(timeless(&one), timeless(&two));
25016    }
25017
25018    // -------------------------------------------------------------- dropping
25019
25020    /// The two spellings take opposite defaults, which is measured and is the
25021    /// only difference between them that a client can see.
25022    #[test]
25023    fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
25024        let mut f = profiling();
25025        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
25026        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25027
25028        let mut f = profiling();
25029        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25030        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25031
25032        let mut f = profiling();
25033        assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
25034        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25035
25036        let mut f = profiling();
25037        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
25038        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25039    }
25040
25041    /// Each spelling takes its own word and refuses the other one's, which
25042    /// reads as an oversight and is what a real server answers.
25043    #[test]
25044    fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
25045        let mut f = profiling();
25046        let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
25047        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
25048        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
25049        // Refused rather than half done, so the index is still there.
25050        assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
25051    }
25052
25053    /// Only what the index read is deleted, which is not the same as
25054    /// everything under its prefix.
25055    #[test]
25056    fn dropping_the_documents_leaves_a_key_the_index_never_read() {
25057        let mut f = profiling();
25058        f.run(&[b"SET", b"p:4", b"alpha"]);
25059        f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
25060        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25061        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25062        assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
25063    }
25064
25065    /// An index still standing over the same keys hears about them going,
25066    /// rather than answering later with keys that are not there.
25067    #[test]
25068    fn another_index_over_the_same_keys_loses_the_documents_too() {
25069        let mut f = profiling();
25070        f.run(&[
25071            b"FT.CREATE",
25072            b"other",
25073            b"PREFIX",
25074            b"1",
25075            b"p:",
25076            b"SCHEMA",
25077            b"t",
25078            b"TEXT",
25079        ]);
25080        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25081        assert_eq!(
25082            f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
25083            "*1\r\n:0\r\n"
25084        );
25085    }
25086
25087    /// A drop that found nothing to drop deletes nothing either, which is the
25088    /// one case where the shortcut spelling answers `OK` without a sweep.
25089    #[test]
25090    fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
25091        let mut f = profiling();
25092        assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
25093        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25094        assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
25095        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25096    }
25097
25098    // --------------------------------------------------------------- config
25099
25100    /// The two shapes a dump comes back in, which are the one mix of simple
25101    /// strings and bulk strings the group sends.
25102    #[test]
25103    fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
25104        let mut f = Fixture::new();
25105        assert_eq!(
25106            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25107            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25108        );
25109        assert_eq!(
25110            f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25111            "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
25112        );
25113        let mut g = Fixture::new();
25114        g.run(&[b"HELLO", b"3"]);
25115        assert_eq!(
25116            g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25117            "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25118        );
25119        assert_eq!(
25120            g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25121            "%1\r\n+EXTLOAD\r\n_\r\n"
25122        );
25123    }
25124
25125    /// The help text rides along in the middle of the same row, flat on RESP2
25126    /// and as a map of its own on RESP3.
25127    #[test]
25128    fn a_help_row_carries_the_description_and_the_value_together() {
25129        let mut f = Fixture::new();
25130        assert_eq!(
25131            f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25132            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25133             +Value\r\n$3\r\n500\r\n"
25134        );
25135        let mut g = Fixture::new();
25136        g.run(&[b"HELLO", b"3"]);
25137        assert_eq!(
25138            g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25139            "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
25140             +Value\r\n$3\r\n500\r\n"
25141        );
25142    }
25143
25144    /// A name is matched whole, ignoring case, and the single word star is the
25145    /// only thing that means all of them.
25146    #[test]
25147    fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
25148        let mut f = Fixture::new();
25149        assert_eq!(
25150            f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
25151            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25152        );
25153        for name in [
25154            b"TIMEOUT*".as_slice(),
25155            b"?IMEOUT",
25156            b"*TIMEOUT*",
25157            b"TIME",
25158            b"NOSUCH",
25159            b"",
25160        ] {
25161            assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
25162        }
25163        assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
25164        assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
25165    }
25166
25167    /// Words after the name are stepped over rather than refused, on both of
25168    /// the two reads.
25169    #[test]
25170    fn a_read_ignores_whatever_follows_the_name() {
25171        let mut f = Fixture::new();
25172        assert_eq!(
25173            f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
25174            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25175        );
25176        assert_eq!(
25177            f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
25178            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25179             +Value\r\n$3\r\n500\r\n"
25180        );
25181    }
25182
25183    /// The container reports its own name and the subcommand it was given in
25184    /// the two lines the dispatcher writes.
25185    #[test]
25186    fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
25187        let mut f = Fixture::new();
25188        assert_eq!(
25189            f.run(&[b"FT.CONFIG"]),
25190            "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
25191        );
25192        for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
25193            let want = format!(
25194                "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
25195                String::from_utf8_lossy(sub)
25196            );
25197            assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
25198        }
25199        assert_eq!(
25200            f.run(&[b"ft.config", b"get"]),
25201            "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
25202        );
25203        assert_eq!(
25204            f.run(&[b"FT.CONFIG", b"bogus"]),
25205            "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
25206        );
25207    }
25208
25209    /// The name, then whether it can move, then the value, then the count of
25210    /// words, and each of the first three answers before the next is looked at.
25211    #[test]
25212    fn a_write_checks_the_name_then_the_setting_then_the_value() {
25213        let mut f = Fixture::new();
25214        for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
25215            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
25216            cmd.extend(tail);
25217            assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
25218        }
25219        for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
25220            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
25221            cmd.extend(tail);
25222            assert_eq!(
25223                f.run(&cmd),
25224                "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
25225            );
25226        }
25227        assert_eq!(
25228            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
25229            "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
25230        );
25231    }
25232
25233    /// Too many words is a status and not an error, and the value has already
25234    /// been written by the time it goes out.
25235    #[test]
25236    fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
25237        let mut f = Fixture::new();
25238        assert_eq!(
25239            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
25240            "+OK\r\n"
25241        );
25242        assert_eq!(
25243            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
25244            "+EXCESSARGS\r\n"
25245        );
25246        assert_eq!(
25247            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25248            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
25249        );
25250    }
25251
25252    /// Strictly first and loosely second, so a hexadecimal and a leading zero
25253    /// and an exponent all land and a fraction does not.
25254    #[test]
25255    fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
25256        let mut f = Fixture::new();
25257        for (given, want) in [
25258            (b"0x10".as_slice(), "16"),
25259            (b"0X1f", "31"),
25260            (b"+0x10", "16"),
25261            (b"+5", "5"),
25262            (b"010", "10"),
25263            (b"08", "8"),
25264            (b"0777", "777"),
25265            (b"1e3", "1000"),
25266            (b"0.0", "0"),
25267            (b"-0.0", "0"),
25268        ] {
25269            assert_eq!(
25270                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25271                "+OK\r\n",
25272                "{given:?}"
25273            );
25274            let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
25275            assert_eq!(
25276                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25277                want,
25278                "{given:?}"
25279            );
25280        }
25281        for given in [
25282            b" 5".as_slice(),
25283            b"5 ",
25284            b"1.5",
25285            b"1e-3",
25286            b"x",
25287            b"",
25288            b"0b11",
25289            b"0xg",
25290            b"nan",
25291            b"inf",
25292            b"1e100",
25293            b"99999999999999999999",
25294        ] {
25295            assert_eq!(
25296                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25297                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25298                "{given:?}"
25299            );
25300        }
25301    }
25302
25303    /// Which of the two readers found a negative decides what it is told, and
25304    /// on a setting with no range at all neither of them is refused.
25305    #[test]
25306    fn a_negative_is_answered_by_whichever_reader_found_it() {
25307        let mut f = Fixture::new();
25308        for given in [b"-1".as_slice(), b"-16"] {
25309            assert_eq!(
25310                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25311                "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
25312                "{given:?}"
25313            );
25314        }
25315        for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
25316            assert_eq!(
25317                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25318                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25319                "{given:?}"
25320            );
25321        }
25322        let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
25323        for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
25324            assert_eq!(
25325                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25326                "+OK\r\n",
25327                "{given:?}"
25328            );
25329            assert_eq!(
25330                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25331                unlimited,
25332                "{given:?}"
25333            );
25334        }
25335    }
25336
25337    /// The two settings with no range truncate into a signed thirty two bit
25338    /// slot and say so once the number has gone under.
25339    #[test]
25340    fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
25341        let mut f = Fixture::new();
25342        for (given, want) in [
25343            (b"2147483647".as_slice(), "2147483647"),
25344            (b"2147483648", "unlimited"),
25345            (b"4294967295", "unlimited"),
25346            (b"9223372036854775806", "unlimited"),
25347            (b"0", "0"),
25348        ] {
25349            assert_eq!(
25350                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25351                "+OK\r\n",
25352                "{given:?}"
25353            );
25354            let want = format!(
25355                "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
25356                want.len()
25357            );
25358            assert_eq!(
25359                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25360                want,
25361                "{given:?}"
25362            );
25363        }
25364    }
25365
25366    /// A number past what a setting will take says which way it went, and the
25367    /// ones with a softer roof of their own say what that roof is about.
25368    #[test]
25369    fn a_number_out_of_range_names_the_limit_it_crossed() {
25370        let mut f = Fixture::new();
25371        let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
25372        for (name, given) in [
25373            (b"MINPREFIX".as_slice(), b"0".as_slice()),
25374            (b"MAX_AGGREGATE_GROUPS", b"0"),
25375            (b"BM25STD_TANH_FACTOR", b"0"),
25376            (b"DEFAULT_DIALECT", b"0"),
25377            (b"MINSTEMLEN", b"4294967296"),
25378            (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
25379            (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
25380            (b"CONNECT_TIMEOUT", b"2147483648"),
25381        ] {
25382            assert_eq!(
25383                f.run(&[b"FT.CONFIG", b"SET", name, given]),
25384                bounds,
25385                "{name:?}"
25386            );
25387        }
25388        for (name, given, want) in [
25389            (
25390                b"MINSTEMLEN".as_slice(),
25391                b"1".as_slice(),
25392                "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
25393            ),
25394            (
25395                b"MAX_AGGREGATE_GROUPS",
25396                b"67108865",
25397                "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
25398            ),
25399            (
25400                b"WORKERS",
25401                b"17",
25402                "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
25403            ),
25404            (
25405                b"_NUMERIC_RANGES_PARENTS",
25406                b"3",
25407                "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
25408                 depth for balance\r\n",
25409            ),
25410            (
25411                b"DEFAULT_DIALECT",
25412                b"5",
25413                "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
25414            ),
25415            (
25416                b"_BG_INDEX_MEM_PCT_THR",
25417                b"101",
25418                "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
25419                 100%\r\n",
25420            ),
25421            (
25422                b"BM25STD_TANH_FACTOR",
25423                b"10001",
25424                "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
25425                 inclusive\r\n",
25426            ),
25427            (
25428                b"BG_INDEX_SLEEP_DURATION_US",
25429                b"1000000",
25430                "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
25431                 999999 (usleep POSIX limit)\r\n",
25432            ),
25433        ] {
25434            assert_eq!(
25435                f.run(&[b"FT.CONFIG", b"SET", name, given]),
25436                want,
25437                "{name:?}"
25438            );
25439        }
25440    }
25441
25442    /// The two trimming delays are measured against each other, and the answer
25443    /// names both settings and both numbers.
25444    #[test]
25445    fn the_trimming_delays_are_checked_against_one_another() {
25446        let mut f = Fixture::new();
25447        assert_eq!(
25448            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
25449            "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
25450             _MAX_TRIM_DELAY_MS (5000)\r\n"
25451        );
25452        assert_eq!(
25453            f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
25454            "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
25455             _MIN_TRIM_DELAY_MS (2000)\r\n"
25456        );
25457        assert_eq!(
25458            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
25459            "+OK\r\n"
25460        );
25461    }
25462
25463    /// Two of the word settings fold the spelling on the way in and the scorer
25464    /// does not, which is the one place in the table case counts.
25465    #[test]
25466    fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
25467        let mut f = Fixture::new();
25468        assert_eq!(
25469            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
25470            "+OK\r\n"
25471        );
25472        assert_eq!(
25473            f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
25474            "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
25475        );
25476        assert_eq!(
25477            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
25478            "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
25479        );
25480        assert_eq!(
25481            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
25482            "+OK\r\n"
25483        );
25484        assert_eq!(
25485            f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
25486            "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
25487        );
25488        assert_eq!(
25489            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
25490            "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
25491        );
25492        let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
25493        for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
25494            assert_eq!(
25495                f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
25496                bad,
25497                "{given:?}"
25498            );
25499        }
25500        assert_eq!(
25501            f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
25502            "+OK\r\n"
25503        );
25504    }
25505
25506    /// True and false, either case, and none of the other words a client might
25507    /// reach for.
25508    #[test]
25509    fn a_yes_or_no_setting_takes_those_two_words_only() {
25510        let mut f = Fixture::new();
25511        assert_eq!(
25512            f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
25513            "+OK\r\n"
25514        );
25515        assert_eq!(
25516            f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
25517            "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
25518        );
25519        for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
25520            assert_eq!(
25521                f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
25522                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25523                "{given:?}"
25524            );
25525        }
25526    }
25527
25528    /// Two pairs of names sit over one number each, and one of that second pair
25529    /// takes no value at all.
25530    #[test]
25531    fn two_names_for_one_setting_move_together() {
25532        let mut f = Fixture::new();
25533        f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
25534        assert_eq!(
25535            f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
25536            "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
25537        );
25538        f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
25539        assert_eq!(
25540            f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
25541            "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
25542        );
25543        let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25544        let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25545        f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
25546        assert_eq!(
25547            f.run(&[b"FT.CONFIG", b"GET", short]),
25548            "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
25549        );
25550        assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
25551        assert_eq!(
25552            f.run(&[b"FT.CONFIG", b"GET", long]),
25553            "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
25554        );
25555    }
25556
25557    /// The one setting that takes a write and never gives it back.
25558    #[test]
25559    fn a_password_reads_back_as_stars_whatever_was_written() {
25560        let mut f = Fixture::new();
25561        assert_eq!(
25562            f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
25563            "+OK\r\n"
25564        );
25565        assert_eq!(
25566            f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
25567            "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
25568        );
25569    }
25570
25571    /// The settings are not in the keyspace, so unlike the dictionaries and the
25572    /// synonym groups beside them they live through an emptied one.
25573    #[test]
25574    fn a_flush_leaves_the_settings_alone() {
25575        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
25576            let mut f = Fixture::new();
25577            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
25578            f.run(&[flush]);
25579            assert_eq!(
25580                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25581                "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
25582                "{flush:?}"
25583            );
25584        }
25585    }
25586
25587    // ---------------------------------------------------------------- debug
25588
25589    /// A small index with one of everything a dump can read, so the tests below
25590    /// all name the same three documents and the same four fields.
25591    fn debugging() -> Fixture {
25592        let mut f = Fixture::new();
25593        f.run(&[
25594            b"FT.CREATE",
25595            b"dx",
25596            b"PREFIX",
25597            b"1",
25598            b"d:",
25599            b"SCHEMA",
25600            b"t",
25601            b"TEXT",
25602            b"g",
25603            b"TAG",
25604            b"n",
25605            b"NUMERIC",
25606            b"s",
25607            b"TEXT",
25608            b"SORTABLE",
25609        ]);
25610        f.run(&[
25611            b"HSET",
25612            b"d:1",
25613            b"t",
25614            b"running dogs",
25615            b"g",
25616            b"red,blue",
25617            b"n",
25618            b"1",
25619            b"s",
25620            b"Alpha",
25621        ]);
25622        f.run(&[
25623            b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
25624        ]);
25625        f.run(&[
25626            b"HSET",
25627            b"d:3",
25628            b"t",
25629            b"dogs alpha",
25630            b"g",
25631            b"green",
25632            b"n",
25633            b"3",
25634        ]);
25635        f
25636    }
25637
25638    /// The whole dictionary in byte order, with the stems in it as entries of
25639    /// their own rather than hidden behind the words they came from.
25640    #[test]
25641    fn a_term_dump_lists_the_stems_beside_the_words() {
25642        let mut f = debugging();
25643        assert_eq!(
25644            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
25645            "*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\
25646             $4\r\ndogs\r\n$7\r\nrunning\r\n"
25647        );
25648    }
25649
25650    /// A posting list is looked up on the bytes given and nothing folds them, so
25651    /// the term that a query would have found is not the term a dump wants.
25652    #[test]
25653    fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
25654        let mut f = debugging();
25655        assert_eq!(
25656            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25657            "*2\r\n:1\r\n:2\r\n"
25658        );
25659        assert_eq!(
25660            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
25661            "*2\r\n:1\r\n:2\r\n"
25662        );
25663        for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
25664            assert_eq!(
25665                f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
25666                "-Can not find the inverted index\r\n",
25667                "{term:?}"
25668            );
25669        }
25670    }
25671
25672    /// Tag values come back folded and in byte order, each with the documents
25673    /// that hold it, and a document with two values is under both of them.
25674    #[test]
25675    fn a_tag_dump_pairs_every_value_with_its_documents() {
25676        let mut f = debugging();
25677        assert_eq!(
25678            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
25679            "*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\
25680             *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
25681        );
25682    }
25683
25684    /// One list holding every document in the field, which is D-96: a range tree
25685    /// answers one list per range and this answers the one it keeps.
25686    #[test]
25687    fn a_number_dump_answers_a_single_range() {
25688        let mut f = debugging();
25689        assert_eq!(
25690            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
25691            "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
25692        );
25693    }
25694
25695    /// A point is a number underneath, so the field that holds points answers
25696    /// the subcommand that dumps numbers and not the one that dumps tags.
25697    #[test]
25698    fn a_geo_field_is_dumped_as_a_numeric_one() {
25699        let mut f = Fixture::new();
25700        f.run(&[
25701            b"FT.CREATE",
25702            b"gx",
25703            b"PREFIX",
25704            b"1",
25705            b"q:",
25706            b"SCHEMA",
25707            b"loc",
25708            b"GEO",
25709            b"gg",
25710            b"AS",
25711            b"tag",
25712            b"TAG",
25713        ]);
25714        f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
25715        f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
25716        assert_eq!(
25717            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
25718            "*1\r\n*2\r\n:1\r\n:2\r\n"
25719        );
25720        assert_eq!(
25721            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
25722            "-Could not find given field in index spec\r\n"
25723        );
25724    }
25725
25726    /// A field is named the way a query names it, so the attribute is the name
25727    /// and the identifier the value was read from is not one.
25728    #[test]
25729    fn a_dump_takes_the_attribute_and_not_the_identifier() {
25730        let mut f = Fixture::new();
25731        f.run(&[
25732            b"FT.CREATE",
25733            b"zx",
25734            b"PREFIX",
25735            b"1",
25736            b"z:",
25737            b"SCHEMA",
25738            b"gg",
25739            b"AS",
25740            b"tag",
25741            b"TAG",
25742        ]);
25743        f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
25744        assert_eq!(
25745            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
25746            "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
25747        );
25748        assert_eq!(
25749            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
25750            "-Could not find given field in index spec\r\n"
25751        );
25752    }
25753
25754    /// The seven keys, with the score as a bulk string here and a double there,
25755    /// and the whole row flat on one protocol and a map on the other.
25756    #[test]
25757    fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
25758        let mut f = debugging();
25759        assert_eq!(
25760            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25761            "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25762             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
25763             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25764             +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\
25765             $5\r\nvalue\r\n$5\r\nalpha\r\n"
25766        );
25767        let mut g = debugging();
25768        g.run(&[b"HELLO", b"3"]);
25769        assert_eq!(
25770            g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25771            "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25772             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
25773             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25774             +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\
25775             $5\r\nvalue\r\n$5\r\nalpha\r\n"
25776        );
25777    }
25778
25779    /// A document that wrote nothing into a sortable slot has no sortables key
25780    /// at all, so the row is a key shorter rather than carrying an empty list.
25781    #[test]
25782    fn a_document_with_no_sortable_value_drops_the_key() {
25783        let mut f = debugging();
25784        assert_eq!(
25785            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
25786            "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
25787             +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"
25788        );
25789    }
25790
25791    /// The flag word is the number and then the names it stands for, and an
25792    /// index built without offsets has none of the three set.
25793    #[test]
25794    fn the_flag_word_spells_out_the_bits_it_carries() {
25795        let mut f = Fixture::new();
25796        f.run(&[
25797            b"FT.CREATE",
25798            b"nx",
25799            b"NOOFFSETS",
25800            b"PREFIX",
25801            b"1",
25802            b"o:",
25803            b"SCHEMA",
25804            b"t",
25805            b"TEXT",
25806        ]);
25807        f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
25808        assert!(
25809            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
25810                .contains("$6\r\n(0x0):\r\n")
25811        );
25812    }
25813
25814    /// Obfuscation replaces the field name with where the field sits in the
25815    /// whole schema, which is not where its value sits among the sortables.
25816    #[test]
25817    fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
25818        let mut f = debugging();
25819        assert!(
25820            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
25821                .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
25822        );
25823    }
25824
25825    /// The keyword is read where it belongs and anything after it is stepped
25826    /// over, whatever the line that complains about it says.
25827    #[test]
25828    fn a_document_row_reads_its_keyword_at_a_fixed_place() {
25829        let mut f = debugging();
25830        let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
25831        assert_eq!(
25832            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
25833            want
25834        );
25835        assert_eq!(
25836            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
25837            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25838        );
25839        assert_eq!(
25840            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
25841            "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
25842        );
25843    }
25844
25845    /// The key is looked up before the keyword is read, so a key nobody indexed
25846    /// beats a keyword nobody wrote.
25847    #[test]
25848    fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
25849        let mut f = debugging();
25850        assert_eq!(
25851            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
25852            "-Document not found in index\r\n"
25853        );
25854        assert_eq!(
25855            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
25856            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25857        );
25858    }
25859
25860    /// The two directions of the document table, and the number nobody handed
25861    /// out reads as one that was given up rather than as one that never was.
25862    #[test]
25863    fn a_document_number_goes_both_ways() {
25864        let mut f = debugging();
25865        assert_eq!(
25866            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25867            "$3\r\nd:2\r\n"
25868        );
25869        assert_eq!(
25870            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25871            ":2\r\n"
25872        );
25873        assert_eq!(
25874            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
25875            ":0\r\n"
25876        );
25877        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
25878        for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
25879            assert_eq!(
25880                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25881                "-document was removed\r\n",
25882                "{id:?}"
25883            );
25884        }
25885    }
25886
25887    /// A document number is read the strict way Redis reads an integer, so a
25888    /// leading zero, a leading plus and a leading space are all refused.
25889    #[test]
25890    fn a_document_number_is_read_the_strict_way() {
25891        let mut f = debugging();
25892        for id in [
25893            b"x".as_slice(),
25894            b"1.5",
25895            b" 1",
25896            b"+1",
25897            b"01",
25898            b"0x1",
25899            b"",
25900            b"9223372036854775808",
25901            b"18446744073709551615",
25902        ] {
25903            assert_eq!(
25904                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25905                "-bad id given\r\n",
25906                "{id:?}"
25907            );
25908        }
25909    }
25910
25911    /// A number a document has given up is still in every list it was in, so a
25912    /// dump names documents that the table says are gone.
25913    #[test]
25914    fn a_dump_keeps_a_number_the_table_has_given_up() {
25915        let mut f = debugging();
25916        f.run(&[b"DEL", b"d:2"]);
25917        assert_eq!(
25918            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25919            "*2\r\n:1\r\n:2\r\n"
25920        );
25921        assert_eq!(
25922            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25923            "-document was removed\r\n"
25924        );
25925        assert_eq!(
25926            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25927            ":0\r\n"
25928        );
25929    }
25930
25931    /// A rewrite hands out a new number and leaves the old one behind, so the
25932    /// counter climbs past the number of documents there are.
25933    #[test]
25934    fn a_rewrite_takes_a_number_of_its_own() {
25935        let mut f = debugging();
25936        f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
25937        assert_eq!(
25938            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
25939            ":4\r\n"
25940        );
25941        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
25942        assert_eq!(
25943            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
25944            "-document was removed\r\n"
25945        );
25946        assert_eq!(
25947            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25948            "*2\r\n:1\r\n:2\r\n"
25949        );
25950    }
25951
25952    /// An alias reads the index it stands for, the same as a query does.
25953    #[test]
25954    fn a_dump_follows_an_alias() {
25955        let mut f = debugging();
25956        f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
25957        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
25958        assert_eq!(
25959            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
25960            "$3\r\nd:1\r\n"
25961        );
25962    }
25963
25964    /// The index name is matched as written and the subcommand name is not, and
25965    /// an index nobody made is reported as a context that could not be built.
25966    #[test]
25967    fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
25968        let mut f = debugging();
25969        assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
25970        assert_eq!(
25971            f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
25972            "-Can not create a search ctx\r\n"
25973        );
25974        assert_eq!(
25975            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
25976            "-Can not create a search ctx\r\n"
25977        );
25978    }
25979
25980    /// A field with nothing written into it answers an empty dump rather than an
25981    /// error, since the field is in the schema and only the values are missing.
25982    #[test]
25983    fn an_empty_field_dumps_as_nothing_at_all() {
25984        let mut f = Fixture::new();
25985        f.run(&[
25986            b"FT.CREATE",
25987            b"ex",
25988            b"PREFIX",
25989            b"1",
25990            b"e:",
25991            b"SCHEMA",
25992            b"t",
25993            b"TEXT",
25994            b"g",
25995            b"TAG",
25996            b"n",
25997            b"NUMERIC",
25998        ]);
25999        assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
26000        assert_eq!(
26001            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
26002            "*0\r\n"
26003        );
26004        assert_eq!(
26005            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
26006            "*0\r\n"
26007        );
26008        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
26009    }
26010
26011    /// The two lines the dispatcher owns are the two that carry a code word, and
26012    /// every subcommand but `DOCINFO` counts its arguments exactly.
26013    #[test]
26014    fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
26015        let mut f = debugging();
26016        for (sub, extra) in [
26017            (b"DUMP_TERMS".as_slice(), 1),
26018            (b"GET_MAX_DOC_ID", 1),
26019            (b"DUMP_INVIDX", 2),
26020            (b"DUMP_TAGIDX", 2),
26021            (b"DUMP_NUMIDX", 2),
26022            (b"IDTODOCID", 2),
26023            (b"DOCIDTOID", 2),
26024        ] {
26025            let want = format!(
26026                "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
26027                str::from_utf8(sub).unwrap()
26028            );
26029            for given in [extra - 1, extra + 1] {
26030                let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
26031                cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
26032                assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
26033            }
26034            let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
26035            right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
26036            assert_ne!(f.run(&right), want, "{sub:?}");
26037        }
26038        assert_eq!(
26039            f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
26040            "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
26041        );
26042    }
26043
26044    /// The eight names that answer rather than the sixty two a real server
26045    /// registers, which is D-97, and anything after the name is stepped over.
26046    #[test]
26047    fn the_help_names_the_subcommands_that_answer() {
26048        let mut f = Fixture::new();
26049        let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
26050             $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
26051             $14\r\nGET_MAX_DOC_ID\r\n";
26052        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
26053        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
26054    }
26055
26056    // ------------------------------------------------------------- synonyms
26057
26058    /// The terms are folded on the way in and the group ids are not, and one
26059    /// term can be in more than one group.
26060    #[test]
26061    fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
26062        let mut f = Fixture::new();
26063        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26064        assert_eq!(
26065            f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
26066            "+OK\r\n"
26067        );
26068        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
26069        assert_eq!(
26070            f.run(&[b"FT.SYNDUMP", b"e"]),
26071            "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
26072             $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
26073        );
26074    }
26075
26076    /// A group is not a comparison made at query time. It is a term of its
26077    /// own, so a word in a group reads as a union of the word, the groups it
26078    /// is in and its stem.
26079    #[test]
26080    fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
26081        let mut f = Fixture::new();
26082        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26083        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
26084        assert_eq!(
26085            f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
26086            "$69\r\nUNION {\n  jogging\n  ~gr(expanded)\n  +jog(expanded)\n  jog(expanded)\n}\n\r\n"
26087        );
26088    }
26089
26090    /// The lookup on the document side is on the word and never on the stem,
26091    /// and a group written after the documents were still finds them because
26092    /// the index is read again.
26093    ///
26094    /// The group holds `running` and `d2` says `runs`, so a query for another
26095    /// word of the group finds `d1` and leaves `d2` where it is. A query for
26096    /// `running` itself does find `d2`, through the stem branch of the union
26097    /// rather than through the group, which is why the two asserts differ.
26098    #[test]
26099    fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
26100        let mut f = Fixture::new();
26101        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26102        f.run(&[b"HSET", b"d1", b"t", b"boy"]);
26103        f.run(&[b"HSET", b"d2", b"t", b"runs"]);
26104        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
26105        assert_eq!(
26106            f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
26107            "*2\r\n:1\r\n$2\r\nd1\r\n"
26108        );
26109        assert_eq!(
26110            f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
26111            "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
26112        );
26113    }
26114
26115    /// Neither command makes an index and neither forgives a name that is not
26116    /// there, in the same words the rest of the group uses.
26117    #[test]
26118    fn a_synonym_command_on_a_name_that_is_not_there_fails() {
26119        let mut f = Fixture::new();
26120        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
26121        assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
26122        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
26123    }
26124
26125    /// The words after `PARAMS n` are counted before their shape is looked at,
26126    /// so a count that reaches past the end of the command and a count that is
26127    /// merely odd are two different errors.
26128    #[test]
26129    fn params_counts_the_words_before_it_pairs_them_up() {
26130        let mut f = Fixture::new();
26131        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26132        let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
26133                    Expected an argument, but none provided\r\n";
26134        let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
26135        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
26136        assert_eq!(
26137            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
26138            none
26139        );
26140        assert_eq!(
26141            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
26142            odd
26143        );
26144        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
26145        assert_eq!(
26146            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
26147            "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
26148        );
26149    }
26150
26151    // --------------------------------------------------------------- vectors
26152
26153    /// Five documents a unit apart along one axis, written in the opposite
26154    /// order to the one they sit in, so a reply in document order and a reply
26155    /// in distance order are two different replies.
26156    ///
26157    /// `d1` is furthest from the origin and `d5` is on it. The text field
26158    /// splits them so a query can narrow before it measures: `d1`, `d2` and
26159    /// `d4` say `alpha` and the other two say `beta`.
26160    fn vectored(f: &mut Fixture) {
26161        f.run(&[
26162            b"FT.CREATE",
26163            b"h",
26164            b"SCHEMA",
26165            b"t",
26166            b"TEXT",
26167            b"v",
26168            b"VECTOR",
26169            b"FLAT",
26170            b"6",
26171            b"TYPE",
26172            b"FLOAT32",
26173            b"DIM",
26174            b"2",
26175            b"DISTANCE_METRIC",
26176            b"L2",
26177        ]);
26178        let at: [&[u8]; 5] = [
26179            b"\x00\x00\x80\x40\x00\x00\x00\x00",
26180            b"\x00\x00\x40\x40\x00\x00\x00\x00",
26181            b"\x00\x00\x00\x40\x00\x00\x00\x00",
26182            b"\x00\x00\x80\x3f\x00\x00\x00\x00",
26183            b"\x00\x00\x00\x00\x00\x00\x00\x00",
26184        ];
26185        for (n, point) in at.iter().enumerate() {
26186            let key = format!("d{}", n + 1);
26187            let word: &[u8] = match n {
26188                0 | 1 | 3 => b"alpha",
26189                _ => b"beta",
26190            };
26191            f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
26192        }
26193    }
26194
26195    /// The origin, which every query below asks about.
26196    const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
26197
26198    /// A `KNN` picks the k nearest and then answers them in document order,
26199    /// which is measured: asking for three of five that were written furthest
26200    /// first answers the last three written and not the first three.
26201    #[test]
26202    fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
26203        let mut f = Fixture::new();
26204        vectored(&mut f);
26205        assert_eq!(
26206            f.run(&[
26207                b"FT.SEARCH",
26208                b"h",
26209                b"*=>[KNN 5 @v $vec]",
26210                b"PARAMS",
26211                b"2",
26212                b"vec",
26213                ORIGIN,
26214                b"DIALECT",
26215                b"2",
26216                b"NOCONTENT",
26217            ]),
26218            "*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"
26219        );
26220        assert_eq!(
26221            f.run(&[
26222                b"FT.SEARCH",
26223                b"h",
26224                b"*=>[KNN 3 @v $vec]",
26225                b"PARAMS",
26226                b"2",
26227                b"vec",
26228                ORIGIN,
26229                b"DIALECT",
26230                b"2",
26231                b"NOCONTENT",
26232            ]),
26233            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26234        );
26235    }
26236
26237    /// A range takes what is really inside it, where the distances are squared
26238    /// so the five documents sit at 16, 9, 4, 1 and 0.
26239    #[test]
26240    fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
26241        let mut f = Fixture::new();
26242        vectored(&mut f);
26243        for (radius, want) in [
26244            ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
26245            ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
26246            (
26247                "9",
26248                "*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",
26249            ),
26250        ] {
26251            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26252            assert_eq!(
26253                f.run(&[
26254                    b"FT.SEARCH",
26255                    b"h",
26256                    query.as_bytes(),
26257                    b"PARAMS",
26258                    b"2",
26259                    b"vec",
26260                    ORIGIN,
26261                    b"DIALECT",
26262                    b"2",
26263                    b"NOCONTENT",
26264                ]),
26265                want,
26266                "radius {radius}"
26267            );
26268        }
26269    }
26270
26271    /// A `KNN` behind a query is the nearest of what the query matched, so
26272    /// asking for two of the three documents that say `alpha` answers the two
26273    /// of those three that are nearest and not the two nearest overall.
26274    #[test]
26275    fn a_knn_measures_what_the_query_in_front_of_it_matched() {
26276        let mut f = Fixture::new();
26277        vectored(&mut f);
26278        assert_eq!(
26279            f.run(&[
26280                b"FT.SEARCH",
26281                b"h",
26282                b"alpha=>[KNN 2 @v $vec]",
26283                b"PARAMS",
26284                b"2",
26285                b"vec",
26286                ORIGIN,
26287                b"DIALECT",
26288                b"2",
26289                b"NOCONTENT",
26290            ]),
26291            "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
26292        );
26293    }
26294
26295    /// A `KNN` counts in whole numbers and a range measures from zero, and the
26296    /// two are refused in their own words.
26297    ///
26298    /// The count is a token of its own and is checked where it stands, ahead of
26299    /// the field and ahead of the vector. A count that arrives through `PARAMS`
26300    /// is read by looser rules than one written into the query, which is
26301    /// measured: a leading plus is fine in a parameter and a syntax error in
26302    /// the query text.
26303    #[test]
26304    fn a_count_and_a_radius_are_refused_in_their_own_words() {
26305        let mut f = Fixture::new();
26306        vectored(&mut f);
26307        let ask = |f: &mut Fixture, query: &str| {
26308            f.run(&[
26309                b"FT.SEARCH",
26310                b"h",
26311                query.as_bytes(),
26312                b"PARAMS",
26313                b"2",
26314                b"vec",
26315                ORIGIN,
26316                b"DIALECT",
26317                b"2",
26318                b"NOCONTENT",
26319            ])
26320        };
26321        for (query, at, near) in [
26322            ("*=>[KNN -1 @v $vec]", 8, "-1"),
26323            ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
26324            ("*=>[KNN +3 @v $vec]", 8, "+3"),
26325            ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
26326            ("*=>[KNN abc @v $vec]", 8, "abc"),
26327            ("*=>[KNN 3 $vec]", 10, "vec"),
26328            ("*=>[KNN 3 @v vec]", 13, "vec"),
26329            ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
26330        ] {
26331            assert_eq!(
26332                ask(&mut f, query),
26333                format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
26334                "{query}"
26335            );
26336        }
26337
26338        // Read as a double the way a real server reads it, so the bound plus
26339        // thirty two rounds back onto the bound and gets in.
26340        let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26341                     query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
26342        assert_eq!(
26343            ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
26344            "*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"
26345        );
26346        assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
26347        assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
26348
26349        for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
26350            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26351            assert_eq!(
26352                ask(&mut f, &query),
26353                format!(
26354                    "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26355                     negative radius ({printed}) given in a range query\r\n"
26356                ),
26357                "{query}"
26358            );
26359        }
26360        // A radius of minus zero is not below zero and is a radius of zero.
26361        assert_eq!(
26362            ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
26363            "*2\r\n:1\r\n$2\r\nd5\r\n"
26364        );
26365    }
26366
26367    /// A count passed with `PARAMS` is read the way a real server reads one,
26368    /// which is not the way the same digits are read in the query text.
26369    #[test]
26370    fn a_count_that_came_from_params_is_read_by_its_own_rules() {
26371        let mut f = Fixture::new();
26372        vectored(&mut f);
26373        let ask = |f: &mut Fixture, count: &[u8]| {
26374            f.run(&[
26375                b"FT.SEARCH",
26376                b"h",
26377                b"*=>[KNN $k @v $vec]",
26378                b"PARAMS",
26379                b"4",
26380                b"vec",
26381                ORIGIN,
26382                b"k",
26383                count,
26384                b"DIALECT",
26385                b"2",
26386                b"NOCONTENT",
26387            ])
26388        };
26389        let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
26390        assert_eq!(ask(&mut f, b"3"), three);
26391        assert_eq!(ask(&mut f, b"  3"), three);
26392        assert_eq!(ask(&mut f, b"+3"), three);
26393        for bad in [
26394            &b"3.0"[..],
26395            b"0x3",
26396            b"-1",
26397            b"abc",
26398            b"",
26399            b"99999999999999999999",
26400        ] {
26401            let value = String::from_utf8_lossy(bad).into_owned();
26402            assert_eq!(
26403                ask(&mut f, bad),
26404                format!(
26405                    "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
26406                     for parameter `k`\r\n"
26407                ),
26408                "{value}"
26409            );
26410        }
26411        assert_eq!(
26412            ask(&mut f, b"288230376151711777"),
26413            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26414             query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
26415        );
26416    }
26417
26418    /// A vector the wrong size is refused against the field it was passed to,
26419    /// naming both sizes in bytes.
26420    #[test]
26421    fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
26422        let mut f = Fixture::new();
26423        vectored(&mut f);
26424        assert_eq!(
26425            f.run(&[
26426                b"FT.SEARCH",
26427                b"h",
26428                b"*=>[KNN 5 @v $vec]",
26429                b"PARAMS",
26430                b"2",
26431                b"vec",
26432                b"abc",
26433                b"DIALECT",
26434                b"2",
26435                b"NOCONTENT",
26436            ]),
26437            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26438             query vector blob size (3) does not match index's expected size (8).\r\n"
26439        );
26440    }
26441
26442    /// A nearest neighbour clause puts its distance on every row it answers,
26443    /// under `__v_score` unless the query renamed it. A range clause puts
26444    /// nothing there at all unless the query named it, which is what
26445    /// `YIELD_DISTANCE_AS` is for.
26446    #[test]
26447    fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
26448        let mut f = Fixture::new();
26449        vectored(&mut f);
26450        let ask = |f: &mut Fixture, query: &str| {
26451            f.run(&[
26452                b"FT.SEARCH",
26453                b"h",
26454                query.as_bytes(),
26455                b"PARAMS",
26456                b"2",
26457                b"vec",
26458                ORIGIN,
26459                b"DIALECT",
26460                b"2",
26461                b"LIMIT",
26462                b"0",
26463                b"1",
26464            ])
26465        };
26466        assert_eq!(
26467            ask(&mut f, "*=>[KNN 3 @v $vec]"),
26468            "*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"
26469        );
26470        assert_eq!(
26471            ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
26472            "*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"
26473        );
26474        assert_eq!(
26475            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
26476            "*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"
26477        );
26478        assert_eq!(
26479            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
26480            "*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"
26481        );
26482    }
26483
26484    /// What decides whether a `RETURN` answers the distance is the name the row
26485    /// would carry it under and not the field it would have been read from,
26486    /// because it is on the row before any key is read.
26487    ///
26488    /// So naming it answers it, renaming it answers nothing at all, and giving
26489    /// its name to another field answers the distance under that name.
26490    #[test]
26491    fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
26492        let mut f = Fixture::new();
26493        vectored(&mut f);
26494        let ask = |f: &mut Fixture, ret: &[&[u8]]| {
26495            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
26496            args.extend_from_slice(ret);
26497            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26498            f.run(&args)
26499        };
26500        assert_eq!(
26501            ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
26502            "*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"
26503        );
26504        assert_eq!(
26505            ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
26506            "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
26507        );
26508        assert_eq!(
26509            ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
26510            "*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"
26511        );
26512        assert_eq!(
26513            ask(&mut f, &[b"RETURN", b"1", b"t"]),
26514            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
26515        );
26516        // The distance goes in front of the rest whatever order they were
26517        // named in, and `NOCONTENT` takes it away with everything else.
26518        assert_eq!(
26519            ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
26520            "*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"
26521        );
26522        assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
26523    }
26524
26525    /// A `SORTBY` can name a distance the query yielded, which sorts by the
26526    /// number rather than by anything the key holds. A name the query did not
26527    /// yield is refused the way any other unknown property is.
26528    #[test]
26529    fn a_sortby_can_name_a_distance_the_query_yielded() {
26530        let mut f = Fixture::new();
26531        vectored(&mut f);
26532        let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
26533            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
26534            if desc {
26535                args.push(b"DESC");
26536            }
26537            args.extend_from_slice(&[
26538                b"PARAMS",
26539                b"2",
26540                b"vec",
26541                ORIGIN,
26542                b"DIALECT",
26543                b"2",
26544                b"NOCONTENT",
26545            ]);
26546            f.run(&args)
26547        };
26548        assert_eq!(
26549            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
26550            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26551        );
26552        assert_eq!(
26553            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
26554            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26555        );
26556        assert_eq!(
26557            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
26558            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26559        );
26560        // Renaming it takes the old name away, and a query with no vector
26561        // clause in it never had the property at all.
26562        let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
26563                       not loaded nor in schema\r\n";
26564        assert_eq!(
26565            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
26566            missing
26567        );
26568        assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
26569        // The query is read before the property is looked up, which is
26570        // measured: a query that will not parse is answered first.
26571        assert_eq!(
26572            ask(&mut f, "foo(", b"zz", false),
26573            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26574        );
26575    }
26576
26577    /// Two vector clauses in one query answer two distances, outermost first.
26578    #[test]
26579    fn two_vector_clauses_answer_two_distances() {
26580        let mut f = Fixture::new();
26581        vectored(&mut f);
26582        assert_eq!(
26583            f.run(&[
26584                b"FT.SEARCH",
26585                b"h",
26586                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
26587                b"RETURN",
26588                b"2",
26589                b"rr",
26590                b"__v_score",
26591                b"PARAMS",
26592                b"2",
26593                b"vec",
26594                ORIGIN,
26595                b"DIALECT",
26596                b"2",
26597            ]),
26598            "*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"
26599        );
26600    }
26601
26602    /// An aggregation carries the distance on every row whether or not the
26603    /// pipeline ever mentions it, and carries it in front of everything a
26604    /// `LOAD` asked for.
26605    #[test]
26606    fn an_aggregation_answers_a_distance_nothing_asked_for() {
26607        let mut f = Fixture::new();
26608        vectored(&mut f);
26609        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26610            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26611            args.extend_from_slice(rest);
26612            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26613            f.run(&args)
26614        };
26615        assert_eq!(
26616            ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
26617            "*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"
26618        );
26619        assert_eq!(
26620            ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
26621            "*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"
26622        );
26623        assert_eq!(
26624            ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
26625            "*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"
26626        );
26627        // A range shows nothing until the query names it.
26628        assert_eq!(
26629            ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
26630            "*3\r\n:1\r\n*0\r\n*0\r\n"
26631        );
26632        assert_eq!(
26633            ask(
26634                &mut f,
26635                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26636                &[]
26637            ),
26638            "*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"
26639        );
26640    }
26641
26642    /// A nearest neighbour clause hands its documents back nearest first and an
26643    /// aggregation keeps them that way, where a search sorts them into document
26644    /// order. A tie goes to the document written first.
26645    #[test]
26646    fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
26647        let mut f = Fixture::new();
26648        vectored(&mut f);
26649        // Sitting on `d3`, so `d2` and `d4` are the same distance away.
26650        const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
26651        let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
26652            f.run(&[
26653                b"FT.AGGREGATE",
26654                b"h",
26655                query.as_bytes(),
26656                b"LOAD",
26657                b"1",
26658                b"@t",
26659                b"PARAMS",
26660                b"2",
26661                b"vec",
26662                vec,
26663                b"DIALECT",
26664                b"2",
26665            ])
26666        };
26667        assert_eq!(
26668            ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
26669            "*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"
26670        );
26671        // A range does no ordering, so those rows stay in document order.
26672        assert_eq!(
26673            ask(
26674                &mut f,
26675                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26676                MIDDLE
26677            ),
26678            "*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"
26679        );
26680    }
26681
26682    /// Every step of the pipeline can name a distance the query yielded, and a
26683    /// query with no vector clause in it is refused for the name three
26684    /// different ways depending on which step asked.
26685    #[test]
26686    fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
26687        let mut f = Fixture::new();
26688        vectored(&mut f);
26689        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26690            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26691            args.extend_from_slice(rest);
26692            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26693            f.run(&args)
26694        };
26695        let knn = "*=>[KNN 2 @v $vec]";
26696        assert_eq!(
26697            ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
26698            "*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"
26699        );
26700        assert_eq!(
26701            ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
26702            "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
26703        );
26704        assert_eq!(
26705            ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
26706            "*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"
26707        );
26708        assert_eq!(
26709            ask(
26710                &mut f,
26711                knn,
26712                &[
26713                    b"GROUPBY",
26714                    b"1",
26715                    b"@t",
26716                    b"REDUCE",
26717                    b"MAX",
26718                    b"1",
26719                    b"@__v_score",
26720                    b"AS",
26721                    b"m"
26722                ]
26723            ),
26724            "*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"
26725        );
26726        assert_eq!(
26727            ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
26728            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
26729             `__v_score`\r\n"
26730        );
26731        assert_eq!(
26732            ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
26733            "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
26734        );
26735        assert_eq!(
26736            ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
26737            "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
26738             schema\r\n"
26739        );
26740    }
26741
26742    /// An aggregation reads every word before it reads the query, and reads the
26743    /// query before it ties anything on the pipeline to a place on the row.
26744    ///
26745    /// So a command with a fault in all three answers the one about the words,
26746    /// a command with a fault in the last two answers the one about the query,
26747    /// and the pipeline speaks last. That is measured, and it is the whole
26748    /// reason the arguments are read twice.
26749    #[test]
26750    fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
26751        let mut f = Fixture::new();
26752        vectored(&mut f);
26753        let ask = |f: &mut Fixture, rest: &[&[u8]]| {
26754            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
26755            args.extend_from_slice(rest);
26756            f.run(&args)
26757        };
26758        assert_eq!(
26759            ask(
26760                &mut f,
26761                &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
26762            ),
26763            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
26764        );
26765        assert_eq!(
26766            ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
26767            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26768        );
26769        assert_eq!(
26770            ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
26771            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26772        );
26773        // An expression that will not read is the pipeline's fault too, so it
26774        // speaks after the query and after a property named before it.
26775        assert_eq!(
26776            ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
26777            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26778        );
26779        assert_eq!(
26780            ask(
26781                &mut f,
26782                &[
26783                    b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
26784                ]
26785            ),
26786            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26787        );
26788        assert_eq!(
26789            ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
26790            "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
26791        );
26792    }
26793
26794    /// A vector clause says which of the ways of answering one it took, and a
26795    /// range says nothing at all when there is no distance to hand back.
26796    #[test]
26797    fn a_vector_step_says_which_way_it_was_answered() {
26798        let mut f = Fixture::new();
26799        vectored(&mut f);
26800        let tree = |f: &mut Fixture, query: &[u8]| {
26801            let reply = timeless(&f.run(&[
26802                b"FT.PROFILE",
26803                b"h",
26804                b"AGGREGATE",
26805                b"QUERY",
26806                query,
26807                b"PARAMS",
26808                b"2",
26809                b"vec",
26810                ORIGIN,
26811                b"DIALECT",
26812                b"2",
26813            ]));
26814            let at = reply.find("+Iterators profile").expect("a tree");
26815            let end = reply.find("+Result processors").expect("a list of steps");
26816            reply[at..end].to_string()
26817        };
26818        assert_eq!(
26819            tree(&mut f, b"*=>[KNN 3 @v $vec]"),
26820            "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26821             +Number of reading operations\r\n:3\r\n\
26822             +Vector search mode\r\n+STANDARD_KNN\r\n"
26823        );
26824        // Renaming the distance changes nothing about how it was answered.
26825        assert_eq!(
26826            tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
26827            tree(&mut f, b"*=>[KNN 3 @v $vec]")
26828        );
26829        // A range with nothing to yield is not a vector step at all, and one
26830        // that yields names the distance in its own type.
26831        assert_eq!(
26832            tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
26833            "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
26834             +Number of reading operations\r\n:4\r\n"
26835        );
26836        assert_eq!(
26837            tree(
26838                &mut f,
26839                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
26840            ),
26841            "+Iterators profile\r\n*8\r\n\
26842             +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
26843             +Number of reading operations\r\n:4\r\n\
26844             +Vector search mode\r\n+RANGE_QUERY\r\n"
26845        );
26846    }
26847
26848    /// What a vector clause narrowed itself down with hangs under it as a
26849    /// single child, and the step that works the distances out is behind the
26850    /// index whenever the query yields one.
26851    #[test]
26852    fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
26853        let mut f = Fixture::new();
26854        vectored(&mut f);
26855        let ask = |f: &mut Fixture, query: &[u8]| {
26856            timeless(&f.run(&[
26857                b"FT.PROFILE",
26858                b"h",
26859                b"AGGREGATE",
26860                b"QUERY",
26861                query,
26862                b"PARAMS",
26863                b"2",
26864                b"vec",
26865                ORIGIN,
26866                b"DIALECT",
26867                b"2",
26868            ]))
26869        };
26870        let cut = |reply: &str| {
26871            let at = reply.find("+Iterators profile").expect("a tree");
26872            reply[at..].to_string()
26873        };
26874        assert_eq!(
26875            cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
26876            "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26877             +Number of reading operations\r\n:3\r\n\
26878             +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
26879             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
26880             +Number of reading operations\r\n:3\r\n\
26881             +Estimated number of matches\r\n:3\r\n\
26882             +Result processors profile\r\n*2\r\n\
26883             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26884             *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
26885             +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
26886        );
26887        // A range nobody named yields nothing, so nothing works a distance out
26888        // and the step is not there.
26889        assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
26890            "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
26891             +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
26892        ));
26893        // A nearest neighbour clause with nothing in front of it yields all
26894        // the same, so the step is there without a child above it.
26895        assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
26896    }
26897
26898    /// A `LIMIT 0 0` on an aggregation is a client asking for the total and
26899    /// nothing else, so the step that would have paged the rows counts them
26900    /// instead, whether or not a `SORTBY` put an order in front of it.
26901    #[test]
26902    fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
26903        let mut f = profiling();
26904        let steps = |f: &mut Fixture, words: &[&[u8]]| {
26905            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
26906            argv.extend_from_slice(words);
26907            let reply = timeless(&f.run(&argv));
26908            let at = reply.find("+Result processors").expect("a list of steps");
26909            reply[at..].to_string()
26910        };
26911        assert_eq!(
26912            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
26913            "+Result processors profile\r\n*2\r\n\
26914             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26915             *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
26916             +Coordinator\r\n*0\r\n"
26917        );
26918        assert!(
26919            steps(
26920                &mut f,
26921                &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
26922            )
26923            .contains("+Type\r\n+Counter\r\n")
26924        );
26925        // A window that keeps something is still a window.
26926        assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
26927            "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
26928             +Results processed\r\n:2\r\n"
26929        ));
26930    }
26931
26932    // ----------------------------------------------------------- spellcheck
26933
26934    /// The score is how many documents hold the suggestion over how many
26935    /// documents there are, and how close the suggestion is to the word does
26936    /// not come into it at all, so the nearer of the two words here is second.
26937    #[test]
26938    fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
26939        let mut f = Fixture::new();
26940        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26941        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26942        f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
26943        assert_eq!(
26944            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
26945            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
26946             *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"
26947        );
26948    }
26949
26950    /// On RESP3 the whole thing is wrapped in a map under one name, a word
26951    /// carries a list of one pair maps, and the score is a double rather than
26952    /// a string.
26953    #[test]
26954    fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
26955        let mut f = Fixture::new();
26956        f.run(&[b"HELLO", b"3"]);
26957        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26958        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26959        assert_eq!(
26960            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
26961            "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
26962             *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
26963        );
26964    }
26965
26966    /// A word the index already holds is not a mistake and is left out of the
26967    /// answer, and that check never looks at the field the query named, while
26968    /// the search for candidates does.
26969    #[test]
26970    fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
26971        let mut f = Fixture::new();
26972        f.run(&[
26973            b"FT.CREATE",
26974            b"e",
26975            b"SCHEMA",
26976            b"a",
26977            b"TEXT",
26978            b"NOSTEM",
26979            b"b",
26980            b"TEXT",
26981            b"NOSTEM",
26982        ]);
26983        f.run(&[b"HSET", b"d1", b"b", b"world"]);
26984        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
26985        assert_eq!(
26986            f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
26987            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
26988        );
26989    }
26990
26991    /// A dictionary named by `INCLUDE` adds words the index never read, scored
26992    /// zero and reported in the spelling the dictionary was given, and one
26993    /// named by `EXCLUDE` says a word is spelled right after all.
26994    #[test]
26995    fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
26996        let mut f = Fixture::new();
26997        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26998        f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
26999        assert_eq!(
27000            f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
27001            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
27002             *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"
27003        );
27004        assert_eq!(
27005            f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
27006            "*0\r\n"
27007        );
27008        assert_eq!(
27009            f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
27010            "-Dict does not exist: nope\r\n"
27011        );
27012    }
27013
27014    /// The first `DISTANCE` counts and the rest are dropped, an argument
27015    /// nobody recognises is stepped over rather than refused, and a distance
27016    /// outside one to four is the one thing here that does fail.
27017    #[test]
27018    fn a_spellcheck_reads_its_arguments_leniently() {
27019        let mut f = Fixture::new();
27020        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27021        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27022        let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27023                   *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
27024        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
27025        let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
27026        let args: &[&[u8]] = &[
27027            b"FT.SPELLCHECK",
27028            b"e",
27029            b"helqp",
27030            b"DISTANCE",
27031            b"1",
27032            b"DISTANCE",
27033            b"4",
27034        ];
27035        assert_eq!(f.run(args), none);
27036        assert_eq!(
27037            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
27038            "-bad distance given, distance must be a natural number between 1 to 4\r\n"
27039        );
27040        assert_eq!(
27041            f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
27042            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
27043        );
27044    }
27045
27046    // -------------------------------------------------------------- suggest
27047
27048    /// The reply is the size of the dictionary afterwards, which is neither
27049    /// what was added nor whether anything changed.
27050    #[test]
27051    fn an_add_answers_how_many_suggestions_are_in_there_now() {
27052        let mut f = Fixture::new();
27053        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
27054        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
27055        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
27056        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
27057        assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
27058    }
27059
27060    /// A suggestion dictionary is the one thing the search module puts in the
27061    /// keyspace, so every keyspace command reaches it.
27062    #[test]
27063    fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
27064        let mut f = Fixture::new();
27065        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27066        assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
27067        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
27068        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
27069        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
27070        assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
27071        assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
27072        assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
27073        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
27074    }
27075
27076    /// The last suggestion out takes the key with it, which most module types
27077    /// do not do.
27078    #[test]
27079    fn deleting_the_last_suggestion_deletes_the_key() {
27080        let mut f = Fixture::new();
27081        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27082        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
27083        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
27084        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
27085        assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
27086    }
27087
27088    /// A key holding anything else is refused rather than overwritten, on all
27089    /// four of them.
27090    #[test]
27091    fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
27092        let mut f = Fixture::new();
27093        f.run(&[b"SET", b"s", b"x"]);
27094        for cmd in [
27095            vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
27096            vec![&b"FT.SUGGET"[..], b"s", b"t"],
27097            vec![&b"FT.SUGDEL"[..], b"s", b"t"],
27098            vec![&b"FT.SUGLEN"[..], b"s"],
27099        ] {
27100            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
27101        }
27102        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
27103    }
27104
27105    /// The scores in here were read off a real server, single precision and
27106    /// all. An exact match answers a sentinel so it sorts in front.
27107    #[test]
27108    fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
27109        let mut f = Fixture::new();
27110        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27111        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27112        f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
27113        assert_eq!(
27114            f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
27115            "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
27116             $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
27117             $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
27118        );
27119        assert_eq!(
27120            f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
27121            "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
27122        );
27123        assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
27124    }
27125
27126    /// `FUZZY` is one edit, and the edit is a rune rather than a byte.
27127    #[test]
27128    fn fuzzy_allows_one_edit_and_nothing_allows_two() {
27129        let mut f = Fixture::new();
27130        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27131        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
27132        assert_eq!(
27133            f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
27134            "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
27135        );
27136        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
27137    }
27138
27139    /// Five without a `MAX`, and the terms come back in score order.
27140    #[test]
27141    fn a_lookup_answers_five_unless_it_is_told_otherwise() {
27142        let mut f = Fixture::new();
27143        for (term, score) in [
27144            (&b"a1"[..], &b"1"[..]),
27145            (b"a2", b"2"),
27146            (b"a3", b"3"),
27147            (b"a4", b"4"),
27148            (b"a5", b"5"),
27149            (b"a6", b"6"),
27150        ] {
27151            f.run(&[b"FT.SUGADD", b"s", term, score]);
27152        }
27153        assert_eq!(
27154            f.run(&[b"FT.SUGGET", b"s", b"a"]),
27155            "*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"
27156        );
27157        assert_eq!(
27158            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
27159            "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
27160        );
27161        // A `MAX` larger than the dictionary answers what there is.
27162        assert!(
27163            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
27164                .starts_with("*6\r\n")
27165        );
27166    }
27167
27168    /// A payload is replaced only when one is given, and an empty one is no
27169    /// payload at all.
27170    #[test]
27171    fn a_payload_comes_back_beside_the_term_or_a_null_does() {
27172        let mut f = Fixture::new();
27173        f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
27174        assert_eq!(
27175            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27176            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27177        );
27178        f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
27179        assert_eq!(
27180            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27181            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27182        );
27183        // An empty payload is the same as not having given one at all, so it
27184        // leaves the payload where it is rather than clearing it.
27185        f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
27186        assert_eq!(
27187            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27188            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27189        );
27190        // A term that never had one answers a null.
27191        f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
27192        assert_eq!(
27193            f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
27194            "*2\r\n$5\r\nother\r\n$-1\r\n"
27195        );
27196    }
27197
27198    /// `INCR` adds to the score that is there rather than replacing it, and
27199    /// three tenths a tenth at a time is the reading that shows the score is
27200    /// held in single precision.
27201    #[test]
27202    fn incr_adds_to_the_score_that_is_already_there() {
27203        let mut f = Fixture::new();
27204        for _ in 0..3 {
27205            f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
27206        }
27207        assert_eq!(
27208            f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
27209            "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
27210        );
27211    }
27212
27213    /// The five error sentences, none of which are written the same way.
27214    #[test]
27215    fn the_suggestion_errors_are_the_lines_the_module_sends() {
27216        let mut f = Fixture::new();
27217        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27218        assert_eq!(
27219            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
27220            "-ERR invalid score\r\n"
27221        );
27222        // The unknown word is complained about before the score is converted.
27223        assert_eq!(
27224            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
27225            "-Unknown argument `NOPE`\r\n"
27226        );
27227        assert_eq!(
27228            f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
27229            "-Invalid payload: Expected an argument, but none provided\r\n"
27230        );
27231        // Too many words is an arity error and not an unknown argument.
27232        assert!(
27233            f.run(&[
27234                b"FT.SUGADD",
27235                b"s",
27236                b"t",
27237                b"1",
27238                b"PAYLOAD",
27239                b"a",
27240                b"PAYLOAD",
27241                b"b"
27242            ])
27243            .contains("wrong number of arguments")
27244        );
27245        assert_eq!(
27246            f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
27247            "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
27248        );
27249        // A count read as a whole number and then found to be out of range,
27250        // against one that had to be read as a double first, where anything
27251        // under one is a conversion that failed rather than a range that did.
27252        for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
27253            assert_eq!(
27254                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27255                "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
27256                "{}",
27257                String::from_utf8_lossy(max)
27258            );
27259        }
27260        for max in [
27261            &b"abc"[..],
27262            b"0.0",
27263            b"00",
27264            b"-0",
27265            b"+0",
27266            b"0.5",
27267            b"-1.5",
27268            b"1e400",
27269        ] {
27270            assert_eq!(
27271                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27272                "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
27273                "{}",
27274                String::from_utf8_lossy(max)
27275            );
27276        }
27277        for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
27278            assert_eq!(
27279                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27280                "*1\r\n$3\r\none\r\n",
27281                "{}",
27282                String::from_utf8_lossy(max)
27283            );
27284        }
27285        assert_eq!(
27286            f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
27287            "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
27288        );
27289        // A score too large for a double is refused where one spelled out is
27290        // taken, which is the module reading errno after the conversion.
27291        assert_eq!(
27292            f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
27293            "-ERR invalid score\r\n"
27294        );
27295        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
27296    }
27297
27298    /// An empty term is taken and not stored, so the reply is the length that
27299    /// was already there and nothing new comes back. The key is still made,
27300    /// and a delete that finds nothing is what clears it away again.
27301    #[test]
27302    fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
27303        let mut f = Fixture::new();
27304        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27305        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
27306        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
27307        assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
27308        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
27309        assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
27310        assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
27311        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
27312    }
27313
27314    /// A key that will not read is counted against the index and against the
27315    /// field, and `FT.INFO` says so.
27316    #[test]
27317    fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
27318        let mut f = Fixture::new();
27319        f.run(&[
27320            b"FT.CREATE",
27321            b"ix",
27322            b"PREFIX",
27323            b"1",
27324            b"p:",
27325            b"SCHEMA",
27326            b"n",
27327            b"NUMERIC",
27328        ]);
27329        f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
27330        assert_eq!(held(&f, b"ix"), (0, 0));
27331
27332        let reply = f.run(&[b"FT.INFO", b"ix"]);
27333        assert!(
27334            reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
27335            "{reply}"
27336        );
27337        assert!(reply.contains("hash_indexing_failures"), "{reply}");
27338    }
27339
27340    /// An index can only be made on database zero, and the check comes after
27341    /// the `IFNX` shortcut and before everything else.
27342    #[test]
27343    fn an_index_can_only_be_made_on_database_zero() {
27344        let mut f = Fixture::new();
27345        f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
27346        f.run(&[b"SELECT", b"1"]);
27347        let refused = "-Cannot create index on db != 0\r\n";
27348        assert_eq!(
27349            f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
27350            refused
27351        );
27352        // The name is taken, and it still answers about the database.
27353        assert_eq!(
27354            f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27355            refused
27356        );
27357        // And so does one whose arguments are nonsense.
27358        assert_eq!(
27359            f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
27360            refused
27361        );
27362        // `IFNX` over a name that is taken is the one that gets through.
27363        assert_eq!(
27364            f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27365            "+OK\r\n"
27366        );
27367        assert_eq!(f.server.search.lock().len(), 1);
27368    }
27369
27370    /// The scan reads the database the create was run on, and after that the
27371    /// index follows its keys in every database.
27372    ///
27373    /// The asymmetry is a real server's, measured, and it is the sort of thing
27374    /// nobody would arrive at by choosing.
27375    #[test]
27376    fn the_scan_is_one_database_and_the_following_is_all_of_them() {
27377        let mut f = Fixture::new();
27378        f.run(&[b"SELECT", b"1"]);
27379        f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
27380        f.run(&[b"SELECT", b"0"]);
27381        f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
27382        f.run(&[
27383            b"FT.CREATE",
27384            b"ix",
27385            b"PREFIX",
27386            b"1",
27387            b"p:",
27388            b"SCHEMA",
27389            b"t",
27390            b"TEXT",
27391        ]);
27392        assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
27393
27394        f.run(&[b"SELECT", b"1"]);
27395        f.run(&[b"HSET", b"p:8", b"t", b"later"]);
27396        assert_eq!(
27397            held(&f, b"ix"),
27398            (2, 2),
27399            "and then it follows every database"
27400        );
27401    }
27402
27403    /// Four documents over the two kinds of field a query can ask about, which
27404    /// is the corpus the searches below read.
27405    fn corpus(f: &mut Fixture) {
27406        f.run(&[
27407            b"FT.CREATE",
27408            b"sx",
27409            b"PREFIX",
27410            b"1",
27411            b"d:",
27412            b"SCHEMA",
27413            b"t",
27414            b"TEXT",
27415            b"g",
27416            b"TAG",
27417            b"n",
27418            b"NUMERIC",
27419        ]);
27420        for (key, text, tag, number) in [
27421            (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
27422            (b"d:2", "alpha gamma", "bb", "2"),
27423            (b"d:3", "delta", "cc", "3"),
27424            (b"d:4", "alpha beta gamma", "aa,cc", "4"),
27425        ] {
27426            f.run(&[
27427                b"HSET",
27428                key,
27429                b"t",
27430                text.as_bytes(),
27431                b"g",
27432                tag.as_bytes(),
27433                b"n",
27434                number.as_bytes(),
27435            ]);
27436        }
27437    }
27438
27439    /// A corpus with something to sort by: a text field the index keeps a copy
27440    /// of, a number, the same text field under another name, and a text field
27441    /// the index keeps nothing of.
27442    fn sortable(f: &mut Fixture) {
27443        f.run(&[
27444            b"FT.CREATE",
27445            b"sy",
27446            b"PREFIX",
27447            b"1",
27448            b"s:",
27449            b"SCHEMA",
27450            b"t",
27451            b"TEXT",
27452            b"SORTABLE",
27453            b"n",
27454            b"NUMERIC",
27455            b"SORTABLE",
27456            b"body",
27457            b"AS",
27458            b"b",
27459            b"TEXT",
27460            b"SORTABLE",
27461            b"p",
27462            b"TEXT",
27463        ]);
27464        for (key, text, number) in [
27465            (b"s:1".as_slice(), "Banana Split", "2"),
27466            (b"s:2", "apple", "10"),
27467        ] {
27468            f.run(&[
27469                b"HSET",
27470                key,
27471                b"t",
27472                text.as_bytes(),
27473                b"n",
27474                number.as_bytes(),
27475                b"body",
27476                text.as_bytes(),
27477                b"p",
27478                b"alpha",
27479            ]);
27480        }
27481        // A key with nothing under either sortable field, which is what sorts
27482        // last whichever way round the sort runs.
27483        f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
27484    }
27485
27486    /// A sort runs off the copy of the value the index keeps, and a row with no
27487    /// value at all is last both ways round.
27488    #[test]
27489    fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
27490        let mut f = Fixture::new();
27491        sortable(&mut f);
27492        assert_eq!(
27493            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
27494            "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27495        );
27496        assert_eq!(
27497            f.run(&[
27498                b"FT.SEARCH",
27499                b"sy",
27500                b"alpha",
27501                b"SORTBY",
27502                b"n",
27503                b"DESC",
27504                b"NOCONTENT"
27505            ]),
27506            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27507        );
27508        // The copy of a text field is folded, so `apple` sorts before
27509        // `Banana Split` where a comparison of the bytes would not.
27510        assert_eq!(
27511            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
27512            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27513        );
27514    }
27515
27516    /// A field the index keeps no copy of is sorted by the value read off the
27517    /// key, which happens after the walk rather than during it.
27518    #[test]
27519    fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
27520        let mut f = Fixture::new();
27521        sortable(&mut f);
27522        f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
27523        assert_eq!(
27524            f.run(&[
27525                b"FT.SEARCH",
27526                b"sy",
27527                b"alpha",
27528                b"SORTBY",
27529                b"p",
27530                b"NOCONTENT",
27531                b"LIMIT",
27532                b"0",
27533                b"2"
27534            ]),
27535            "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27536        );
27537        // Nothing is folded on this side, because the schema never asked for a
27538        // copy to fold, so the value goes into the sort as it was written.
27539        assert_eq!(
27540            f.run(&[
27541                b"FT.SEARCH",
27542                b"sy",
27543                b"alpha",
27544                b"SORTBY",
27545                b"p",
27546                b"WITHSORTKEYS",
27547                b"NOCONTENT",
27548                b"LIMIT",
27549                b"2",
27550                b"1"
27551            ]),
27552            "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
27553        );
27554    }
27555
27556    /// The value the sort compared goes beside every row, as a number after a
27557    /// hash, as text after a dollar, and as a null on a row that had none.
27558    #[test]
27559    fn a_search_can_send_the_value_it_sorted_by_back() {
27560        let mut f = Fixture::new();
27561        sortable(&mut f);
27562        assert_eq!(
27563            f.run(&[
27564                b"FT.SEARCH",
27565                b"sy",
27566                b"alpha",
27567                b"SORTBY",
27568                b"n",
27569                b"WITHSORTKEYS",
27570                b"NOCONTENT"
27571            ]),
27572            concat!(
27573                "*7\r\n:3\r\n",
27574                "$3\r\ns:1\r\n$2\r\n#2\r\n",
27575                "$3\r\ns:2\r\n$3\r\n#10\r\n",
27576                "$3\r\ns:3\r\n$-1\r\n"
27577            )
27578        );
27579        assert_eq!(
27580            f.run(&[
27581                b"FT.SEARCH",
27582                b"sy",
27583                b"alpha",
27584                b"SORTBY",
27585                b"t",
27586                b"WITHSORTKEYS",
27587                b"NOCONTENT"
27588            ]),
27589            concat!(
27590                "*7\r\n:3\r\n",
27591                "$3\r\ns:2\r\n$6\r\n$apple\r\n",
27592                "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
27593                "$3\r\ns:3\r\n$-1\r\n"
27594            )
27595        );
27596        // Asking for a sort key without sorting is taken and answers a null on
27597        // every row, which is what a real server does.
27598        assert_eq!(
27599            f.run(&[
27600                b"FT.SEARCH",
27601                b"sy",
27602                b"banana",
27603                b"WITHSORTKEYS",
27604                b"NOCONTENT"
27605            ]),
27606            "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
27607        );
27608    }
27609
27610    /// The field a search sorted by is written in front of the fields of the
27611    /// key, and the key's own value for it wins when the two share a name.
27612    #[test]
27613    fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
27614        let mut f = Fixture::new();
27615        sortable(&mut f);
27616        // `b` is what the schema calls the field the key calls `body`, so the
27617        // folded copy comes back under one name and the value as it was written
27618        // comes back under the other.
27619        assert_eq!(
27620            f.run(&[
27621                b"FT.SEARCH",
27622                b"sy",
27623                b"alpha",
27624                b"SORTBY",
27625                b"b",
27626                b"LIMIT",
27627                b"0",
27628                b"1"
27629            ]),
27630            concat!(
27631                "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
27632                "$1\r\nb\r\n$5\r\napple\r\n",
27633                "$1\r\nt\r\n$5\r\napple\r\n",
27634                "$1\r\nn\r\n$2\r\n10\r\n",
27635                "$4\r\nbody\r\n$5\r\napple\r\n",
27636                "$1\r\np\r\n$5\r\nalpha\r\n"
27637            )
27638        );
27639        // With a `RETURN` list there is nothing to put in, so the field is moved
27640        // to the front of the names that were asked for instead.
27641        assert_eq!(
27642            f.run(&[
27643                b"FT.SEARCH",
27644                b"sy",
27645                b"alpha",
27646                b"SORTBY",
27647                b"b",
27648                b"RETURN",
27649                b"2",
27650                b"p",
27651                b"b",
27652                b"LIMIT",
27653                b"0",
27654                b"1"
27655            ]),
27656            concat!(
27657                "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
27658                "$1\r\nb\r\n$5\r\napple\r\n",
27659                "$1\r\np\r\n$5\r\nalpha\r\n"
27660            )
27661        );
27662    }
27663
27664    /// The four ways a `SORTBY` on a search is refused.
27665    #[test]
27666    fn a_search_refuses_the_sorts_it_cannot_run() {
27667        let mut f = Fixture::new();
27668        sortable(&mut f);
27669        assert_eq!(
27670            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
27671            "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
27672        );
27673        assert_eq!(
27674            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
27675            "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
27676        );
27677        assert_eq!(
27678            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
27679            "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
27680        );
27681        assert_eq!(
27682            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
27683            "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
27684        );
27685        // The property is looked up once the whole list has read cleanly, so a
27686        // word after it that nobody knows is the error that comes back.
27687        assert_eq!(
27688            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
27689            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
27690        );
27691    }
27692
27693    /// An index over two text fields, a number and a tag, holding one key whose
27694    /// `a` runs long enough to be worth cutting down and whose `b` and `g` hold
27695    /// nothing the query matches.
27696    fn marking(f: &mut Fixture) {
27697        f.run(&[
27698            b"FT.CREATE",
27699            b"mk",
27700            b"ON",
27701            b"HASH",
27702            b"PREFIX",
27703            b"1",
27704            b"m:",
27705            b"SCHEMA",
27706            b"a",
27707            b"TEXT",
27708            b"b",
27709            b"TEXT",
27710            b"n",
27711            b"NUMERIC",
27712            b"g",
27713            b"TAG",
27714        ]);
27715        f.run(&[
27716            b"HSET",
27717            b"m:1",
27718            b"a",
27719            b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
27720            b"b",
27721            b"t1 t2 t3 t4 t5 t6 t7 t8",
27722            b"n",
27723            b"1",
27724            b"g",
27725            b"red",
27726        ]);
27727    }
27728
27729    /// A field the query matched comes back as fragments and a field it did not
27730    /// comes back as its own front.
27731    #[test]
27732    fn a_summarize_cuts_a_field_down_to_what_matched() {
27733        let mut f = Fixture::new();
27734        marking(&mut f);
27735        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
27736        assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
27737        // `b` holds no match, so it keeps its front and loses its last word.
27738        assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
27739        // And so does the tag, which is a value like any other to this clause.
27740        assert!(got.contains("$1\r\nr\r\n"), "{got}");
27741    }
27742
27743    /// `FRAGS` is applied before the context either side of a fragment is worked
27744    /// out, so the fragment that is left runs over the match of the one that was
27745    /// dropped rather than stopping on it.
27746    #[test]
27747    fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
27748        let mut f = Fixture::new();
27749        marking(&mut f);
27750        let got = f.run(&[
27751            b"FT.SEARCH",
27752            b"mk",
27753            b"fox",
27754            b"SUMMARIZE",
27755            b"FRAGS",
27756            b"1",
27757            b"LEN",
27758            b"20",
27759        ]);
27760        assert!(
27761            got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
27762            "{got}"
27763        );
27764        // Keep both and the first stops on the second rather than running over
27765        // it, on the same query and the same budget.
27766        let two = f.run(&[
27767            b"FT.SEARCH",
27768            b"mk",
27769            b"fox",
27770            b"SUMMARIZE",
27771            b"FRAGS",
27772            b"2",
27773            b"LEN",
27774            b"20",
27775        ]);
27776        assert!(
27777            two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
27778            "{two}"
27779        );
27780    }
27781
27782    /// A `HIGHLIGHT` wraps every match, and on a field with no match in it the
27783    /// clause also calls off the cutting down a `SUMMARIZE` would have done.
27784    #[test]
27785    fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
27786        let mut f = Fixture::new();
27787        marking(&mut f);
27788        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
27789        assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
27790        let both = f.run(&[
27791            b"FT.SEARCH",
27792            b"mk",
27793            b"fox",
27794            b"SUMMARIZE",
27795            b"LEN",
27796            b"2",
27797            b"HIGHLIGHT",
27798        ]);
27799        assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
27800        // `b` still holds no match, and this time it comes back whole.
27801        assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
27802        assert!(both.contains("$3\r\nred\r\n"), "{both}");
27803        // Naming a field one clause does not cover leaves it cut down again.
27804        let split = f.run(&[
27805            b"FT.SEARCH",
27806            b"mk",
27807            b"fox",
27808            b"SUMMARIZE",
27809            b"FIELDS",
27810            b"1",
27811            b"b",
27812            b"LEN",
27813            b"2",
27814            b"HIGHLIGHT",
27815            b"FIELDS",
27816            b"1",
27817            b"a",
27818        ]);
27819        assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
27820    }
27821
27822    /// A tag is never marked, in its own field or in a text field beside it.
27823    #[test]
27824    fn a_highlight_does_not_mark_a_tag() {
27825        let mut f = Fixture::new();
27826        marking(&mut f);
27827        f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
27828        let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
27829        assert!(!got.contains("<b>"), "{got}");
27830        assert!(got.contains("red and blue"), "{got}");
27831    }
27832
27833    /// A search answers a total and then a row for every key in the window,
27834    /// with the fields of that key after it.
27835    #[test]
27836    fn a_search_answers_a_total_and_then_the_rows() {
27837        let mut f = Fixture::new();
27838        corpus(&mut f);
27839        assert_eq!(
27840            f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
27841            "*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"
27842        );
27843        // The fields are what the key holds and not what the schema names, so
27844        // a field nobody indexed comes back too.
27845        f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
27846        assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
27847        // `NOCONTENT` leaves the keys on their own, and `LIMIT 0 0` leaves
27848        // the total on its own.
27849        assert_eq!(
27850            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27851            "*2\r\n:1\r\n$3\r\nd:3\r\n"
27852        );
27853        assert_eq!(
27854            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27855            "*1\r\n:3\r\n"
27856        );
27857    }
27858
27859    /// The window is ten rows when nobody said, and the cap is on how wide it
27860    /// is rather than on where it starts.
27861    #[test]
27862    fn the_window_is_ten_rows_and_a_million_wide_at_most() {
27863        let mut f = Fixture::new();
27864        corpus(&mut f);
27865        assert_eq!(
27866            f.run(&[
27867                b"FT.SEARCH",
27868                b"sx",
27869                b"alpha",
27870                b"NOCONTENT",
27871                b"LIMIT",
27872                b"1",
27873                b"1"
27874            ]),
27875            "*2\r\n:3\r\n$3\r\nd:2\r\n"
27876        );
27877        assert_eq!(
27878            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
27879            "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
27880        );
27881        assert_eq!(
27882            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
27883            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27884        );
27885        assert_eq!(
27886            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
27887            "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
27888        );
27889        assert_eq!(
27890            f.run(&[
27891                b"FT.SEARCH",
27892                b"sx",
27893                b"alpha",
27894                b"NOCONTENT",
27895                b"LIMIT",
27896                b"999999",
27897                b"1000000"
27898            ]),
27899            "*1\r\n:3\r\n"
27900        );
27901    }
27902
27903    /// `RETURN 0` reads on the wire like `NOCONTENT` and is not the same
27904    /// thing, because a later `RETURN` puts the fields back and a later
27905    /// `RETURN` after a `NOCONTENT` does not.
27906    #[test]
27907    fn a_return_of_nothing_is_not_the_same_as_nocontent() {
27908        let mut f = Fixture::new();
27909        corpus(&mut f);
27910        let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
27911        assert_eq!(
27912            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
27913            bare
27914        );
27915        assert_eq!(
27916            f.run(&[
27917                b"FT.SEARCH",
27918                b"sx",
27919                b"delta",
27920                b"NOCONTENT",
27921                b"RETURN",
27922                b"1",
27923                b"t"
27924            ]),
27925            bare
27926        );
27927        assert_eq!(
27928            f.run(&[
27929                b"FT.SEARCH",
27930                b"sx",
27931                b"delta",
27932                b"RETURN",
27933                b"0",
27934                b"RETURN",
27935                b"1",
27936                b"t"
27937            ]),
27938            "*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"
27939        );
27940    }
27941
27942    /// The count after `RETURN` counts words and not fields, so the `AS` and
27943    /// the name after it are two of them.
27944    #[test]
27945    fn the_count_after_return_counts_words() {
27946        let mut f = Fixture::new();
27947        corpus(&mut f);
27948        // Two words is one renamed field, and the name is the one it comes
27949        // back under.
27950        assert_eq!(
27951            f.run(&[
27952                b"FT.SEARCH",
27953                b"sx",
27954                b"delta",
27955                b"RETURN",
27956                b"3",
27957                b"t",
27958                b"AS",
27959                b"x"
27960            ]),
27961            "*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"
27962        );
27963        // A count that stops on the `AS` has nothing to rename to, and one
27964        // that reaches past the last word is short an argument.
27965        assert_eq!(
27966            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
27967            "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
27968        );
27969        assert_eq!(
27970            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
27971            "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
27972        );
27973        // A count that stops before the `AS` asks for a field called `AS`,
27974        // which no key holds, and a field the key does not hold is left out
27975        // rather than sent empty.
27976        assert_eq!(
27977            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
27978            "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
27979        );
27980    }
27981
27982    /// A `FILTER` is a numeric range written outside the query, and it is only
27983    /// the wrong way round on a field the schema holds as a number.
27984    #[test]
27985    fn a_filter_is_a_range_written_outside_the_query() {
27986        let mut f = Fixture::new();
27987        corpus(&mut f);
27988        assert_eq!(
27989            f.run(&[
27990                b"FT.SEARCH",
27991                b"sx",
27992                b"alpha",
27993                b"NOCONTENT",
27994                b"FILTER",
27995                b"n",
27996                b"2",
27997                b"4"
27998            ]),
27999            "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
28000        );
28001        assert_eq!(
28002            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
28003            "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
28004        );
28005        assert_eq!(
28006            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
28007            "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
28008        );
28009        assert_eq!(
28010            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
28011            "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
28012        );
28013        // The same range on a field that is not a number at all, and on a
28014        // field that is not there, answers nothing rather than refusing.
28015        for field in [b"g".as_slice(), b"nope"] {
28016            assert_eq!(
28017                f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
28018                "*1\r\n:0\r\n"
28019            );
28020        }
28021    }
28022
28023    /// The index is resolved before the arguments after it are read, so a name
28024    /// that is not there answers about the name whatever else is wrong.
28025    #[test]
28026    fn the_index_is_found_before_the_arguments_are_read() {
28027        let mut f = Fixture::new();
28028        corpus(&mut f);
28029        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
28030        assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
28031        assert_eq!(
28032            f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
28033            missing
28034        );
28035        // And the arguments are read before the query is, so a query that
28036        // will not parse still answers about the argument.
28037        assert_eq!(
28038            f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
28039            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
28040        );
28041    }
28042
28043    /// `INKEYS` filters the answer before the total is taken, which is not
28044    /// where a client would guess it happens.
28045    #[test]
28046    fn inkeys_comes_off_the_total() {
28047        let mut f = Fixture::new();
28048        corpus(&mut f);
28049        assert_eq!(
28050            f.run(&[
28051                b"FT.SEARCH",
28052                b"sx",
28053                b"alpha",
28054                b"NOCONTENT",
28055                b"INKEYS",
28056                b"1",
28057                b"d:1"
28058            ]),
28059            "*2\r\n:1\r\n$3\r\nd:1\r\n"
28060        );
28061        assert_eq!(
28062            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
28063            "*1\r\n:0\r\n"
28064        );
28065    }
28066
28067    /// The fields come from the database the session is on, and a row whose
28068    /// key will not load there is dropped from the reply and taken off the
28069    /// total.
28070    ///
28071    /// Measured against a real server, which follows a key on every database
28072    /// and then loads it from one.
28073    #[test]
28074    fn the_fields_are_read_from_the_session_database() {
28075        let mut f = Fixture::new();
28076        corpus(&mut f);
28077        f.run(&[b"SELECT", b"1"]);
28078        f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
28079        // Both documents are in the index, and only one of them is in this
28080        // database.
28081        assert_eq!(
28082            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28083            "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
28084        );
28085        assert_eq!(
28086            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28087            "*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"
28088        );
28089    }
28090
28091    /// The deeper protocol answers a map of five rather than an array, with
28092    /// every row a map of its own.
28093    #[test]
28094    fn the_third_protocol_answers_a_map_of_five() {
28095        let mut f = Fixture::new();
28096        corpus(&mut f);
28097        f.out = Out::new(Proto::Resp3);
28098        assert_eq!(
28099            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28100            concat!(
28101                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28102                "%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",
28103                "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28104            )
28105        );
28106    }
28107
28108    /// A window of nothing is a client asking for the count on its own, and a
28109    /// window of nothing that starts somewhere else is a contradiction all
28110    /// three commands refuse in the same words.
28111    #[test]
28112    fn a_window_of_nothing_has_to_start_at_the_top() {
28113        let mut f = Fixture::new();
28114        corpus(&mut f);
28115        let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
28116        assert_eq!(
28117            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28118            refused
28119        );
28120        assert_eq!(
28121            f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28122            refused
28123        );
28124        assert_eq!(
28125            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28126            refused
28127        );
28128        assert_eq!(
28129            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28130            "*1\r\n:3\r\n"
28131        );
28132    }
28133
28134    /// An aggregation answers a count and then a list of properties for every
28135    /// row, which is empty until something asks for a field.
28136    #[test]
28137    fn an_aggregation_answers_a_count_and_then_the_properties() {
28138        let mut f = Fixture::new();
28139        corpus(&mut f);
28140        assert_eq!(
28141            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
28142            "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
28143        );
28144        // Every row, and not the ten a search would have cut it down to. The
28145        // count in front of them is one because that is how far the reply had
28146        // got when it was written, which is measured against a real server.
28147        assert_eq!(
28148            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
28149            concat!(
28150                "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28151                "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
28152                "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
28153            )
28154        );
28155        // Ascending document number, because nothing sorts the answer. The
28156        // second and fourth documents are the ones the window lands on and the
28157        // best scoring one is not among them.
28158        assert_eq!(
28159            f.run(&[
28160                b"FT.AGGREGATE",
28161                b"sx",
28162                b"alpha",
28163                b"LOAD",
28164                b"1",
28165                b"@n",
28166                b"LIMIT",
28167                b"1",
28168                b"2"
28169            ]),
28170            "*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"
28171        );
28172        // A query nothing answers is a count of nothing and no rows at all.
28173        assert_eq!(
28174            f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
28175            "*1\r\n:0\r\n"
28176        );
28177    }
28178
28179    /// `LOAD` counts words rather than fields, names the property after the
28180    /// path unless an `AS` renames it, and reads everything the key holds when
28181    /// it is given a star.
28182    #[test]
28183    fn a_load_counts_words_and_can_rename_what_it_reads() {
28184        let mut f = Fixture::new();
28185        corpus(&mut f);
28186        // Three words, which are the path, the `AS` and the name.
28187        assert_eq!(
28188            f.run(&[
28189                b"FT.AGGREGATE",
28190                b"sx",
28191                b"alpha",
28192                b"LOAD",
28193                b"3",
28194                b"@t",
28195                b"AS",
28196                b"text"
28197            ]),
28198            concat!(
28199                "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
28200                "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
28201                "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
28202            )
28203        );
28204        assert_eq!(
28205            f.run(&[
28206                b"FT.AGGREGATE",
28207                b"sx",
28208                b"alpha",
28209                b"LOAD",
28210                b"*",
28211                b"LIMIT",
28212                b"0",
28213                b"1"
28214            ]),
28215            concat!(
28216                "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28217                "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
28218            )
28219        );
28220        // A field the key does not hold is left out rather than sent empty.
28221        assert_eq!(
28222            f.run(&[
28223                b"FT.AGGREGATE",
28224                b"sx",
28225                b"alpha",
28226                b"LOAD",
28227                b"2",
28228                b"@n",
28229                b"@nope",
28230                b"LIMIT",
28231                b"0",
28232                b"2"
28233            ]),
28234            "*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"
28235        );
28236    }
28237
28238    /// The `LOAD` grammar, which has four ways to go wrong and one of them is
28239    /// only reported once the rest of the argument list has read cleanly.
28240    #[test]
28241    fn a_load_refuses_a_count_it_cannot_use() {
28242        let mut f = Fixture::new();
28243        corpus(&mut f);
28244        let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
28245        assert_eq!(
28246            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
28247            format!("{head}Expected number of fields or `*`\r\n")
28248        );
28249        assert_eq!(
28250            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
28251            format!("{head}Value is outside acceptable bounds\r\n")
28252        );
28253        assert_eq!(
28254            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
28255            format!("{head}Expected an argument, but none provided\r\n")
28256        );
28257        assert_eq!(
28258            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
28259            format!("{head}Expected an argument, but none provided\r\n")
28260        );
28261        // A count that runs out on the `AS` is held back, because the word
28262        // after it is read as an argument of its own and may be worth an error
28263        // of its own. Nothing follows here, so the held back line is the one.
28264        assert_eq!(
28265            f.run(&[
28266                b"FT.AGGREGATE",
28267                b"sx",
28268                b"alpha",
28269                b"LOAD",
28270                b"2",
28271                b"@t",
28272                b"AS"
28273            ]),
28274            "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
28275        );
28276        // And here the word after it is one an aggregation stops taking once a
28277        // step has been read, so that is what the client hears about.
28278        assert_eq!(
28279            f.run(&[
28280                b"FT.AGGREGATE",
28281                b"sx",
28282                b"alpha",
28283                b"LOAD",
28284                b"2",
28285                b"@t",
28286                b"AS",
28287                b"VERBATIM"
28288            ]),
28289            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
28290        );
28291        // A `LOAD 0` is a step that names nothing. It shuts the same door
28292        // without becoming a loader, so the count stays the one a query with no
28293        // `LOAD` gets.
28294        assert_eq!(
28295            f.run(&[
28296                b"FT.AGGREGATE",
28297                b"sx",
28298                b"alpha",
28299                b"LOAD",
28300                b"0",
28301                b"LIMIT",
28302                b"0",
28303                b"1"
28304            ]),
28305            "*2\r\n:1\r\n*0\r\n"
28306        );
28307    }
28308
28309    /// Reading a step of the pipeline stops the words about the search itself
28310    /// being taken, and `LIMIT` and `TIMEOUT` are not steps.
28311    #[test]
28312    fn a_pipeline_step_closes_the_door_on_the_search_words() {
28313        let mut f = Fixture::new();
28314        corpus(&mut f);
28315        assert_eq!(
28316            f.run(&[
28317                b"FT.AGGREGATE",
28318                b"sx",
28319                b"alpha",
28320                b"LOAD",
28321                b"1",
28322                b"@t",
28323                b"VERBATIM"
28324            ]),
28325            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
28326        );
28327        assert_eq!(
28328            f.run(&[
28329                b"FT.AGGREGATE",
28330                b"sx",
28331                b"alpha",
28332                b"LIMIT",
28333                b"0",
28334                b"1",
28335                b"VERBATIM"
28336            ]),
28337            "*2\r\n:1\r\n*0\r\n"
28338        );
28339        // Three words a search takes that this command names in its refusal
28340        // rather than calling them unknown.
28341        for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
28342            let name = core::str::from_utf8(word).expect("the three words are text");
28343            assert_eq!(
28344                f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
28345                format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
28346            );
28347        }
28348    }
28349
28350    /// `ADDSCORES` writes the score as a property to twelve significant digits
28351    /// where `WITHSCORES` writes it beside the row in full.
28352    #[test]
28353    fn addscores_writes_a_shorter_score_than_withscores() {
28354        let mut f = Fixture::new();
28355        corpus(&mut f);
28356        assert_eq!(
28357            f.run(&[
28358                b"FT.AGGREGATE",
28359                b"sx",
28360                b"alpha",
28361                b"ADDSCORES",
28362                b"LOAD",
28363                b"1",
28364                b"@n",
28365                b"LIMIT",
28366                b"0",
28367                b"2"
28368            ]),
28369            concat!(
28370                "*3\r\n:1\r\n",
28371                "*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",
28372                "*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"
28373            )
28374        );
28375        // `NOCONTENT` takes the properties away and leaves whatever was asked
28376        // for beside them, and a sort key is always null because nothing sorts
28377        // by one yet.
28378        assert_eq!(
28379            f.run(&[
28380                b"FT.AGGREGATE",
28381                b"sx",
28382                b"alpha",
28383                b"NOCONTENT",
28384                b"WITHSCORES",
28385                b"LIMIT",
28386                b"0",
28387                b"2"
28388            ]),
28389            "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
28390        );
28391        assert_eq!(
28392            f.run(&[
28393                b"FT.AGGREGATE",
28394                b"sx",
28395                b"alpha",
28396                b"WITHSORTKEYS",
28397                b"LOAD",
28398                b"1",
28399                b"@n",
28400                b"LIMIT",
28401                b"0",
28402                b"1"
28403            ]),
28404            "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
28405        );
28406    }
28407
28408    /// The one scorer that has to see the whole answer first turns the count
28409    /// into the real total and hands the rows back backwards.
28410    #[test]
28411    fn a_normalising_scorer_answers_the_rows_backwards() {
28412        let mut f = Fixture::new();
28413        corpus(&mut f);
28414        assert_eq!(
28415            f.run(&[
28416                b"FT.AGGREGATE",
28417                b"sx",
28418                b"alpha",
28419                b"SCORER",
28420                b"BM25STD.NORM",
28421                b"ADDSCORES",
28422                b"LOAD",
28423                b"1",
28424                b"@n",
28425                b"LIMIT",
28426                b"1",
28427                b"2"
28428            ]),
28429            concat!(
28430                "*3\r\n:3\r\n",
28431                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
28432                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
28433            )
28434        );
28435        // Without `ADDSCORES` nothing on the row needs the score, so the rows
28436        // come back the way every other query answers them.
28437        assert_eq!(
28438            f.run(&[
28439                b"FT.AGGREGATE",
28440                b"sx",
28441                b"alpha",
28442                b"SCORER",
28443                b"BM25STD.NORM",
28444                b"LOAD",
28445                b"1",
28446                b"@n",
28447                b"LIMIT",
28448                b"1",
28449                b"2"
28450            ]),
28451            "*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"
28452        );
28453    }
28454
28455    /// The deeper protocol answers the same map of five a search answers, with
28456    /// the `id` gone because an aggregation is about the properties.
28457    #[test]
28458    fn an_aggregation_answers_a_map_of_five_as_well() {
28459        let mut f = Fixture::new();
28460        corpus(&mut f);
28461        f.out = Out::new(Proto::Resp3);
28462        assert_eq!(
28463            f.run(&[
28464                b"FT.AGGREGATE",
28465                b"sx",
28466                b"alpha",
28467                b"ADDSCORES",
28468                b"WITHSCORES",
28469                b"WITHSORTKEYS",
28470                b"LOAD",
28471                b"1",
28472                b"@n",
28473                b"LIMIT",
28474                b"0",
28475                b"1"
28476            ]),
28477            concat!(
28478                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28479                "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
28480                "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
28481                "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
28482                "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28483            )
28484        );
28485        // The count is worked out from the rows the reply reached under this
28486        // protocol, where under RESP2 it is worked out from the first of them.
28487        assert_eq!(
28488            f.run(&[
28489                b"FT.AGGREGATE",
28490                b"sx",
28491                b"alpha",
28492                b"NOCONTENT",
28493                b"LIMIT",
28494                b"0",
28495                b"1"
28496            ]),
28497            concat!(
28498                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28499                "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28500            )
28501        );
28502    }
28503}