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                // Against this database rather than whichever one the write
1553                // that provoked the eviction was aimed at, since the key that
1554                // goes is this one's. The funnel is already armed above and
1555                // this is a second one inside it, which is what the answer
1556                // going back into the drain is for.
1557                let armed = notify::arm(self, self.slot_db(i));
1558                let gone = self.slot(i).evict_one();
1559                notify::drain(self, armed);
1560                gone
1561            };
1562            if gave {
1563                self.evict_db.store((i + 1) % self.slots(), Relaxed);
1564                self.mine().mark(1u64 << self.slot_db(i));
1565                return true;
1566            }
1567        }
1568        false
1569    }
1570
1571    /// The sweep the shard loop calls, at most once a millisecond.
1572    ///
1573    /// The gate is the whole difference between this and [`Server::expire_step`].
1574    /// A maintenance slice runs on every turn of the loop and a turn is a
1575    /// hundred nanoseconds, so an ungated sweep would draw a fresh sample ten
1576    /// thousand times per millisecond and spend a real share of the shard on
1577    /// looking for keys that cannot have died since the last look. Nothing in a
1578    /// database changes fast enough to be worth asking about more often than the
1579    /// clock can tell the difference, and the clock here is milliseconds.
1580    ///
1581    /// A millisecond is also far finer than Redis, whose slow cycle runs at ten
1582    /// hertz, so this is not the thing that decides how promptly memory comes
1583    /// back. What it decides is that an idle server sweeps a thousand times a
1584    /// second rather than a million.
1585    pub fn expire_slice(&self, budget: usize) -> usize {
1586        let now = self.clock.now_ms();
1587        if now == self.expire_ms.load(Relaxed) {
1588            return 0;
1589        }
1590        self.expire_ms.store(now, Relaxed);
1591        self.expire_step(budget)
1592    }
1593
1594    /// Sweep dead keys out of the databases, spending at most `budget` looks.
1595    ///
1596    /// Answers what it spent, so the caller can charge its maintenance slice for
1597    /// it. See [`yo_kv::expiry`] for why the budget is in keys looked at.
1598    ///
1599    /// Round robin from its own cursor, and every database gets offered whatever
1600    /// is left of the budget rather than a sixteenth of it each, so a server on
1601    /// database zero only, which is nearly every server, spends the whole slice
1602    /// where the keys are. The fifteen empty ones cost a comparison apiece
1603    /// because a database with no key carrying a deadline says so without
1604    /// drawing anything.
1605    ///
1606    /// The cursor moves to the database after whichever one did the work, so two
1607    /// busy databases take turns instead of the lower numbered one starving the
1608    /// other.
1609    pub fn expire_step(&self, budget: usize) -> usize {
1610        let mut spent = 0;
1611        let from = self.expire_db.load(Relaxed);
1612        for turn in 0..self.slots() {
1613            if spent >= budget {
1614                break;
1615            }
1616            let i = (from + turn) % self.slots();
1617            // Nothing armed this thread, because nothing asked for any of this:
1618            // the shard loop is between commands. So the sweep arms and drains
1619            // around itself, and a key it takes is news to a subscriber in the
1620            // same way a key a lookup took on the way past is.
1621            let armed = notify::arm(self, self.slot_db(i));
1622            let c = self.slot(i).expire_cycle(budget - spent);
1623            notify::drain(self, armed);
1624            spent += c.examined;
1625            if c.expired > 0 {
1626                self.expire_db.store((i + 1) % self.slots(), Relaxed);
1627                self.mine().note(1u64 << self.slot_db(i));
1628            }
1629        }
1630        spent
1631    }
1632
1633    /// One slice of compaction for a server that is over its limit.
1634    ///
1635    /// Takes the databases in the same order [`Server::compact_step`] does and
1636    /// stops at the first one that had something to move, and it asks with the
1637    /// ratios off. See [`Keyspace::compact_hard`] for what that changes.
1638    fn compact_hard_step(&self) -> Option<usize> {
1639        let from = self.next_db.load(Relaxed);
1640        for turn in 0..self.slots() {
1641            let i = (from + turn) % self.slots();
1642            if let Some(moved) = self.slot(i).compact_hard() {
1643                self.next_db.store((i + 1) % self.slots(), Relaxed);
1644                return Some(moved);
1645            }
1646        }
1647        None
1648    }
1649
1650    /// Take what every thread has marked and add it to the turn's own mask.
1651    ///
1652    /// The mask the turn works from is its own and not a shared one, because a
1653    /// mask it read in place and then cleared a bit of would be a mask that lost
1654    /// whatever another thread marked in between. A swap cannot lose a mark: a
1655    /// thread that ors while the swap happens either gets its bit in before the
1656    /// swap or leaves it there afterwards, and the second one costs one look at
1657    /// a database the turn has already been through.
1658    fn collect_marks(&self) {
1659        let mut marked = 0;
1660        for thread in &self.locals {
1661            marked |= thread.dirty.swap(0, Relaxed);
1662        }
1663        self.mine().note(marked);
1664    }
1665
1666    /// Give one database's dead space back, if any database has enough of it to
1667    /// be worth the move. `None` when no database had a candidate.
1668    ///
1669    /// Once per batch, next to the clock. Overwriting a key writes a new record
1670    /// and counts the old one dead, so without this a server holds everything
1671    /// it has ever written: 400000 sets over 100000 keys measured at 742 bytes
1672    /// a key against Redis at 144 for the same load, and the whole difference
1673    /// was dead records nothing ever came back for.
1674    ///
1675    /// At most one segment moves per call and the search starts one database
1676    /// further along each time, so the cost of asking is a comparison per
1677    /// database and the cost of acting is bounded by a segment.
1678    pub fn compact_step(&self) -> Option<usize> {
1679        self.collect_marks();
1680        let mine = self.mine();
1681        let from = self.next_db.load(Relaxed);
1682        for turn in 0..self.slots() {
1683            let i = (from + turn) % self.slots();
1684            // Nothing has run against this database since it last said it had
1685            // nothing to collect, so it still has nothing to collect and the
1686            // line it lives on stays where it is.
1687            let at = self.slot_db(i);
1688            if !mine.wanted(at) {
1689                continue;
1690            }
1691            if let Some(moved) = self.slot(i).compact_step() {
1692                self.next_db.store((i + 1) % self.slots(), Relaxed);
1693                return Some(moved);
1694            }
1695            // Only once every stripe of the database has said it has nothing,
1696            // since the bit is per database and one stripe answering for all of
1697            // them would stop the others being asked at all.
1698            if i % self.width == self.width - 1 {
1699                mine.done(at);
1700            }
1701        }
1702        None
1703    }
1704}
1705
1706impl Server {
1707    /// Whether anybody is watching anything.
1708    ///
1709    /// The one thing every write asks about watches, and it is a relaxed load of
1710    /// a word that is zero and shared on a server where no client has ever sent
1711    /// `WATCH`. Relaxed is enough because the answer only has to be right by the
1712    /// time it matters: a `WATCH` that has not been published yet has not
1713    /// returned to its client either, so no client can have started a
1714    /// transaction that depends on it.
1715    fn watching(&self) -> bool {
1716        self.watched.load(Relaxed) != 0
1717    }
1718
1719    /// Which classes of keyspace notification are turned on.
1720    ///
1721    /// Zero is off, which is the default and is what nearly every server runs
1722    /// with. Relaxed for the same reason the watch count is: a `CONFIG SET` that
1723    /// has not been published to another thread yet has not answered its client
1724    /// either.
1725    pub(crate) fn notify_flags(&self) -> u32 {
1726        self.notify.load(Relaxed)
1727    }
1728
1729    /// Turn a set of notification classes on, or turn them all off with zero.
1730    pub(crate) fn set_notify_flags(&self, flags: u32) {
1731        self.notify.store(flags, Relaxed);
1732    }
1733
1734    /// Note how many watched keys there are, after the table changed.
1735    ///
1736    /// Taken from the table under the same lock the change was made under, so
1737    /// the count can never say nobody is watching while somebody is.
1738    fn recount(&self, watches: &Watches) {
1739        self.watched.store(watches.len(), Relaxed);
1740    }
1741}
1742
1743impl Default for Server {
1744    fn default() -> Server {
1745        Server::new()
1746    }
1747}
1748
1749/// What one connection has chosen.
1750pub struct Session {
1751    db: usize,
1752    id: u64,
1753    /// Which connection slot on the front this session belongs to.
1754    ///
1755    /// Carried here so that a command can say where a reply for this connection
1756    /// goes without the front having to be asked. Pub/sub is what needs it: a
1757    /// subscription is a row on the server naming a slot, and the subscribe
1758    /// command is the only moment the connection and the server are both in
1759    /// hand. [`u32::MAX`] for a session that is not on a front, which is a test.
1760    conn: u32,
1761    name: Vec<u8>,
1762    /// The `HIMPORT` fieldsets this connection has prepared.
1763    ///
1764    /// Connection state and not keyspace state, which is the reference's design
1765    /// and not a shortcut: a fieldset is invisible to every other connection and
1766    /// the keys built from one outlive it.
1767    sets: himport::Fieldsets,
1768    /// Whether the command running right now was called by a script.
1769    ///
1770    /// The one thing it changes is what a blocking command does when it finds
1771    /// nothing to take. A client that sent `BLPOP` waits; a script that called
1772    /// `BLPOP` cannot, because the whole server is waiting on the script, and a
1773    /// script that parked would park everything behind it. So inside a script a
1774    /// blocking command times out at once and answers the null a client that
1775    /// waited its full timeout would have got. That is a real server's rule and
1776    /// it is why `BLPOP` is not on the list a script may not call.
1777    scripted: bool,
1778    /// The commands held since `MULTI`, `None` when no transaction is open.
1779    ///
1780    /// Connection state and nothing else. A transaction is invisible to every
1781    /// other connection until `EXEC` runs it, and a connection that goes away
1782    /// with one open has simply not run it.
1783    multi: Option<multi::Queue>,
1784    /// What this connection asked `WATCH` about, and what those keys looked
1785    /// like at the time.
1786    ///
1787    /// The other half is on the server, beside the keys, because a write by
1788    /// another thread has to reach it. See `multi` for why keeping the value
1789    /// here and comparing it at `EXEC` is not the same thing.
1790    watching: Vec<multi::Watched>,
1791    /// Whether the command running right now was handed over by `EXEC`.
1792    ///
1793    /// The one thing it changes is the RESP2 subscribe mode refusal, which a
1794    /// real server makes in `processCommand` and so does not make for a command
1795    /// that was queued: `MULTI`, `SUBSCRIBE z`, `GET x`, `EXEC` runs the `GET`
1796    /// on 8.10.1 even though sending it on its own would have been refused.
1797    running: bool,
1798    /// The buffer `EXEC` decodes the queued commands through.
1799    ///
1800    /// It lives here rather than in `exec` so that its capacity survives the
1801    /// transaction. A fresh one has no room for spans, so the first command of
1802    /// every transaction would allocate, and a client that runs transactions in
1803    /// a loop would be allocating on a command path forever. Everywhere else
1804    /// the buffer belongs to the connection already and the same reserve is
1805    /// free after the first command.
1806    replay: crate::request::Argv,
1807    /// What this connection has subscribed to, `None` until it subscribes to
1808    /// anything.
1809    ///
1810    /// Boxed so that a connection that never subscribes carries a null pointer
1811    /// rather than three empty vectors. The other half is on the server, keyed
1812    /// by name, because a publish arrives on a connection that cannot see this
1813    /// one. See the `pubsub` module.
1814    subs: Option<Box<pubsub::Subs>>,
1815}
1816
1817impl Session {
1818    /// A new connection, on database zero with no name.
1819    #[must_use]
1820    pub fn new(id: u64) -> Session {
1821        Session {
1822            db: 0,
1823            id,
1824            conn: u32::MAX,
1825            name: Vec::new(),
1826            sets: himport::Fieldsets::default(),
1827            scripted: false,
1828            multi: None,
1829            watching: Vec::new(),
1830            running: false,
1831            replay: crate::request::Argv::new(),
1832            subs: None,
1833        }
1834    }
1835
1836    /// Whether a script is what is asking, which only a blocking command reads.
1837    pub(crate) const fn scripted(&self) -> bool {
1838        self.scripted
1839    }
1840
1841    /// Whether `EXEC` is what is asking.
1842    pub(crate) const fn running(&self) -> bool {
1843        self.running
1844    }
1845
1846    /// Say which connection slot this session is in.
1847    ///
1848    /// Called by the front when it opens the connection, which is the only place
1849    /// that knows. A session nobody tells is not on a front, and the one thing
1850    /// that reads this checks the client id before it acts on it.
1851    pub(crate) const fn set_conn(&mut self, conn: u32) {
1852        self.conn = conn;
1853    }
1854
1855    /// The connection id, which `HELLO` reports and `CLIENT` will.
1856    #[must_use]
1857    pub const fn id(&self) -> u64 {
1858        self.id
1859    }
1860
1861    /// Which database this connection is working in.
1862    #[must_use]
1863    pub const fn db(&self) -> usize {
1864        self.db
1865    }
1866
1867    /// The name the client gave itself, empty if it gave none.
1868    #[must_use]
1869    pub fn name(&self) -> &[u8] {
1870        &self.name
1871    }
1872
1873    /// Put everything back the way it was when the connection was opened.
1874    ///
1875    /// The protocol is not here because it is not here: it lives in the reply
1876    /// buffer, and `RESET` sets it back there.
1877    pub fn reset(&mut self) {
1878        self.db = 0;
1879        self.name.clear();
1880        // `SELECT` leaves these alone and `RESET` does not, both checked
1881        // against 8.10.1, which is the one pair of answers you could not guess
1882        // from what the command is for.
1883        self.sets.clear();
1884    }
1885
1886    /// Record the name from `HELLO ... SETNAME`.
1887    fn set_name(&mut self, name: &[u8]) {
1888        yo_alloc::allow(|| {
1889            self.name.clear();
1890            self.name.extend_from_slice(name);
1891        });
1892    }
1893}
1894
1895/// Give back everything a connection was holding on the server.
1896///
1897/// The transaction, the watches and the subscriptions, and it is here rather
1898/// than in [`Session::reset`] because letting go of any of the three is a change
1899/// to the server. A `Session` on its own cannot reach one, and a connection that
1900/// dropped its lists without saying so would leave rows nobody is watching and
1901/// subscriptions nobody is listening to, which would keep every write and every
1902/// publish on the server paying for clients that are not there.
1903pub fn forget_session(server: &Server, session: &mut Session) {
1904    multi::release(server, session);
1905    pubsub::release(server, session);
1906}
1907
1908/// Run one command and write its reply.
1909///
1910/// The name is looked up and the arity is checked here, once, so that no body
1911/// has to. Everything after that is the command's own.
1912pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1913    // The decoder never produces a command with no name. If one ever arrives,
1914    // it is not something to answer.
1915    if args.is_empty() {
1916        return Flow::Continue;
1917    }
1918    resolved(server, session, lookup(args.name()), args, out)
1919}
1920
1921/// The same, for a caller that has already found the command.
1922///
1923/// The engine frames a command before it runs it, and between those two it also
1924/// asks which key the command touches so the record can be prefetched. That is
1925/// two more chances to look the name up, and looking it up three times to run it
1926/// once is three times the cost of the cheapest thing in the path. So the engine
1927/// resolves the name where it frames the command, carries the answer on the
1928/// framed command, and both the other two take it from there.
1929///
1930/// `spec` is `None` for a name that is not a command, which is the same thing
1931/// [`lookup`] says and lands in the same reply.
1932pub fn resolved(
1933    server: &Server,
1934    session: &mut Session,
1935    spec: Option<&'static Spec>,
1936    args: Args<'_>,
1937    out: &mut Out,
1938) -> Flow {
1939    if args.is_empty() {
1940        return Flow::Continue;
1941    }
1942    server.mine().stats.commands.bump();
1943
1944    // The four refusals below are the ones a real server makes in
1945    // `processCommand`, before the command's own body is reached, and they are
1946    // the ones that kill an open transaction. That is the whole of the rule: an
1947    // error raised here means `EXEC` will refuse to run anything, and an error
1948    // raised by a command body does not, which is why `MULTI` inside `MULTI`
1949    // complains and leaves the transaction alive.
1950    let Some(spec) = spec else {
1951        multi::refuse(server, session, None, &args::unknown_command(args), out);
1952        return Flow::Continue;
1953    };
1954    if !arity_ok(spec, args.len()) {
1955        server.mine().cmdstats.at(spec).rejected.bump();
1956        multi::refuse(
1957            server,
1958            session,
1959            Some(spec),
1960            &args::wrong_arity(spec.name),
1961            out,
1962        );
1963        return Flow::Continue;
1964    }
1965    if session.in_multi()
1966        && let Some(e) = multi::refused_in_multi(spec)
1967    {
1968        server.mine().cmdstats.at(spec).rejected.bump();
1969        multi::refuse(server, session, Some(spec), &e, out);
1970        return Flow::Continue;
1971    }
1972
1973    // The limit first, so a server with no `maxmemory`, which is the default and
1974    // is nearly all of them, pays one comparison against a field that is already
1975    // warm. Every command and not only the writes, because that is where Redis
1976    // puts it: making room is the server's job whatever the client asked for,
1977    // and the flag only decides who gets told no when there is no room to make.
1978    //
1979    // The flag is Redis's own `denyoom` and the list of commands carrying it is
1980    // Redis's list, so a command that only frees is let through with nothing
1981    // left, which is what lets a client dig itself out with `DEL`.
1982    if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1983        server.mine().cmdstats.at(spec).rejected.bump();
1984        session.dirty_multi();
1985        out.error_line(b"OOM ", OOM);
1986        return Flow::Continue;
1987    }
1988
1989    // A RESP2 connection that has subscribed to something may only send a
1990    // handful of commands, because RESP2 sends a published message as an
1991    // ordinary array and a client with a reply outstanding could not tell the
1992    // two apart. Here, after the refusals above and before the queue below,
1993    // which is where a real server puts it: `EXEC` sent while subscribed comes
1994    // back as an `EXECABORT` rather than as this error, and a command `EXEC`
1995    // hands over is not asked at all.
1996    if let Some(e) = pubsub::refused(session, spec, out) {
1997        server.mine().cmdstats.at(spec).rejected.bump();
1998        multi::refuse(server, session, Some(spec), &e, out);
1999        return Flow::Continue;
2000    }
2001
2002    // Held rather than run, and the reply is `QUEUED`. After the refusals above
2003    // and before everything below, which is where a real server puts it: a
2004    // command has to be a real command with the right number of arguments to be
2005    // queued at all, and nothing it would have done gets done now.
2006    if session.queues(spec.name) {
2007        return multi::queue(session, args, out);
2008    }
2009
2010    // Which databases the maintenance turn after this batch has to ask. Marked
2011    // for every command and not only for the writes, because a read can make
2012    // garbage too: a `GET` on a key whose expiry has passed reaps it, and the
2013    // record it dropped is exactly the kind of thing the collector is for.
2014    // `COPY`, `SWAPDB` and `FLUSHALL` reach a database nobody selected, so the
2015    // two groups that hold them mark all of them rather than the session's.
2016    server.mine().mark(match spec.group {
2017        "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
2018        | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
2019            1u64 << session.db
2020        }
2021        _ => ALL_DATABASES,
2022    });
2023
2024    let mark = out.len();
2025    // Before the group, because the five that block are list commands and would
2026    // otherwise land in `lists`, which is handed one database and nothing that
2027    // could park a client. The flag is the right thing to branch on rather than
2028    // a list of names: it is what `COMMAND INFO` reports about exactly these
2029    // commands, and the sorted set and stream ones that arrive later carry it
2030    // too.
2031    // What the command is about to do to the keyspace, for anybody subscribed to
2032    // hear about it. Armed here and drained after the group, because the bodies
2033    // below are handed a database and their arguments and have no way to reach
2034    // the pub/sub registry from there. Off costs one thread local store.
2035    let armed = notify::arm(server, session.db);
2036    let done = if spec.flags.contains(&"blocking") {
2037        blocking::execute(server, session, spec, args, out)
2038    } else {
2039        match spec.group {
2040            "string" => {
2041                let db = session.db;
2042                strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2043            }
2044            // Its own group and its own file, and the same values underneath:
2045            // a bitmap is a string, so `STRLEN` on one answers and `SETBIT` on
2046            // something a `SET` left behind works.
2047            "bitmap" => {
2048                let db = session.db;
2049                bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2050            }
2051            // The same again: a sketch is a string with a documented layout, so
2052            // `GET` hands one to a client and `SET` takes it back.
2053            "hyperloglog" => {
2054                let db = session.db;
2055                hll::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2056            }
2057            "set" => {
2058                let db = session.db;
2059                sets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2060            }
2061            // The one hash command whose state is not in the keyspace. A
2062            // fieldset belongs to the connection, so this is handed the session
2063            // as well as the database, the same exception `MIGRATE` gets in the
2064            // keyspace group for the socket it keeps.
2065            "hash" if spec.name == "himport" => {
2066                let db = session.db;
2067                himport::execute(&server.dbs[db], &mut session.sets, args, out)
2068                    .map(|()| Flow::Continue)
2069            }
2070            // The one group that reaches back into the server after it has
2071            // written its reply, because a hash is what a search index is
2072            // made of. What comes back is what the indexes have to be told,
2073            // which is not the same as whether the command was a write.
2074            "hash" => {
2075                let db = session.db;
2076                let changed = hashes::execute(&server.dbs[db], db, spec, args, out);
2077                changed.map(|changed| {
2078                    indexing::changed(server, db, args.get(1), changed);
2079                    Flow::Continue
2080                })
2081            }
2082            "list" => {
2083                let db = session.db;
2084                lists::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2085            }
2086            "zset" => {
2087                let db = session.db;
2088                zsets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2089            }
2090            // A geo key is a sorted set and these are sorted set commands with
2091            // arithmetic on the way in and on the way out, so a client can ZREM
2092            // a place out of one and ZCARD it to count them.
2093            "geo" => {
2094                let db = session.db;
2095                geo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2096            }
2097            "array" => {
2098                let db = session.db;
2099                arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2100            }
2101            "graph" => {
2102                let db = session.db;
2103                graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2104            }
2105            // A document under a key, reached by a path. The group is Redis's
2106            // module surface and the storage is ours, the same trade the vector
2107            // set group makes.
2108            "json" => {
2109                let db = session.db;
2110                json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2111            }
2112            "vector" => {
2113                let db = session.db;
2114                vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2115            }
2116            "bloom" => {
2117                let db = session.db;
2118                bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2119            }
2120            "cuckoo" => {
2121                let db = session.db;
2122                cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2123            }
2124            "cms" => {
2125                let db = session.db;
2126                cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2127            }
2128            "topk" => {
2129                let db = session.db;
2130                topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2131            }
2132            "tdigest" => {
2133                let db = session.db;
2134                tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2135            }
2136            "ts" => {
2137                let db = session.db;
2138                ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2139            }
2140            // The clock is read before the database is borrowed, because every
2141            // stream command needs the time and it lives on the server. An
2142            // `XADD` with no ID, an `XCLAIM` working out what is idle and an
2143            // `XINFO` reporting it all have to agree about what moment this is.
2144            "stream" => {
2145                let db = session.db;
2146                let now = server.now_ms();
2147                streams::execute(&server.dbs[db], db, spec, args, now, out).map(|()| Flow::Continue)
2148            }
2149            // The one keyspace command that needs more than the databases,
2150            // because the socket it talks down is held on the server between
2151            // commands and not opened again for each one.
2152            "keyspace" if spec.name == "migrate" => {
2153                migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
2154            }
2155            // Every database and not the one the session is on, because `COPY` takes
2156            // a `DB n` and writes into a database nobody selected. The other group
2157            // that reaches back into the server afterwards, and it hands back a list
2158            // rather than one answer, because `DEL a b c` is three keys and a rename
2159            // is two.
2160            "keyspace" => {
2161                let mut touched = indexing::Touched::new(server);
2162                let done =
2163                    keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
2164                done.map(|()| {
2165                    indexing::touched(server, &touched);
2166                    Flow::Continue
2167                })
2168            }
2169            // No database at all, because an index is not a key. The registry
2170            // is the whole of what these sixteen commands touch, and then
2171            // `FT.CREATE` hands back the name it made so the keys that
2172            // already match its prefix can be read into it. The lock goes
2173            // before the scan runs, since the scan takes it again for every
2174            // key it reads.
2175            "search" if spec.name == "FT.SEARCH" => {
2176                // The two search commands that read documents, and so the two
2177                // that need the keyspace as well as the registry. They take and
2178                // let go of the registry themselves, because they cannot hold
2179                // that and a stripe at the same time.
2180                search::find(server, session.db, args, out).map(|()| Flow::Continue)
2181            }
2182            "search" if spec.name == "FT.AGGREGATE" => {
2183                search::roll(server, session.db, args, out).map(|()| Flow::Continue)
2184            }
2185            "search" if spec.name == "FT.HYBRID" => {
2186                search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
2187            }
2188            "search" if spec.name == "FT.PROFILE" => {
2189                // Which is one of those two with the working shown, so it needs
2190                // everything they need and takes the same route to it.
2191                search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
2192            }
2193            // The four search commands that name a key rather than an index.
2194            // A suggestion dictionary is a real key with a type of its own, so
2195            // these are handed a database and never touch the registry.
2196            "search" if spec.name.starts_with("FT.SUG") => {
2197                let db = session.db;
2198                suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2199            }
2200            // The five deprecated document commands, which are the other search
2201            // commands that need the keyspace as well as the registry: what they
2202            // write and read is an ordinary hash.
2203            "search"
2204                if matches!(
2205                    spec.name,
2206                    "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
2207                ) =>
2208            {
2209                let db = session.db;
2210                search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
2211            }
2212            "search" if spec.name == "FT.CURSOR" => {
2213                // Its own arm because the cursors are not in the registry, and
2214                // it takes and lets go of the registry itself to look up the
2215                // index name it is given.
2216                search::cursor::execute(server, args, out).map(|()| Flow::Continue)
2217            }
2218            "search" => {
2219                let db = session.db;
2220                let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
2221                made.map(|made| {
2222                    match made {
2223                        Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
2224                        Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
2225                        None => {}
2226                    }
2227                    Flow::Continue
2228                })
2229            }
2230            "scripting" => {
2231                scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
2232            }
2233            "transactions" => multi::execute(server, session, spec, args, out),
2234            // No database either, and the one group whose replies do not all go
2235            // to the connection that asked. The session is in it because a
2236            // subscription is connection state as well as server state.
2237            "pubsub" => pubsub::execute(server, session, spec, args, out),
2238            _ => server::execute(server, session, spec, args, out),
2239        }
2240    };
2241    // Before the error is written and not after, because a command that failed
2242    // half way through still changed whatever it changed before it failed and a
2243    // real server has already published those. Draining here also keeps the
2244    // notifications of a command run by `EXEC` in front of the next one's.
2245    notify::drain(server, armed);
2246
2247    let flow = match done {
2248        Ok(flow) => flow,
2249        Err(e) => {
2250            out.truncate(mark);
2251            write_error(out, &e);
2252            Flow::Continue
2253        }
2254    };
2255
2256    // After the command rather than before, so that whether each key it named is
2257    // there is read at the moment a real server would have signalled the change.
2258    // The load is what this costs a server nobody has sent `WATCH` to, and the
2259    // flag is Redis's own, so a command that only reads is never asked.
2260    if server.watching() && spec.flags.contains(&"write") {
2261        multi::touched(server, session, spec, args);
2262    }
2263
2264    // Counted here and not before the call, which is where Redis counts it, so
2265    // that `INFO commandstats` leaves out the `INFO` that asked for it in the
2266    // same way theirs does.
2267    //
2268    // Failure is read off the reply rather than off the `Result`, because the
2269    // two are not the same set. A command that ran out of arguments comes back
2270    // as an `Err` and a command that was sent the wrong password writes its own
2271    // error line and comes back `Ok`, and both of those are a call that failed.
2272    // The first byte at the mark is what a client would branch on, and it is `-`
2273    // for an error on either protocol and `!` for RESP3's long form.
2274    let row = server.mine().cmdstats.at(spec);
2275    row.calls.bump();
2276    if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2277        row.failed.bump();
2278    }
2279    flow
2280}
2281
2282/// The error line for an error value.
2283///
2284/// The prefix is what a client branches on, and there are three of them:
2285/// `WRONGTYPE` for a command sent at the wrong kind of value, `INVALIDOBJ` for a
2286/// HyperLogLog whose opcodes do not add up, and `ERR` for everything else. The three errors that need a different one,
2287/// `NOPROTO`, `WRONGPASS` and `OOM`, are written where they are decided rather
2288/// than routed through here. `OOM` is not a [`Code`] of its own because
2289/// [`Code::Full`] already covers the string that is too long for
2290/// `proto-max-bulk-len`, and that one goes out as `ERR` on a real server.
2291fn write_error(out: &mut Out, e: &Error) {
2292    let prefix: &[u8] = match e.code() {
2293        Code::WrongType => b"WRONGTYPE ",
2294        // Only the HyperLogLog commands answer this one, and the prefix is the
2295        // sentence a client branches on to tell a sketch it cannot read from a
2296        // sketch it sent wrong.
2297        Code::Corrupt => b"INVALIDOBJ ",
2298        _ => b"ERR ",
2299    };
2300    out.error_line(prefix, e.message().as_bytes());
2301}
2302
2303#[cfg(test)]
2304mod tests {
2305    use super::*;
2306    use crate::proto::{Limits, Proto};
2307    use crate::request::Argv;
2308
2309    /// Build the wire bytes for a command.
2310    ///
2311    /// Tests go through the codec rather than around it, so an argument in a
2312    /// test is the same borrowed slice a connection produces.
2313    pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2314        let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2315        for p in parts {
2316            wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2317            wire.extend_from_slice(p);
2318            wire.extend_from_slice(b"\r\n");
2319        }
2320        wire
2321    }
2322
2323    /// A server, a connection and a buffer, driven the way the reactor will.
2324    struct Fixture {
2325        server: Server,
2326        session: Session,
2327        argv: Argv,
2328        out: Out,
2329    }
2330
2331    impl Fixture {
2332        fn new() -> Fixture {
2333            Fixture::on(Server::new())
2334        }
2335
2336        /// The same, on a server whose databases are cut into `width` stripes.
2337        fn striped(width: usize) -> Fixture {
2338            Fixture::on(Server::with_width(width))
2339        }
2340
2341        fn on(server: Server) -> Fixture {
2342            Fixture {
2343                server,
2344                session: Session::new(7),
2345                argv: Argv::new(),
2346                out: Out::new(Proto::Resp2),
2347            }
2348        }
2349
2350        /// Run one command and answer with the bytes it wrote.
2351        fn run(&mut self, parts: &[&[u8]]) -> String {
2352            self.flow(parts).1
2353        }
2354
2355        /// Run one command and answer with the bytes exactly as written.
2356        ///
2357        /// [`Fixture::run`] goes through `from_utf8_lossy`, which is fine for
2358        /// every reply that is text and destroys a `DUMP` payload, since a
2359        /// payload is arbitrary bytes and a checksum on the end of them.
2360        fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2361            let wire = encode(parts);
2362            self.argv.decode(&wire, &Limits::default()).unwrap();
2363            self.out.clear();
2364            execute(
2365                &self.server,
2366                &mut self.session,
2367                Args::new(&self.argv, &wire),
2368                &mut self.out,
2369            );
2370            self.out.as_slice().to_vec()
2371        }
2372
2373        /// Move every clock in the server on by `ms`.
2374        fn advance(&mut self, ms: u64) {
2375            self.server.advance_clock_ms(ms);
2376        }
2377
2378        /// Run one command as a second connection to the same server.
2379        ///
2380        /// What `WATCH` is for is a write another connection made, and a test
2381        /// that only has one connection cannot tell the two apart.
2382        fn other(&mut self, parts: &[&[u8]]) -> String {
2383            self.other_in(self.session.db(), parts)
2384        }
2385
2386        /// The same, on a database of its own.
2387        fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
2388            let mut session = Session::new(8);
2389            session.db = db;
2390            let reply = self.by(&mut session, parts);
2391            forget_session(&self.server, &mut session);
2392            reply
2393        }
2394
2395        /// Run one command on a session the caller holds.
2396        fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
2397            let wire = encode(parts);
2398            let mut argv = Argv::new();
2399            argv.decode(&wire, &Limits::default()).unwrap();
2400            let mut out = Out::new(Proto::Resp2);
2401            execute(&self.server, session, Args::new(&argv, &wire), &mut out);
2402            String::from_utf8_lossy(out.as_slice()).into_owned()
2403        }
2404
2405        /// The same, with what the connection should do next.
2406        fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2407            let wire = encode(parts);
2408            self.argv.decode(&wire, &Limits::default()).unwrap();
2409            self.out.clear();
2410            let flow = execute(
2411                &self.server,
2412                &mut self.session,
2413                Args::new(&self.argv, &wire),
2414                &mut self.out,
2415            );
2416            (
2417                flow,
2418                String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2419            )
2420        }
2421    }
2422
2423    #[test]
2424    fn multi_holds_commands_and_exec_runs_them() {
2425        let mut f = Fixture::new();
2426        assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
2427        assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
2428        assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
2429        // Nothing ran while it was being queued.
2430        assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
2431        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
2432        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
2433    }
2434
2435    /// The test the `high_water` claim in `multi::exec` asks for.
2436    ///
2437    /// A `Vec` reaches the allocator exactly when its capacity changes, so a
2438    /// replay buffer whose room is the same before and after is one that did
2439    /// not allocate. The first transaction is what sets the room, which is the
2440    /// high water mark, and the second is the one that has to be free. Before
2441    /// the buffer moved onto the session this failed on every transaction,
2442    /// because `exec` made a new one each time and the room went back to zero.
2443    #[test]
2444    fn the_second_exec_of_a_shape_does_not_grow_the_buffer() {
2445        let mut f = Fixture::new();
2446        for _ in 0..2 {
2447            f.run(&[b"MULTI"]);
2448            f.run(&[b"SET", b"k", b"1"]);
2449            f.run(&[b"INCR", b"k"]);
2450            f.run(&[b"EXEC"]);
2451        }
2452        let room = f.session.replay.room();
2453        assert!(room > 0, "the first transaction should have set the room");
2454        f.run(&[b"MULTI"]);
2455        f.run(&[b"SET", b"k", b"1"]);
2456        f.run(&[b"INCR", b"k"]);
2457        f.run(&[b"EXEC"]);
2458        assert_eq!(f.session.replay.room(), room);
2459    }
2460
2461    #[test]
2462    fn an_empty_transaction_answers_an_empty_array() {
2463        let mut f = Fixture::new();
2464        f.run(&[b"MULTI"]);
2465        assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
2466    }
2467
2468    #[test]
2469    fn exec_and_discard_want_a_transaction_to_be_open() {
2470        let mut f = Fixture::new();
2471        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2472        assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
2473        // And `UNWATCH` does not, which is the one of the three that is happy
2474        // being sent for no reason.
2475        assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
2476    }
2477
2478    #[test]
2479    fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
2480        let mut f = Fixture::new();
2481        f.run(&[b"MULTI"]);
2482        assert_eq!(
2483            f.run(&[b"MULTI"]),
2484            "-ERR MULTI calls can not be nested\r\n",
2485            "nested MULTI is raised by the command and not by the funnel"
2486        );
2487        assert_eq!(
2488            f.run(&[b"WATCH", b"k"]),
2489            "-ERR WATCH inside MULTI is not allowed\r\n"
2490        );
2491        f.run(&[b"SET", b"k", b"1"]);
2492        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
2493    }
2494
2495    #[test]
2496    fn an_error_the_funnel_raises_kills_the_transaction() {
2497        for bad in [
2498            &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
2499            &[b"GET".as_slice()],
2500        ] {
2501            let mut f = Fixture::new();
2502            f.run(&[b"MULTI"]);
2503            assert!(f.run(bad).starts_with("-ERR "));
2504            assert_eq!(
2505                f.run(&[b"SET", b"k", b"1"]),
2506                "+QUEUED\r\n",
2507                "a dead transaction still answers QUEUED, which is Redis"
2508            );
2509            assert_eq!(
2510                f.run(&[b"EXEC"]),
2511                "-EXECABORT Transaction discarded because of previous errors.\r\n"
2512            );
2513            assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2514        }
2515    }
2516
2517    #[test]
2518    fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
2519        let mut f = Fixture::new();
2520        f.run(&[b"MULTI"]);
2521        f.run(&[b"SET", b"k", b"1"]);
2522        assert_eq!(
2523            f.run(&[b"EXEC", b"x"]),
2524            "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
2525        );
2526        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2527        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2528    }
2529
2530    #[test]
2531    fn a_command_a_transaction_may_not_hold_kills_it() {
2532        let mut f = Fixture::new();
2533        f.run(&[b"MULTI"]);
2534        assert_eq!(
2535            f.run(&[b"SHUTDOWN", b"NOSAVE"]),
2536            "-ERR Command not allowed inside a transaction\r\n"
2537        );
2538        assert_eq!(
2539            f.run(&[b"EXEC"]),
2540            "-EXECABORT Transaction discarded because of previous errors.\r\n"
2541        );
2542    }
2543
2544    #[test]
2545    fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
2546        let mut f = Fixture::new();
2547        f.run(&[b"RPUSH", b"l", b"v"]);
2548        f.run(&[b"MULTI"]);
2549        f.run(&[b"INCR", b"l"]);
2550        f.run(&[b"SET", b"y", b"2"]);
2551        assert_eq!(
2552            f.run(&[b"EXEC"]),
2553            "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
2554        );
2555        assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
2556    }
2557
2558    #[test]
2559    fn discard_and_reset_both_throw_the_queue_away() {
2560        let mut f = Fixture::new();
2561        f.run(&[b"MULTI"]);
2562        f.run(&[b"SET", b"k", b"1"]);
2563        assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
2564        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2565        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2566
2567        f.run(&[b"MULTI"]);
2568        f.run(&[b"SET", b"k", b"1"]);
2569        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2570        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2571        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2572    }
2573
2574    #[test]
2575    fn select_is_queued_and_applied_when_exec_runs_it() {
2576        let mut f = Fixture::new();
2577        f.run(&[b"MULTI"]);
2578        assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
2579        f.run(&[b"SET", b"k", b"1"]);
2580        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
2581        assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
2582        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
2583    }
2584
2585    #[test]
2586    fn a_write_by_another_connection_fails_the_transaction() {
2587        let mut f = Fixture::new();
2588        f.run(&[b"SET", b"k", b"1"]);
2589        assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
2590        f.other(&[b"SET", b"k", b"2"]);
2591        f.run(&[b"MULTI"]);
2592        f.run(&[b"GET", b"k"]);
2593        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2594    }
2595
2596    #[test]
2597    fn a_write_that_puts_the_same_value_back_still_fails_it() {
2598        let mut f = Fixture::new();
2599        f.run(&[b"SET", b"k", b"1"]);
2600        f.run(&[b"WATCH", b"k"]);
2601        f.other(&[b"SET", b"k", b"1"]);
2602        f.run(&[b"MULTI"]);
2603        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2604    }
2605
2606    #[test]
2607    fn a_read_by_another_connection_does_not() {
2608        let mut f = Fixture::new();
2609        f.run(&[b"SET", b"k", b"1"]);
2610        f.run(&[b"WATCH", b"k"]);
2611        f.other(&[b"GET", b"k"]);
2612        f.other(&[b"STRLEN", b"k"]);
2613        f.run(&[b"MULTI"]);
2614        f.run(&[b"GET", b"k"]);
2615        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2616    }
2617
2618    #[test]
2619    fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
2620        let mut f = Fixture::new();
2621        f.run(&[b"WATCH", b"k"]);
2622        f.other(&[b"DEL", b"k"]);
2623        f.run(&[b"MULTI"]);
2624        f.run(&[b"PING"]);
2625        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2626        // And creating it does, which is the other half of the same rule.
2627        f.run(&[b"WATCH", b"k"]);
2628        f.other(&[b"SET", b"k", b"1"]);
2629        f.run(&[b"MULTI"]);
2630        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2631    }
2632
2633    #[test]
2634    fn a_watched_key_that_expires_fails_the_transaction() {
2635        let mut f = Fixture::new();
2636        f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
2637        f.run(&[b"WATCH", b"k"]);
2638        f.run(&[b"MULTI"]);
2639        f.advance(100);
2640        assert_eq!(
2641            f.run(&[b"EXEC"]),
2642            "*-1\r\n",
2643            "nothing wrote to the key, so only the liveness check can catch this"
2644        );
2645    }
2646
2647    #[test]
2648    fn every_way_a_transaction_ends_lets_go_of_the_watches() {
2649        for end in [
2650            &[b"EXEC".as_slice()] as &[&[u8]],
2651            &[b"DISCARD".as_slice()],
2652            &[b"UNWATCH".as_slice()],
2653            &[b"RESET".as_slice()],
2654        ] {
2655            let mut f = Fixture::new();
2656            f.run(&[b"SET", b"k", b"1"]);
2657            f.run(&[b"WATCH", b"k"]);
2658            if end[0] != b"UNWATCH" && end[0] != b"RESET" {
2659                f.run(&[b"MULTI"]);
2660            }
2661            f.run(end);
2662            assert!(!f.server.watching(), "{end:?} left a row behind");
2663            // And the connection can start again with nothing carried over.
2664            f.other(&[b"SET", b"k", b"2"]);
2665            f.run(&[b"MULTI"]);
2666            f.run(&[b"GET", b"k"]);
2667            assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
2668        }
2669    }
2670
2671    #[test]
2672    fn a_connection_going_away_lets_go_of_its_watches() {
2673        let mut f = Fixture::new();
2674        f.run(&[b"SET", b"k", b"1"]);
2675        f.run(&[b"WATCH", b"k"]);
2676        assert!(f.server.watching());
2677        forget_session(&f.server, &mut f.session);
2678        assert!(!f.server.watching());
2679    }
2680
2681    #[test]
2682    fn watching_the_same_key_twice_is_one_watch() {
2683        let mut f = Fixture::new();
2684        f.run(&[b"SET", b"k", b"1"]);
2685        f.run(&[b"WATCH", b"k", b"k"]);
2686        f.run(&[b"UNWATCH"]);
2687        assert!(
2688            !f.server.watching(),
2689            "the row counts watchers, so a doubled watch would leave one behind"
2690        );
2691    }
2692
2693    #[test]
2694    fn two_connections_can_watch_the_same_key() {
2695        let mut f = Fixture::new();
2696        f.run(&[b"SET", b"k", b"1"]);
2697        f.run(&[b"WATCH", b"k"]);
2698        let mut second = Session::new(9);
2699        second.db = f.session.db();
2700        assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
2701        // One lets go and the other's watch still works.
2702        forget_session(&f.server, &mut second);
2703        assert!(f.server.watching());
2704        f.other(&[b"SET", b"k", b"2"]);
2705        f.run(&[b"MULTI"]);
2706        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2707    }
2708
2709    #[test]
2710    fn flushdb_fails_a_watch_on_a_key_that_was_there() {
2711        let mut f = Fixture::new();
2712        f.run(&[b"SET", b"k", b"1"]);
2713        f.run(&[b"WATCH", b"k"]);
2714        f.other(&[b"FLUSHDB"]);
2715        f.run(&[b"MULTI"]);
2716        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2717    }
2718
2719    #[test]
2720    fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
2721        let mut f = Fixture::new();
2722        f.run(&[b"WATCH", b"k"]);
2723        f.other(&[b"FLUSHDB"]);
2724        f.run(&[b"MULTI"]);
2725        f.run(&[b"PING"]);
2726        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2727    }
2728
2729    #[test]
2730    fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
2731        let mut f = Fixture::new();
2732        f.run(&[b"SET", b"k", b"1"]);
2733        f.run(&[b"WATCH", b"k"]);
2734        // The same name in another database is another key.
2735        let elsewhere = f.session.db() + 1;
2736        f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
2737        f.run(&[b"MULTI"]);
2738        f.run(&[b"GET", b"k"]);
2739        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2740    }
2741
2742    #[test]
2743    fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
2744        let mut f = Fixture::new();
2745        f.run(&[b"RPUSH", b"src", b"1"]);
2746        f.run(&[b"WATCH", b"dst"]);
2747        f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
2748        f.run(&[b"MULTI"]);
2749        assert_eq!(
2750            f.run(&[b"EXEC"]),
2751            "*-1\r\n",
2752            "SORT is movablekeys, so every watched key in the database is asked"
2753        );
2754    }
2755
2756    #[test]
2757    fn a_server_nobody_is_watching_says_so() {
2758        let mut f = Fixture::new();
2759        assert!(!f.server.watching());
2760        f.run(&[b"SET", b"k", b"1"]);
2761        assert!(!f.server.watching());
2762    }
2763
2764    /// The count on the end of a subscribe reply is channels and patterns
2765    /// together, which is a thing a client uses to know when it is out of
2766    /// subscribe mode and so has to be the number the mode is decided on.
2767    /// Shard channels are counted on their own because they are their own
2768    /// namespace.
2769    #[test]
2770    fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
2771        let mut f = Fixture::new();
2772        assert_eq!(
2773            f.run(&[b"SUBSCRIBE", b"a", b"b"]),
2774            "*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"
2775        );
2776        assert_eq!(
2777            f.run(&[b"PSUBSCRIBE", b"c*"]),
2778            "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
2779        );
2780        assert_eq!(
2781            f.run(&[b"SSUBSCRIBE", b"s"]),
2782            "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
2783        );
2784        // Subscribing again to something already held answers again with the
2785        // count unchanged, rather than counting it twice or saying nothing.
2786        assert_eq!(
2787            f.run(&[b"SUBSCRIBE", b"a"]),
2788            "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
2789        );
2790    }
2791
2792    /// Unsubscribe has three shapes and a client has to be able to tell them
2793    /// apart, because the last one is what tells it the mode is over.
2794    #[test]
2795    fn unsubscribe_answers_for_names_it_was_not_holding_too() {
2796        let mut f = Fixture::new();
2797        f.run(&[b"SUBSCRIBE", b"a"]);
2798
2799        // A name that was never subscribed still gets a reply, with the count
2800        // as it stands.
2801        assert_eq!(
2802            f.run(&[b"UNSUBSCRIBE", b"zz"]),
2803            "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
2804        );
2805        // With no names, one reply per channel held, counting down.
2806        f.run(&[b"SUBSCRIBE", b"b"]);
2807        f.run(&[b"PSUBSCRIBE", b"p*"]);
2808        assert_eq!(
2809            f.run(&[b"UNSUBSCRIBE"]),
2810            "*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"
2811        );
2812        // With no names and none of that family held, one reply with a nil
2813        // where the name goes and the count that is left.
2814        assert_eq!(
2815            f.run(&[b"UNSUBSCRIBE"]),
2816            "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
2817            "the pattern is still held, so the count is one"
2818        );
2819        assert_eq!(
2820            f.run(&[b"SUNSUBSCRIBE"]),
2821            "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
2822            "shard channels are counted on their own"
2823        );
2824    }
2825
2826    /// The gate is on the funnel and the funnel is what `EXEC` goes through
2827    /// for the commands it queued, so it has to know it is running one.
2828    /// Redis lets a queued command through, and a transaction that subscribes
2829    /// and then reads is the case that says which way round it is.
2830    #[test]
2831    fn the_subscribe_gate_does_not_reach_inside_exec() {
2832        let mut f = Fixture::new();
2833        f.run(&[b"SET", b"k", b"1"]);
2834        f.run(&[b"MULTI"]);
2835        assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
2836        assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
2837        assert_eq!(
2838            f.run(&[b"EXEC"]),
2839            "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
2840        );
2841        // And once EXEC is done the connection really is subscribed, so the
2842        // gate is back on.
2843        assert_eq!(
2844            f.run(&[b"GET", b"k"]),
2845            "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
2846        );
2847    }
2848
2849    /// `EXEC` sent by a subscribed RESP2 client is refused by the gate like
2850    /// anything else, and a refusal on the funnel kills the transaction.
2851    #[test]
2852    fn exec_sent_by_a_subscriber_aborts_the_transaction() {
2853        let mut f = Fixture::new();
2854        f.run(&[b"MULTI"]);
2855        f.run(&[b"SET", b"k", b"1"]);
2856        f.run(&[b"SUBSCRIBE", b"z"]);
2857        f.run(&[b"EXEC"]);
2858        f.run(&[b"MULTI"]);
2859        assert_eq!(
2860            f.run(&[b"EXEC"]),
2861            "-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"
2862        );
2863    }
2864
2865    /// `RESET` is one of the few things a subscriber may send, and what it
2866    /// resets includes every subscription it is holding.
2867    #[test]
2868    fn reset_lets_go_of_every_subscription() {
2869        let mut f = Fixture::new();
2870        f.run(&[b"SUBSCRIBE", b"a"]);
2871        f.run(&[b"PSUBSCRIBE", b"p*"]);
2872        f.run(&[b"SSUBSCRIBE", b"s"]);
2873        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2874        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2875        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2876        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
2877        // And the connection takes ordinary commands again.
2878        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2879    }
2880
2881    /// What `PUBSUB` can be asked, on a server with one subscriber holding one
2882    /// of each.
2883    #[test]
2884    fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
2885        let mut f = Fixture::new();
2886        let mut sub = Session::new(9);
2887        f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
2888        f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
2889        f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
2890
2891        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
2892        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
2893        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
2894        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
2895        assert_eq!(
2896            f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
2897            "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
2898        );
2899        assert_eq!(
2900            f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
2901            "*2\r\n$1\r\na\r\n:1\r\n",
2902            "the shard channel and the channel share a name and not a count"
2903        );
2904        assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
2905
2906        forget_session(&f.server, &mut sub);
2907        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2908        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2909    }
2910
2911    /// The one setting whose value is neither a number nor a word, and whose
2912    /// spelling on the way out is not the spelling on the way in.
2913    #[test]
2914    fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
2915        let mut f = Fixture::new();
2916        assert_eq!(
2917            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2918            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2919        );
2920        assert_eq!(
2921            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
2922            "+OK\r\n"
2923        );
2924        // `A` is a class of its own on the way in and stays one on the way out,
2925        // and the two channel letters move to the end.
2926        assert_eq!(
2927            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2928            "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
2929        );
2930        assert_eq!(
2931            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
2932            "+OK\r\n"
2933        );
2934        assert_eq!(
2935            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2936            "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
2937        );
2938    }
2939
2940    #[test]
2941    fn a_letter_the_notification_setting_does_not_know_is_refused() {
2942        let mut f = Fixture::new();
2943        assert_eq!(
2944            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
2945            "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
2946             - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
2947        );
2948        // And nothing was applied, since the whole setting is parsed before any
2949        // of it is stored.
2950        assert_eq!(
2951            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2952            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2953        );
2954    }
2955
2956    /// One mistake in a `PUBSUB` subcommand has two error shapes depending on
2957    /// which subcommand it is, because the ones with a fixed argument count are
2958    /// checked by the subcommand table and the ones without fall through to
2959    /// the generic syntax error. Both are copied here rather than tidied,
2960    /// since a client that matches on the text sees the difference.
2961    #[test]
2962    fn pubsub_says_no_two_different_ways() {
2963        let mut f = Fixture::new();
2964        assert_eq!(
2965            f.run(&[b"PUBSUB"]),
2966            "-ERR wrong number of arguments for 'pubsub' command\r\n"
2967        );
2968        assert_eq!(
2969            f.run(&[b"PUBSUB", b"NOPE"]),
2970            "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
2971        );
2972        assert_eq!(
2973            f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
2974            "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
2975        );
2976        assert_eq!(
2977            f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
2978            "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
2979        );
2980        assert_eq!(
2981            f.run(&[b"PUBSUB", b"HELP", b"x"]),
2982            "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
2983        );
2984    }
2985
2986    /// Publishing to nobody costs a lookup and answers zero, which is the
2987    /// common case on a server that has pub/sub compiled in and not in use.
2988    #[test]
2989    fn publishing_to_nobody_answers_zero() {
2990        let mut f = Fixture::new();
2991        assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
2992        assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
2993        // An empty channel name is a name like any other.
2994        assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
2995    }
2996
2997    /// A publish counts everybody it reached, which is not the same as the
2998    /// number of subscribers: one connection holding two patterns that both
2999    /// match is two.
3000    #[test]
3001    fn a_publish_counts_the_deliveries_and_not_the_clients() {
3002        let mut f = Fixture::new();
3003        let mut sub = Session::new(9);
3004        f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
3005        f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
3006        f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
3007        assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
3008        forget_session(&f.server, &mut sub);
3009    }
3010
3011    /// What a client does all day: write the same keys again and again. Every
3012    /// one of those writes leaves the previous record behind, so a server that
3013    /// never compacts holds every version of every key it has ever been sent.
3014    ///
3015    /// Not under Miri, and not because of anything it would find. The bound
3016    /// only means something once several megabytes have gone through the
3017    /// arena, which reclaims a segment at a time and has segments of two
3018    /// megabytes, so a server that reclaimed nothing would still be under the
3019    /// bound in any smaller version of this. Thirty two megabytes is thirty
3020    /// two thousand commands and was over forty minutes interpreted. The paths
3021    /// it walks are walked by the hundreds of tests around it that write a key
3022    /// and read it back, which do run there.
3023    #[cfg_attr(miri, ignore = "megabytes through the arena")]
3024    #[test]
3025    fn rewriting_the_same_keys_does_not_grow_the_server() {
3026        let mut f = Fixture::new();
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        f.server.compact_step();
3034        let after_first = f.server.memory_bytes();
3035
3036        // 64 KiB a pass, five hundred passes, and the same 64 keys at the end
3037        // of it. Thirty two megabytes written to hold sixty four kilobytes,
3038        // which is the shape of a real workload and is enough churn to fill
3039        // sixteen segments if nothing ever comes back.
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    }
3055
3056    /// The same churn on a database nobody starts on, either side of a quiet
3057    /// spell long enough for the maintenance turn to stop asking about it.
3058    ///
3059    /// The turn after each batch skips a database that has already said it has
3060    /// nothing to collect and has not been touched since, which is what keeps a
3061    /// server whose clients are all on database zero from loading and storing
3062    /// in the other fifteen every batch to be told no. Two things could go
3063    /// wrong with that. A database might never be marked at all, so this uses
3064    /// database nine, which nothing marks by accident. And a database whose
3065    /// mark was cleared might never get it back, so this drains the collector
3066    /// until it says there is nothing left, checks the mark really is gone, and
3067    /// then writes another thirty two megabytes through the same sixty four
3068    /// keys. If either went wrong the server would hold all of it.
3069    ///
3070    /// Not under Miri, for the reason on the test above: the volume is the
3071    /// claim, and the volume is what the interpreter charges for.
3072    #[cfg_attr(miri, ignore = "megabytes through the arena")]
3073    #[test]
3074    fn a_database_nobody_started_on_is_still_collected() {
3075        let mut f = Fixture::new();
3076        assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
3077        let val = vec![b'v'; 1024];
3078        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3079
3080        for k in &keys {
3081            f.run(&[b"SET", k, &val]);
3082        }
3083        while f.server.compact_step().is_some() {}
3084        assert!(
3085            !f.server.mine().wanted(9),
3086            "database nine was drained and should not be asked again until it is written to"
3087        );
3088        let after_first = f.server.memory_bytes();
3089
3090        for _ in 0..500 {
3091            for k in &keys {
3092                f.run(&[b"SET", k, &val]);
3093            }
3094            f.server.compact_step();
3095        }
3096
3097        assert!(
3098            f.server.memory_bytes() <= after_first * 2,
3099            "held {} after five hundred passes against {after_first} after one",
3100            f.server.memory_bytes()
3101        );
3102        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3103        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3104        // And nothing landed anywhere else on the way.
3105        f.run(&[b"SELECT", b"0"]);
3106        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3107    }
3108
3109    #[test]
3110    fn a_command_goes_from_bytes_to_bytes() {
3111        let mut f = Fixture::new();
3112        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3113        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3114        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3115        assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
3116        // The name is matched whatever case it came in, and so are the options.
3117        assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
3118        assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
3119    }
3120
3121    #[test]
3122    fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
3123        let mut f = Fixture::new();
3124        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
3125        // A key named twice exists twice and can only be deleted once, and both
3126        // of those are Redis's answers rather than tidier ones.
3127        assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
3128        assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
3129        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3130        // UNLINK is the same body and reports the same way.
3131        assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
3132        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3133    }
3134
3135    #[test]
3136    fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
3137        let mut f = Fixture::new();
3138        f.run(&[b"SET", b"k", b"v"]);
3139        // A simple string on both protocols, which is unusual: most replies
3140        // that carry a word are bulk strings.
3141        assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
3142        assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
3143    }
3144
3145    #[test]
3146    fn touch_counts_the_way_exists_counts() {
3147        let mut f = Fixture::new();
3148        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3149        assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
3150        assert_eq!(
3151            f.run(&[b"TOUCH", b"a", b"a"]),
3152            ":2\r\n",
3153            "twice counts twice"
3154        );
3155        assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
3156        assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
3157    }
3158
3159    #[test]
3160    fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
3161        let mut f = Fixture::new();
3162        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3163        f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
3164
3165        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
3166        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3167        assert_eq!(
3168            f.run(&[b"TTL", b"b"]),
3169            ":100\r\n",
3170            "the source's and not b's"
3171        );
3172        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3173    }
3174
3175    #[test]
3176    fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
3177        let mut f = Fixture::new();
3178        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
3179        // The source is checked before the destination, so this is the error
3180        // and not the zero RENAMENX would otherwise answer for a taken name.
3181        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
3182    }
3183
3184    #[test]
3185    fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
3186        let mut f = Fixture::new();
3187        f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
3188
3189        assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
3190        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3191        // Renaming onto itself is 0 here and OK for plain RENAME, which is the
3192        // one call the two disagree about and neither does any work for.
3193        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
3194        assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
3195        assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
3196        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
3197    }
3198
3199    #[test]
3200    fn renaming_a_set_does_not_touch_a_member() {
3201        let mut f = Fixture::new();
3202        for i in 0..300 {
3203            f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
3204        }
3205        let before = f.server.memory_bytes();
3206
3207        assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
3208        assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
3209        assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
3210        assert!(
3211            f.server.memory_bytes().abs_diff(before) < 256,
3212            "the members were copied: {} against {before}",
3213            f.server.memory_bytes()
3214        );
3215    }
3216
3217    #[test]
3218    fn a_copy_is_a_second_value_and_not_a_second_name() {
3219        let mut f = Fixture::new();
3220        f.run(&[b"SADD", b"s", b"m1", b"m2"]);
3221
3222        assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
3223        f.run(&[b"SADD", b"t", b"m3"]);
3224        assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
3225        assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
3226    }
3227
3228    /// Every type a key can hold, copied, because two of them used to panic.
3229    ///
3230    /// `COPY` reads the value out of the source through one match on the type
3231    /// tag, and that match had a catch all at the bottom from back when a set
3232    /// and a hash were the only bodies. The list and the sorted set landed after
3233    /// it and nobody came back, so `COPY mylist other` took the shard down. It
3234    /// is an ordinary command against a type the server supports everywhere
3235    /// else, so this walks all five rather than the two that were broken: the
3236    /// point is that the next type cannot land the same way.
3237    #[test]
3238    fn every_type_can_be_copied() {
3239        let mut f = Fixture::new();
3240        f.run(&[b"SET", b"str", b"v1"]);
3241        f.run(&[b"SADD", b"set", b"m1"]);
3242        f.run(&[b"HSET", b"hash", b"f", b"v"]);
3243        f.run(&[b"RPUSH", b"list", b"a", b"b"]);
3244        f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
3245
3246        for name in [
3247            &b"str"[..],
3248            &b"set"[..],
3249            &b"hash"[..],
3250            &b"list"[..],
3251            &b"zset"[..],
3252        ] {
3253            let dst = [name, b":copy"].concat();
3254            assert_eq!(
3255                f.run(&[b"COPY", name, &dst]),
3256                ":1\r\n",
3257                "copying {}",
3258                String::from_utf8_lossy(name)
3259            );
3260            assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
3261        }
3262
3263        assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
3264            let mut want = String::from("*2\r\n");
3265            want.push_str("$1\r\na\r\n$1\r\nb\r\n");
3266            want
3267        });
3268        assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
3269
3270        // And the copy is its own value, not a second name for the source.
3271        f.run(&[b"RPUSH", b"list:copy", b"c"]);
3272        assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
3273        assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
3274    }
3275
3276    #[test]
3277    fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
3278        let mut f = Fixture::new();
3279        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3280        f.run(&[b"SET", b"b", b"v2"]);
3281
3282        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
3283        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3284        assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
3285        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3286        assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
3287        assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
3288    }
3289
3290    #[test]
3291    fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
3292        let mut f = Fixture::new();
3293        f.run(&[b"SET", b"a", b"v1"]);
3294
3295        // Same key, different database, so this is not the same object and is
3296        // an ordinary copy. Same key in the same database is the error below.
3297        assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
3298        f.run(&[b"SELECT", b"1"]);
3299        assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
3300        assert_eq!(
3301            f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
3302            ":0\r\n",
3303            "taken"
3304        );
3305        assert_eq!(
3306            f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
3307            ":1\r\n"
3308        );
3309    }
3310
3311    #[test]
3312    fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
3313        let mut f = Fixture::new();
3314        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3315        assert_eq!(
3316            f.run(&[b"SORT", b"l"]),
3317            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3318        );
3319        // DESC then ASC is ASC, because the only thing ASC does is undo a DESC.
3320        assert_eq!(
3321            f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
3322            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3323        );
3324        assert_eq!(
3325            f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
3326            "*1\r\n$1\r\n2\r\n"
3327        );
3328    }
3329
3330    #[test]
3331    fn sort_reads_a_key_per_element_for_by_and_for_get() {
3332        let mut f = Fixture::new();
3333        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3334        f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
3335        // `b` weighs less so it comes first, and its `GET` hits where `a`'s
3336        // misses, which is a nil in the middle of the array and not a short one.
3337        assert_eq!(
3338            f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
3339            "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
3340        );
3341    }
3342
3343    #[test]
3344    fn sort_store_writes_a_list_and_answers_its_length() {
3345        let mut f = Fixture::new();
3346        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3347        assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
3348        assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
3349        assert_eq!(
3350            f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
3351            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3352        );
3353        // An empty result takes the destination with it rather than leaving a
3354        // list that holds nothing.
3355        assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
3356        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
3357    }
3358
3359    #[test]
3360    fn sort_ro_does_not_know_the_word_store() {
3361        let mut f = Fixture::new();
3362        f.run(&[b"RPUSH", b"l", b"2", b"1"]);
3363        assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
3364        assert_eq!(
3365            f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
3366            "-ERR syntax error\r\n"
3367        );
3368        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3369    }
3370
3371    #[test]
3372    fn sort_refuses_what_it_cannot_sort() {
3373        let mut f = Fixture::new();
3374        assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
3375        f.run(&[b"SET", b"s", b"x"]);
3376        assert_eq!(
3377            f.run(&[b"SORT", b"s"]),
3378            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
3379        );
3380        f.run(&[b"RPUSH", b"words", b"one", b"two"]);
3381        assert_eq!(
3382            f.run(&[b"SORT", b"words"]),
3383            "-ERR One or more scores can't be converted into double\r\n"
3384        );
3385        assert_eq!(
3386            f.run(&[b"SORT", b"words", b"ALPHA"]),
3387            "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
3388        );
3389        assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
3390    }
3391
3392    #[test]
3393    fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
3394        let mut f = Fixture::new();
3395        assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
3396        assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
3397        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3398        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3399        assert_eq!(
3400            f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
3401            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3402        );
3403        // And back, which proves the body survived the trip rather than being
3404        // rebuilt from a copy that happened to look the same.
3405        assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
3406        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3407    }
3408
3409    #[test]
3410    fn move_answers_zero_when_either_end_says_no() {
3411        let mut f = Fixture::new();
3412        assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
3413        assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
3414        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3415        assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
3416        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3417        // The destination is taken, so nothing moves and the source is still
3418        // there with what it had.
3419        assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
3420        assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
3421        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3422        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
3423    }
3424
3425    #[test]
3426    fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
3427        let mut f = Fixture::new();
3428        assert_eq!(
3429            f.run(&[b"MOVE", b"a", b"0"]),
3430            "-ERR source and destination objects are the same\r\n"
3431        );
3432        assert_eq!(
3433            f.run(&[b"MOVE", b"a", b"99"]),
3434            "-ERR DB index is out of range\r\n"
3435        );
3436        assert_eq!(
3437            f.run(&[b"MOVE", b"a", b"-1"]),
3438            "-ERR DB index is out of range\r\n"
3439        );
3440        assert_eq!(
3441            f.run(&[b"MOVE", b"a", b"x"]),
3442            "-ERR value is not an integer or out of range\r\n"
3443        );
3444    }
3445
3446    #[test]
3447    fn swapdb_swaps_what_two_connections_would_see() {
3448        let mut f = Fixture::new();
3449        assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
3450        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3451        assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
3452        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3453
3454        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3455        // Still on database zero, and database zero is a different database.
3456        assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
3457        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3458        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3459        // A database swapped with itself is fine and changes nothing.
3460        assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
3461        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3462    }
3463
3464    /// Every database on a server reads the server's clock and not one of its
3465    /// own. They used to be told the time one at a time and now they share the
3466    /// reading, so a server that built its databases from a second clock would
3467    /// answer a deadline worked out against a time nobody had set.
3468    #[test]
3469    fn a_wide_server_puts_its_databases_on_its_own_clock() {
3470        let mut f = Fixture::striped(8);
3471        f.server.set_clock_ms(1_700_000_000_000);
3472        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
3473        assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
3474        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3475        f.server.set_clock_ms(1_700_000_050_000);
3476        assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
3477    }
3478
3479    /// The swap is stripe by stripe, so a database cut into more than one
3480    /// stripe is the case that would catch it exchanging some of the keys and
3481    /// leaving the rest. Sixteen keys over four stripes is enough that every
3482    /// stripe has something in it whatever the hashes come out as.
3483    #[test]
3484    fn swapdb_swaps_every_stripe_of_a_wide_database() {
3485        let mut f = Fixture::striped(4);
3486        for i in 0..16u32 {
3487            let key = format!("k{i}");
3488            assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
3489        }
3490        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3491        assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
3492        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3493
3494        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3495        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3496        assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
3497        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3498        assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
3499        for i in 0..16u32 {
3500            let key = format!("k{i}");
3501            assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
3502        }
3503    }
3504
3505    #[test]
3506    fn swapdb_says_which_index_it_could_not_read() {
3507        let mut f = Fixture::new();
3508        assert_eq!(
3509            f.run(&[b"SWAPDB", b"x", b"1"]),
3510            "-ERR invalid first DB index\r\n"
3511        );
3512        assert_eq!(
3513            f.run(&[b"SWAPDB", b"0", b"y"]),
3514            "-ERR invalid second DB index\r\n"
3515        );
3516        // A number too big to be an index on a server that keeps one in an int
3517        // is the same complaint, and a plausible one that is not ours is the
3518        // range complaint instead. The split is Redis's.
3519        assert_eq!(
3520            f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
3521            "-ERR invalid first DB index\r\n"
3522        );
3523        assert_eq!(
3524            f.run(&[b"SWAPDB", b"0", b"99"]),
3525            "-ERR DB index is out of range\r\n"
3526        );
3527        assert_eq!(
3528            f.run(&[b"SWAPDB", b"-1", b"0"]),
3529            "-ERR DB index is out of range\r\n"
3530        );
3531    }
3532
3533    #[test]
3534    fn wait_answers_zero_replicas_without_waiting() {
3535        let mut f = Fixture::new();
3536        assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
3537        assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
3538        // A replica that is never going to arrive, and a timeout that would be
3539        // a real wait on a server that had one.
3540        assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
3541        // Negative replicas is not an error, because zero is already more than
3542        // it asked for.
3543        assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
3544        assert_eq!(
3545            f.run(&[b"WAIT", b"x", b"0"]),
3546            "-ERR value is not an integer or out of range\r\n"
3547        );
3548        assert_eq!(
3549            f.run(&[b"WAIT", b"0", b"-1"]),
3550            "-ERR timeout is negative\r\n"
3551        );
3552        assert_eq!(
3553            f.run(&[b"WAIT", b"0", b"1.5"]),
3554            "-ERR timeout is not an integer or out of range\r\n"
3555        );
3556    }
3557
3558    #[test]
3559    fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
3560        let mut f = Fixture::new();
3561        assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
3562        assert_eq!(
3563            f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
3564            "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
3565        );
3566        assert_eq!(
3567            f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
3568            "-ERR value is out of range, value must between 0 and 1\r\n"
3569        );
3570        assert_eq!(
3571            f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
3572            "-ERR value is out of range, must be positive\r\n"
3573        );
3574        // The arguments are all read before the server looks at itself, so a
3575        // bad timeout beats the append only complaint even with numlocal set.
3576        assert_eq!(
3577            f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
3578            "-ERR timeout is negative\r\n"
3579        );
3580    }
3581
3582    /// The bytes inside a bulk reply, with the header and the trailing break
3583    /// taken off. Every `DUMP` test needs this and none of them care how the
3584    /// length was written.
3585    fn payload(reply: &[u8]) -> Vec<u8> {
3586        let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
3587        reply[head + 2..reply.len() - 2].to_vec()
3588    }
3589
3590    #[test]
3591    fn a_value_survives_a_dump_and_a_restore() {
3592        let mut f = Fixture::new();
3593        f.run(&[b"SET", b"s", b"hello"]);
3594        f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
3595        f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
3596        f.run(&[b"SADD", b"u", b"x", b"y"]);
3597        f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
3598        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
3599
3600        for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
3601            let mut copy = key.to_vec();
3602            copy.push(b'2');
3603            let bytes = payload(&f.raw(&[b"DUMP", key]));
3604            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
3605            assert_eq!(f.run(&[b"TYPE", &copy]), f.run(&[b"TYPE", key]));
3606        }
3607
3608        assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
3609        assert_eq!(
3610            f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
3611            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
3612        );
3613        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
3614        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
3615        assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
3616        assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
3617        // The encoding survives too, since the payload names the plainest legal
3618        // type and the loader puts the value back on the rung it belongs on.
3619        assert_eq!(
3620            f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
3621            f.run(&[b"OBJECT", b"ENCODING", b"t"])
3622        );
3623    }
3624
3625    #[test]
3626    fn a_dumped_hash_keeps_its_field_deadlines() {
3627        let mut f = Fixture::new();
3628        f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
3629        assert_eq!(
3630            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
3631            "*1\r\n:1\r\n"
3632        );
3633        let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
3634        assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
3635        assert_eq!(
3636            f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
3637            "*2\r\n:-1\r\n:100\r\n"
3638        );
3639    }
3640
3641    #[test]
3642    fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
3643        let mut f = Fixture::new();
3644        f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
3645        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3646        assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
3647        assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
3648        assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
3649        assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
3650        // An absolute deadline that has already gone is not an error. The key is
3651        // not created and the reply is the same OK a live one gets.
3652        assert_eq!(
3653            f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
3654            "+OK\r\n"
3655        );
3656        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3657    }
3658
3659    #[test]
3660    fn dump_answers_nothing_for_a_key_that_is_not_there() {
3661        let mut f = Fixture::new();
3662        assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
3663        f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
3664        f.advance(50);
3665        assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
3666    }
3667
3668    #[test]
3669    fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
3670        let mut f = Fixture::new();
3671        f.run(&[b"SET", b"a", b"first"]);
3672        f.run(&[b"SET", b"b", b"second"]);
3673        let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
3674        assert_eq!(
3675            f.run(&[b"RESTORE", b"a", b"0", &bytes]),
3676            "-BUSYKEY Target key name already exists.\r\n"
3677        );
3678        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
3679        assert_eq!(
3680            f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
3681            "+OK\r\n"
3682        );
3683        assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
3684    }
3685
3686    /// The busy key comes before the payload, which is not the order the
3687    /// arguments read in. Whether a key is taken should not depend on whether
3688    /// the bytes behind it happened to be good.
3689    #[test]
3690    fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
3691        let mut f = Fixture::new();
3692        f.run(&[b"SET", b"a", b"v"]);
3693        assert_eq!(
3694            f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
3695            "-BUSYKEY Target key name already exists.\r\n"
3696        );
3697        // And the options come before even that, so a bad FREQ beats the busy
3698        // key the same way a bad DB beats a missing source in COPY.
3699        assert_eq!(
3700            f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
3701            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3702        );
3703    }
3704
3705    #[test]
3706    fn restore_can_tell_a_bad_footer_from_bad_bytes() {
3707        let mut f = Fixture::new();
3708        f.run(&[b"SET", b"a", b"hello"]);
3709        let good = payload(&f.raw(&[b"DUMP", b"a"]));
3710
3711        let mut flipped = good.clone();
3712        flipped[2] ^= 0x40;
3713        assert_eq!(
3714            f.run(&[b"RESTORE", b"b", b"0", &flipped]),
3715            "-ERR DUMP payload version or checksum are wrong\r\n"
3716        );
3717        assert_eq!(
3718            f.run(&[b"RESTORE", b"b", b"0", b"short"]),
3719            "-ERR DUMP payload version or checksum are wrong\r\n"
3720        );
3721        // A footer that is right over a body that is not. The type byte says
3722        // string and there is nothing behind it, so the checksum agrees and the
3723        // value does not exist.
3724        let mut truncated = good[..1].to_vec();
3725        truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
3726        let crc = yo_common::crc::crc64(0, &truncated);
3727        truncated.extend_from_slice(&crc.to_le_bytes());
3728        assert_eq!(
3729            f.run(&[b"RESTORE", b"b", b"0", &truncated]),
3730            "-ERR Bad data format\r\n"
3731        );
3732        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
3733    }
3734
3735    #[test]
3736    fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
3737        let mut f = Fixture::new();
3738        f.run(&[b"SET", b"a", b"v"]);
3739        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3740        assert_eq!(
3741            f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
3742            "-ERR Invalid TTL value, must be >= 0\r\n"
3743        );
3744        assert_eq!(
3745            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
3746            "-ERR Invalid IDLETIME value, must be >= 0\r\n"
3747        );
3748        assert_eq!(
3749            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
3750            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3751        );
3752        // Both are accepted and both are then dropped, which is D-26.
3753        assert_eq!(
3754            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
3755            "+OK\r\n"
3756        );
3757        assert_eq!(
3758            f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
3759            "+OK\r\n"
3760        );
3761    }
3762
3763    /// Neither word is refused for being the wrong one. Each is only accepted
3764    /// while the other is unset, so the second of the two falls through to the
3765    /// plain syntax error rather than getting a message of its own.
3766    #[test]
3767    fn restore_takes_idletime_or_freq_and_not_both() {
3768        let mut f = Fixture::new();
3769        f.run(&[b"SET", b"a", b"v"]);
3770        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3771        assert_eq!(
3772            f.run(&[
3773                b"RESTORE",
3774                b"b",
3775                b"0",
3776                &bytes,
3777                b"IDLETIME",
3778                b"1",
3779                b"FREQ",
3780                b"2"
3781            ]),
3782            "-ERR syntax error\r\n"
3783        );
3784        assert_eq!(
3785            f.run(&[
3786                b"RESTORE",
3787                b"b",
3788                b"0",
3789                &bytes,
3790                b"FREQ",
3791                b"2",
3792                b"IDLETIME",
3793                b"1"
3794            ]),
3795            "-ERR syntax error\r\n"
3796        );
3797        assert_eq!(
3798            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
3799            "-ERR syntax error\r\n"
3800        );
3801        assert_eq!(
3802            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
3803            "-ERR syntax error\r\n"
3804        );
3805    }
3806
3807    #[test]
3808    fn copy_checks_its_options_before_it_looks_for_anything() {
3809        let mut f = Fixture::new();
3810        // No key exists at all, and every one of these is still the option
3811        // complaint rather than a zero, which is the order a real server uses.
3812        assert_eq!(
3813            f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
3814            "-ERR DB index is out of range\r\n"
3815        );
3816        assert_eq!(
3817            f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
3818            "-ERR DB index is out of range\r\n"
3819        );
3820        assert_eq!(
3821            f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
3822            "-ERR value is not an integer or out of range\r\n"
3823        );
3824        assert_eq!(
3825            f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
3826            "-ERR syntax error\r\n"
3827        );
3828        assert_eq!(
3829            f.run(&[b"COPY", b"a", b"a"]),
3830            "-ERR source and destination objects are the same\r\n"
3831        );
3832        // Repeated, reordered and lowercased, and the last DB wins.
3833        assert_eq!(
3834            f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
3835            ":0\r\n"
3836        );
3837    }
3838
3839    #[test]
3840    fn time_is_two_bulk_strings_and_moves() {
3841        let mut f = Fixture::new();
3842        let first = f.run(&[b"TIME"]);
3843        assert!(first.starts_with("*2\r\n$"), "got {first}");
3844        let parts: Vec<&str> = first.split("\r\n").collect();
3845        let secs: i64 = parts[2].parse().expect("seconds as decimal text");
3846        let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
3847        assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
3848        assert!((0..1_000_000).contains(&micros), "got {micros}");
3849        // The coarse clock the keyspace uses is a cached millisecond that a
3850        // background tick refreshes, so a TIME built on it would answer the
3851        // same microsecond twice in a row here.
3852        assert_ne!(first, f.run(&[b"TIME"]));
3853    }
3854
3855    #[test]
3856    fn a_keyspace_scan_walks_every_key_once() {
3857        // The count below is thirty two, so ninety six keys is three pages of
3858        // cursor and says the same thing as five hundred at a fifth of the
3859        // interpreted work.
3860        let n = if cfg!(miri) { 96 } else { 500 };
3861        let mut f = Fixture::new();
3862        for i in 0..n {
3863            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
3864        }
3865
3866        let mut seen: Vec<String> = Vec::new();
3867        let mut cursor = "0".to_owned();
3868        let mut calls = 0;
3869        loop {
3870            let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
3871            seen.extend(keys);
3872            cursor = next;
3873            calls += 1;
3874            assert!(calls < 10_000, "the cursor is not advancing");
3875            if cursor == "0" {
3876                break;
3877            }
3878        }
3879
3880        seen.sort();
3881        seen.dedup();
3882        assert_eq!(seen.len(), n, "every key once and only once");
3883        // And more than one call to get them, or the COUNT is being ignored and
3884        // the loop above proved nothing about resuming.
3885        assert!(calls > 1, "{n} keys came back in one batch");
3886    }
3887
3888    #[test]
3889    fn a_scan_narrows_by_pattern_and_by_type() {
3890        let mut f = Fixture::new();
3891        f.run(&[b"SET", b"str", b"v"]);
3892        f.run(&[b"SADD", b"members", b"a"]);
3893        f.run(&[b"HSET", b"fields", b"f", b"v"]);
3894
3895        let all = |f: &mut Fixture, args: &[&[u8]]| {
3896            let mut out: Vec<String> = Vec::new();
3897            let mut cursor = "0".to_owned();
3898            loop {
3899                let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
3900                line.extend_from_slice(args);
3901                let (next, keys) = scan_reply(&f.run(&line));
3902                out.extend(keys);
3903                cursor = next;
3904                if cursor == "0" {
3905                    break;
3906                }
3907            }
3908            out.sort();
3909            out
3910        };
3911
3912        assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
3913        assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
3914        assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
3915        // Case insensitive, the same as Redis's own comparison.
3916        assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
3917        // A type nothing can hold is not an error, it just matches nothing.
3918        assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
3919        assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
3920        // Both filters at once, and they are an and rather than an or.
3921        assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
3922    }
3923
3924    #[test]
3925    fn a_scan_says_what_is_wrong_with_it() {
3926        let mut f = Fixture::new();
3927        assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3928        assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3929        assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3930        assert_eq!(
3931            f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3932            "-ERR syntax error\r\n"
3933        );
3934        assert_eq!(
3935            f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3936            "-ERR value is not an integer or out of range\r\n"
3937        );
3938        assert_eq!(
3939            f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3940            "-ERR syntax error\r\n"
3941        );
3942        // A cursor the client made up is a cursor. It resumes somewhere
3943        // arbitrary and answers whatever is there, which is what Redis does and
3944        // is the only behaviour that does not need the server to remember every
3945        // cursor it has handed out.
3946        assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3947    }
3948
3949    #[test]
3950    fn keys_and_randomkey_look_at_the_whole_database() {
3951        let mut f = Fixture::new();
3952        assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3953        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3954
3955        for name in ["one", "two", "three"] {
3956            f.run(&[b"SET", name.as_bytes(), b"v"]);
3957        }
3958        assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3959        assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3960        assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3961
3962        for _ in 0..50 {
3963            let got = f.run(&[b"RANDOMKEY"]);
3964            assert!(
3965                ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3966                "got {got}"
3967            );
3968        }
3969    }
3970
3971    #[test]
3972    fn a_walk_does_not_answer_keys_that_have_expired() {
3973        let mut f = Fixture::new();
3974        f.run(&[b"SET", b"alive", b"v"]);
3975        f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3976        f.server.advance_clock_ms(2);
3977        assert_eq!(
3978            f.run(&[b"DBSIZE"]),
3979            ":2\r\n",
3980            "nothing has collected it yet"
3981        );
3982
3983        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3984        let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3985        assert_eq!(keys, ["alive"]);
3986        for _ in 0..20 {
3987            assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
3988        }
3989        // The walk collected it on the way past, which is what makes DBSIZE
3990        // here answer what Redis answers once its own cycle has been round.
3991        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3992    }
3993
3994    #[test]
3995    fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
3996        let mut f = Fixture::new();
3997        f.run(&[b"SET", b"k", b"v"]);
3998        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
3999        assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
4000
4001        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
4002        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4003        let ms = int(&f.run(&[b"PTTL", b"k"]));
4004        assert!((99_000..=100_000).contains(&ms), "got {ms}");
4005
4006        // The absolute pair, derived from the same one number the store kept.
4007        let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
4008        let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4009        assert_eq!(at, (at_ms + 500) / 1000);
4010        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
4011
4012        assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
4013        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
4014        assert_eq!(
4015            f.run(&[b"PERSIST", b"k"]),
4016            ":0\r\n",
4017            "nothing to take off the second time"
4018        );
4019        assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
4020        assert_eq!(
4021            f.run(&[b"GET", b"k"]),
4022            "$1\r\nv\r\n",
4023            "and the value went through all of that untouched"
4024        );
4025    }
4026
4027    #[test]
4028    fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
4029        let mut f = Fixture::new();
4030        f.run(&[b"SET", b"str", b"v"]);
4031        f.run(&[b"SADD", b"set", b"a", b"b"]);
4032        f.run(&[b"HSET", b"hash", b"f", b"v"]);
4033
4034        for key in [b"str".as_slice(), b"set", b"hash"] {
4035            assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
4036            assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
4037        }
4038        // The body is not touched by any of that, which is the whole reason the
4039        // deadline lives in the record and the body lives somewhere else.
4040        assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
4041        assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
4042        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
4043    }
4044
4045    #[test]
4046    fn a_deadline_that_has_already_gone_deletes_the_key_now() {
4047        let mut f = Fixture::new();
4048        for key in [b"a".as_slice(), b"b", b"c", b"d"] {
4049            f.run(&[b"SET", key, b"v"]);
4050        }
4051        // Four ways of naming a moment that has passed, and all four are a
4052        // delete answering 1 rather than an error. Zero is a moment, minus one
4053        // is a moment, and the hash field commands refuse the negative one.
4054        assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
4055        assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
4056        assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
4057        assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
4058        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4059        assert_eq!(
4060            f.run(&[b"EXPIRE", b"a", b"100"]),
4061            ":0\r\n",
4062            "and the key really went, so there is nothing to put a deadline on"
4063        );
4064    }
4065
4066    #[test]
4067    fn the_four_conditions_decide_whether_the_deadline_moves() {
4068        let mut f = Fixture::new();
4069        f.run(&[b"SET", b"k", b"v"]);
4070
4071        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
4072        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
4073        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
4074        assert_eq!(
4075            f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
4076            ":1\r\n",
4077            "no deadline reads as infinitely far away, so LT passes where GT fails"
4078        );
4079
4080        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
4081        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
4082        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4083        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
4084        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
4085        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4086
4087        // The condition is answered before the past check, so this is a 0 and
4088        // the key survives. The other order would delete it.
4089        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
4090        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
4091        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
4092        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
4093    }
4094
4095    #[test]
4096    fn the_conditions_are_a_set_and_not_a_keyword() {
4097        let mut f = Fixture::new();
4098        f.run(&[b"SET", b"k", b"v"]);
4099
4100        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
4101        assert_eq!(
4102            f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
4103            ":0\r\n",
4104            "the same keyword twice means it once, and NX now has a deadline to fail on"
4105        );
4106
4107        // XX with LT is the one pair that is not either of them on its own: LT
4108        // alone would accept a key with no deadline and this does not.
4109        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
4110        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4111        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
4112        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
4113        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4114        f.run(&[b"PERSIST", b"k"]);
4115        assert_eq!(
4116            f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
4117            ":0\r\n",
4118            "where LT on its own would have taken it"
4119        );
4120        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
4121    }
4122
4123    #[test]
4124    fn a_key_is_gone_once_its_moment_passes() {
4125        let mut f = Fixture::new();
4126        f.run(&[b"SET", b"k", b"v"]);
4127        f.run(&[b"EXPIRE", b"k", b"100"]);
4128
4129        let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4130        f.server.set_clock_ms(at as u64 + 1);
4131        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4132        assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
4133        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
4134        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4135    }
4136
4137    #[test]
4138    fn the_expiry_commands_refuse_what_a_real_server_refuses() {
4139        let mut f = Fixture::new();
4140        f.run(&[b"SET", b"k", b"v"]);
4141        for (bad, want) in [
4142            (
4143                &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
4144                "-ERR value is not an integer or out of range\r\n",
4145            ),
4146            (
4147                &[b"EXPIRE", b"k", b"100", b"MAYBE"],
4148                "-ERR Unsupported option MAYBE\r\n",
4149            ),
4150            (
4151                &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
4152                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4153            ),
4154            (
4155                &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
4156                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4157            ),
4158            (
4159                &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
4160                "-ERR GT and LT options at the same time are not compatible\r\n",
4161            ),
4162            // Seconds that overflow when multiplied into milliseconds. Every
4163            // message names the command it came from.
4164            (
4165                &[b"EXPIRE", b"k", b"9223372036854775807"],
4166                "-ERR invalid expire time in 'expire' command\r\n",
4167            ),
4168            (
4169                &[b"EXPIREAT", b"k", b"9223372036854775807"],
4170                "-ERR invalid expire time in 'expireat' command\r\n",
4171            ),
4172            (
4173                &[b"PEXPIRE", b"k", b"9223372036854775807"],
4174                "-ERR invalid expire time in 'pexpire' command\r\n",
4175            ),
4176        ] {
4177            assert_eq!(f.run(bad), want, "for {bad:?}");
4178        }
4179        assert_eq!(
4180            f.run(&[b"TTL", b"k"]),
4181            ":-1\r\n",
4182            "and none of those put a deadline on anything"
4183        );
4184
4185        // The one of the four that has no arithmetic to overflow. Redis takes
4186        // it and holds the number as given, and a record here holds forty six
4187        // bits, so it lands in the year 4199 instead. D-17.
4188        assert_eq!(
4189            f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
4190            ":1\r\n"
4191        );
4192        assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
4193    }
4194
4195    #[test]
4196    fn flushing_empties_this_database_or_every_one_of_them() {
4197        let mut f = Fixture::new();
4198        f.run(&[b"SELECT", b"0"]);
4199        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4200        f.run(&[b"SELECT", b"1"]);
4201        f.run(&[b"SET", b"c", b"3"]);
4202        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4203        // ASYNC and SYNC are both taken and neither changes anything, since the
4204        // keyspace is empty before the OK goes out either way.
4205        assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
4206        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4207        // Only database one was emptied.
4208        f.run(&[b"SELECT", b"0"]);
4209        assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
4210        assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
4211        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4212        f.run(&[b"SELECT", b"1"]);
4213        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4214        // Anything else after the name is a syntax error, and so is a third
4215        // argument even when the second one is a word we take.
4216        assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
4217        assert_eq!(
4218            f.run(&[b"FLUSHDB", b"sync", b"sync"]),
4219            "-ERR syntax error\r\n"
4220        );
4221    }
4222
4223    #[test]
4224    fn the_script_cache_and_the_library_set_answer_for_being_empty() {
4225        let mut f = Fixture::new();
4226        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4227        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
4228        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
4229        // Nothing is cached, so nothing is there, one answer per hash asked
4230        // about.
4231        assert_eq!(
4232            f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
4233            "*2\r\n:0\r\n:0\r\n"
4234        );
4235        assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
4236        assert_eq!(
4237            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
4238            "*0\r\n"
4239        );
4240        assert_eq!(
4241            f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
4242            "-ERR Library not found\r\n"
4243        );
4244
4245        // Redis's two messages here are its own, one per container, and one of
4246        // them reads like a typo.
4247        assert_eq!(
4248            f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
4249            "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
4250        );
4251        assert_eq!(
4252            f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
4253            "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
4254        );
4255        // A second argument after the mode is the generic one instead, because
4256        // the count is checked before the word is looked at. The subcommand in
4257        // the sentence is the client's own spelling and not the canonical one,
4258        // which is the same thing `unknown subcommand` does.
4259        assert_eq!(
4260            f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
4261            "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
4262        );
4263        assert_eq!(
4264            f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
4265            "-ERR Unknown argument bogus\r\n"
4266        );
4267        assert_eq!(
4268            f.run(&[b"SCRIPT", b"EXISTS"]),
4269            "-ERR wrong number of arguments for 'script|exists' command\r\n"
4270        );
4271
4272        assert_eq!(
4273            f.run(&[b"FUNCTION", b"NOPE"]),
4274            "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
4275        );
4276    }
4277
4278    #[test]
4279    fn the_script_cache_holds_what_was_loaded_into_it() {
4280        let mut f = Fixture::new();
4281        // The hash is the sha1 of the body and nothing else, so it is the same
4282        // number a real server answers and a client can compute it itself.
4283        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4284        assert_eq!(
4285            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4286            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4287        );
4288        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4289        assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
4290        // Loading is idempotent and a body that will not parse is refused
4291        // where it was written rather than where it is called.
4292        assert_eq!(
4293            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4294            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4295        );
4296        assert!(
4297            f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
4298                .starts_with("-ERR Error compiling script"),
4299        );
4300
4301        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4302        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
4303        assert_eq!(
4304            f.run(&[b"EVALSHA", sha, b"0"]),
4305            "-NOSCRIPT No matching script. Please use EVAL.\r\n"
4306        );
4307
4308        // Running the body puts it in the cache too, which is what makes the
4309        // load then call then fall back to load pattern a client uses work.
4310        assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
4311        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4312
4313        // Nothing here can run long enough to be killed, which is D-101, so
4314        // the answer is the one a real server gives when nothing is stuck.
4315        assert_eq!(
4316            f.run(&[b"SCRIPT", b"KILL"]),
4317            "-NOTBUSY No scripts in execution right now.\r\n"
4318        );
4319        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
4320        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
4321        assert_eq!(
4322            f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
4323            "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
4324        );
4325    }
4326
4327    #[test]
4328    fn eval_counts_its_keys_before_it_compiles_anything() {
4329        let mut f = Fixture::new();
4330        assert_eq!(
4331            f.run(&[b"EVAL", b"return 1"]),
4332            "-ERR wrong number of arguments for 'eval' command\r\n"
4333        );
4334        assert_eq!(
4335            f.run(&[b"EVAL", b"return 1", b"abc"]),
4336            "-ERR value is not an integer or out of range\r\n"
4337        );
4338        assert_eq!(
4339            f.run(&[b"EVAL", b"return 1", b"-1"]),
4340            "-ERR Number of keys can't be negative\r\n"
4341        );
4342        assert_eq!(
4343            f.run(&[b"EVAL", b"return 1", b"1"]),
4344            "-ERR Number of keys can't be greater than number of args\r\n"
4345        );
4346        // The count splits the tail, and everything past the keys is ARGV.
4347        assert_eq!(
4348            f.run(&[
4349                b"EVAL",
4350                b"return {KEYS[1],KEYS[2],ARGV[1]}",
4351                b"2",
4352                b"a",
4353                b"b",
4354                b"c"
4355            ]),
4356            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4357        );
4358        assert_eq!(
4359            f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
4360            ":0\r\n"
4361        );
4362        assert_eq!(
4363            f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
4364            ":2\r\n"
4365        );
4366    }
4367
4368    #[test]
4369    fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
4370        let mut f = Fixture::new();
4371        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4372
4373        // A number is truncated toward zero rather than rounded, and the two
4374        // ends of the range saturate the way the cast does.
4375        assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
4376        assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
4377        assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
4378        assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
4379        assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
4380        assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
4381        assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
4382
4383        assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
4384        assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
4385        // Everything that is not there is the same nothing.
4386        assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
4387        assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
4388        assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
4389        assert_eq!(eval(&mut f, b""), "$-1\r\n");
4390
4391        // A table is an array that stops at the first hole, which is what makes
4392        // a script build a reply by appending rather than by indexing.
4393        assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
4394        assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
4395        assert_eq!(
4396            eval(&mut f, b"return {1,'a',{2}}"),
4397            "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
4398        );
4399
4400        // The named fields, in the order a real server looks for them.
4401        assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
4402        assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
4403        assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
4404        assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
4405        // A line break inside one of them becomes a space, because the reply is
4406        // a single line and a client that saw the break would lose the frame.
4407        assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a  b\r\n");
4408        // A field of the wrong type is not that kind of reply at all, and falls
4409        // through to the array walk, which finds nothing.
4410        assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
4411        assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
4412    }
4413
4414    #[test]
4415    fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
4416        let mut f = Fixture::new();
4417        // Under RESP2 the four typed tables have to come back as something a
4418        // client that only knows RESP2 can read.
4419        assert_eq!(
4420            f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
4421            "$3\r\n3.5\r\n"
4422        );
4423        assert_eq!(
4424            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4425            "$3\r\n123\r\n"
4426        );
4427        assert_eq!(
4428            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4429            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4430        );
4431        assert_eq!(
4432            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4433            "*1\r\n$1\r\na\r\n"
4434        );
4435        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
4436
4437        f.out = Out::new(Proto::Resp3);
4438        assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
4439        assert_eq!(
4440            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4441            "(123\r\n"
4442        );
4443        assert_eq!(
4444            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4445            "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
4446        );
4447        assert_eq!(
4448            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4449            "~1\r\n$1\r\na\r\n"
4450        );
4451        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
4452    }
4453
4454    #[test]
4455    fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
4456        let mut f = Fixture::new();
4457        f.run(&[b"SET", b"s", b"hello"]);
4458        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4459        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4460
4461        assert_eq!(
4462            eval(&mut f, b"return type(redis.call('get','s'))"),
4463            "$6\r\nstring\r\n"
4464        );
4465        assert_eq!(
4466            eval(&mut f, b"return type(redis.call('llen','l'))"),
4467            "$6\r\nnumber\r\n"
4468        );
4469        assert_eq!(
4470            eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
4471            "$5\r\ntable\r\n"
4472        );
4473        // A status is a table with one field, which is what lets a script pass
4474        // one straight back out again.
4475        assert_eq!(
4476            eval(&mut f, b"return redis.call('set','s','v')['ok']"),
4477            "$2\r\nOK\r\n"
4478        );
4479        // A missing key is false under RESP2 and nil once the script asks for
4480        // RESP3, which is the one conversion the script gets to choose.
4481        assert_eq!(
4482            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4483            "$5\r\nfalse\r\n"
4484        );
4485        assert_eq!(
4486            eval(
4487                &mut f,
4488                b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
4489            ),
4490            "$3\r\nnil\r\n"
4491        );
4492        // The choice does not outlive the script that made it.
4493        assert_eq!(
4494            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4495            "$5\r\nfalse\r\n"
4496        );
4497    }
4498
4499    #[test]
4500    fn an_error_from_a_script_names_the_line_it_came_from() {
4501        let mut f = Fixture::new();
4502        // The position is the script's own, not the prelude's, and the suffix
4503        // names the script so a client can find it in the cache.
4504        assert_eq!(
4505            f.run(&[b"EVAL", b"error('boom')", b"0"]),
4506            "-ERR user_script:1: boom script: \
4507             82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
4508        );
4509        // Level zero says the message already knows where it came from.
4510        assert_eq!(
4511            f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
4512            "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
4513        );
4514        // A table with an err field keeps its own text and gets the suffix.
4515        assert!(
4516            f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
4517                .starts_with("-structured script: "),
4518        );
4519        // A script that will not parse is refused before it runs, so there is
4520        // no script and nothing to name.
4521        assert_eq!(
4522            f.run(&[b"EVAL", b"return this is not lua", b"0"]),
4523            "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
4524        );
4525
4526        // A table that came out of pcall is a string by the time the script
4527        // sees it, which is a real server's own wrapping and not Lua's.
4528        assert_eq!(
4529            f.run(&[
4530                b"EVAL",
4531                b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
4532                b"0"
4533            ]),
4534            "$8\r\nstring:z\r\n"
4535        );
4536        assert_eq!(
4537            f.run(&[
4538                b"EVAL",
4539                b"local a, b = pcall(function() error({a=1}) end) return type(b)",
4540                b"0"
4541            ]),
4542            "$5\r\ntable\r\n"
4543        );
4544    }
4545
4546    #[test]
4547    fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
4548        let mut f = Fixture::new();
4549        let sentence = |f: &mut Fixture, body: &[u8]| {
4550            let reply = f.run(&[b"EVAL", body, b"0"]);
4551            reply.split(" script: ").next().unwrap().to_owned()
4552        };
4553
4554        assert_eq!(
4555            sentence(&mut f, b"return redis.call()"),
4556            "-ERR Please specify at least one argument for this redis lib call"
4557        );
4558        assert_eq!(
4559            sentence(&mut f, b"return redis.call('get', {})"),
4560            "-ERR Lua redis lib command arguments must be strings or integers"
4561        );
4562        assert_eq!(
4563            sentence(&mut f, b"return redis.call('nosuchcmd')"),
4564            "-ERR Unknown Redis command called from script"
4565        );
4566        assert_eq!(
4567            sentence(&mut f, b"return redis.call('get')"),
4568            "-ERR Wrong number of args calling Redis command from script"
4569        );
4570        // The commands that make no sense inside a script are refused by name
4571        // rather than by not being implemented, so the sentence is the same one
4572        // a real server writes for each of them.
4573        for name in [
4574            &b"return redis.call('multi')"[..],
4575            b"return redis.call('exec')",
4576            b"return redis.call('watch','k')",
4577            b"return redis.call('subscribe','c')",
4578            b"return redis.call('debug','jmap')",
4579            b"return redis.call('eval','return 1',0)",
4580            b"return redis.call('config','get','maxmemory')",
4581        ] {
4582            assert_eq!(
4583                sentence(&mut f, name),
4584                "-ERR This Redis command is not allowed from script",
4585                "for {}",
4586                String::from_utf8_lossy(name)
4587            );
4588        }
4589        // HELP is the one subcommand of a refused container that is allowed,
4590        // because it reads nothing and changes nothing.
4591        assert!(
4592            f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
4593                .starts_with('*'),
4594        );
4595
4596        // pcall answers the same sentence as a value instead of raising it, and
4597        // the value has an err field a script can read.
4598        assert_eq!(
4599            f.run(&[
4600                b"EVAL",
4601                b"local x = redis.pcall('nosuchcmd') return x.err",
4602                b"0"
4603            ]),
4604            "$44\r\nERR Unknown Redis command called from script\r\n"
4605        );
4606        // Returning it unread raises it, because the table has an err field.
4607        assert_eq!(
4608            f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
4609            "-ERR Unknown Redis command called from script\r\n"
4610        );
4611    }
4612
4613    #[test]
4614    fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
4615        let mut f = Fixture::new();
4616        f.run(&[b"SET", b"k", b"v"]);
4617        assert_eq!(
4618            f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
4619            "$1\r\nv\r\n"
4620        );
4621        assert!(
4622            f.run(&[
4623                b"EVAL_RO",
4624                b"return redis.call('set', KEYS[1], 'x')",
4625                b"1",
4626                b"k"
4627            ])
4628            .starts_with("-ERR Write commands are not allowed from read-only scripts."),
4629        );
4630        // The write did not happen, and the same body under EVAL does.
4631        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
4632        assert_eq!(
4633            f.run(&[
4634                b"EVAL",
4635                b"return redis.call('set', KEYS[1], 'x')",
4636                b"1",
4637                b"k"
4638            ]),
4639            "+OK\r\n"
4640        );
4641        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
4642
4643        // EVALSHA_RO runs a cached body under the same rule.
4644        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4645        f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
4646        assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
4647    }
4648
4649    #[test]
4650    fn a_script_cannot_leave_anything_behind_for_the_next_one() {
4651        let mut f = Fixture::new();
4652        // A plain global write and a write through a name on the redis table
4653        // both raise, with the position the script wrote them at.
4654        for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
4655            let reply = f.run(&[b"EVAL", body, b"0"]);
4656            assert!(
4657                reply
4658                    .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
4659                "{body:?} gave {reply}",
4660            );
4661        }
4662        // Walking round the guard with rawset or setmetatable raises too, and
4663        // without the position, which is where a real server raises it from.
4664        for body in [
4665            &b"rawset(redis, 'call', 1)"[..],
4666            b"rawset(_G, 'zz', 1)",
4667            b"setmetatable(_G, {})",
4668            b"setmetatable(redis, {})",
4669        ] {
4670            let reply = f.run(&[b"EVAL", body, b"0"]);
4671            assert!(
4672                reply.starts_with("-ERR Attempt to modify a readonly table script: "),
4673                "{body:?} gave {reply}",
4674            );
4675        }
4676        // Reading a name that is not there is a mistake rather than a nil, so a
4677        // misspelled global stops the script instead of doing nothing quietly.
4678        assert!(
4679            f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
4680                .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
4681        );
4682        // Reading a name that is not on the redis table is a nil, which is how
4683        // a script tests for a helper that an older server does not have.
4684        assert_eq!(
4685            f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
4686            "$3\r\nnil\r\n"
4687        );
4688
4689        // The one write that lands, D-103, is taken back out before the next
4690        // script starts, so nothing a script does reaches the one after it.
4691        assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
4692        assert_eq!(
4693            f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
4694            "$8\r\nfunction\r\n"
4695        );
4696        assert_eq!(
4697            f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
4698            "$8\r\nfunction\r\n"
4699        );
4700    }
4701
4702    #[test]
4703    fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
4704        let mut f = Fixture::new();
4705        // The guard in front of the table is empty, so the three base library
4706        // readers that skip a metatable are pointed at the real table behind
4707        // it. A script counts what a real server counts.
4708        assert_eq!(
4709            f.run(&[
4710                b"EVAL",
4711                b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
4712                b"0",
4713            ]),
4714            ":23\r\n"
4715        );
4716        assert_eq!(
4717            f.run(&[
4718                b"EVAL",
4719                b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
4720                  table.sort(t) return table.concat(t, ' ')",
4721                b"0",
4722            ]),
4723            "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
4724             REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
4725             acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
4726             set_repl setresp sha1hex status_reply\r\n"
4727        );
4728        // The loop hands over the values as well as the names, so the twelve
4729        // helpers are callable from inside a traversal and not just findable.
4730        assert_eq!(
4731            f.run(&[
4732                b"EVAL",
4733                b"local n = 0 for k, v in pairs(redis) do \
4734                  if type(v) == 'function' then n = n + 1 end end return n",
4735                b"0",
4736            ]),
4737            ":12\r\n"
4738        );
4739        // The other two readers agree with it.
4740        assert_eq!(
4741            f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
4742            "$6\r\nstring\r\n"
4743        );
4744        assert_eq!(
4745            f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
4746            "$8\r\nfunction\r\n"
4747        );
4748        assert_eq!(
4749            f.run(&[
4750                b"EVAL",
4751                b"return tostring(rawget(redis, 'nosuchfield'))",
4752                b"0",
4753            ]),
4754            "$3\r\nnil\r\n"
4755        );
4756        // Reading round the guard is the only thing that was given back. A
4757        // write still lands on the guard and still raises.
4758        for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
4759            assert!(
4760                f.run(&[b"EVAL", body, b"0"])
4761                    .contains("Attempt to modify a readonly table script: "),
4762                "{body:?}",
4763            );
4764        }
4765        // A table nobody guards walks the way it always did, whether a script
4766        // made it or the standard library did.
4767        assert_eq!(
4768            f.run(&[
4769                b"EVAL",
4770                b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
4771                b"0",
4772            ]),
4773            ":2\r\n"
4774        );
4775        assert_eq!(
4776            f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
4777            "$3\r\nnil\r\n"
4778        );
4779        assert_eq!(
4780            f.run(&[
4781                b"EVAL",
4782                b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
4783                  return type(f)",
4784                b"0",
4785            ]),
4786            "$8\r\nfunction\r\n"
4787        );
4788    }
4789
4790    #[test]
4791    fn a_script_gets_the_bit_library_a_real_server_carries() {
4792        let mut f = Fixture::new();
4793        // Every answer is a signed word, which is why the ones past two to the
4794        // thirty one come back negative.
4795        for (body, want) in [
4796            ("bit.tobit(1)", ":1\r\n"),
4797            ("bit.tobit(2^32 + 1)", ":1\r\n"),
4798            ("bit.tobit(2^31)", ":-2147483648\r\n"),
4799            ("bit.tobit(0xffffffff)", ":-1\r\n"),
4800            // The rounding is to the nearest and not toward zero.
4801            ("bit.tobit(1.5)", ":2\r\n"),
4802            ("bit.tobit(2.5)", ":2\r\n"),
4803            ("bit.bnot(0)", ":-1\r\n"),
4804            ("bit.band(0xff, 0x0f)", ":15\r\n"),
4805            ("bit.band(1, 2, 3)", ":0\r\n"),
4806            ("bit.bor(1, 2, 4)", ":7\r\n"),
4807            ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
4808            // Only the low five bits of a count are read.
4809            ("bit.lshift(1, 31)", ":-2147483648\r\n"),
4810            ("bit.lshift(1, 32)", ":1\r\n"),
4811            ("bit.lshift(1, 33)", ":2\r\n"),
4812            ("bit.rshift(-1, 1)", ":2147483647\r\n"),
4813            ("bit.arshift(-1, 1)", ":-1\r\n"),
4814            ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
4815            ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
4816            ("bit.bswap(0x12345678)", ":2018915346\r\n"),
4817            // A string that reads as a number is a number, which is Lua's rule
4818            // and not a courtesy of this library.
4819            ("bit.tobit('0x10')", ":16\r\n"),
4820        ] {
4821            let script = format!("return {body}");
4822            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4823        }
4824        // The digits are the low ones, a negative count asks for upper case,
4825        // and a count outside eight is brought back to it.
4826        for (body, want) in [
4827            ("bit.tohex(1)", "00000001"),
4828            ("bit.tohex(-1)", "ffffffff"),
4829            ("bit.tohex(255, 2)", "ff"),
4830            ("bit.tohex(255, -8)", "000000FF"),
4831            ("bit.tohex(0x87654321, 4)", "4321"),
4832            ("bit.tohex(1, 0)", ""),
4833            ("bit.tohex(1, 9)", "00000001"),
4834        ] {
4835            let script = format!("return {body}");
4836            assert_eq!(
4837                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4838                format!("${}\r\n{want}\r\n", want.len()),
4839                "{body}",
4840            );
4841        }
4842        // A bad argument names the position, the function and what was passed,
4843        // and the line in front of it is the script's own.
4844        for (body, want) in [
4845            (
4846                "return bit.band()",
4847                "bad argument #1 to 'band' (number expected, got no value)",
4848            ),
4849            (
4850                "return bit.band('x')",
4851                "bad argument #1 to 'band' (number expected, got string)",
4852            ),
4853            (
4854                "return bit.tobit(true)",
4855                "bad argument #1 to 'tobit' (number expected, got boolean)",
4856            ),
4857            (
4858                "return bit.lshift(1)",
4859                "bad argument #2 to 'lshift' (number expected, got no value)",
4860            ),
4861        ] {
4862            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4863            assert!(
4864                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4865                "{body} gave {reply}",
4866            );
4867        }
4868        // The name in the message is the one the call site used, so a call that
4869        // went through `pcall` has no name to report.
4870        assert_eq!(
4871            f.run(&[
4872                b"EVAL",
4873                b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
4874                b"0",
4875            ]),
4876            "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
4877        );
4878        // The table is readable and not writable, the same as `redis`.
4879        assert_eq!(
4880            f.run(&[
4881                b"EVAL",
4882                b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
4883                  table.sort(t) return table.concat(t, ' ')",
4884                b"0",
4885            ]),
4886            "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
4887        );
4888        for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
4889            assert!(
4890                f.run(&[b"EVAL", body, b"0"])
4891                    .contains("Attempt to modify a readonly table script: "),
4892                "{body:?}",
4893            );
4894        }
4895    }
4896
4897    #[test]
4898    fn a_script_gets_the_cjson_library_a_real_server_carries() {
4899        let mut f = Fixture::new();
4900        // Encoding, including the three shapes nobody guesses right: an empty
4901        // table is an object, a number is fourteen significant digits, and a
4902        // hole in an array is a null rather than a shorter array.
4903        for (body, want) in [
4904            ("cjson.encode(nil)", "null"),
4905            ("cjson.encode(true)", "true"),
4906            ("cjson.encode(cjson.null)", "null"),
4907            ("cjson.encode(100)", "100"),
4908            ("cjson.encode(1/3)", "0.33333333333333"),
4909            ("cjson.encode(1e300)", "1e+300"),
4910            ("cjson.encode(2^53)", "9.007199254741e+15"),
4911            ("cjson.encode({})", "{}"),
4912            ("cjson.encode({1,2,3})", "[1,2,3]"),
4913            ("cjson.encode({a=1})", "{\"a\":1}"),
4914            ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
4915            ("cjson.encode({[0]=1})", "{\"0\":1}"),
4916            ("cjson.encode('a\\nb')", "\"a\\nb\""),
4917            // A tab and a backslash have short escapes, a vertical tab does not.
4918            ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
4919            ("cjson.encode('\\11')", "\"\\u000b\""),
4920            // Reading and writing again is the shortest way to say the decoder
4921            // built what the encoder expected.
4922            (
4923                "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
4924                "[1,[2,{\"a\":null}]]",
4925            ),
4926            // An empty array comes back as an object, because a table with
4927            // nothing in it has nothing to say about which it was.
4928            ("cjson.encode(cjson.decode('[]'))", "{}"),
4929        ] {
4930            let script = format!("return {body}");
4931            assert_eq!(
4932                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4933                format!("${}\r\n{want}\r\n", want.len()),
4934                "{body}",
4935            );
4936        }
4937        // Decoding, where the leniency about numbers is on by default and a
4938        // null is a value of its own rather than a missing key.
4939        for (body, want) in [
4940            ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
4941            ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
4942            ("cjson.decode('0x10')", ":16\r\n"),
4943            ("cjson.decode('+1')", ":1\r\n"),
4944            ("cjson.decode('01')", ":1\r\n"),
4945            ("cjson.decode(1) + 1", ":2\r\n"),
4946            // A long bracket, because Lua 5.1 would eat the backslash first.
4947            ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
4948            ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
4949            ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
4950        ] {
4951            let script = format!("return {body}");
4952            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4953        }
4954        // The settings, each of which answers with what it now holds.
4955        for (body, want) in [
4956            (
4957                "cjson.encode_number_precision(3) return cjson.encode(1/3)",
4958                "0.333",
4959            ),
4960            (
4961                "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
4962                "null",
4963            ),
4964            (
4965                "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
4966                "inf",
4967            ),
4968            (
4969                "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
4970                "{\"1\":1,\"100\":1}",
4971            ),
4972            (
4973                "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
4974                "[]",
4975            ),
4976            ("return tostring(cjson.encode_max_depth())", "1000"),
4977            ("return tostring(cjson.encode_keep_buffer(false))", "false"),
4978            ("return tostring(cjson.encode_sparse_array())", "false"),
4979            // A setting one script changed is not a setting the next one sees,
4980            // which is D-105.
4981            ("return tostring(cjson.encode_number_precision())", "14"),
4982        ] {
4983            assert_eq!(
4984                f.run(&[b"EVAL", body.as_bytes(), b"0"]),
4985                format!("${}\r\n{want}\r\n", want.len()),
4986                "{body}",
4987            );
4988        }
4989        // A failure names what stopped it and, when it was the text, where.
4990        for (body, want) in [
4991            (
4992                "return cjson.encode(1/0)",
4993                "Cannot serialise number: must not be NaN or Inf",
4994            ),
4995            (
4996                "return cjson.encode({[1]=1,[100]=1})",
4997                "Cannot serialise table: excessively sparse array",
4998            ),
4999            (
5000                "return cjson.encode({[true]=1})",
5001                "Cannot serialise boolean: table key must be a number or string",
5002            ),
5003            (
5004                "return cjson.encode(tostring)",
5005                "Cannot serialise function: type not supported",
5006            ),
5007            (
5008                "return cjson.encode()",
5009                "bad argument #1 to 'encode' (expected 1 argument)",
5010            ),
5011            (
5012                "return cjson.decode('[1,2')",
5013                "Expected comma or array end but found T_END at character 5",
5014            ),
5015            (
5016                "return cjson.decode('{\"a\" 1}')",
5017                "Expected colon but found T_NUMBER at character 6",
5018            ),
5019            (
5020                "return cjson.decode('tru')",
5021                "Expected value but found invalid token at character 1",
5022            ),
5023            (
5024                "return cjson.decode('[1] 2')",
5025                "Expected the end but found T_NUMBER at character 5",
5026            ),
5027            (
5028                "return cjson.encode_max_depth(0)",
5029                "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
5030            ),
5031            (
5032                "return cjson.encode_invalid_numbers('yes')",
5033                "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
5034            ),
5035            (
5036                "return cjson.encode_max_depth(1, 2)",
5037                "bad argument #2 to 'encode_max_depth' (found too many arguments)",
5038            ),
5039        ] {
5040            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5041            assert!(
5042                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5043                "{body} gave {reply}",
5044            );
5045        }
5046        // A module of its own, with settings of its own and no guard on it,
5047        // which is what a real server hands back.
5048        assert_eq!(
5049            f.run(&[
5050                b"EVAL",
5051                b"local n = cjson.new() n.encode_number_precision(3) \
5052                  return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
5053                b"0",
5054            ]),
5055            "$22\r\n0.33333333333333 0.333\r\n"
5056        );
5057        // The table is readable and not writable, the same as `redis`.
5058        let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
5059                     decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
5060                     encode_max_depth encode_number_precision encode_sparse_array new null";
5061        assert_eq!(
5062            f.run(&[
5063                b"EVAL",
5064                b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
5065                  table.sort(t) return table.concat(t, ' ')",
5066                b"0",
5067            ]),
5068            format!("${}\r\n{names}\r\n", names.len())
5069        );
5070        for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
5071            assert!(
5072                f.run(&[b"EVAL", body, b"0"])
5073                    .contains("Attempt to modify a readonly table script: "),
5074                "{body:?}",
5075            );
5076        }
5077    }
5078
5079    #[test]
5080    fn a_script_gets_the_struct_library_a_real_server_carries() {
5081        let mut f = Fixture::new();
5082        // Packing, where the sizes are the ones a sixty four bit build gives
5083        // and the order is the machine's own unless the format says otherwise.
5084        for (body, want) in [
5085            ("#struct.pack('i4', 1)", ":4\r\n"),
5086            ("#struct.pack('l', 1)", ":8\r\n"),
5087            ("#struct.pack('d', 1)", ":8\r\n"),
5088            ("#struct.pack('f', 1)", ":4\r\n"),
5089            ("#struct.pack('s', 'abc')", ":4\r\n"),
5090            ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
5091            ("#struct.pack('x')", ":1\r\n"),
5092            ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
5093            ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
5094            ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
5095            // Past eight bytes the C shifts an unsigned long off the end, so
5096            // the rest of the bytes are zero and a negative is not carried.
5097            ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
5098            ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
5099            // A count of zero on `c` writes the whole string, `s` adds the
5100            // terminator, and `x` writes a zero byte nobody reads back.
5101            ("#struct.pack('c0', 'abcd')", ":4\r\n"),
5102            ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
5103            ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
5104        ] {
5105            let script = format!("return {body}");
5106            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5107        }
5108        // Sizes, including the two the C is lenient about: an unknown letter
5109        // and a bare digit are both nothing at all rather than a complaint.
5110        for (body, want) in [
5111            ("struct.size('i')", ":4\r\n"),
5112            ("struct.size('l')", ":8\r\n"),
5113            ("struct.size('T')", ":8\r\n"),
5114            ("struct.size('h')", ":2\r\n"),
5115            ("struct.size('c10')", ":10\r\n"),
5116            ("struct.size('ic')", ":5\r\n"),
5117            ("struct.size('!8ic')", ":5\r\n"),
5118            ("struct.size('!4i')", ":4\r\n"),
5119            // Nothing is padded until `!` turns alignment on, and then a
5120            // double is pushed out to the next eight byte boundary.
5121            ("struct.size('bd')", ":9\r\n"),
5122            ("struct.size('!bd')", ":16\r\n"),
5123            ("struct.size('A')", ":0\r\n"),
5124            ("struct.size('7')", ":0\r\n"),
5125        ] {
5126            let script = format!("return {body}");
5127            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5128        }
5129        // Unpacking, which hands back the values and then where it stopped, so
5130        // the last number can be passed straight back in as the next offset.
5131        for (body, want) in [
5132            ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
5133            ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
5134            ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
5135            ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
5136            ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
5137            (
5138                "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
5139                ":-70000\r\n",
5140            ),
5141            ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
5142            // A `c0` takes its length from the value read just before it and
5143            // swallows it, so one byte says how long the next three are and
5144            // only the string and the position come back.
5145            ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
5146            ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
5147        ] {
5148            let script = format!("return {body}");
5149            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5150        }
5151        for (body, want) in [
5152            ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
5153            ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
5154            ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
5155        ] {
5156            let script = format!("return {body}");
5157            assert_eq!(
5158                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5159                format!("${}\r\n{want}\r\n", want.len()),
5160                "{body}",
5161            );
5162        }
5163        // A failure names the argument the C names, which is not always the
5164        // argument a reader would pick.
5165        for (body, want) in [
5166            (
5167                "return struct.pack()",
5168                "bad argument #1 to 'pack' (string expected, got no value)",
5169            ),
5170            // The C pushes a nil before it reads anything, so a missing value
5171            // is a nil rather than nothing at all.
5172            (
5173                "return struct.pack('i4')",
5174                "bad argument #2 to 'pack' (number expected, got nil)",
5175            ),
5176            // And it reads the string with a post increment before it checks
5177            // the length, so the number here is one past the real argument.
5178            (
5179                "return struct.pack('c6', 'abc')",
5180                "bad argument #3 to 'pack' (string too short)",
5181            ),
5182            (
5183                "return struct.pack('A', 'x')",
5184                "bad argument #1 to 'pack' (invalid format option 'A')",
5185            ),
5186            (
5187                "return struct.pack('i33', 1)",
5188                "integral size 33 is larger than limit of 32",
5189            ),
5190            (
5191                "return struct.pack('!3i', 1)",
5192                "alignment 3 is not a power of 2",
5193            ),
5194            (
5195                "return struct.unpack()",
5196                "bad argument #1 to 'unpack' (string expected, got no value)",
5197            ),
5198            (
5199                "return struct.unpack('i4')",
5200                "bad argument #2 to 'unpack' (string expected, got no value)",
5201            ),
5202            (
5203                "return struct.unpack('i4', 'ab')",
5204                "bad argument #2 to 'unpack' (data string too short)",
5205            ),
5206            (
5207                "return struct.unpack('i1', 'abc', 0)",
5208                "bad argument #3 to 'unpack' (offset must be 1 or greater)",
5209            ),
5210            (
5211                "return struct.unpack('c0', 'abc')",
5212                "format 'c0' needs a previous size",
5213            ),
5214            (
5215                "return struct.unpack('s', 'abc')",
5216                "unfinished string in data",
5217            ),
5218            (
5219                "return struct.size()",
5220                "bad argument #1 to 'size' (string expected, got no value)",
5221            ),
5222            (
5223                "return struct.size('s')",
5224                "bad argument #1 to 'size' (option 's' has no fixed size)",
5225            ),
5226            (
5227                "return struct.size('c0')",
5228                "bad argument #1 to 'size' (option 'c0' has no fixed size)",
5229            ),
5230        ] {
5231            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5232            assert!(
5233                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5234                "{body} gave {reply}",
5235            );
5236        }
5237        // Three members and no version, which is all the C registers.
5238        let names = "pack size unpack";
5239        assert_eq!(
5240            f.run(&[
5241                b"EVAL",
5242                b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
5243                  table.sort(t) return table.concat(t, ' ')",
5244                b"0",
5245            ]),
5246            format!("${}\r\n{names}\r\n", names.len())
5247        );
5248        for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
5249            assert!(
5250                f.run(&[b"EVAL", body, b"0"])
5251                    .contains("Attempt to modify a readonly table script: "),
5252                "{body:?}",
5253            );
5254        }
5255    }
5256
5257    #[test]
5258    fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
5259        let mut f = Fixture::new();
5260        // Every value goes out in the shortest form that holds it, and several
5261        // arguments are packed one after another into one string.
5262        let hex = "local function hx(s) return (string.gsub(s, '.', \
5263                   function(c) return string.format('%02x', string.byte(c)) end)) end ";
5264        for (body, want) in [
5265            ("cmsgpack.pack(nil)", "c0"),
5266            ("cmsgpack.pack(true)", "c3"),
5267            ("cmsgpack.pack(false)", "c2"),
5268            ("cmsgpack.pack(0)", "00"),
5269            ("cmsgpack.pack(127)", "7f"),
5270            ("cmsgpack.pack(128)", "cc80"),
5271            ("cmsgpack.pack(-1)", "ff"),
5272            ("cmsgpack.pack(-33)", "d0df"),
5273            ("cmsgpack.pack(65535)", "cdffff"),
5274            ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
5275            ("cmsgpack.pack(2^53)", "cf0020000000000000"),
5276            ("cmsgpack.pack(-2^63)", "d38000000000000000"),
5277            // Past what an integer holds it is a number again, and a number
5278            // goes out narrow whenever four bytes give it back unchanged.
5279            ("cmsgpack.pack(2^64)", "ca5f800000"),
5280            ("cmsgpack.pack(1.5)", "ca3fc00000"),
5281            ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
5282            ("cmsgpack.pack('abc')", "a3616263"),
5283            ("cmsgpack.pack('')", "a0"),
5284            ("cmsgpack.pack({})", "90"),
5285            ("cmsgpack.pack({1, 2})", "920102"),
5286            ("cmsgpack.pack({a = 1})", "81a16101"),
5287            ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
5288            // Sixteen levels of table are packed and the seventeenth is a nil,
5289            // which is what the C does rather than refusing the whole thing.
5290            (
5291                "(function() local t = {} local c = t \
5292                 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
5293                "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
5294                 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
5295            ),
5296        ] {
5297            let script = format!("{hex} return hx({body})");
5298            assert_eq!(
5299                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5300                format!("${}\r\n{want}\r\n", want.len()),
5301                "{body}",
5302            );
5303        }
5304        // Unpacking reads the whole stream, so a string holding three values
5305        // hands back three. The two that take an offset put where they got to
5306        // in front of the values, and answer minus one when nothing is left.
5307        for (body, want) in [
5308            ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
5309            ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
5310            ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
5311            ("select('#', cmsgpack.unpack(''))", 0),
5312            ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
5313            ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5314            ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5315            ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
5316            ("select(1, cmsgpack.unpack_one('\\1'))", -1),
5317            ("select(1, cmsgpack.unpack_one('', 0))", -1),
5318            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
5319            ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
5320            // A limit of nothing at all takes the read everything path, which
5321            // has no offset in front of it.
5322            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
5323            ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
5324        ] {
5325            let script = format!("return {body}");
5326            assert_eq!(
5327                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5328                format!(":{want}\r\n"),
5329                "{body}",
5330            );
5331        }
5332        for (body, want) in [
5333            ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
5334            ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
5335            ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
5336            (
5337                "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
5338                "inf",
5339            ),
5340            ("cmsgpack._NAME", "cmsgpack"),
5341            ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
5342            (
5343                "cmsgpack._COPYRIGHT",
5344                "Copyright (C) 2012, Salvatore Sanfilippo",
5345            ),
5346            (
5347                "cmsgpack._DESCRIPTION",
5348                "MessagePack C implementation for Lua",
5349            ),
5350        ] {
5351            let script = format!("return {body}");
5352            assert_eq!(
5353                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5354                format!("${}\r\n{want}\r\n", want.len()),
5355                "{body}",
5356            );
5357        }
5358        for (body, want) in [
5359            // The C counts the arguments before it reads any of them, so the
5360            // one it names when there are none is the one before the first.
5361            (
5362                "return cmsgpack.pack()",
5363                "bad argument #0 to 'pack' (MessagePack pack needs input.)",
5364            ),
5365            (
5366                "return cmsgpack.unpack()",
5367                "bad argument #1 to 'unpack' (string expected, got no value)",
5368            ),
5369            (
5370                "return cmsgpack.unpack(string.char(193))",
5371                "Bad data format in input.",
5372            ),
5373            (
5374                "return cmsgpack.unpack(string.char(204))",
5375                "Missing bytes in input.",
5376            ),
5377            (
5378                "return cmsgpack.unpack(string.char(146, 1))",
5379                "Missing bytes in input.",
5380            ),
5381            (
5382                "return cmsgpack.unpack_one('\\1', 5)",
5383                "Start offset 5 greater than input length 1.",
5384            ),
5385            (
5386                "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
5387                "Start offset 5 greater than input length 2.",
5388            ),
5389            // The second number here is the length of the input rather than
5390            // the limit, which is a mixed up argument in the C kept on purpose.
5391            (
5392                "return cmsgpack.unpack_one('\\1', -1)",
5393                "Invalid request to unpack with offset of -1 and limit of 1.",
5394            ),
5395            (
5396                "return cmsgpack.unpack_limit('\\1', -1, 0)",
5397                "Invalid request to unpack with offset of 0 and limit of 1.",
5398            ),
5399        ] {
5400            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5401            assert!(
5402                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5403                "{body} gave {reply}",
5404            );
5405        }
5406        // Four calls and the four names the C sets on the table beside them.
5407        let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
5408        assert_eq!(
5409            f.run(&[
5410                b"EVAL",
5411                b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
5412                  table.sort(t) return table.concat(t, ' ')",
5413                b"0",
5414            ]),
5415            format!("${}\r\n{names}\r\n", names.len())
5416        );
5417        for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
5418            assert!(
5419                f.run(&[b"EVAL", body, b"0"])
5420                    .contains("Attempt to modify a readonly table script: "),
5421                "{body:?}",
5422            );
5423        }
5424        // A library is a table like any other from a script's side, so packing
5425        // one walks its members rather than finding the guard in front empty.
5426        assert_eq!(
5427            f.run(&[
5428                b"EVAL",
5429                b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
5430                b"0",
5431            ]),
5432            "$8\r\ncmsgpack\r\n"
5433        );
5434    }
5435
5436    /// The library used by most of the function tests below.
5437    ///
5438    /// Written out once because every one of them wants a library that has
5439    /// something to call, and because the line numbers in the failures a couple
5440    /// of them check are line numbers in this.
5441    const LIB: &[u8] = b"#!lua name=mylib\n\
5442        local counter = 0\n\
5443        redis.register_function{function_name = 'ping', description = 'says pong',\n\
5444        callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
5445        redis.register_function('count', function() counter = counter + 1 return counter end)\n\
5446        redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
5447        redis.register_function('setit', function(keys, args) \
5448        return redis.call('SET', keys[1], args[1]) end)\n\
5449        redis.register_function('raise', function() error('boom') end)\n";
5450
5451    /// A second library, for the tests that need two of them.
5452    const OTHER: &[u8] = b"#!lua name=other\n\
5453        redis.register_function('twice', function(keys, args) return 2 end)\n";
5454
5455    #[test]
5456    fn a_library_is_loaded_once_and_called_by_name_forever_after() {
5457        let mut f = Fixture::new();
5458        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5459        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5460        // The dictionary FCALL looks in is one for the whole server and it does
5461        // not care about case, which is why this finds the same function.
5462        assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
5463        // Keys and arguments arrive as the two arguments of the callback rather
5464        // than as globals, and a function that reads KEYS is reading a name
5465        // that is not there.
5466        assert_eq!(
5467            f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
5468            "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5469        );
5470        assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
5471        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
5472        // A library's own local outlives the call that made it, which is the
5473        // whole reason a library is not a script.
5474        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5475        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
5476        // The name a failure ends with is the function's, where a script's is
5477        // its digest, and the line is a line in the library.
5478        assert_eq!(
5479            f.run(&[b"FCALL", b"raise", b"0"]),
5480            "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
5481        );
5482        // Deleting is by the exact name, so the upper case spelling that found
5483        // the function a moment ago does not find the library.
5484        assert_eq!(
5485            f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
5486            "-ERR Library not found\r\n"
5487        );
5488        assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
5489        assert_eq!(
5490            f.run(&[b"FCALL", b"ping", b"0"]),
5491            "-ERR Function not found\r\n"
5492        );
5493    }
5494
5495    #[test]
5496    fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
5497        let mut f = Fixture::new();
5498        for (code, want) in [
5499            (&b"return 1"[..], "ERR Missing library metadata"),
5500            (b"#!lua name=x", "ERR Invalid library metadata"),
5501            (b"#!\n", "ERR Library name was not given"),
5502            (b"#!lua\nx", "ERR Library name was not given"),
5503            (
5504                b"#!lua name=a name=b\nx",
5505                "ERR Invalid metadata value, name argument was given multiple times",
5506            ),
5507            (
5508                b"#!lua nome=a\nx",
5509                "ERR Invalid metadata value given: nome=a",
5510            ),
5511            (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
5512            (
5513                b"#!lua name=a-b\nx",
5514                "ERR Library names can only contain letters, numbers, or underscores(_) \
5515                 and must be at least one character long",
5516            ),
5517            (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
5518            (
5519                b"#!lua name=c\nthis is not lua",
5520                "ERR Error compiling function: user_function:2: '=' expected near 'is'",
5521            ),
5522            // Nothing at all is on the global table during a load except one
5523            // table with eight names on it, so `error` is as absent as anything
5524            // a library misspelled would be.
5525            (
5526                b"#!lua name=r\nerror('boom')",
5527                "ERR Error registering functions: ERR user_function:2: \
5528                 Script attempted to access nonexistent global variable 'error'",
5529            ),
5530            // And `redis` is there but `redis.call` is not, so the name the
5531            // complaint gives is `call` and not `redis`.
5532            (
5533                b"#!lua name=r\nredis.call('PING')",
5534                "ERR Error registering functions: ERR user_function:2: \
5535                 Script attempted to access nonexistent global variable 'call'",
5536            ),
5537            (
5538                b"#!lua name=r\nx = 1",
5539                "ERR Error registering functions: ERR user_function:2: \
5540                 Attempt to modify a readonly table",
5541            ),
5542            (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
5543        ] {
5544            assert_eq!(
5545                f.run(&[b"FUNCTION", b"LOAD", code]),
5546                format!("-{want}\r\n"),
5547                "{}",
5548                String::from_utf8_lossy(code),
5549            );
5550        }
5551    }
5552
5553    #[test]
5554    fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
5555        let mut f = Fixture::new();
5556        for (call, want) in [
5557            (
5558                &b"redis.register_function()"[..],
5559                "wrong number of arguments to redis.register_function",
5560            ),
5561            (
5562                b"redis.register_function('a', function() end, 1)",
5563                "wrong number of arguments to redis.register_function",
5564            ),
5565            (
5566                b"redis.register_function('a')",
5567                "calling redis.register_function with a single argument is only \
5568                 applicable to Lua table (representing named arguments).",
5569            ),
5570            (
5571                b"redis.register_function({foo = 'a'})",
5572                "unknown argument given to redis.register_function",
5573            ),
5574            (
5575                b"redis.register_function({callback = function() end})",
5576                "redis.register_function must get a function name argument",
5577            ),
5578            (
5579                b"redis.register_function({function_name = 'a'})",
5580                "redis.register_function must get a callback argument",
5581            ),
5582            (
5583                b"redis.register_function({function_name = {}, callback = function() end})",
5584                "function_name argument given to redis.register_function must be a string",
5585            ),
5586            (
5587                b"redis.register_function({function_name = 'a', description = {}, \
5588                  callback = function() end})",
5589                "description argument given to redis.register_function must be a string",
5590            ),
5591            (
5592                b"redis.register_function({function_name = 'a', callback = 1})",
5593                "callback argument given to redis.register_function must be a function",
5594            ),
5595            (
5596                b"redis.register_function({function_name = 'a', callback = function() end, \
5597                  flags = 1})",
5598                "flags argument to redis.register_function must be a table \
5599                 representing function flags",
5600            ),
5601            (
5602                b"redis.register_function({function_name = 'a', callback = function() end, \
5603                  flags = {'zz'}})",
5604                "unknown flag given",
5605            ),
5606            (
5607                b"redis.register_function({}, function() end)",
5608                "first argument to redis.register_function must be a string",
5609            ),
5610            (
5611                b"redis.register_function('a', 1)",
5612                "second argument to redis.register_function must be a function",
5613            ),
5614            (
5615                b"redis.register_function('a-b', function() end)",
5616                "Library names can only contain letters, numbers, or underscores(_) \
5617                 and must be at least one character long",
5618            ),
5619            (
5620                b"redis.register_function('d', function() end) \
5621                  redis.register_function('d', function() end)",
5622                "Function already exists in the library",
5623            ),
5624        ] {
5625            let mut code = b"#!lua name=e\n".to_vec();
5626            code.extend_from_slice(call);
5627            // Two `ERR` in a row on purpose. The sentence comes back as a table
5628            // with the code already on it, which is what keeps the position off
5629            // the front of it, and then the code goes on the line as well.
5630            assert_eq!(
5631                f.run(&[b"FUNCTION", b"LOAD", &code]),
5632                format!("-ERR Error registering functions: ERR {want}\r\n"),
5633                "{}",
5634                String::from_utf8_lossy(call),
5635            );
5636        }
5637        // A number is a name, because the C reads an argument that should be a
5638        // string through a helper that takes a number and prints it.
5639        assert_eq!(
5640            f.run(&[
5641                b"FUNCTION",
5642                b"LOAD",
5643                b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
5644            ]),
5645            "$1\r\nn\r\n"
5646        );
5647        assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
5648        // The dictionary inside one library is case sensitive where the one
5649        // across libraries is not, so these are two functions.
5650        assert_eq!(
5651            f.run(&[
5652                b"FUNCTION",
5653                b"LOAD",
5654                b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
5655                  redis.register_function('D', function() return 2 end)",
5656            ]),
5657            "$1\r\nc\r\n"
5658        );
5659    }
5660
5661    #[test]
5662    fn a_library_cannot_take_a_name_another_library_already_has() {
5663        let mut f = Fixture::new();
5664        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5665        assert_eq!(
5666            f.run(&[b"FUNCTION", b"LOAD", LIB]),
5667            "-ERR Library 'mylib' already exists\r\n"
5668        );
5669        // A different library that registers a name the first one already has,
5670        // which is checked without regard to case because the dictionary it is
5671        // checked against is.
5672        assert_eq!(
5673            f.run(&[
5674                b"FUNCTION",
5675                b"LOAD",
5676                b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
5677            ]),
5678            "-ERR Function PING already exists\r\n"
5679        );
5680        // REPLACE reloads a library over itself, and the collision check leaves
5681        // the library being replaced out or nothing could ever be reloaded.
5682        assert_eq!(
5683            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
5684            "$5\r\nmylib\r\n"
5685        );
5686        // The counter went back to zero with the reload, since the library is a
5687        // new one and its locals are new with it.
5688        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5689        assert_eq!(
5690            f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
5691            "-ERR Unknown option given: NOPE\r\n"
5692        );
5693        // The loop that reads the options stops one short of the end, so the
5694        // last argument is the code whatever it looks like.
5695        assert_eq!(
5696            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
5697            "-ERR Missing library metadata\r\n"
5698        );
5699        assert_eq!(
5700            f.run(&[b"FUNCTION", b"LOAD"]),
5701            "-ERR wrong number of arguments for 'function|load' command\r\n"
5702        );
5703    }
5704
5705    #[test]
5706    fn fcall_checks_the_name_before_it_looks_at_anything_else() {
5707        let mut f = Fixture::new();
5708        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5709        for (args, want) in [
5710            (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
5711            (&[b"ping", b"x"], "ERR Bad number of keys provided"),
5712            (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
5713            (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
5714            (
5715                &[b"ping", b"99999999999999999999"],
5716                "ERR Bad number of keys provided",
5717            ),
5718            (
5719                &[b"ping", b"3", b"a"],
5720                "ERR Number of keys can't be greater than number of args",
5721            ),
5722            (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
5723        ] {
5724            let mut wire: Vec<&[u8]> = vec![b"FCALL"];
5725            wire.extend_from_slice(args);
5726            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5727        }
5728        // The read-only spelling refuses a function the library did not mark
5729        // no-writes, and it refuses it before anything runs.
5730        assert_eq!(
5731            f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
5732            "-ERR Can not execute a script with write flag using *_ro command.\r\n"
5733        );
5734        assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
5735        assert_eq!(
5736            f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
5737            "-ERR Function not found\r\n"
5738        );
5739        // And a function that was marked no-writes is held to it whichever
5740        // spelling called it.
5741        assert_eq!(
5742            f.run(&[
5743                b"FUNCTION",
5744                b"LOAD",
5745                b"#!lua name=w\nredis.register_function{function_name = 'w', \
5746                  flags = {'no-writes'}, callback = function(keys) \
5747                  return redis.call('SET', keys[1], 'x') end}",
5748            ]),
5749            "$1\r\nw\r\n"
5750        );
5751        assert!(
5752            f.run(&[b"FCALL", b"w", b"1", b"k"])
5753                .starts_with("-ERR Write commands are not allowed from read-only scripts."),
5754        );
5755    }
5756
5757    #[test]
5758    fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
5759        let mut f = Fixture::new();
5760        // The three names on the `redis` table that only mean something inside
5761        // EVAL are not there, and neither is the error handler EVAL installs.
5762        let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
5763                     REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
5764                     acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
5765                     status_reply";
5766        let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
5767                       gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
5768                       rawequal rawget rawset redis select setmetatable string struct table \
5769                       tonumber tostring type unpack xpcall";
5770        assert_eq!(
5771            f.run(&[
5772                b"FUNCTION",
5773                b"LOAD",
5774                b"#!lua name=g\n\
5775                  local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
5776                  table.sort(o) return table.concat(o, ' ') end\n\
5777                  redis.register_function('names', function() return sorted(redis) end)\n\
5778                  redis.register_function('globals', function() return sorted(_G) end)\n\
5779                  redis.register_function('keysg', function() return KEYS[1] end)\n\
5780                  redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
5781                  redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
5782                  redis.register_function('gwr', function() _G.pcall = 1 end)\n",
5783            ]),
5784            "$1\r\ng\r\n"
5785        );
5786        assert_eq!(
5787            f.run(&[b"FCALL", b"names", b"0"]),
5788            format!("${}\r\n{names}\r\n", names.len())
5789        );
5790        assert_eq!(
5791            f.run(&[b"FCALL", b"globals", b"0"]),
5792            format!("${}\r\n{globals}\r\n", globals.len())
5793        );
5794        // No `KEYS`, and reading a global that is not there is a mistake rather
5795        // than a nil, so this is the sandbox's own complaint.
5796        assert!(
5797            f.run(&[b"FCALL", b"keysg", b"1", b"k"])
5798                .contains("nonexistent global variable 'KEYS'"),
5799        );
5800        // The `redis` table has no error metatable on it, unlike the global
5801        // table, so a name that is not on it is a nil and not a complaint.
5802        assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
5803        // The global table cannot be written to either way round, which is a
5804        // stricter rule than the one a script runs under.
5805        for name in [&b"wr"[..], b"gwr"] {
5806            assert!(
5807                f.run(&[b"FCALL", name, b"0"])
5808                    .contains("Attempt to modify a readonly table"),
5809                "{}",
5810                String::from_utf8_lossy(name),
5811            );
5812        }
5813    }
5814
5815    #[test]
5816    fn function_list_says_what_every_library_registered() {
5817        let mut f = Fixture::new();
5818        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5819        // One map per library on RESP3, and the functions inside it in the
5820        // order the library registered them, which is D-109.
5821        f.out = Out::new(Proto::Resp3);
5822        let listed = f.run(&[b"FUNCTION", b"LIST"]);
5823        assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
5824        assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
5825        assert!(listed.contains(
5826            "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
5827             $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
5828        ));
5829        // A function with no description gets a null rather than an empty
5830        // string, and no flags is an empty set rather than a missing field.
5831        assert!(listed.contains(
5832            "$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"
5833        ));
5834        assert!(!listed.contains("library_code"));
5835        assert!(
5836            f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
5837                .contains("library_code")
5838        );
5839        // The pattern is matched without regard to case, which is a third rule
5840        // again next to the two the two dictionaries use.
5841        assert!(
5842            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
5843                .starts_with("*1\r\n")
5844        );
5845        assert_eq!(
5846            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
5847            "*0\r\n"
5848        );
5849        // On RESP2 the same reply is a flat array of six, which is what `map`
5850        // means on a protocol that has no map.
5851        f.out = Out::new(Proto::Resp2);
5852        assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
5853        for (args, want) in [
5854            (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
5855            (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
5856            (
5857                &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
5858                "ERR Unknown argument LIBRARYNAME",
5859            ),
5860            (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
5861        ] {
5862            let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
5863            wire.extend_from_slice(args);
5864            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5865        }
5866    }
5867
5868    #[test]
5869    fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
5870        let mut f = Fixture::new();
5871        f.out = Out::new(Proto::Resp3);
5872        assert_eq!(
5873            f.run(&[b"FUNCTION", b"STATS"]),
5874            "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
5875             %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
5876        );
5877        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5878        assert!(
5879            f.run(&[b"FUNCTION", b"STATS"])
5880                .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
5881        );
5882        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5883        assert!(
5884            f.run(&[b"FUNCTION", b"STATS"])
5885                .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
5886        );
5887    }
5888
5889    #[test]
5890    fn every_function_subcommand_complains_about_its_own_arity() {
5891        let mut f = Fixture::new();
5892        for (args, want) in [
5893            (
5894                &[&b"STATS"[..], b"X"][..],
5895                "ERR wrong number of arguments for 'function|stats' command",
5896            ),
5897            (
5898                &[b"KILL", b"X"],
5899                "ERR wrong number of arguments for 'function|kill' command",
5900            ),
5901            (
5902                &[b"HELP", b"X"],
5903                "ERR wrong number of arguments for 'function|help' command",
5904            ),
5905            (
5906                &[b"DELETE"],
5907                "ERR wrong number of arguments for 'function|delete' command",
5908            ),
5909            (
5910                &[b"DELETE", b"a", b"b"],
5911                "ERR wrong number of arguments for 'function|delete' command",
5912            ),
5913            (
5914                &[b"DUMP", b"X"],
5915                "ERR wrong number of arguments for 'function|dump' command",
5916            ),
5917            (
5918                &[b"RESTORE"],
5919                "ERR wrong number of arguments for 'function|restore' command",
5920            ),
5921            // RESTORE is the other one that falls through to the generic
5922            // sentence, and for the same reason FLUSH does.
5923            (
5924                &[b"RESTORE", b"a", b"FLUSH", b"X"],
5925                "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
5926                 Try FUNCTION HELP.",
5927            ),
5928            (
5929                &[b"RESTORE", b"a", b"ZZ"],
5930                "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
5931                 or REPLACE.",
5932            ),
5933            // FLUSH is the one that does not, because it checks the count
5934            // itself before it looks at the argument.
5935            (
5936                &[b"FLUSH", b"SYNC", b"X"],
5937                "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
5938                 Try FUNCTION HELP.",
5939            ),
5940            (
5941                &[b"FLUSH", b"ZZ"],
5942                "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
5943            ),
5944            (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
5945        ] {
5946            let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
5947            wire.extend_from_slice(args);
5948            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5949        }
5950        assert_eq!(
5951            f.run(&[b"FUNCTION"]),
5952            "-ERR wrong number of arguments for 'function' command\r\n"
5953        );
5954        assert_eq!(
5955            f.run(&[b"FUNCTION", b"KILL"]),
5956            "-NOTBUSY No scripts in execution right now.\r\n"
5957        );
5958    }
5959
5960    /// The two ends of the same pipe, so they are tested as one.
5961    ///
5962    /// An empty server dumps ten bytes rather than nothing, because the footer
5963    /// is there whether or not a library is in front of it, and restoring those
5964    /// ten bytes is a working no op.
5965    #[test]
5966    fn a_library_survives_a_dump_and_a_restore() {
5967        let mut f = Fixture::new();
5968        let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5969        assert_eq!(empty.len(), 10);
5970        assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
5971
5972        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5973        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5974        assert!(full.len() > empty.len());
5975
5976        // The default policy is APPEND, so restoring onto the library the
5977        // payload came from is a name collision and not a quiet replacement.
5978        assert_eq!(
5979            f.run(&[b"FUNCTION", b"RESTORE", &full]),
5980            "-ERR Library mylib already exists\r\n"
5981        );
5982        assert_eq!(
5983            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5984            "+OK\r\n"
5985        );
5986        assert_eq!(
5987            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5988            "+OK\r\n"
5989        );
5990        // Whichever way it went back, the functions in it still run.
5991        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5992
5993        // FLUSH keeps only what the payload held, so a library that was there
5994        // and is not in the payload is gone.
5995        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
5996        assert_eq!(
5997            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5998            "+OK\r\n"
5999        );
6000        assert_eq!(
6001            f.run(&[b"FUNCTION", b"DELETE", b"other"]),
6002            "-ERR Library not found\r\n"
6003        );
6004    }
6005
6006    /// A payload that is going to be refused has to leave the server alone.
6007    ///
6008    /// Every one of these is refused for a different reason and at a different
6009    /// depth, from bytes that are not a payload at all down to a library that
6010    /// compiles and then collides, and the library that was already there has to
6011    /// still be there afterwards in every case.
6012    #[test]
6013    fn a_restore_that_fails_changes_nothing() {
6014        let mut f = Fixture::new();
6015        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6016        let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6017
6018        // Put the footer back on, so that each of these is refused for the
6019        // reason it is meant to be testing rather than for a checksum the edit
6020        // broke on the way.
6021        let reseal = |body: &[u8], version: u16| {
6022            let mut out = body.to_vec();
6023            out.extend_from_slice(&version.to_le_bytes());
6024            let crc = yo_common::crc::crc64(0, &out);
6025            out.extend_from_slice(&crc.to_le_bytes());
6026            out
6027        };
6028        let body = &good[..good.len() - 10];
6029
6030        let mut torn = good.clone();
6031        let n = torn.len();
6032        torn[n - 1] ^= 0xff;
6033        let future = reseal(body, 999);
6034        // The opcode in front of the one library, changed to the one the 7.0
6035        // release candidates wrote and then to one that is not a library at all.
6036        let mut pre_ga = body.to_vec();
6037        pre_ga[0] = 246;
6038        let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
6039        let mut other = body.to_vec();
6040        other[0] = 0;
6041        let other = reseal(&other, yo_kv::rdb::VERSION);
6042        // A library whose length says there is more of it than there is.
6043        let mut cut = body.to_vec();
6044        cut.truncate(body.len() - 1);
6045        let cut = reseal(&cut, yo_kv::rdb::VERSION);
6046
6047        for (bytes, want) in [
6048            (vec![], "ERR DUMP payload version or checksum are wrong"),
6049            (
6050                b"0123456789".to_vec(),
6051                "ERR DUMP payload version or checksum are wrong",
6052            ),
6053            (torn, "ERR DUMP payload version or checksum are wrong"),
6054            (future, "ERR DUMP payload version or checksum are wrong"),
6055            (pre_ga, "ERR Pre-GA function format not supported"),
6056            (other, "ERR given type is not a function"),
6057            (cut, "ERR Failed loading library payload"),
6058        ] {
6059            assert_eq!(
6060                f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
6061                format!("-{want}\r\n")
6062            );
6063        }
6064
6065        // Still exactly the one library, and it still runs.
6066        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6067        let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6068        assert_eq!(again, good);
6069    }
6070
6071    /// A REPLACE takes a library's name off another library and still refuses to
6072    /// take a function name off one it is leaving alone.
6073    #[test]
6074    fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
6075        let mut f = Fixture::new();
6076        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6077        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6078        // A second library registering the name the payload's library uses.
6079        let clash =
6080            b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
6081                .as_slice();
6082        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6083        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
6084        assert_eq!(
6085            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6086            "-ERR Function ping already exists\r\n"
6087        );
6088        // Untouched, so the name still belongs to the library that had it.
6089        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
6090    }
6091
6092    #[test]
6093    fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
6094        let mut f = Fixture::new();
6095        assert_eq!(
6096            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
6097            "*1\r\n$1\r\nk\r\n"
6098        );
6099        assert_eq!(
6100            f.run(&[
6101                b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
6102            ]),
6103            "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
6104        );
6105        // None is a real answer for a script and the arguments past the count
6106        // are not keys, so they are not listed.
6107        assert_eq!(
6108            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
6109            "*0\r\n"
6110        );
6111        // A count that makes no sense finds no keys rather than being an error,
6112        // which is what a real server's key spec does with it.
6113        assert_eq!(
6114            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
6115            "*0\r\n"
6116        );
6117        assert_eq!(
6118            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
6119            "*0\r\n"
6120        );
6121        assert_eq!(
6122            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
6123            "*0\r\n"
6124        );
6125        // The count itself has to be there, and that is an arity question.
6126        assert_eq!(
6127            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
6128            "-ERR Invalid number of arguments specified for command\r\n"
6129        );
6130    }
6131
6132    #[test]
6133    fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
6134        let mut f = Fixture::new();
6135        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
6136
6137        assert_eq!(
6138            eval(&mut f, b"return redis.sha1hex('')"),
6139            "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
6140        );
6141        assert_eq!(
6142            eval(&mut f, b"return redis.sha1hex('return 1')"),
6143            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
6144        );
6145        // A message with no space in it gets the generic code in front, and one
6146        // that already looks like a coded error is left alone.
6147        assert_eq!(
6148            eval(&mut f, b"return redis.error_reply('boom')"),
6149            "-ERR boom\r\n"
6150        );
6151        assert_eq!(
6152            eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
6153            "-WRONGTYPE nope\r\n"
6154        );
6155        assert_eq!(
6156            eval(&mut f, b"return redis.status_reply('fine')"),
6157            "+fine\r\n"
6158        );
6159        // Neither of them raises when it is called wrongly, they answer a value
6160        // that is an error, which is a difference a script can see.
6161        assert_eq!(
6162            eval(&mut f, b"return redis.error_reply(1)"),
6163            "-ERR wrong number or type of arguments\r\n"
6164        );
6165        assert_eq!(
6166            eval(&mut f, b"local x = redis.status_reply() return x.err"),
6167            "$37\r\nERR wrong number or type of arguments\r\n"
6168        );
6169
6170        // The constants a script branches on.
6171        assert_eq!(
6172            eval(
6173                &mut f,
6174                b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
6175            ),
6176            "$4\r\n0123\r\n"
6177        );
6178        assert_eq!(
6179            eval(
6180                &mut f,
6181                b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
6182            ),
6183            "$5\r\n01223\r\n"
6184        );
6185        // The calls that exist so an old script keeps working.
6186        assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
6187        assert_eq!(
6188            eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
6189            ":1\r\n"
6190        );
6191        assert_eq!(
6192            eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
6193            ":1\r\n"
6194        );
6195        assert_eq!(
6196            eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
6197            ":1\r\n"
6198        );
6199        // Each of those checks its arguments the way a real server does.
6200        assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
6201        assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
6202        assert!(
6203            eval(&mut f, b"redis.log('x', 'y')")
6204                .contains("First argument must be a number (log level)."),
6205        );
6206        assert!(
6207            eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
6208                .contains("Invalid command passed to redis.acl_check_cmd()"),
6209        );
6210        assert!(
6211            eval(&mut f, b"return redis.acl_check_cmd('get')")
6212                .contains("Wrong number of args for redis.acl_check_cmd()"),
6213        );
6214    }
6215
6216    #[test]
6217    fn a_counter_is_an_integer_and_not_a_string_of_digits() {
6218        let mut f = Fixture::new();
6219        assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
6220        assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
6221        assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
6222        // Read back as a string it is still an integer, written out as digits
6223        // only because somebody asked for them.
6224        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
6225        assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
6226        // A counter that is not a number is the error the store raises and this
6227        // layer only spells, which is the whole point of the split.
6228        f.run(&[b"SET", b"k", b"hello"]);
6229        assert_eq!(
6230            f.run(&[b"INCR", b"k"]),
6231            "-ERR value is not an integer or out of range\r\n"
6232        );
6233        assert_eq!(
6234            f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
6235            "-ERR increment would produce NaN or Infinity\r\n"
6236        );
6237    }
6238
6239    /// Every one of these was read off a running 8.8. They are the answers a
6240    /// client library's own test suite checks, and the shapes are not
6241    /// guessable: `DIGEST` is hexadecimal in a bulk string, `MSETEX` is an
6242    /// integer, `INCREX` is a pair.
6243    #[test]
6244    fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
6245        let mut f = Fixture::new();
6246        assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
6247        // The same digest a real 8.8 answers for the same five bytes, which is
6248        // what makes `IFDEQ` usable against a mixed deployment.
6249        assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
6250        assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
6251        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
6252        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
6253        assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
6254        assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
6255        assert_eq!(
6256            f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
6257            "*2\r\n:1\r\n:0\r\n",
6258            "a refused increment reports the value it left alone and applied nothing"
6259        );
6260        assert_eq!(
6261            f.run(&[
6262                b"INCREX",
6263                b"n",
6264                b"BYINT",
6265                b"5",
6266                b"UBOUND",
6267                b"3",
6268                b"SATURATE"
6269            ]),
6270            "*2\r\n:3\r\n:2\r\n"
6271        );
6272        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
6273        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
6274    }
6275
6276    #[test]
6277    fn the_same_answers_come_out_in_resp3_spelling() {
6278        let mut f = Fixture::new();
6279        assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
6280        assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
6281        // A float counter is a double on RESP3 and the digits in a bulk string
6282        // on RESP2, and `INCRBYFLOAT` is a bulk string on both.
6283        assert_eq!(
6284            f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
6285            "*2\r\n,1.5\r\n,1.5\r\n"
6286        );
6287        assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
6288        // `RESET` puts the protocol back, which is the part that is easy to
6289        // miss and leaves a pooled connection speaking the wrong one.
6290        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6291        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
6292    }
6293
6294    #[test]
6295    fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
6296        let mut f = Fixture::new();
6297        let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
6298        assert_eq!(flow, Flow::Continue);
6299        assert_eq!(
6300            reply,
6301            "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
6302        );
6303        // A name with a line ending in it cannot write its own frame into the
6304        // stream, which is the reason the error writer maps them to spaces.
6305        let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
6306        assert_eq!(reply.matches("\r\n").count(), 1);
6307    }
6308
6309    #[test]
6310    fn arity_is_checked_before_the_command_is() {
6311        let mut f = Fixture::new();
6312        assert_eq!(
6313            f.run(&[b"GET"]),
6314            "-ERR wrong number of arguments for 'get' command\r\n"
6315        );
6316        assert_eq!(
6317            f.run(&[b"MSET", b"k"]),
6318            "-ERR wrong number of arguments for 'mset' command\r\n"
6319        );
6320        // The table says `PING` takes one or more and a real server then
6321        // refuses three, which is the sort of thing that only shows up against
6322        // the real thing.
6323        assert_eq!(
6324            f.run(&[b"PING", b"a", b"b"]),
6325            "-ERR wrong number of arguments for 'ping' command\r\n"
6326        );
6327        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
6328        assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
6329        // `DELEX` takes two or four and nothing between.
6330        assert_eq!(
6331            f.run(&[b"DELEX", b"k", b"IFEQ"]),
6332            "-ERR wrong number of arguments for 'delex' command\r\n"
6333        );
6334    }
6335
6336    /// The option rules, all of them measured against 8.8 rather than read off
6337    /// the documentation. The surprising one is that `SET` accepts the same
6338    /// keyword twice and `INCREX` does not.
6339    #[test]
6340    fn the_option_combinations_are_the_ones_a_real_server_accepts() {
6341        let mut f = Fixture::new();
6342        let syntax = "-ERR syntax error\r\n";
6343        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
6344        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
6345        assert_eq!(
6346            f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
6347            syntax
6348        );
6349        assert_eq!(
6350            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
6351            syntax
6352        );
6353        assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
6354        // Twice is fine, and the last one wins.
6355        assert_eq!(
6356            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
6357            "+OK\r\n"
6358        );
6359        assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
6360        assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
6361        // `INCREX` refuses what `SET` allows.
6362        assert_eq!(
6363            f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
6364            syntax
6365        );
6366        assert_eq!(
6367            f.run(&[b"INCREX", b"n", b"ENX"]),
6368            "-ERR ENX flag requires an expiration\r\n"
6369        );
6370        assert_eq!(
6371            f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
6372            "-ERR UBOUND is not an integer or out of range\r\n"
6373        );
6374        assert_eq!(
6375            f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
6376            "-ERR LBOUND can't be greater than UBOUND\r\n"
6377        );
6378        assert_eq!(
6379            f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
6380            "-ERR If you want both the length and indexes, please just use IDX.\r\n"
6381        );
6382    }
6383
6384    /// Where the expiration rules bite. The one worth the test is `GETEX` on a
6385    /// key that is not there, which answers null without ever looking at the
6386    /// expiration it was given.
6387    #[test]
6388    fn the_expiry_rules_are_redis_own() {
6389        let mut f = Fixture::new();
6390        let bad = "-ERR invalid expire time in 'set' command\r\n";
6391        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
6392        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
6393        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
6394        assert_eq!(
6395            f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
6396            bad
6397        );
6398        assert_eq!(
6399            f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
6400            "-ERR value is not an integer or out of range\r\n"
6401        );
6402        assert_eq!(
6403            f.run(&[b"SETEX", b"k", b"0", b"v"]),
6404            "-ERR invalid expire time in 'setex' command\r\n"
6405        );
6406        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
6407        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
6408        assert_eq!(
6409            f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
6410            "-ERR syntax error\r\n",
6411            "the option list is still checked before the key is looked up"
6412        );
6413        // A deadline in the past is accepted and the key goes with it.
6414        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6415        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
6416        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6417    }
6418
6419    #[test]
6420    fn mset_takes_its_pairs_from_the_read_buffer() {
6421        let mut f = Fixture::new();
6422        assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
6423        assert_eq!(
6424            f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
6425            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
6426        );
6427        assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
6428        assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
6429        assert_eq!(
6430            f.run(&[b"MSETEX", b"2", b"e", b"5"]),
6431            "-ERR wrong number of key-value pairs\r\n"
6432        );
6433        assert_eq!(
6434            f.run(&[b"MSETEX", b"0", b"e", b"5"]),
6435            "-ERR invalid numkeys value\r\n"
6436        );
6437        assert_eq!(
6438            f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
6439            "-ERR invalid numkeys value\r\n"
6440        );
6441    }
6442
6443    #[test]
6444    fn lcs_answers_the_length_the_string_and_the_runs() {
6445        let mut f = Fixture::new();
6446        f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
6447        assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
6448        assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
6449        assert_eq!(
6450            f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
6451            "*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"
6452        );
6453        // Without `IDX` the two options that only mean something with it are
6454        // accepted and ignored, which is what a real server does.
6455        assert_eq!(
6456            f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
6457            "$6\r\nmytext\r\n"
6458        );
6459    }
6460
6461    #[test]
6462    fn select_moves_the_connection_and_the_databases_stay_apart() {
6463        let mut f = Fixture::new();
6464        f.run(&[b"SET", b"k", b"zero"]);
6465        assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
6466        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6467        f.run(&[b"SET", b"k", b"four"]);
6468        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
6469        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6470        assert_eq!(
6471            f.run(&[b"SELECT", b"99"]),
6472            "-ERR DB index is out of range\r\n"
6473        );
6474        assert_eq!(
6475            f.run(&[b"SELECT", b"-1"]),
6476            "-ERR DB index is out of range\r\n"
6477        );
6478        assert_eq!(
6479            f.run(&[b"SELECT", b"abc"]),
6480            "-ERR value is not an integer or out of range\r\n"
6481        );
6482        // `RESET` brings it back to zero.
6483        f.run(&[b"SELECT", b"4"]);
6484        f.run(&[b"RESET"]);
6485        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6486    }
6487
6488    #[test]
6489    fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
6490        let mut f = Fixture::new();
6491        let reply = f.run(&[b"HELLO"]);
6492        assert!(reply.starts_with("*14\r\n"), "{reply}");
6493        assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
6494        assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
6495        assert!(
6496            reply.contains(":7\r\n"),
6497            "the connection id is in there: {reply}"
6498        );
6499        assert_eq!(
6500            f.run(&[b"HELLO", b"4"]),
6501            "-NOPROTO unsupported protocol version\r\n"
6502        );
6503        assert_eq!(
6504            f.run(&[b"HELLO", b"abc"]),
6505            "-ERR Protocol version is not an integer or out of range\r\n"
6506        );
6507        assert_eq!(
6508            f.run(&[b"HELLO", b"3", b"SETNAME"]),
6509            "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
6510        );
6511        assert!(
6512            f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
6513                .starts_with("%7\r\n")
6514        );
6515        assert_eq!(f.session.name(), b"bob");
6516        f.run(&[b"RESET"]);
6517        assert_eq!(f.session.name(), b"");
6518    }
6519
6520    #[test]
6521    fn command_describes_this_server_in_the_shape_a_driver_reads() {
6522        let mut f = Fixture::new();
6523        let count = format!(":{}\r\n", COMMANDS.len());
6524        assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
6525        let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
6526        assert_eq!(
6527            info,
6528            "*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\
6529             *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
6530        );
6531        // A null in the list, and the plain one: `$-1` and not `*-1`.
6532        assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
6533        assert_eq!(
6534            f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
6535            "*1\r\n$8\r\ngetrange\r\n"
6536        );
6537        assert_eq!(
6538            f.run(&[b"COMMAND", b"NOPE"]),
6539            "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
6540        );
6541    }
6542
6543    /// A cluster aware client asks this question and then routes on the
6544    /// answer, so `MSETEX`, whose keys are not where the table says, is the one
6545    /// that matters.
6546    #[test]
6547    fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
6548        let mut f = Fixture::new();
6549        assert_eq!(
6550            f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
6551            "*1\r\n$1\r\nk\r\n"
6552        );
6553        assert_eq!(
6554            f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
6555            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6556        );
6557        assert_eq!(
6558            f.run(&[
6559                b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
6560            ]),
6561            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6562        );
6563        assert_eq!(
6564            f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
6565            "-ERR The command has no key arguments\r\n"
6566        );
6567        assert_eq!(
6568            f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
6569            "-ERR Invalid number of arguments specified for command\r\n"
6570        );
6571    }
6572
6573    #[test]
6574    fn config_answers_what_it_can_and_refuses_what_it_cannot() {
6575        let mut f = Fixture::new();
6576        assert_eq!(
6577            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6578            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
6579        );
6580        // A pattern matches more than one, and a setting two patterns both ask
6581        // for is still sent once.
6582        let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
6583        assert!(both.starts_with("*6\r\n"), "{both}");
6584        assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
6585        assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
6586        assert_eq!(
6587            f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
6588            "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
6589        );
6590        assert_eq!(
6591            f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
6592            "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
6593        );
6594        assert_eq!(
6595            f.run(&[b"CONFIG", b"GET"]),
6596            "-ERR wrong number of arguments for 'config|get' command\r\n"
6597        );
6598        // Too few arguments and an odd number of them are different
6599        // complaints, which is the sort of thing only the real server tells
6600        // you.
6601        assert_eq!(
6602            f.run(&[b"CONFIG", b"SET", b"appendonly"]),
6603            "-ERR wrong number of arguments for 'config|set' command\r\n"
6604        );
6605        assert_eq!(
6606            f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
6607            "-ERR syntax error\r\n"
6608        );
6609        assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
6610        assert_eq!(
6611            f.run(&[b"CONFIG", b"REWRITE"]),
6612            "-ERR The server is running without a config file\r\n"
6613        );
6614    }
6615
6616    #[test]
6617    fn the_eviction_policy_reads_back_what_was_written_to_it() {
6618        let mut f = Fixture::new();
6619        assert_eq!(
6620            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6621            "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
6622        );
6623        assert_eq!(
6624            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
6625            "+OK\r\n",
6626            "the name is matched without regard to case, like every other one"
6627        );
6628        assert_eq!(
6629            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6630            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6631        );
6632        // And INFO agrees with CONFIG, which it did not when it was a literal.
6633        assert!(
6634            f.run(&[b"INFO", b"memory"])
6635                .contains("maxmemory_policy:allkeys-lfu"),
6636            "INFO and CONFIG disagree about the policy"
6637        );
6638        // The refusal names every legal value in the order the real server's
6639        // enum table lists them, because a client comparing the message compares
6640        // the whole string.
6641        assert_eq!(
6642            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
6643            "-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"
6644        );
6645        // A bad pair leaves the good one in the same command alone, and the
6646        // policy is checked by the same pass that checks the numbers.
6647        assert_eq!(
6648            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6649            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6650        );
6651        f.run(&[
6652            b"CONFIG",
6653            b"SET",
6654            b"hash-max-listpack-entries",
6655            b"7",
6656            b"maxmemory-policy",
6657            b"nonsense",
6658        ]);
6659        assert_eq!(
6660            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6661            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
6662        );
6663    }
6664
6665    #[test]
6666    fn the_three_eviction_numbers_read_back_too() {
6667        let mut f = Fixture::new();
6668        for (name, default, set) in [
6669            ("maxmemory-samples", "5", "12"),
6670            ("lfu-log-factor", "10", "3"),
6671            ("lfu-decay-time", "1", "60"),
6672        ] {
6673            let get = || {
6674                format!(
6675                    "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
6676                    name.len(),
6677                    default.len()
6678                )
6679            };
6680            assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
6681            assert_eq!(
6682                f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
6683                "+OK\r\n"
6684            );
6685            assert_eq!(
6686                f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
6687                format!(
6688                    "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
6689                    name.len(),
6690                    set.len()
6691                )
6692            );
6693            // A number that is not a number is refused with the same sentence
6694            // every other number gets, which names the setting the client typed.
6695            assert_eq!(
6696                f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
6697                format!(
6698                    "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
6699                )
6700            );
6701        }
6702    }
6703
6704    #[test]
6705    fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
6706        let mut f = Fixture::new();
6707        assert_eq!(
6708            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6709            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
6710            "no limit is the default"
6711        );
6712        // The pairing is Redis's and it is a trap: the bare letter is a power of
6713        // ten and the one with the b is a power of two.
6714        for (typed, bytes) in [
6715            (&b"1024"[..], "1024"),
6716            (b"1k", "1000"),
6717            (b"1kb", "1024"),
6718            (b"1M", "1000000"),
6719            (b"1Mb", "1048576"),
6720            (b"1gb", "1073741824"),
6721            (b"100mb", "104857600"),
6722        ] {
6723            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
6724            assert_eq!(
6725                f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6726                format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
6727                "set {}",
6728                String::from_utf8_lossy(typed)
6729            );
6730        }
6731        assert!(
6732            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6733            "the report agrees with the setting"
6734        );
6735
6736        // A unit nobody has heard of, and a negative number, which is not a very
6737        // large one however it is spelled.
6738        for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
6739            assert_eq!(
6740                f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
6741                "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
6742                "refused {}",
6743                String::from_utf8_lossy(bad)
6744            );
6745        }
6746        assert!(
6747            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6748            "and the refusal left the old one alone"
6749        );
6750    }
6751
6752    #[test]
6753    fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
6754        let mut f = Fixture::new();
6755        f.run(&[b"SET", b"here", b"already"]);
6756        // A byte, which is under what an empty server holds, so nothing this
6757        // command could do would get it under. The default policy is
6758        // `noeviction`, so nothing is what it does.
6759        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
6760        assert_eq!(
6761            f.run(&[b"SET", b"k", b"v"]),
6762            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6763        );
6764        assert_eq!(
6765            f.run(&[b"LPUSH", b"l", b"v"]),
6766            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6767        );
6768        // Reading is allowed, and so is the one thing that would help.
6769        assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
6770        assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
6771        assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
6772
6773        // Taking the limit away lets the write through again.
6774        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6775        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6776    }
6777
6778    /// Not under Miri, for the reason in `filled`: what it is watching is a
6779    /// whole two megabyte segment going back, so the megabytes are the claim
6780    /// and there is no smaller version of it that says the same thing.
6781    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
6782    #[test]
6783    fn an_allkeys_policy_makes_room_instead_of_refusing() {
6784        let mut f = Fixture::new();
6785        let val = vec![b'v'; 256];
6786        for i in 0..24000u32 {
6787            let k = format!("key:{i:08}");
6788            f.run(&[b"SET", k.as_bytes(), &val]);
6789        }
6790        let full = f.server.memory_bytes();
6791        assert!(
6792            full > 3 * 1024 * 1024,
6793            "the arena is several segments: {full}"
6794        );
6795
6796        // Two megabytes under what it is holding, which is one segment's worth,
6797        // so getting there means giving a whole segment back and not just
6798        // dropping a few records.
6799        let limit = full - 2 * 1024 * 1024;
6800        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
6801        f.run(&[
6802            b"CONFIG",
6803            b"SET",
6804            b"maxmemory",
6805            limit.to_string().as_bytes(),
6806        ]);
6807
6808        // Writes keep working the whole way down. The budget means one command
6809        // does not do it all, so this runs until the server has settled and
6810        // checks that nothing was refused on the way.
6811        for i in 0..2000u32 {
6812            let k = format!("new:{i:08}");
6813            assert_eq!(
6814                f.run(&[b"SET", k.as_bytes(), &val]),
6815                "+OK\r\n",
6816                "write {i} was refused"
6817            );
6818            f.server.refresh_memory();
6819            if f.server.memory_bytes() <= limit {
6820                break;
6821            }
6822        }
6823        assert!(
6824            f.server.memory_bytes() <= limit,
6825            "it never got under: {} against {limit}",
6826            f.server.memory_bytes()
6827        );
6828        let info = f.run(&[b"INFO", b"stats"]);
6829        assert!(!info.contains("evicted_keys:0"), "{info}");
6830        assert!(
6831            f.run(&[b"DBSIZE"]) != ":0\r\n",
6832            "and it did not empty the database to get there"
6833        );
6834    }
6835
6836    /// Not under Miri. Every round is eleven commands over six collections
6837    /// holding two hundred byte values, which is a third of a second each
6838    /// interpreted, and the rounds cannot come down far: one in seven takes an
6839    /// entry back out, so under about a hundred and seventy of them the
6840    /// collections never reach the hundred and twenty eight entries where the
6841    /// small representations give up and become the big ones, and a
6842    /// representation changing under the running total is one of the five
6843    /// things this is here to watch. What is left is an hour, for an accounting
6844    /// claim rather than a safety one, and the commands it sends are sent a few
6845    /// at a time by the tests around it.
6846    #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
6847    #[test]
6848    fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
6849        // The limit is judged against a number kept as the collections move,
6850        // rather than found by asking all of them, and the two have to be the
6851        // same number or the limit is enforced against a fiction. This does the
6852        // things that move it, which is growing a collection, shrinking one,
6853        // changing its representation, deleting it and reusing its slot, across
6854        // all five types, and checks the two against each other as it goes.
6855        let mut f = Fixture::new();
6856        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6857        let big = vec![b'v'; 200];
6858
6859        for i in 0..400u32 {
6860            let n = i.to_string();
6861            let n = n.as_bytes();
6862            f.run(&[b"SADD", b"s", n]);
6863            f.run(&[b"SADD", b"s2", &big]);
6864            f.run(&[b"HSET", b"h", n, &big]);
6865            f.run(&[b"RPUSH", b"l", &big]);
6866            f.run(&[b"ZADD", b"z", n, n]);
6867            f.run(&[b"ARSET", b"a", n, &big]);
6868            if i % 7 == 0 {
6869                f.run(&[b"SREM", b"s", n]);
6870                f.run(&[b"HDEL", b"h", n]);
6871                f.run(&[b"LPOP", b"l"]);
6872                f.run(&[b"ZREM", b"z", n]);
6873                f.run(&[b"ARDEL", b"a", n]);
6874            }
6875            if i % 53 == 0 {
6876                // Every type deleted and made again, so a slot goes on the free
6877                // list and comes back holding something else.
6878                f.run(&[b"DEL", b"s2"]);
6879            }
6880            assert_eq!(
6881                f.server.settled_memory(),
6882                f.server.memory_bytes(),
6883                "after round {i}"
6884            );
6885        }
6886
6887        // The run has to have built something, or the two numbers agreeing is
6888        // two zeroes agreeing.
6889        assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
6890        assert!(
6891            f.server.memory_bytes() > 512 * 1024,
6892            "{}",
6893            f.server.memory_bytes()
6894        );
6895
6896        // And it survives the collections going away entirely.
6897        f.run(&[b"FLUSHALL"]);
6898        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6899    }
6900
6901    #[test]
6902    fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
6903        // A server with no limit does not keep the running total, so setting a
6904        // limit on a database that is already full has to start it from a walk.
6905        // If it did not, the first reading would be zero and the server would
6906        // think it had all the room in the world.
6907        let mut f = Fixture::new();
6908        for i in 0..200u32 {
6909            let n = i.to_string();
6910            f.run(&[b"SADD", b"s", n.as_bytes()]);
6911            f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
6912        }
6913        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6914        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6915
6916        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6917        for i in 200..400u32 {
6918            let n = i.to_string();
6919            f.run(&[b"SADD", b"s", n.as_bytes()]);
6920        }
6921        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6922        assert_eq!(
6923            f.server.settled_memory(),
6924            f.server.memory_bytes(),
6925            "the writes it was not watching are in the number it started from"
6926        );
6927    }
6928
6929    #[test]
6930    fn evicted_keys_and_expired_keys_are_different_numbers() {
6931        let mut f = Fixture::new();
6932        // Nothing has been evicted and nothing can be under the default policy,
6933        // so this stays at zero while the other one moves.
6934        f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
6935        f.server.advance_clock_ms(20);
6936        f.run(&[b"GET", b"gone"]);
6937        let info = f.run(&[b"INFO", b"stats"]);
6938        assert!(info.contains("expired_keys:1"), "{info}");
6939        assert!(info.contains("evicted_keys:0"), "{info}");
6940    }
6941
6942    #[test]
6943    fn the_object_subcommands_follow_the_policy() {
6944        let mut f = Fixture::new();
6945        f.run(&[b"SET", b"s", b"v"]);
6946        // Under the default the clock is kept and the counter is not, and under
6947        // an LFU policy it is the other way round. Each subcommand refuses on
6948        // the side where its reading of the three bytes means nothing.
6949        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6950        assert!(
6951            f.run(&[b"OBJECT", b"FREQ", b"s"])
6952                .starts_with("-ERR An LFU maxmemory policy is not selected"),
6953        );
6954
6955        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
6956        assert!(
6957            f.run(&[b"OBJECT", b"IDLETIME", b"s"])
6958                .starts_with("-ERR An LFU maxmemory policy is selected"),
6959        );
6960        // The key was written under a clock policy, so what comes back is that
6961        // clock read as a counter. It is a number and not an error, which is the
6962        // point: switching at runtime does not invalidate anything, it only makes
6963        // the old field mean something else until the key is used again.
6964        assert!(
6965            f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
6966            "FREQ should answer under an LFU policy"
6967        );
6968    }
6969
6970    #[test]
6971    fn object_says_which_rung_of_the_ladder_a_key_is_on() {
6972        let mut f = Fixture::new();
6973        f.run(&[b"SET", b"s", b"hello"]);
6974        f.run(&[b"SET", b"n", b"123"]);
6975        f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
6976        f.run(&[b"SADD", b"ss", b"a", b"b"]);
6977        f.run(&[b"HSET", b"h", b"f", b"v"]);
6978        for (key, want) in [
6979            (b"s".as_slice(), "embstr"),
6980            (b"n", "int"),
6981            (b"si", "intset"),
6982            (b"ss", "listpack"),
6983            (b"h", "listpack"),
6984        ] {
6985            let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
6986            assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
6987        }
6988
6989        // A field deadline widens the blob rather than promoting it, and this
6990        // is the only place a client can see that happen.
6991        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
6992        assert_eq!(
6993            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6994            "$10\r\nlistpackex\r\n"
6995        );
6996
6997        assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
6998        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6999        assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
7000    }
7001
7002    #[test]
7003    fn object_answers_nil_for_a_key_that_is_not_there() {
7004        let mut f = Fixture::new();
7005        for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
7006            assert_eq!(
7007                f.run(&[b"OBJECT", sub, b"nokey"]),
7008                "$-1\r\n",
7009                "a nil and not an error, which is what 8.10.1 does"
7010            );
7011        }
7012        // And the key is looked up before FREQ has its complaint, so the
7013        // complaint only reaches a key that exists.
7014        f.run(&[b"SET", b"s", b"v"]);
7015        assert!(
7016            f.run(&[b"OBJECT", b"FREQ", b"s"])
7017                .starts_with("-ERR An LFU maxmemory policy is not"),
7018        );
7019        assert_eq!(
7020            f.run(&[b"OBJECT", b"NOPE", b"s"]),
7021            "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
7022        );
7023        assert_eq!(
7024            f.run(&[b"OBJECT", b"ENCODING"]),
7025            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7026        );
7027        assert_eq!(
7028            f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
7029            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7030        );
7031        assert_eq!(
7032            f.run(&[b"OBJECT"]),
7033            "-ERR wrong number of arguments for 'object' command\r\n"
7034        );
7035    }
7036
7037    #[test]
7038    fn config_moves_the_ladder_and_object_encoding_agrees() {
7039        let mut f = Fixture::new();
7040        assert_eq!(
7041            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7042            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7043            "512 and not the 128 everyone remembers, which is what 8.10.1 says"
7044        );
7045        // The old spelling is the same number under a different name, and a
7046        // glob that catches both sends both.
7047        assert_eq!(
7048            f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
7049            "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
7050        );
7051        assert!(
7052            f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
7053                .starts_with("*8\r\n")
7054        );
7055        assert!(
7056            f.run(&[b"CONFIG", b"GET", b"set-max-*"])
7057                .starts_with("*6\r\n")
7058        );
7059
7060        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
7061        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
7062
7063        assert_eq!(
7064            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
7065            "+OK\r\n",
7066            "written under the old name and read back under the new one"
7067        );
7068        assert_eq!(
7069            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7070            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
7071        );
7072        assert_eq!(
7073            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7074            "$8\r\nlistpack\r\n",
7075            "the hash that already exists is left exactly where it was"
7076        );
7077        f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
7078        assert_eq!(
7079            f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
7080            "$9\r\nhashtable\r\n",
7081            "and the next one built goes straight to a table"
7082        );
7083
7084        // The set has three of these and all three move.
7085        f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
7086        f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
7087        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
7088        f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
7089        f.run(&[b"SADD", b"s2", b"abcdefgh"]);
7090        assert_eq!(
7091            f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
7092            "$9\r\nhashtable\r\n"
7093        );
7094    }
7095
7096    #[test]
7097    fn config_set_takes_all_of_the_ladder_or_none_of_it() {
7098        let mut f = Fixture::new();
7099        assert_eq!(
7100            f.run(&[
7101                b"CONFIG",
7102                b"SET",
7103                b"hash-max-listpack-entries",
7104                b"7",
7105                b"set-max-listpack-entries",
7106                b"abc"
7107            ]),
7108            "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
7109        );
7110        assert_eq!(
7111            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7112            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7113            "the pair in front of the bad one did not go in"
7114        );
7115        // The name in the complaint is the one that was typed, so the old
7116        // spelling comes back as the old spelling.
7117        assert_eq!(
7118            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
7119            "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
7120        );
7121        assert_eq!(
7122            f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
7123            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
7124        );
7125        // A number past what an i64 holds is the parse complaint and not the
7126        // range one, which is upstream reading it before it checks it.
7127        assert_eq!(
7128            f.run(&[
7129                b"CONFIG",
7130                b"SET",
7131                b"set-max-intset-entries",
7132                b"99999999999999999999"
7133            ]),
7134            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
7135        );
7136        assert_eq!(
7137            f.run(&[
7138                b"CONFIG",
7139                b"SET",
7140                b"set-max-intset-entries",
7141                b"9223372036854775807"
7142            ]),
7143            "+OK\r\n"
7144        );
7145    }
7146
7147    #[test]
7148    fn a_setting_moved_on_one_database_moved_on_all_of_them() {
7149        let mut f = Fixture::new();
7150        f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
7151        f.run(&[b"SELECT", b"3"]);
7152        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7153        assert_eq!(
7154            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7155            "$9\r\nhashtable\r\n",
7156            "these are one server wide number in Redis, whatever a Keyspace carries"
7157        );
7158    }
7159
7160    #[test]
7161    fn info_reports_the_numbers_it_can_stand_behind() {
7162        let mut f = Fixture::new();
7163        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
7164        let all = f.run(&[b"INFO"]);
7165        assert!(all.contains("redis_version:8.8.0"), "{all}");
7166        assert!(
7167            all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
7168            "{all}"
7169        );
7170        assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
7171        assert!(all.contains("role:master"), "{all}");
7172        // One section is one section.
7173        let clients = f.run(&[b"INFO", b"clients"]);
7174        assert!(clients.contains("connected_clients:0"), "{clients}");
7175        assert!(!clients.contains("redis_version"), "{clients}");
7176        assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
7177    }
7178
7179    /// The sections a bare `INFO` gives back, and the ones you have to ask for.
7180    ///
7181    /// This is Redis's `unit/info-command` written against the fixture. Every
7182    /// assertion in it is one of theirs, in their order, and the two fields it
7183    /// turns on are the two that suite was failing on: `master_repl_offset`,
7184    /// which is in the default set, and `rejected_calls`, which is not.
7185    #[test]
7186    fn commandstats_is_asked_for_and_replication_is_not() {
7187        let mut f = Fixture::new();
7188        for arg in ["", "all", "default", "everything"] {
7189            let info = if arg.is_empty() {
7190                f.run(&[b"INFO"])
7191            } else {
7192                f.run(&[b"INFO", arg.as_bytes()])
7193            };
7194            assert!(info.contains("redis_version"), "{arg}: {info}");
7195            assert!(info.contains("used_cpu_user"), "{arg}: {info}");
7196            assert!(info.contains("used_memory"), "{arg}: {info}");
7197            assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
7198            let asked = arg == "all" || arg == "everything";
7199            assert_eq!(
7200                info.contains("rejected_calls"),
7201                asked,
7202                "{arg} should{} carry the command counters: {info}",
7203                if asked { "" } else { " not" }
7204            );
7205        }
7206
7207        let cpu = f.run(&[b"INFO", b"cpu"]);
7208        assert!(cpu.contains("used_cpu_user"), "{cpu}");
7209        assert!(!cpu.contains("used_memory"), "{cpu}");
7210
7211        // Their case, to make the point that a section name is not case
7212        // sensitive any more than a command name is.
7213        let stats = f.run(&[b"INFO", b"commandSTATS"]);
7214        assert!(!stats.contains("used_memory"), "{stats}");
7215        assert!(stats.contains("rejected_calls"), "{stats}");
7216
7217        // Two sections named, and neither of them pulls in a third.
7218        let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
7219        assert!(pair.contains("used_cpu_user"), "{pair}");
7220        assert!(!pair.contains("master_repl_offset"), "{pair}");
7221
7222        let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
7223        assert!(with_all.contains("used_memory"), "{with_all}");
7224        assert!(with_all.contains("master_repl_offset"), "{with_all}");
7225        assert!(with_all.contains("rejected_calls"), "{with_all}");
7226        // A section named twice is still written once.
7227        assert_eq!(
7228            with_all.matches("used_cpu_user_children").count(),
7229            1,
7230            "{with_all}"
7231        );
7232
7233        let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
7234        assert!(with_default.contains("used_memory"), "{with_default}");
7235        assert!(
7236            with_default.contains("master_repl_offset"),
7237            "{with_default}"
7238        );
7239        assert!(!with_default.contains("rejected_calls"), "{with_default}");
7240        assert_eq!(
7241            with_default.matches("used_cpu_user_children").count(),
7242            1,
7243            "{with_default}"
7244        );
7245    }
7246
7247    /// The memory section says what this process may use, not what the machine
7248    /// has.
7249    ///
7250    /// The distinction is the whole point of it. A server inside a container
7251    /// that reports the host's memory is a server whose operator sizes it for
7252    /// memory it will be killed for touching, so all three numbers are there:
7253    /// what the machine has, what the cgroup allows, and the quarter of the
7254    /// tighter one that pools are sized from.
7255    #[test]
7256    fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
7257        let mut f = Fixture::new();
7258        let info = f.run(&[b"INFO", b"memory"]);
7259        for field in [
7260            "total_system_memory:",
7261            "mem_cgroup_limit:",
7262            "mem_limit:",
7263            "mem_budget:",
7264        ] {
7265            assert!(info.contains(field), "no {field} in {info}");
7266        }
7267
7268        let field = |name: &str| -> u64 {
7269            info.lines()
7270                .find_map(|l| l.strip_prefix(name))
7271                .unwrap_or_else(|| panic!("no {name} in {info}"))
7272                .trim()
7273                .parse()
7274                .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
7275        };
7276        let limit = field("mem_limit:");
7277        assert_eq!(field("mem_budget:"), limit / 4, "{info}");
7278        // Zero means there is no limit to report, which is a real answer on a
7279        // machine with no cgroups and no way to ask how big it is.
7280        if limit != 0 {
7281            let host = field("total_system_memory:");
7282            let cgroup = field("mem_cgroup_limit:");
7283            assert!(
7284                limit == host || limit == cgroup,
7285                "the limit came from neither number: {info}"
7286            );
7287        }
7288    }
7289
7290    /// The three counters, each on the path that raises it.
7291    ///
7292    /// `calls` on a command that worked, `failed_calls` on one that ran and
7293    /// answered with an error, and `rejected_calls` on one that never ran at
7294    /// all. The last two are the pair that is easy to collapse into one number
7295    /// and that Redis keeps apart, because a client sending the wrong number of
7296    /// arguments and a client asking for a list element that is not there are
7297    /// not the same problem.
7298    #[test]
7299    fn a_command_counts_what_it_did_separately_from_what_it_refused() {
7300        let mut f = Fixture::new();
7301        f.run(&[b"SET", b"k", b"v"]);
7302        f.run(&[b"SET", b"k", b"w"]);
7303        // Ran, and answered with an error, because `k` is not a list.
7304        f.run(&[b"LPUSH", b"k", b"x"]);
7305        // Never ran: `LPUSH` takes at least three arguments.
7306        f.run(&[b"LPUSH", b"k"]);
7307
7308        let stats = f.run(&[b"INFO", b"commandstats"]);
7309        assert!(
7310            stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
7311            "{stats}"
7312        );
7313        assert!(
7314            stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
7315            "{stats}"
7316        );
7317        assert!(
7318            !stats.contains("cmdstat_zadd"),
7319            "a command nobody has sent has no row: {stats}"
7320        );
7321    }
7322
7323    /// A cache that writes with a deadline and never reads back used to hold
7324    /// every key it had ever written, because lazy expiry needs somebody to walk
7325    /// past a key before it can reclaim it and nobody ever did.
7326    #[test]
7327    fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
7328        // Four thousand keys is four thousand trips through dispatch, and what
7329        // Miri charges for is trips rather than keys, so this was over five
7330        // minutes there. An eighth of each keeps everything the test is about,
7331        // which is three keys with a deadline for every one without and a
7332        // sweep that has to reclaim all of the first kind and none of the
7333        // second.
7334        let (dead, live) = if cfg!(miri) {
7335            (375, 125)
7336        } else {
7337            (3_000, 1_000)
7338        };
7339        let mut f = Fixture::new();
7340        for i in 0..dead {
7341            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7342        }
7343        for i in 0..live {
7344            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7345        }
7346        let all = format!(":{}\r\n", dead + live);
7347        assert_eq!(f.run(&[b"DBSIZE"]), all);
7348        f.advance(100);
7349        assert_eq!(
7350            f.run(&[b"DBSIZE"]),
7351            all,
7352            "DBSIZE counts records and nothing has read past the dead ones yet"
7353        );
7354
7355        // What the shard loop does, one slice at a time.
7356        let rest = format!(":{live}\r\n");
7357        let mut spent = 0;
7358        for _ in 0..2_000 {
7359            spent += f.server.expire_step(4096);
7360            if f.run(&[b"DBSIZE"]) == rest {
7361                break;
7362            }
7363        }
7364        assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
7365        assert!(
7366            f.run(&[b"INFO", b"stats"])
7367                .contains(&format!("expired_keys:{dead}"))
7368        );
7369        for i in 0..live {
7370            assert_eq!(
7371                f.run(&[b"GET", format!("k{i}").as_bytes()]),
7372                "$1\r\nv\r\n",
7373                "it took a key that had no deadline"
7374            );
7375        }
7376    }
7377
7378    #[test]
7379    fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
7380        // The keys are only here so that the database the sweep walks is not an
7381        // empty one. Two hundred of them fills as many slots as a sweep looks
7382        // at and is a tenth of the interpreted work.
7383        let n = if cfg!(miri) { 200 } else { 2_000 };
7384        let mut f = Fixture::new();
7385        for i in 0..n {
7386            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7387        }
7388        assert_eq!(f.server.expire_step(4096), 0);
7389        // And one database having them does not make the other fifteen pay.
7390        f.run(&[b"SELECT", b"3"]);
7391        f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
7392        f.advance(100);
7393        for _ in 0..64 {
7394            f.server.expire_step(4096);
7395        }
7396        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7397        f.run(&[b"SELECT", b"0"]);
7398        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
7399        assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
7400    }
7401
7402    /// The gate, which is what stops a maintenance slice that runs every hundred
7403    /// nanoseconds from drawing a sample every hundred nanoseconds.
7404    #[test]
7405    fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
7406        let mut f = Fixture::new();
7407        for i in 0..500u32 {
7408            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7409        }
7410        f.advance(100);
7411        let at = f.server.striped(0).now_ms();
7412        f.server.set_clock_ms(at);
7413        // A small budget, so that one slice cannot finish the job and a second
7414        // one having nothing to do would mean the gate and not an empty
7415        // database.
7416        assert!(f.server.expire_slice(8) > 0, "the first one works");
7417        for _ in 0..1_000 {
7418            assert_eq!(
7419                f.server.expire_slice(8),
7420                0,
7421                "the millisecond has not moved and neither should this"
7422            );
7423        }
7424        assert!(
7425            f.server.striped(0).expires() > 400,
7426            "there is plenty left to take"
7427        );
7428        f.server.set_clock_ms(at + 1);
7429        assert!(f.server.expire_slice(8) > 0, "and then it goes again");
7430    }
7431
7432    /// `expires=` used to be a hardcoded zero, which meant a dashboard watching
7433    /// how much of a cache is volatile was reading a constant.
7434    #[test]
7435    fn info_keyspace_counts_the_keys_that_have_a_deadline() {
7436        let mut f = Fixture::new();
7437        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
7438        assert!(
7439            f.run(&[b"INFO", b"keyspace"])
7440                .contains("db0:keys=3,expires=0"),
7441            "none of them has one yet"
7442        );
7443        f.run(&[b"EXPIRE", b"a", b"1000"]);
7444        f.run(&[b"EXPIRE", b"b", b"1000"]);
7445        let two = f.run(&[b"INFO", b"keyspace"]);
7446        assert!(two.contains("db0:keys=3,expires=2"), "{two}");
7447        f.run(&[b"PERSIST", b"a"]);
7448        f.run(&[b"DEL", b"b"]);
7449        let none = f.run(&[b"INFO", b"keyspace"]);
7450        assert!(none.contains("db0:keys=2,expires=0"), "{none}");
7451
7452        // Each database answers for itself, the way Redis reports it.
7453        f.run(&[b"SELECT", b"1"]);
7454        f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
7455        let both = f.run(&[b"INFO", b"keyspace"]);
7456        assert!(both.contains("db0:keys=2,expires=0"), "{both}");
7457        assert!(both.contains("db1:keys=1,expires=1"), "{both}");
7458    }
7459
7460    /// Not under Miri, which reads a zero on purpose because it has no
7461    /// `getrusage` to call, so the second half of this would burn a billion
7462    /// interpreted multiplications waiting for a number that is never going to
7463    /// move. The first half, that the section is there and has the fields Redis
7464    /// clients look for, is checked by the `INFO` tests above as well, and
7465    /// those do run there.
7466    #[cfg(unix)]
7467    #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
7468    #[test]
7469    fn info_cpu_reports_processor_time_that_was_really_measured() {
7470        let mut f = Fixture::new();
7471        let cpu = f.run(&[b"INFO", b"cpu"]);
7472        assert!(cpu.contains("# CPU"), "{cpu}");
7473        // Redis's unit/info-command asks for this one by name in three tests.
7474        assert!(cpu.contains("used_cpu_user:"), "{cpu}");
7475        assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
7476        assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
7477        assert!(!cpu.contains("redis_version"), "{cpu}");
7478
7479        // It is a measurement and not a constant, so it goes up when work
7480        // happens. A tight loop rather than a sleep, because sleeping is the
7481        // one thing that does not move this number.
7482        let before = used_cpu_user(&cpu);
7483        let mut n = 0u64;
7484        let mut rounds = 0;
7485        while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
7486            for i in 0..1_000_000u64 {
7487                n = n.wrapping_add(i.wrapping_mul(i));
7488            }
7489            rounds += 1;
7490            // A bound rather than a spin, so a platform where this number does
7491            // not move fails here instead of hanging. Even a clock with whole
7492            // millisecond granularity gets there in the first round or two.
7493            assert!(rounds < 1_000, "cpu time never moved, n is {n}");
7494        }
7495    }
7496
7497    /// Pull `used_cpu_user` back out of an `INFO cpu` reply.
7498    #[cfg(unix)]
7499    fn used_cpu_user(info: &str) -> f64 {
7500        info.lines()
7501            .find_map(|l| l.strip_prefix("used_cpu_user:"))
7502            .expect("no used_cpu_user in the reply")
7503            .trim()
7504            .parse()
7505            .expect("used_cpu_user is not a number")
7506    }
7507
7508    /// The safety net under the rule that a body checks its arguments before
7509    /// it writes anything. `MGET` writes its array header first and then reads
7510    /// each key, so if a later argument could fail the header would already be
7511    /// out. Nothing in the string group does that today and this is what would
7512    /// catch the first one that did.
7513    #[test]
7514    fn a_command_that_fails_leaves_nothing_half_written() {
7515        let mut f = Fixture::new();
7516        let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
7517        assert_eq!(reply, "-ERR offset is out of range\r\n");
7518        assert!(!reply.contains(':'), "no integer went out in front of it");
7519    }
7520
7521    #[test]
7522    fn quit_answers_first_and_closes_after() {
7523        let mut f = Fixture::new();
7524        let (flow, reply) = f.flow(&[b"QUIT"]);
7525        assert_eq!(reply, "+OK\r\n");
7526        assert_eq!(flow, Flow::Close);
7527    }
7528
7529    /// A server that has not been asked to stop is not stopping, and one that
7530    /// has says so without writing anything back.
7531    ///
7532    /// The empty reply is the point. Redis answers nothing at all here and the
7533    /// client sees the socket close, and an `OK` would be a promise from a
7534    /// process that is about to not exist.
7535    #[test]
7536    fn shutdown_writes_nothing_and_sets_the_flag() {
7537        let mut f = Fixture::new();
7538        assert!(!f.server.stopping(), "nobody has asked yet");
7539
7540        let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
7541        assert_eq!(reply, "");
7542        assert_eq!(flow, Flow::Close);
7543        assert!(f.server.stopping());
7544    }
7545
7546    /// Every flag combination 8.10.1 takes, and every one it refuses.
7547    ///
7548    /// The refusals are the half worth pinning down. `SAVE` and `NOSAVE`
7549    /// contradict each other, `ABORT` says to do nothing so it cannot be
7550    /// combined with a word about how to do it, and repeating any one of them
7551    /// is fine. All of it was read off a running 8.10.1 rather than worked out
7552    /// from the documentation, which does not say.
7553    #[test]
7554    fn shutdown_takes_the_flags_redis_takes() {
7555        for flags in [
7556            &[b"NOSAVE".as_slice()][..],
7557            &[b"SAVE"],
7558            &[b"NOW"],
7559            &[b"FORCE"],
7560            &[b"nosave"],
7561            &[b"NOW", b"NOW"],
7562            &[b"SAVE", b"SAVE"],
7563            &[b"NOSAVE", b"NOW", b"FORCE"],
7564        ] {
7565            let mut f = Fixture::new();
7566            let mut parts = vec![b"SHUTDOWN".as_slice()];
7567            parts.extend_from_slice(flags);
7568            let (flow, reply) = f.flow(&parts);
7569            assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
7570            assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
7571            assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
7572        }
7573
7574        for flags in [
7575            &[b"BOGUS".as_slice()][..],
7576            &[b"SAVE", b"NOSAVE"],
7577            &[b"NOSAVE", b"SAVE"],
7578            &[b"ABORT", b"NOW"],
7579            &[b"NOSAVE", b"ABORT"],
7580            &[b"NOW", b"FORCE", b"ABORT"],
7581        ] {
7582            let mut f = Fixture::new();
7583            let mut parts = vec![b"SHUTDOWN".as_slice()];
7584            parts.extend_from_slice(flags);
7585            assert_eq!(
7586                f.run(&parts),
7587                "-ERR syntax error\r\n",
7588                "SHUTDOWN {flags:?} was accepted"
7589            );
7590            assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
7591        }
7592    }
7593
7594    /// `ABORT` has nothing to call off, ever.
7595    ///
7596    /// A shutdown here is decided and done inside one turn of the loop, so
7597    /// there is no window in which one is in progress. That makes Redis's
7598    /// message for a cancel with nothing to cancel the right answer every time
7599    /// rather than only when nothing happens to be pending. Two `ABORT`s is
7600    /// still one `ABORT`, which is what 8.10.1 does.
7601    #[test]
7602    fn shutdown_abort_never_has_anything_to_abort() {
7603        let mut f = Fixture::new();
7604        for parts in [
7605            &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
7606            &[b"SHUTDOWN", b"ABORT", b"ABORT"],
7607        ] {
7608            assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
7609            assert!(!f.server.stopping(), "an abort stopped the server");
7610        }
7611    }
7612
7613    /// A fixture whose server writes into a directory of its own.
7614    ///
7615    /// Every test here really writes files, because the whole point of the
7616    /// command is the files and a backup that is only a state machine would
7617    /// pass a test suite and fail the first person who tried to restore one.
7618    /// The directory carries the test's name so that the suite can run its
7619    /// tests in parallel the way it always does.
7620    struct Backups {
7621        f: Fixture,
7622        dir: PathBuf,
7623    }
7624
7625    impl Backups {
7626        fn new(name: &str) -> Backups {
7627            let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
7628            let _ = std::fs::remove_dir_all(&dir);
7629            std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
7630            let mut f = Fixture::new();
7631            f.server.set_dir(dir.clone());
7632            Backups { f, dir }
7633        }
7634
7635        fn run(&mut self, parts: &[&[u8]]) -> String {
7636            self.f.run(parts)
7637        }
7638
7639        /// The names in `backupdir`, sorted, so a test can say what is on disk.
7640        fn files(&self) -> Vec<String> {
7641            let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
7642                Ok(entries) => entries
7643                    .filter_map(|e| e.ok())
7644                    .map(|e| e.file_name().to_string_lossy().into_owned())
7645                    .collect(),
7646                Err(_) => Vec::new(),
7647            };
7648            names.sort();
7649            names
7650        }
7651
7652        fn read(&self, name: &str) -> Vec<u8> {
7653            std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
7654        }
7655    }
7656
7657    impl Drop for Backups {
7658        fn drop(&mut self) {
7659            let _ = std::fs::remove_dir_all(&self.dir);
7660        }
7661    }
7662
7663    /// The four states and the moves between them, in the order a client walks
7664    /// them, with the files checked at every step.
7665    #[test]
7666    fn backup_walks_the_states_the_reference_walks() {
7667        let mut b = Backups::new("states");
7668        let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
7669
7670        assert!(status(&mut b).contains("idle"));
7671        assert!(b.files().is_empty(), "an idle server has written a backup");
7672
7673        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7674        assert!(status(&mut b).contains("incrementing"));
7675        assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
7676
7677        assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
7678        assert!(status(&mut b).contains("sealed"));
7679        assert_eq!(
7680            b.files(),
7681            [
7682                "appendonly.aof.1.base.rdb",
7683                "appendonly.aof.1.incr.aof",
7684                "appendonly.aof.manifest",
7685            ]
7686        );
7687
7688        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7689        assert!(status(&mut b).contains("idle"));
7690        assert!(b.files().is_empty(), "cleanup left something behind");
7691    }
7692
7693    /// Every move that is refused, in the reference's words.
7694    #[test]
7695    fn backup_refuses_the_moves_the_reference_refuses() {
7696        let mut b = Backups::new("refusals");
7697
7698        assert_eq!(
7699            b.run(&[b"BACKUP", b"SEAL"]),
7700            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7701        );
7702        assert_eq!(
7703            b.run(&[b"BACKUP", b"ABORT"]),
7704            "-ERR No backup in progress\r\n"
7705        );
7706        // Cleanup from idle is not an error, it is a way of saying there was
7707        // nothing to clean up.
7708        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7709
7710        b.run(&[b"BACKUP", b"START"]);
7711        assert_eq!(
7712            b.run(&[b"BACKUP", b"START"]),
7713            "-ERR A backup is already in progress, ABORT it first\r\n"
7714        );
7715        assert_eq!(
7716            b.run(&[b"BACKUP", b"CLEANUP"]),
7717            "-ERR Backup is in progress\r\n"
7718        );
7719
7720        b.run(&[b"BACKUP", b"SEAL"]);
7721        assert_eq!(
7722            b.run(&[b"BACKUP", b"START"]),
7723            "-ERR A sealed backup exists, CLEANUP it first\r\n"
7724        );
7725        assert_eq!(
7726            b.run(&[b"BACKUP", b"SEAL"]),
7727            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7728        );
7729        assert_eq!(
7730            b.run(&[b"BACKUP", b"ABORT"]),
7731            "-ERR No backup in progress\r\n"
7732        );
7733    }
7734
7735    /// An abort takes the base file away and leaves a state saying who did it.
7736    ///
7737    /// The next backup takes the next sequence number rather than reusing the
7738    /// one whose files were just thrown away, so a directory somebody copied a
7739    /// half finished backup out of cannot end up with two different files under
7740    /// one name.
7741    #[test]
7742    fn backup_abort_removes_the_file_and_says_who_did_it() {
7743        let mut b = Backups::new("abort");
7744        b.run(&[b"BACKUP", b"START"]);
7745        assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
7746
7747        let status = b.run(&[b"BACKUP", b"STATUS"]);
7748        assert!(status.contains("failed"), "{status}");
7749        assert!(status.contains("aborted by user"), "{status}");
7750        assert!(b.files().is_empty(), "abort left the base file behind");
7751        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7752
7753        // A start from failed works, and is the second backup.
7754        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7755        assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
7756        let status = b.run(&[b"BACKUP", b"STATUS"]);
7757        assert!(status.contains("incrementing"), "{status}");
7758        assert!(!status.contains("aborted"), "the old error was kept");
7759    }
7760
7761    /// `LIST` names nothing, then one file, then three, and they are absolute.
7762    #[test]
7763    fn backup_list_names_the_files_that_are_pinned_so_far() {
7764        let mut b = Backups::new("list");
7765        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7766
7767        b.run(&[b"BACKUP", b"START"]);
7768        let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
7769        let base = base.to_string_lossy().into_owned();
7770        assert_eq!(
7771            b.run(&[b"BACKUP", b"LIST"]),
7772            format!("*1\r\n${}\r\n{base}\r\n", base.len())
7773        );
7774
7775        b.run(&[b"BACKUP", b"SEAL"]);
7776        let listed = b.run(&[b"BACKUP", b"LIST"]);
7777        assert!(listed.starts_with("*3\r\n"), "{listed}");
7778        // The order is the manifest's order, base then incremental then the
7779        // manifest itself, which is the order a restore needs them in.
7780        let names: Vec<&str> = listed
7781            .lines()
7782            .filter(|l| l.starts_with('/') || l.contains(":\\"))
7783            .collect();
7784        assert_eq!(names.len(), 3, "{listed}");
7785        assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
7786        assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
7787        assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
7788    }
7789
7790    /// The base file is the dataset as it was at `START` and not at `SEAL`.
7791    ///
7792    /// That is D-46 and it is the one thing about this a client can notice, so
7793    /// it is pinned here rather than left to be discovered by whoever restores
7794    /// one. The incremental file is empty for the same reason: there is no
7795    /// append only log underneath this server to copy the writes in between out
7796    /// of.
7797    #[test]
7798    fn a_backup_holds_the_dataset_as_it_was_at_start() {
7799        let mut b = Backups::new("contents");
7800        b.run(&[b"SET", b"bk", b"v1"]);
7801        b.run(&[b"BACKUP", b"START"]);
7802        b.run(&[b"SET", b"bk", b"v2"]);
7803        b.run(&[b"BACKUP", b"SEAL"]);
7804
7805        let base = b.read("appendonly.aof.1.base.rdb");
7806        assert!(base.starts_with(b"REDIS"), "not an RDB file");
7807        assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
7808        assert!(
7809            !base.windows(2).any(|w| w == b"v2"),
7810            "the base file moved on after START"
7811        );
7812        // The aux field a loader acts on, and the one that says this file is
7813        // the base of an append only file rather than a standalone dump. Its
7814        // value is the one byte string 1, which the encoder writes as an
7815        // integer the way a real server writes it.
7816        let at = base
7817            .windows(8)
7818            .position(|w| w == b"aof-base")
7819            .expect("no aof-base aux field");
7820        assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
7821
7822        assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
7823        assert_eq!(
7824            String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
7825            "file appendonly.aof.1.base.rdb seq 1 type b\n\
7826             file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
7827        );
7828    }
7829
7830    /// `STATUS` is a map of four pairs on RESP3 and the same pairs flat on
7831    /// RESP2, which is what every other map shaped reply in this server does.
7832    #[test]
7833    fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
7834        let mut b = Backups::new("status");
7835        b.f.server.set_clock_ms(1_700_000_000_000);
7836
7837        assert_eq!(
7838            b.run(&[b"BACKUP", b"STATUS"]),
7839            "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
7840             $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
7841        );
7842
7843        b.f.out = Out::new(Proto::Resp3);
7844        b.run(&[b"BACKUP", b"START"]);
7845        assert_eq!(
7846            b.run(&[b"BACKUP", b"STATUS"]),
7847            "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
7848             $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
7849        );
7850
7851        b.run(&[b"BACKUP", b"SEAL"]);
7852        let sealed = b.run(&[b"BACKUP", b"STATUS"]);
7853        assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
7854    }
7855
7856    /// A sealed backup that nobody cleans up goes away on its own once
7857    /// `backup-sealed-ttl` seconds have passed since the seal.
7858    #[test]
7859    fn a_sealed_backup_is_swept_away_after_the_timeout() {
7860        let mut b = Backups::new("ttl");
7861        b.f.server.set_clock_ms(1_000_000);
7862        assert_eq!(
7863            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
7864            "+OK\r\n"
7865        );
7866        b.run(&[b"BACKUP", b"START"]);
7867        b.run(&[b"BACKUP", b"SEAL"]);
7868
7869        // A minute short of the deadline, nothing happens.
7870        b.f.server.set_clock_ms(1_000_000 + 59_000);
7871        b.f.server.backup_expire();
7872        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7873        assert_eq!(b.files().len(), 3);
7874
7875        b.f.server.set_clock_ms(1_000_000 + 60_000);
7876        b.f.server.backup_expire();
7877        let status = b.run(&[b"BACKUP", b"STATUS"]);
7878        assert!(status.contains("idle"), "{status}");
7879        assert!(b.files().is_empty(), "the timeout left the files behind");
7880
7881        // Zero is the default and means a sealed backup is kept for ever.
7882        b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
7883        b.run(&[b"BACKUP", b"START"]);
7884        b.run(&[b"BACKUP", b"SEAL"]);
7885        b.f.server.set_clock_ms(9_000_000_000);
7886        b.f.server.backup_expire();
7887        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7888    }
7889
7890    /// The three settings around the command, read and written the way 8.10.1
7891    /// reads and writes them.
7892    #[test]
7893    fn the_backup_settings_behave_the_way_the_reference_does() {
7894        let mut b = Backups::new("config");
7895        let dir = b.dir.to_string_lossy().into_owned();
7896
7897        assert_eq!(
7898            b.run(&[b"CONFIG", b"GET", b"dir"]),
7899            format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
7900        );
7901        assert_eq!(
7902            b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
7903            "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
7904        );
7905        assert_eq!(
7906            b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
7907            "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
7908        );
7909
7910        // `dir` is a protected config, so it is refused even for the value it
7911        // already holds, and `backupdirname` is immutable.
7912        assert_eq!(
7913            b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
7914            "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
7915        );
7916        assert_eq!(
7917            b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
7918            "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
7919        );
7920        assert!(
7921            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
7922                .contains("argument couldn't be parsed into an integer")
7923        );
7924        assert!(
7925            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
7926                .contains("argument must be between 0 and 9223372036854775807 inclusive")
7927        );
7928    }
7929
7930    /// The help text, which has `HELP` in it twice because the reference's does.
7931    #[test]
7932    fn backup_help_is_the_text_the_reference_sends() {
7933        let mut f = Fixture::new();
7934        let help = f.run(&[b"BACKUP", b"HELP"]);
7935        assert!(help.starts_with("*17\r\n"), "{help}");
7936        assert!(
7937            help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
7938        );
7939        assert!(help.contains("+    Start a new backup into the configured 'backupdirname'.\r\n"));
7940        assert!(help.contains("+    Freeze the current backup (BASE + INCR + manifest).\r\n"));
7941        assert!(help.contains("+    Return this help.\r\n+HELP\r\n+    Print this help.\r\n"));
7942    }
7943
7944    /// What a mistyped `BACKUP` gets told.
7945    ///
7946    /// The arity error names `backup` where the reference names `backup|start`,
7947    /// which is D-46: the table reports one arity for the container the way the
7948    /// reference does, and the per subcommand table that would carry the better
7949    /// name is not built yet. Every subcommand is exactly two words, so nothing
7950    /// legal is refused by it.
7951    #[test]
7952    fn backup_refuses_what_it_cannot_read() {
7953        let mut f = Fixture::new();
7954        assert_eq!(
7955            f.run(&[b"BACKUP"]),
7956            "-ERR wrong number of arguments for 'backup' command\r\n"
7957        );
7958        assert_eq!(
7959            f.run(&[b"BACKUP", b"START", b"x"]),
7960            "-ERR wrong number of arguments for 'backup' command\r\n"
7961        );
7962        assert_eq!(
7963            f.run(&[b"BACKUP", b"NOPE"]),
7964            "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
7965        );
7966    }
7967
7968    #[test]
7969    fn the_command_counter_counts_every_command_including_the_bad_ones() {
7970        let mut f = Fixture::new();
7971        f.run(&[b"PING"]);
7972        f.run(&[b"NOPE"]);
7973        f.run(&[b"GET"]);
7974        assert_eq!(f.server.totals().commands, 3);
7975    }
7976
7977    #[test]
7978    fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
7979        let mut server = Server::new();
7980        server.set_threads(2);
7981        // A fresh server has every database on the turn's list, so start from
7982        // nothing to see the one mark arrive.
7983        server.mine().turn.store(0, Relaxed);
7984        server.locals[1].mark(1 << 9);
7985        server.collect_marks();
7986        assert!(server.mine().wanted(9));
7987        // And taken once rather than left to be taken again next turn.
7988        assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
7989    }
7990
7991    #[test]
7992    fn what_two_threads_counted_is_added_up_when_info_asks() {
7993        let mut server = Server::new();
7994        server.set_threads(2);
7995        // Written into the two sets by hand, because what is under test is the
7996        // adding up and not the claiming, and one test thread can only ever
7997        // claim one set.
7998        let ping = lookup(b"PING").expect("PING is a command");
7999        for (at, calls) in [(0, 2), (1, 3)] {
8000            let counters = &server.locals[at];
8001            for _ in 0..calls {
8002                counters.stats.commands.bump();
8003                counters.cmdstats.at(ping).calls.bump();
8004            }
8005            counters.stats.opened();
8006        }
8007        assert_eq!(server.totals().commands, 5);
8008        assert_eq!(server.totals().clients, 2);
8009        assert_eq!(server.totals().connections, 2);
8010        let rows: Vec<_> = server.command_stats().collect();
8011        assert_eq!(rows.len(), 1);
8012        assert_eq!(rows[0].0, "ping");
8013        assert_eq!(rows[0].1.calls, 5);
8014        // A reset takes the totals and leaves the open connections, which are
8015        // still open.
8016        server.reset_stats();
8017        assert_eq!(server.totals().commands, 0);
8018        assert_eq!(server.totals().connections, 0);
8019        assert_eq!(server.totals().clients, 2);
8020    }
8021
8022    #[test]
8023    fn the_parked_count_says_what_the_waiter_list_says() {
8024        let mut f = Fixture::new();
8025        assert_eq!(f.server.parked(), 0);
8026        for client in 1..=3u64 {
8027            f.session = Session::new(client);
8028            assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
8029        }
8030        assert_eq!(f.server.parked(), 3);
8031        assert_eq!(f.server.waiters().len(), 3);
8032
8033        // The three ways the list gets shorter, each of which has to move the
8034        // number with it, because a number left behind is either a walk of the
8035        // list that never happens or one that runs off the end of it.
8036        f.server.forget_waiters(2);
8037        assert_eq!(f.server.parked(), f.server.waiters().len());
8038        f.server.forget_waiters(1);
8039        assert_eq!(f.server.parked(), f.server.waiters().len());
8040        f.run(&[b"RPUSH", b"q", b"v"]);
8041        let mut out = Out::new(Proto::Resp2);
8042        assert!(f.server.serve_waiter(3, 0, &mut out));
8043        f.server.forget_waiters(3);
8044        assert_eq!(f.server.parked(), 0);
8045        assert!(f.server.waiters().is_empty());
8046    }
8047
8048    #[test]
8049    fn a_set_goes_from_bytes_to_bytes() {
8050        let mut f = Fixture::new();
8051        assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
8052        assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
8053        assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
8054        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
8055        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
8056        assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
8057        assert_eq!(
8058            f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
8059            "*3\r\n:1\r\n:0\r\n:1\r\n"
8060        );
8061        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
8062        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8063    }
8064
8065    #[test]
8066    fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
8067        let mut f = Fixture::new();
8068        assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
8069        assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
8070        assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
8071        assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
8072        assert_eq!(
8073            f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
8074            "*2\r\n:0\r\n:0\r\n"
8075        );
8076        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
8077    }
8078
8079    #[test]
8080    fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
8081        // Not cosmetic. A RESP3 client that gets a `~` hands the caller a set
8082        // and one that gets a `*` hands it a list, without either of them being
8083        // told which command was sent.
8084        let mut f = Fixture::new();
8085        f.run(&[b"SADD", b"s", b"one"]);
8086        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
8087
8088        f.run(&[b"HELLO", b"3"]);
8089        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
8090    }
8091
8092    #[test]
8093    fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
8094        // An intset holds the number, so these digits exist for the first time
8095        // in the reply buffer.
8096        let mut f = Fixture::new();
8097        f.run(&[b"SADD", b"s", b"42"]);
8098        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
8099        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
8100        assert_eq!(
8101            f.run(&[b"SISMEMBER", b"s", b"042"]),
8102            ":0\r\n",
8103            "the member is the bytes and not the number they parse to"
8104        );
8105    }
8106
8107    #[test]
8108    fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
8109        let mut f = Fixture::new();
8110        f.run(&[b"SET", b"str", b"v"]);
8111        f.run(&[b"SADD", b"set", b"a"]);
8112
8113        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8114        assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
8115        assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
8116        assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
8117        assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
8118        assert_eq!(f.run(&[b"GET", b"set"]), wrong);
8119        assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
8120        assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
8121        assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
8122
8123        // MGET is the one that does not, because Redis gives nil for the odd
8124        // key out rather than failing the good keys next to it.
8125        assert_eq!(
8126            f.run(&[b"MGET", b"str", b"set", b"nope"]),
8127            "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
8128        );
8129        // And plain SET overwrites any type, which takes the body with it.
8130        assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
8131        assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
8132    }
8133
8134    #[test]
8135    fn a_wrongtype_leaves_nothing_half_written() {
8136        // SMISMEMBER writes an array header and then one reply per member, so
8137        // it is the first command in the server that could get a header out in
8138        // front of an error if it checked its key in the wrong order.
8139        let mut f = Fixture::new();
8140        f.run(&[b"SET", b"k", b"v"]);
8141        let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
8142        assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
8143        assert!(!reply.contains('*'), "an array header went out in front");
8144    }
8145
8146    #[test]
8147    fn emptying_a_set_takes_the_key_with_it() {
8148        let mut f = Fixture::new();
8149        f.run(&[b"SADD", b"s", b"a", b"b"]);
8150        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
8151        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
8152        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8153        assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
8154        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8155    }
8156
8157    /// Pull the cursor and the members out of one `SSCAN` reply.
8158    ///
8159    /// Crude on purpose. A test that walked a set through a real client would
8160    /// be testing the client, and what these tests are about is the shape of
8161    /// the bytes and the fact that a walk sees every member once.
8162    fn split_scan(reply: &str) -> (String, Vec<String>) {
8163        let mut lines = reply.split("\r\n");
8164        assert_eq!(lines.next(), Some("*2"), "got {reply}");
8165        lines.next().expect("the cursor header");
8166        let cursor = lines.next().expect("the cursor").to_owned();
8167        let header = lines.next().expect("the member header");
8168        let n: usize = header[1..].parse().expect("a member count");
8169        let mut members = Vec::with_capacity(n);
8170        for _ in 0..n {
8171            lines.next().expect("a member header");
8172            members.push(lines.next().expect("a member").to_owned());
8173        }
8174        (cursor, members)
8175    }
8176
8177    #[test]
8178    fn popping_takes_a_member_off_the_set_and_hands_it_back() {
8179        let mut f = Fixture::new();
8180        f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
8181
8182        let one = f.run(&[b"SPOP", b"s"]);
8183        assert!(
8184            ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
8185            "got {one}"
8186        );
8187        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8188
8189        // A count takes that many, and the last one takes the key with it.
8190        let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
8191        assert!(rest.starts_with("*3\r\n"), "got {rest}");
8192        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8193        // And a pop at a key that is not there is a nil, not an empty bulk.
8194        assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
8195        assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
8196    }
8197
8198    #[test]
8199    fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
8200        // The one place in the server where the reply type carries something
8201        // the command name does not. SPOP's members are distinct so a RESP3
8202        // client can build a set out of them. SRANDMEMBER with a negative count
8203        // can hand back the same member three times, and a set would lose two.
8204        let mut f = Fixture::new();
8205        f.run(&[b"HELLO", b"3"]);
8206        f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
8207
8208        assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
8209        // And a positive count is an array too, since Redis makes it one.
8210        assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
8211
8212        // A negative count against a set of one is where the difference bites:
8213        // the same member three times, which is a three element reply and would
8214        // have been a one element reply if it had gone out as a set.
8215        f.run(&[b"SADD", b"one", b"z"]);
8216        assert_eq!(
8217            f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
8218            "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
8219        );
8220    }
8221
8222    #[test]
8223    fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
8224        let mut f = Fixture::new();
8225        f.run(&[b"SADD", b"s", b"only"]);
8226        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8227        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8228        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
8229
8230        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
8231        // The count form answers an empty array rather than a nil, which is the
8232        // pair of answers Redis gives and is not the pair it looks like.
8233        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
8234        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
8235        // Asking for more than is there answers all of it once and not padding.
8236        assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
8237    }
8238
8239    #[test]
8240    fn a_pop_count_that_is_not_a_positive_number_says_so() {
8241        let mut f = Fixture::new();
8242        f.run(&[b"SADD", b"s", b"a"]);
8243        let bad = "-ERR value is out of range, must be positive\r\n";
8244        assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
8245        assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
8246        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
8247        // Zero is allowed and is a real answer rather than an error.
8248        assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
8249        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
8250    }
8251
8252    #[test]
8253    fn a_scan_walks_a_set_of_any_size_exactly_once() {
8254        let mut f = Fixture::new();
8255        let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
8256        let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
8257            .into_iter()
8258            .chain(members.iter().map(Vec::as_slice))
8259            .collect();
8260        f.run(&args);
8261
8262        let mut seen = Vec::new();
8263        let mut cursor = "0".to_owned();
8264        loop {
8265            let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
8266            let (next, got) = split_scan(&reply);
8267            seen.extend(got);
8268            cursor = next;
8269            if cursor == "0" {
8270                break;
8271            }
8272        }
8273        seen.sort();
8274        seen.dedup();
8275        assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
8276
8277        // A set small enough to be a listpack answers in one call whatever
8278        // cursor it was handed, which is what Redis does for that encoding.
8279        f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
8280        let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
8281        assert_eq!(cursor, "0");
8282        assert_eq!(got.len(), 3);
8283        // And a key that is not there is a finished scan of nothing.
8284        assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
8285    }
8286
8287    #[test]
8288    fn a_scan_takes_match_and_count_and_refuses_anything_else() {
8289        let mut f = Fixture::new();
8290        f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
8291
8292        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
8293        let mut got = got;
8294        got.sort();
8295        assert_eq!(got, ["aa", "ab"]);
8296
8297        // An integer member has no digits stored anywhere, so MATCH is the one
8298        // place a scan pays to write some.
8299        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
8300        let mut got = got;
8301        got.sort();
8302        assert_eq!(got, ["12", "13"]);
8303
8304        assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
8305        assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
8306        assert_eq!(
8307            f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
8308            "-ERR syntax error\r\n"
8309        );
8310        // A count under one is a syntax error and not a range error, which is
8311        // the odder of Redis's two answers and the reason it is copied exactly.
8312        assert_eq!(
8313            f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
8314            "-ERR syntax error\r\n"
8315        );
8316    }
8317
8318    #[test]
8319    fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
8320        let mut f = Fixture::new();
8321        f.run(&[b"SADD", b"src", b"a", b"b"]);
8322        f.run(&[b"SADD", b"dst", b"c"]);
8323
8324        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
8325        assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
8326        assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
8327        // A member that is not in the source is a zero and moves nothing.
8328        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
8329        assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
8330
8331        // A destination that does not exist gets made, and a source that runs
8332        // out goes away.
8333        assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
8334        assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
8335        assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
8336    }
8337
8338    #[test]
8339    fn moving_checks_the_types_in_the_order_redis_checks_them() {
8340        // Not the order it looks like it should be. A source that is not there
8341        // answers zero without ever looking at the destination, so this is a
8342        // zero and not a WRONGTYPE even though the destination is a string.
8343        let mut f = Fixture::new();
8344        f.run(&[b"SET", b"str", b"v"]);
8345        f.run(&[b"SADD", b"set", b"a"]);
8346
8347        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8348        assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
8349        assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
8350        assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
8351        assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
8352        assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
8353        assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
8354        assert_eq!(
8355            f.run(&[b"SISMEMBER", b"set", b"a"]),
8356            ":1\r\n",
8357            "and none of that moved anything"
8358        );
8359    }
8360
8361    #[test]
8362    fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8363        // SSCAN writes an outer array header before it walks, so it is the
8364        // command most likely to get bytes out in front of an error.
8365        let mut f = Fixture::new();
8366        f.run(&[b"SADD", b"s", b"a"]);
8367        for bad in [
8368            &[b"SSCAN".as_slice(), b"s", b"abc"][..],
8369            &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
8370            &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
8371        ] {
8372            let reply = f.run(bad);
8373            assert!(reply.starts_with("-ERR"), "got {reply}");
8374            assert!(!reply.contains('*'), "an array header went out in front");
8375        }
8376    }
8377
8378    #[test]
8379    fn a_hash_writes_reads_and_deletes_its_fields() {
8380        let mut f = Fixture::new();
8381        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
8382        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
8383        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8384        assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
8385        assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
8386        assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
8387        assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
8388        assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
8389        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
8390        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
8391
8392        // The value the client sent is `9`, so HGET h b must not find the `2`
8393        // that is a value. A search with a step of one would have.
8394        assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
8395
8396        assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
8397        assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
8398        assert_eq!(
8399            f.run(&[b"EXISTS", b"h"]),
8400            ":0\r\n",
8401            "and losing the last field lost the key"
8402        );
8403    }
8404
8405    #[test]
8406    fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
8407        let mut f = Fixture::new();
8408        f.run(&[b"HSET", b"h", b"a", b"1"]);
8409        assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
8410        assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
8411        assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
8412        assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
8413        assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
8414
8415        f.run(&[b"HELLO", b"3"]);
8416        assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
8417        assert_eq!(
8418            f.run(&[b"HGETALL", b"nokey"]),
8419            "%0\r\n",
8420            "a missing key is the empty hash and never a nil"
8421        );
8422        assert_eq!(
8423            f.run(&[b"HKEYS", b"h"]),
8424            "*1\r\n$1\r\na\r\n",
8425            "and the two that answer one side stay arrays"
8426        );
8427    }
8428
8429    #[test]
8430    fn hmget_answers_once_per_field_and_hmset_answers_ok() {
8431        let mut f = Fixture::new();
8432        assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
8433        assert_eq!(
8434            f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
8435            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
8436            "the reply is positional, so b is a nil and not a gap"
8437        );
8438        assert_eq!(
8439            f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
8440            "*2\r\n$-1\r\n$-1\r\n",
8441            "and a missing key is all nils rather than an empty array"
8442        );
8443
8444        assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
8445        assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
8446        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8447    }
8448
8449    #[test]
8450    fn a_hash_counts_up_and_says_so_when_it_cannot() {
8451        let mut f = Fixture::new();
8452        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
8453        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
8454        assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
8455        assert_eq!(
8456            f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
8457            "$4\r\n10.5\r\n",
8458            "a bulk string and not a double, on both protocols"
8459        );
8460
8461        f.run(&[b"HSET", b"h", b"s", b"words"]);
8462        let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
8463        assert!(
8464            bad.starts_with("-ERR hash value is not an integer"),
8465            "{bad}"
8466        );
8467        let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
8468        assert!(
8469            bad.starts_with("-ERR value is not an integer"),
8470            "a bad argument is not yet a hash value, {bad}"
8471        );
8472        assert_eq!(
8473            f.run(&[b"HGET", b"h", b"s"]),
8474            "$5\r\nwords\r\n",
8475            "and neither of them wrote anything"
8476        );
8477    }
8478
8479    #[test]
8480    fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
8481        // Fourteen minutes under Miri at five hundred, which was the slowest
8482        // test in this crate that was not about megabytes. What the count has
8483        // to be is more than one page of the cursor, and the count below is
8484        // thirty two, so ninety six is three pages and asks the same question.
8485        let fields = if cfg!(miri) { 96 } else { 500 };
8486        let mut f = Fixture::new();
8487        for i in 0..fields {
8488            let field = format!("field-{i}");
8489            let value = format!("value-{i}");
8490            f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
8491        }
8492
8493        let mut seen: Vec<String> = Vec::new();
8494        let mut cursor = "0".to_owned();
8495        loop {
8496            let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
8497            let (next, items) = scan_reply(&reply);
8498            assert_eq!(items.len() % 2, 0, "a pair went out half written");
8499            for pair in items.chunks(2) {
8500                assert_eq!(
8501                    pair[0].strip_prefix("field-"),
8502                    pair[1].strip_prefix("value-"),
8503                    "a field came back with someone else's value"
8504                );
8505                seen.push(pair[0].clone());
8506            }
8507            cursor = next;
8508            if cursor == "0" {
8509                break;
8510            }
8511        }
8512        seen.sort();
8513        seen.dedup();
8514        assert_eq!(seen.len(), fields, "every field once and only once");
8515
8516        let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
8517        assert!(
8518            items.iter().all(|s| s.starts_with("field-")),
8519            "NOVALUES still sent the values"
8520        );
8521
8522        let last = fields - 1;
8523        let (_, one) = scan_reply(&f.run(&[
8524            b"HSCAN",
8525            b"h",
8526            b"0",
8527            b"MATCH",
8528            format!("field-{last}").as_bytes(),
8529            b"COUNT",
8530            b"1000",
8531        ]));
8532        assert_eq!(
8533            one,
8534            [format!("field-{last}"), format!("value-{last}")],
8535            "MATCH is on the field"
8536        );
8537    }
8538
8539    #[test]
8540    fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
8541        let mut f = Fixture::new();
8542        f.run(&[b"HSET", b"h", b"a", b"1"]);
8543        assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
8544        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
8545        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
8546        assert_eq!(
8547            f.run(&[b"HRANDFIELD", b"h", b"3"]),
8548            "*1\r\n$1\r\na\r\n",
8549            "a positive count is capped at the size of the hash"
8550        );
8551        assert_eq!(
8552            f.run(&[b"HRANDFIELD", b"h", b"-3"]),
8553            "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
8554            "and a negative one repeats itself"
8555        );
8556        assert_eq!(
8557            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8558            "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8559            "flat on RESP2"
8560        );
8561
8562        f.run(&[b"HELLO", b"3"]);
8563        assert_eq!(
8564            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8565            "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8566            "and nested on RESP3, but still an array and never a map"
8567        );
8568    }
8569
8570    #[test]
8571    fn every_hash_command_says_wrongtype_and_writes_nothing() {
8572        let mut f = Fixture::new();
8573        f.run(&[b"SET", b"str", b"v"]);
8574        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8575
8576        for cmd in [
8577            &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
8578            &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
8579            &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
8580            &[b"HGET".as_slice(), b"str", b"f"][..],
8581            &[b"HMGET".as_slice(), b"str", b"f"][..],
8582            &[b"HDEL".as_slice(), b"str", b"f"][..],
8583            &[b"HLEN".as_slice(), b"str"][..],
8584            &[b"HEXISTS".as_slice(), b"str", b"f"][..],
8585            &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
8586            &[b"HGETALL".as_slice(), b"str"][..],
8587            &[b"HKEYS".as_slice(), b"str"][..],
8588            &[b"HVALS".as_slice(), b"str"][..],
8589            &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
8590            &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
8591            &[b"HRANDFIELD".as_slice(), b"str"][..],
8592            &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
8593            &[b"HSCAN".as_slice(), b"str", b"0"][..],
8594        ] {
8595            let reply = f.run(cmd);
8596            assert_eq!(reply, wrong, "{:?}", cmd[0]);
8597        }
8598        assert_eq!(
8599            f.run(&[b"GET", b"str"]),
8600            "$1\r\nv\r\n",
8601            "and none of them touched the value"
8602        );
8603    }
8604
8605    #[test]
8606    fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8607        let mut f = Fixture::new();
8608        f.run(&[b"HSET", b"h", b"f", b"v"]);
8609        for bad in [
8610            &[b"HSCAN".as_slice(), b"h", b"abc"][..],
8611            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
8612            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
8613            &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
8614        ] {
8615            let reply = f.run(bad);
8616            assert!(reply.starts_with("-ERR"), "got {reply}");
8617            assert!(!reply.contains('*'), "an array header went out in front");
8618        }
8619    }
8620
8621    #[test]
8622    fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
8623        let mut f = Fixture::new();
8624        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8625        assert_eq!(
8626            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
8627            "*1\r\n:1\r\n"
8628        );
8629        assert_eq!(
8630            f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8631            "*3\r\n:100\r\n:-1\r\n:-2\r\n",
8632            "one answer per field, and the two sentinels are TTL's own"
8633        );
8634
8635        // The same deadline in the other three units, all of them derived from
8636        // the one number the store kept.
8637        let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
8638        assert!((99_000..=100_000).contains(&ms), "got {ms}");
8639        let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8640        let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8641        assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
8642        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
8643
8644        assert_eq!(
8645            f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8646            "*3\r\n:1\r\n:-1\r\n:-2\r\n",
8647            "one for the deadline taken off, and it does not say what it was"
8648        );
8649        assert_eq!(
8650            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8651            "*1\r\n:-1\r\n"
8652        );
8653        assert_eq!(
8654            f.run(&[b"HGET", b"h", b"a"]),
8655            "$1\r\n1\r\n",
8656            "and the field is still there with the value it had"
8657        );
8658    }
8659
8660    #[test]
8661    fn a_deadline_that_has_already_gone_deletes_the_field_now() {
8662        let mut f = Fixture::new();
8663        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8664        assert_eq!(
8665            f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
8666            "*1\r\n:2\r\n",
8667            "two, and not one, because nothing was stored"
8668        );
8669        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8670        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8671
8672        assert_eq!(
8673            f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
8674            "*1\r\n:2\r\n"
8675        );
8676        assert_eq!(
8677            f.run(&[b"EXISTS", b"h"]),
8678            ":0\r\n",
8679            "and the last field going took the key with it"
8680        );
8681
8682        // Zero is a delete and not an error, where minus one is an error. That
8683        // is Redis's split and it is easy to get backwards.
8684        f.run(&[b"HSET", b"h", b"a", b"1"]);
8685        assert_eq!(
8686            f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
8687            "*1\r\n:2\r\n"
8688        );
8689    }
8690
8691    #[test]
8692    fn a_field_is_gone_once_its_moment_passes() {
8693        let mut f = Fixture::new();
8694        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8695        assert_eq!(
8696            f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
8697            "*1\r\n:1\r\n"
8698        );
8699        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
8700
8701        // Time moves once per turn of the event loop and nowhere else, so a
8702        // test moves it by hand rather than by sleeping. There is nothing to
8703        // sleep for: the deadline is a number and so is the clock.
8704        f.server.advance_clock_ms(60);
8705        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8706        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8707        assert_eq!(
8708            f.run(&[b"HGETALL", b"h"]),
8709            "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
8710            "and the walks do not hand back a field that has expired"
8711        );
8712    }
8713
8714    #[test]
8715    fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
8716        let mut f = Fixture::new();
8717        for cmd in [
8718            &[
8719                b"HEXPIRE".as_slice(),
8720                b"nokey",
8721                b"100",
8722                b"FIELDS",
8723                b"2",
8724                b"a",
8725                b"b",
8726            ][..],
8727            &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8728            &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8729            &[
8730                b"HEXPIRETIME".as_slice(),
8731                b"nokey",
8732                b"FIELDS",
8733                b"2",
8734                b"a",
8735                b"b",
8736            ][..],
8737            &[
8738                b"HPERSIST".as_slice(),
8739                b"nokey",
8740                b"FIELDS",
8741                b"2",
8742                b"a",
8743                b"b",
8744            ][..],
8745        ] {
8746            assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
8747        }
8748    }
8749
8750    #[test]
8751    fn writing_a_field_clears_the_deadline_that_was_on_it() {
8752        let mut f = Fixture::new();
8753        f.run(&[b"HSET", b"h", b"a", b"1"]);
8754        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
8755        f.run(&[b"HSET", b"h", b"a", b"2"]);
8756        assert_eq!(
8757            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8758            "*1\r\n:-1\r\n",
8759            "Redis has done this since 7.4, and it is why HGETEX exists"
8760        );
8761    }
8762
8763    #[test]
8764    fn the_four_conditions_reach_the_store_the_way_they_were_written() {
8765        let mut f = Fixture::new();
8766        f.run(&[b"HSET", b"h", b"a", b"1"]);
8767        assert_eq!(
8768            f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
8769            "*1\r\n:0\r\n",
8770            "XX on a field with no deadline changes nothing"
8771        );
8772        assert_eq!(
8773            f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
8774            "*1\r\n:1\r\n"
8775        );
8776        assert_eq!(
8777            f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
8778            "*1\r\n:0\r\n",
8779            "and NX will not move one that is already there"
8780        );
8781        assert_eq!(
8782            f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
8783            "*1\r\n:0\r\n"
8784        );
8785        assert_eq!(
8786            f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
8787            "*1\r\n:1\r\n"
8788        );
8789        assert_eq!(
8790            f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
8791            "*1\r\n:1\r\n"
8792        );
8793        assert_eq!(
8794            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8795            "*1\r\n:50\r\n"
8796        );
8797    }
8798
8799    #[test]
8800    fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
8801        let mut f = Fixture::new();
8802        f.run(&[b"HSET", b"h", b"a", b"1"]);
8803        for (bad, want) in [
8804            (
8805                &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
8806                "-ERR invalid expire time, must be >= 0",
8807            ),
8808            (
8809                &[
8810                    b"HEXPIRE".as_slice(),
8811                    b"h",
8812                    b"9999999999999999",
8813                    b"FIELDS",
8814                    b"1",
8815                    b"a",
8816                ][..],
8817                "-ERR invalid expire time in 'hexpire' command",
8818            ),
8819            (
8820                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
8821                "-ERR wrong number of arguments for 'hexpire' command",
8822            ),
8823            (
8824                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
8825                "-ERR Parameter `numFields` should be greater than 0",
8826            ),
8827            (
8828                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
8829                "-ERR wrong number of arguments",
8830            ),
8831            (
8832                &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
8833                "-ERR wrong number of arguments",
8834            ),
8835        ] {
8836            let reply = f.run(bad);
8837            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
8838            assert!(!reply.contains('*'), "an array header went out in front");
8839        }
8840        assert_eq!(
8841            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8842            "*1\r\n:-1\r\n",
8843            "and not one of them put a deadline on anything"
8844        );
8845    }
8846
8847    #[test]
8848    fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
8849        let mut f = Fixture::new();
8850        f.run(&[b"SET", b"str", b"v"]);
8851        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8852
8853        for cmd in [
8854            &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
8855            &[
8856                b"HPEXPIRE".as_slice(),
8857                b"str",
8858                b"100",
8859                b"FIELDS",
8860                b"1",
8861                b"f",
8862            ][..],
8863            &[
8864                b"HEXPIREAT".as_slice(),
8865                b"str",
8866                b"9999999999",
8867                b"FIELDS",
8868                b"1",
8869                b"f",
8870            ][..],
8871            &[
8872                b"HPEXPIREAT".as_slice(),
8873                b"str",
8874                b"9999999999999",
8875                b"FIELDS",
8876                b"1",
8877                b"f",
8878            ][..],
8879            &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8880            &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8881            &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8882            &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8883            &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8884        ] {
8885            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8886        }
8887        assert_eq!(
8888            f.run(&[b"GET", b"str"]),
8889            "$1\r\nv\r\n",
8890            "and none of them touched the value"
8891        );
8892    }
8893
8894    #[test]
8895    fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
8896        let mut f = Fixture::new();
8897        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8898        assert_eq!(
8899            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
8900            "*2\r\n$1\r\n1\r\n$-1\r\n",
8901            "positional, so the field that was not there is a nil in its place"
8902        );
8903        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8904        assert_eq!(
8905            f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
8906            "*1\r\n$-1\r\n"
8907        );
8908        assert_eq!(
8909            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
8910            "*1\r\n$1\r\n2\r\n"
8911        );
8912        assert_eq!(
8913            f.run(&[b"EXISTS", b"h"]),
8914            ":0\r\n",
8915            "and the last field took the key"
8916        );
8917    }
8918
8919    #[test]
8920    fn hgetex_reads_and_moves_the_deadline_in_one_command() {
8921        let mut f = Fixture::new();
8922        f.run(&[b"HSET", b"h", b"a", b"1"]);
8923        assert_eq!(
8924            f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
8925            "*1\r\n$1\r\n1\r\n"
8926        );
8927        assert_eq!(
8928            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8929            "*1\r\n:-1\r\n",
8930            "no option means leave it alone, which is the one place this is not GETEX"
8931        );
8932
8933        f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
8934        assert_eq!(
8935            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8936            "*1\r\n:100\r\n"
8937        );
8938        f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
8939        assert_eq!(
8940            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8941            "*1\r\n:100\r\n",
8942            "and a plain read really does leave it alone"
8943        );
8944        assert_eq!(
8945            f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
8946            "*1\r\n$1\r\n1\r\n"
8947        );
8948        assert_eq!(
8949            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8950            "*1\r\n:-1\r\n"
8951        );
8952
8953        assert_eq!(
8954            f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
8955            "*1\r\n$1\r\n1\r\n",
8956            "the value goes out before the deadline that has already gone is applied"
8957        );
8958        assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
8959        assert_eq!(
8960            f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
8961            "*1\r\n$-1\r\n"
8962        );
8963    }
8964
8965    #[test]
8966    fn hsetex_writes_all_of_it_or_none_of_it() {
8967        let mut f = Fixture::new();
8968        assert_eq!(
8969            f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
8970            ":1\r\n"
8971        );
8972        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8973        assert_eq!(
8974            f.run(&[
8975                b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
8976            ]),
8977            ":0\r\n",
8978            "FNX wants every field named to be missing"
8979        );
8980        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8981        assert_eq!(
8982            f.run(&[b"HEXISTS", b"h", b"new"]),
8983            ":0\r\n",
8984            "and none of the list was written"
8985        );
8986        assert_eq!(
8987            f.run(&[
8988                b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
8989            ]),
8990            ":0\r\n",
8991            "and FXX wants every one of them to be there"
8992        );
8993        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8994        assert_eq!(
8995            f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
8996            ":1\r\n"
8997        );
8998        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8999
9000        assert_eq!(
9001            f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
9002            ":0\r\n"
9003        );
9004        assert_eq!(
9005            f.run(&[b"EXISTS", b"gone"]),
9006            ":0\r\n",
9007            "a key with no fields cannot meet FXX and is not created trying"
9008        );
9009    }
9010
9011    #[test]
9012    fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
9013        let mut f = Fixture::new();
9014        f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
9015        assert_eq!(
9016            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9017            "*1\r\n:100\r\n"
9018        );
9019
9020        f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
9021        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
9022        assert_eq!(
9023            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9024            "*1\r\n:100\r\n",
9025            "KEEPTTL put back what the write cleared"
9026        );
9027
9028        f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
9029        assert_eq!(
9030            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9031            "*1\r\n:-1\r\n",
9032            "and without it a write clears the deadline the way HSET does"
9033        );
9034
9035        // Any order, because Redis reads these in a loop and not in a fixed
9036        // sequence.
9037        assert_eq!(
9038            f.run(&[
9039                b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
9040            ]),
9041            ":1\r\n"
9042        );
9043        assert_eq!(
9044            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9045            "*1\r\n:100\r\n"
9046        );
9047
9048        assert_eq!(
9049            f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
9050            ":1\r\n",
9051            "written, and not the separate code the HEXPIRE family has for this"
9052        );
9053        assert_eq!(
9054            f.run(&[b"EXISTS", b"h"]),
9055            ":0\r\n",
9056            "and storing it and then removing it emptied the hash"
9057        );
9058    }
9059
9060    #[test]
9061    fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
9062        let mut f = Fixture::new();
9063        f.run(&[b"HSET", b"h", b"a", b"1"]);
9064        for (bad, want) in [
9065            // HGETDEL has three sentences of its own for these three mistakes.
9066            (
9067                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9068                "-ERR Number of fields must be a positive integer",
9069            ),
9070            (
9071                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9072                "-ERR The `numfields` parameter must match the number of arguments",
9073            ),
9074            (
9075                &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9076                "-ERR Mandatory argument FIELDS is missing or not at the right position",
9077            ),
9078            // And HGETEX and HSETEX have three different ones between them.
9079            (
9080                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9081                "-ERR invalid number of fields",
9082            ),
9083            (
9084                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9085                "-ERR wrong number of arguments",
9086            ),
9087            (
9088                &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9089                "-ERR unknown argument: FIELD",
9090            ),
9091            (
9092                &[
9093                    b"HGETEX".as_slice(),
9094                    b"h",
9095                    b"KEEPTTL",
9096                    b"FIELDS",
9097                    b"1",
9098                    b"a",
9099                ][..],
9100                "-ERR unknown argument: KEEPTTL",
9101            ),
9102            (
9103                &[
9104                    b"HGETEX".as_slice(),
9105                    b"h",
9106                    b"EX",
9107                    b"100",
9108                    b"PERSIST",
9109                    b"FIELDS",
9110                    b"1",
9111                    b"a",
9112                ][..],
9113                "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
9114            ),
9115            (
9116                &[
9117                    b"HSETEX".as_slice(),
9118                    b"h",
9119                    b"EX",
9120                    b"1",
9121                    b"KEEPTTL",
9122                    b"FIELDS",
9123                    b"1",
9124                    b"a",
9125                    b"1",
9126                ][..],
9127                "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
9128            ),
9129            (
9130                &[
9131                    b"HSETEX".as_slice(),
9132                    b"h",
9133                    b"FNX",
9134                    b"FXX",
9135                    b"FIELDS",
9136                    b"1",
9137                    b"a",
9138                    b"1",
9139                ][..],
9140                "-ERR Only one of FXX or FNX arguments can be specified",
9141            ),
9142            (
9143                &[
9144                    b"HSETEX".as_slice(),
9145                    b"h",
9146                    b"FIELDS",
9147                    b"2",
9148                    b"a",
9149                    b"1",
9150                    b"b",
9151                ][..],
9152                "-ERR wrong number of arguments",
9153            ),
9154            (
9155                &[
9156                    b"HGETEX".as_slice(),
9157                    b"h",
9158                    b"EX",
9159                    b"-1",
9160                    b"FIELDS",
9161                    b"1",
9162                    b"a",
9163                ][..],
9164                "-ERR invalid expire time, must be >= 0",
9165            ),
9166            (
9167                &[
9168                    b"HGETEX".as_slice(),
9169                    b"h",
9170                    b"PXAT",
9171                    b"99999999999999",
9172                    b"FIELDS",
9173                    b"1",
9174                    b"a",
9175                ][..],
9176                "-ERR invalid expire time in 'hgetex' command",
9177            ),
9178            (
9179                &[
9180                    b"HSETEX".as_slice(),
9181                    b"h",
9182                    b"EX",
9183                    b"abc",
9184                    b"FIELDS",
9185                    b"1",
9186                    b"a",
9187                    b"1",
9188                ][..],
9189                "-ERR value is not an integer or out of range",
9190            ),
9191        ] {
9192            let reply = f.run(bad);
9193            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9194            assert!(!reply.contains('*'), "an array header went out in front");
9195        }
9196        assert_eq!(
9197            f.run(&[b"HGET", b"h", b"a"]),
9198            "$1\r\n1\r\n",
9199            "and not one of them wrote anything"
9200        );
9201        assert_eq!(
9202            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9203            "*1\r\n:-1\r\n"
9204        );
9205    }
9206
9207    #[test]
9208    fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
9209        let mut f = Fixture::new();
9210        f.run(&[b"SET", b"str", b"v"]);
9211        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9212        for cmd in [
9213            &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9214            &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9215            &[
9216                b"HGETEX".as_slice(),
9217                b"str",
9218                b"EX",
9219                b"100",
9220                b"FIELDS",
9221                b"1",
9222                b"f",
9223            ][..],
9224            &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
9225        ] {
9226            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9227        }
9228        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
9229    }
9230
9231    /// The two orders `HIMPORT` juggles, which are not the same order.
9232    ///
9233    /// Values arrive in the order the fields were declared in and the hash is
9234    /// built in sorted order, so the first value is not generally the first
9235    /// field. And the sort is by length before bytes, which nothing else here
9236    /// sorts names with: `b` comes before `aa` where a plain byte comparison
9237    /// would put `aa` first. Both read off 8.10.1.
9238    #[test]
9239    fn himport_writes_declared_values_into_sorted_fields() {
9240        let mut f = Fixture::new();
9241        assert_eq!(
9242            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
9243            "+OK\r\n"
9244        );
9245        assert_eq!(
9246            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
9247            "+OK\r\n"
9248        );
9249        assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
9250        assert_eq!(
9251            f.run(&[b"HGETALL", b"k"]),
9252            bulks(&["a", "3", "b", "1", "aa", "2"])
9253        );
9254    }
9255
9256    /// It replaces the key rather than writing over it, so a field the fieldset
9257    /// does not name is gone afterwards and so is the deadline.
9258    #[test]
9259    fn himport_set_replaces_the_whole_key() {
9260        let mut f = Fixture::new();
9261        f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
9262        f.run(&[b"EXPIRE", b"k", b"100"]);
9263        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9264        assert_eq!(
9265            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9266            "+OK\r\n"
9267        );
9268        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9269        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
9270    }
9271
9272    /// A fieldset is connection state. `SELECT` leaves them alone and `RESET`
9273    /// throws them away, and a key built from one outlives it.
9274    #[test]
9275    fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
9276        let mut f = Fixture::new();
9277        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
9278        f.run(&[b"SELECT", b"1"]);
9279        assert_eq!(
9280            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9281            "+OK\r\n"
9282        );
9283        f.run(&[b"SELECT", b"0"]);
9284        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
9285        assert_eq!(
9286            f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
9287            "-ERR no such fieldset\r\n"
9288        );
9289    }
9290
9291    /// Which complaint wins when a line is wrong in more than one place.
9292    ///
9293    /// The type of the key beats both of the others, so a `HIMPORT SET` against
9294    /// a string is a WRONGTYPE even when the fieldset is missing too, which is
9295    /// the ordering a real server has and not the one the argument order
9296    /// suggests.
9297    #[test]
9298    fn himport_complains_in_the_order_a_real_server_does() {
9299        let mut f = Fixture::new();
9300        f.run(&[b"SET", b"str", b"v"]);
9301        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9302        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9303        assert_eq!(
9304            f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
9305            wrong,
9306            "the type beats a missing fieldset"
9307        );
9308        assert_eq!(
9309            f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
9310            wrong,
9311            "and it beats a value count that does not fit"
9312        );
9313        assert_eq!(
9314            f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
9315            "-ERR no such fieldset\r\n"
9316        );
9317        // One sentence for too few and for too many alike.
9318        for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
9319            let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
9320            line.extend_from_slice(values);
9321            assert_eq!(
9322                f.run(&line),
9323                "-ERR value count does not match fieldset field count\r\n",
9324                "{} values into two fields",
9325                values.len()
9326            );
9327        }
9328        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9329    }
9330
9331    /// The arity of each subcommand, and the unknown one.
9332    #[test]
9333    fn himport_checks_each_subcommand_count_under_its_own_name() {
9334        let mut f = Fixture::new();
9335        assert_eq!(
9336            f.run(&[b"HIMPORT"]),
9337            "-ERR wrong number of arguments for 'himport' command\r\n"
9338        );
9339        for (rest, name) in [
9340            (&["PREPARE"][..], "prepare"),
9341            (&["PREPARE", "fs"][..], "prepare"),
9342            (&["SET"][..], "set"),
9343            (&["SET", "k"][..], "set"),
9344            (&["SET", "k", "fs"][..], "set"),
9345            (&["DISCARD"][..], "discard"),
9346            (&["DISCARD", "a", "b"][..], "discard"),
9347            (&["DISCARDALL", "x"][..], "discardall"),
9348        ] {
9349            let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
9350            line.extend(rest.iter().map(|a| a.as_bytes()));
9351            assert_eq!(
9352                f.run(&line),
9353                format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
9354                "HIMPORT {}",
9355                rest.join(" ")
9356            );
9357        }
9358        assert_eq!(
9359            f.run(&[b"HIMPORT", b"NOPE", b"x"]),
9360            "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
9361        );
9362    }
9363
9364    /// A `PREPARE` that fails leaves the name pointing where it pointed, which
9365    /// is the answer of the two that could not be guessed from outside.
9366    #[test]
9367    fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
9368        let mut f = Fixture::new();
9369        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9370        assert_eq!(
9371            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
9372            "-ERR duplicate field name in fieldset\r\n"
9373        );
9374        assert_eq!(
9375            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9376            "+OK\r\n"
9377        );
9378        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9379    }
9380
9381    /// Preparing the same name twice replaces it, and the two discards count
9382    /// what they took rather than answering OK.
9383    #[test]
9384    fn himport_prepare_replaces_and_the_discards_count() {
9385        let mut f = Fixture::new();
9386        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9387        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
9388        assert_eq!(
9389            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9390            "+OK\r\n"
9391        );
9392        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
9393
9394        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
9395        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
9396        f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
9397        f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
9398        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
9399        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
9400    }
9401
9402    /// The one integer of a single element array reply.
9403    /// The number out of a plain integer reply.
9404    ///
9405    /// [`int_reply`] is the same thing wrapped in a one element array, which is
9406    /// the shape every hash field command answers in.
9407    fn int(reply: &str) -> i64 {
9408        let body = reply
9409            .strip_prefix(':')
9410            .and_then(|s| s.strip_suffix("\r\n"))
9411            .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
9412        body.parse().expect("an integer")
9413    }
9414
9415    fn int_reply(reply: &str) -> i64 {
9416        let body = reply
9417            .strip_prefix("*1\r\n:")
9418            .and_then(|s| s.strip_suffix("\r\n"))
9419            .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
9420        body.parse().expect("an integer")
9421    }
9422
9423    /// The cursor and the flat items of a scan reply.
9424    fn scan_reply(reply: &str) -> (String, Vec<String>) {
9425        let mut lines = reply.split("\r\n");
9426        assert_eq!(lines.next(), Some("*2"), "got {reply}");
9427        lines.next().expect("the cursor header");
9428        let cursor = lines.next().expect("a cursor").to_owned();
9429        let header = lines.next().expect("an item count");
9430        let n: usize = header[1..].parse().expect("a count");
9431        let mut items = Vec::with_capacity(n);
9432        for _ in 0..n {
9433            lines.next().expect("an item header");
9434            items.push(lines.next().expect("an item").to_owned());
9435        }
9436        (cursor, items)
9437    }
9438
9439    /// The members of a set reply, sorted, since none of these promise an
9440    /// order and a test that asserted one would be asserting an accident.
9441    fn sorted(reply: &str) -> Vec<String> {
9442        let mut lines = reply.split("\r\n");
9443        let header = lines.next().expect("a header");
9444        assert!(
9445            header.starts_with('*') || header.starts_with('~'),
9446            "got {reply}"
9447        );
9448        let n: usize = header[1..].parse().expect("a member count");
9449        let mut got = Vec::with_capacity(n);
9450        for _ in 0..n {
9451            lines.next().expect("a member header");
9452            got.push(lines.next().expect("a member").to_owned());
9453        }
9454        got.sort();
9455        got
9456    }
9457
9458    #[test]
9459    fn the_algebra_answers_what_the_sets_share_and_do_not() {
9460        let mut f = Fixture::new();
9461        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9462        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9463        f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
9464
9465        assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
9466        assert_eq!(
9467            sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
9468            ["1", "2", "3", "4", "5"]
9469        );
9470        assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
9471        assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
9472
9473        // A key that is not there is an empty set, which empties an
9474        // intersection and does nothing at all to a union.
9475        assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
9476        assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
9477        assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
9478        assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
9479    }
9480
9481    #[test]
9482    fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
9483        let mut f = Fixture::new();
9484        f.run(&[b"SADD", b"a", b"x"]);
9485        assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
9486        assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
9487        assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
9488
9489        f.run(&[b"HELLO", b"3"]);
9490        assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
9491        assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
9492        assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
9493        assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
9494    }
9495
9496    #[test]
9497    fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
9498        let mut f = Fixture::new();
9499        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9500        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9501
9502        assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
9503        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
9504        assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
9505        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
9506        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
9507        assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
9508
9509        // An empty answer deletes the destination rather than leaving an empty
9510        // set behind, and the destination may be one of the sources.
9511        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
9512        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
9513        assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
9514        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
9515
9516        // And a destination holding something else is overwritten, the same way
9517        // SET overwrites, rather than refused.
9518        f.run(&[b"SET", b"str", b"v"]);
9519        assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
9520        assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
9521    }
9522
9523    #[test]
9524    fn sintercard_counts_without_building_and_stops_at_a_limit() {
9525        let mut f = Fixture::new();
9526        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9527        f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
9528
9529        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
9530        assert_eq!(
9531            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9532            ":2\r\n"
9533        );
9534        assert_eq!(
9535            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9536            ":3\r\n",
9537            "a limit of zero is no limit"
9538        );
9539        assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
9540        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
9541
9542        // The counted keys are what make its three error messages its own.
9543        assert_eq!(
9544            f.run(&[b"SINTERCARD", b"0", b"a"]),
9545            "-ERR numkeys should be greater than 0\r\n"
9546        );
9547        assert_eq!(
9548            f.run(&[b"SINTERCARD", b"abc", b"a"]),
9549            "-ERR numkeys should be greater than 0\r\n"
9550        );
9551        assert_eq!(
9552            f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
9553            "-ERR Number of keys can't be greater than number of args\r\n"
9554        );
9555        assert_eq!(
9556            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
9557            "-ERR LIMIT can't be negative\r\n"
9558        );
9559        assert_eq!(
9560            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
9561            "-ERR syntax error\r\n"
9562        );
9563        // A key really can be called LIMIT, which is why the count exists.
9564        f.run(&[b"SADD", b"LIMIT", b"2"]);
9565        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
9566    }
9567
9568    /// The two Redis 8.10 added, which are SINTERCARD's shape over a union and
9569    /// over a difference. Every number here was read off 8.10.1 first.
9570    #[test]
9571    fn sunioncard_and_sdiffcard_count_without_building() {
9572        let mut f = Fixture::new();
9573        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9574        f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
9575
9576        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
9577        assert_eq!(
9578            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9579            ":2\r\n"
9580        );
9581        assert_eq!(
9582            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9583            ":6\r\n",
9584            "a limit of zero is no limit"
9585        );
9586        assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
9587        assert_eq!(
9588            f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
9589            ":4\r\n",
9590            "a missing key adds nothing to a union"
9591        );
9592
9593        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
9594        assert_eq!(
9595            f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
9596            ":1\r\n"
9597        );
9598        assert_eq!(
9599            f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
9600            ":2\r\n",
9601            "a difference is not symmetric"
9602        );
9603        assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
9604        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
9605        assert_eq!(
9606            f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
9607            ":0\r\n",
9608            "nothing taken away from nothing"
9609        );
9610
9611        // The same three messages SINTERCARD has, because the line is the same
9612        // line and is parsed once for all three.
9613        for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
9614            assert_eq!(
9615                f.run(&[name, b"0", b"a"]),
9616                "-ERR numkeys should be greater than 0\r\n"
9617            );
9618            assert_eq!(
9619                f.run(&[name, b"abc", b"a"]),
9620                "-ERR numkeys should be greater than 0\r\n"
9621            );
9622            assert_eq!(
9623                f.run(&[name, b"-1", b"a"]),
9624                "-ERR numkeys should be greater than 0\r\n"
9625            );
9626            assert_eq!(
9627                f.run(&[name, b"3", b"a", b"b"]),
9628                "-ERR Number of keys can't be greater than number of args\r\n"
9629            );
9630            assert_eq!(
9631                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
9632                "-ERR LIMIT can't be negative\r\n"
9633            );
9634            assert_eq!(
9635                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
9636                "-ERR LIMIT can't be negative\r\n",
9637                "a LIMIT that is not a number gets the negative message too"
9638            );
9639            assert_eq!(
9640                f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
9641                "-ERR syntax error\r\n"
9642            );
9643            assert_eq!(
9644                f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
9645                "-ERR syntax error\r\n"
9646            );
9647            assert_eq!(
9648                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
9649                "-ERR syntax error\r\n"
9650            );
9651        }
9652
9653        // And a key called LIMIT is a key, here as much as on SINTERCARD.
9654        f.run(&[b"SADD", b"LIMIT", b"2"]);
9655        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
9656        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
9657    }
9658
9659    #[test]
9660    fn the_algebra_answers_wrongtype_before_it_writes_anything() {
9661        let mut f = Fixture::new();
9662        f.run(&[b"SADD", b"a", b"1"]);
9663        f.run(&[b"SADD", b"d", b"old"]);
9664        f.run(&[b"SET", b"str", b"v"]);
9665
9666        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9667        for bad in [
9668            &[b"SINTER".as_slice(), b"a", b"str"][..],
9669            &[b"SUNION".as_slice(), b"str"][..],
9670            &[b"SDIFF".as_slice(), b"a", b"str"][..],
9671            &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
9672            &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
9673            &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
9674            &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
9675        ] {
9676            let reply = f.run(bad);
9677            assert_eq!(reply, wrong, "for {:?}", bad[0]);
9678        }
9679        assert_eq!(
9680            f.run(&[b"SMEMBERS", b"d"]),
9681            "*1\r\n$3\r\nold\r\n",
9682            "and the destination was left alone every time"
9683        );
9684    }
9685
9686    /// The leak a set can spring that nothing on the wire would ever show: the
9687    /// key goes, the body does not, and `DBSIZE` looks right the whole time.
9688    /// Not under Miri. What this claims is that memory does not grow over two
9689    /// hundred passes, so the passes are the claim rather than the way it
9690    /// happens to be written, and two hundred passes of a two hundred member
9691    /// collection is forty thousand trips through dispatch, which is what an
9692    /// interpreter charges for. A count small enough to run there would leave a
9693    /// server that reclaims nothing inside the bound and the test would pass on
9694    /// a leak. Nothing about memory safety goes uninterpreted either way: this
9695    /// is an accounting claim, and the same commands are run a few at a time by
9696    /// the tests around it.
9697    #[cfg_attr(miri, ignore = "the volume is the claim")]
9698    #[test]
9699    fn churning_sets_does_not_grow_the_server() {
9700        let mut f = Fixture::new();
9701        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
9702        let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
9703            .chain(std::iter::once(&b"s"[..]))
9704            .chain(members.iter().map(Vec::as_slice))
9705            .collect();
9706
9707        f.run(&args);
9708        f.run(&[b"DEL", b"s"]);
9709        f.server.compact_step();
9710        let after_first = f.server.memory_bytes();
9711
9712        for _ in 0..200 {
9713            f.run(&args);
9714            f.run(&[b"DEL", b"s"]);
9715            f.server.compact_step();
9716        }
9717        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9718        assert!(
9719            f.server.memory_bytes() <= after_first * 2,
9720            "held {} after two hundred passes against {after_first} after one",
9721            f.server.memory_bytes()
9722        );
9723    }
9724
9725    // --------------------------------------------------------------- bitmaps
9726
9727    /// The two single bit commands, and the encoding rule underneath them.
9728    ///
9729    /// A write always leaves the value `raw` and a read never re-encodes, which
9730    /// is why the `int` key here is still `int` after a `GETBIT` and is `raw`
9731    /// with its first digit changed after a `SETBIT`.
9732    #[test]
9733    fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
9734        let mut f = Fixture::new();
9735        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
9736        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
9737        assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
9738        assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
9739        assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
9740        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
9741
9742        // Writing a nought past the end still creates the key and still pads.
9743        assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
9744        assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
9745        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
9746
9747        f.run(&[b"SET", b"num", b"12345"]);
9748        assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
9749        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
9750        assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
9751        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
9752        assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
9753    }
9754
9755    /// Counting, in bytes and in bits.
9756    ///
9757    /// The `0 -5 BIT` row is 25 on a real 8.10.1 and Redis's own documentation
9758    /// says 22 for it. The server is the thing being copied here.
9759    #[test]
9760    fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
9761        let mut f = Fixture::new();
9762        f.run(&[b"SET", b"mykey", b"foobar"]);
9763        assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
9764        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
9765        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
9766        assert_eq!(
9767            f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
9768            ":6\r\n"
9769        );
9770        assert_eq!(
9771            f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
9772            ":25\r\n"
9773        );
9774        assert_eq!(
9775            f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
9776            ":17\r\n"
9777        );
9778        assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
9779
9780        // A start past the end is left where it is and the end is pulled back,
9781        // so the range comes out backwards and counts nothing.
9782        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
9783
9784        // A lone start is a syntax error here, where BITPOS allows it.
9785        assert_eq!(
9786            f.run(&[b"BITCOUNT", b"mykey", b"0"]),
9787            "-ERR syntax error\r\n"
9788        );
9789        assert_eq!(
9790            f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
9791            "-ERR syntax error\r\n"
9792        );
9793    }
9794
9795    /// Searching, and the one place a miss is not minus one.
9796    ///
9797    /// A search for a nought that runs to the end of the string answers the
9798    /// length in bits, because the string is treated as if it had noughts after
9799    /// it forever. Give it an explicit end and it answers minus one instead.
9800    #[test]
9801    fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
9802        let mut f = Fixture::new();
9803        f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
9804        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
9805        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
9806        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
9807        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
9808        assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
9809
9810        f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
9811        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
9812        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
9813        assert_eq!(
9814            f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
9815            ":8\r\n"
9816        );
9817
9818        // A missing key is all noughts, so a one is never found and a nought is
9819        // at position zero.
9820        assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
9821        assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
9822    }
9823
9824    /// The eight operations, with the answers a real server gives for them.
9825    #[test]
9826    fn the_eight_combinations_write_what_a_real_server_writes() {
9827        let mut f = Fixture::new();
9828        f.run(&[b"SET", b"a", b"abc"]);
9829        f.run(&[b"SET", b"b", b"abd"]);
9830        let cases: &[(&[u8], &str)] = &[
9831            (b"AND", "ab`"),
9832            (b"OR", "abg"),
9833            (b"XOR", "\u{0}\u{0}\u{7}"),
9834            (b"DIFF", "\u{0}\u{0}\u{3}"),
9835            (b"DIFF1", "\u{0}\u{0}\u{4}"),
9836            (b"ANDOR", "ab`"),
9837            (b"ONE", "\u{0}\u{0}\u{7}"),
9838        ];
9839        for (op, want) in cases {
9840            assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
9841            assert_eq!(
9842                f.run(&[b"GET", b"d"]),
9843                format!("$3\r\n{want}\r\n"),
9844                "{op:?}"
9845            );
9846        }
9847        // The one whose answer is not text, so it is compared as bytes.
9848        assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
9849        assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
9850
9851        // A missing source is a string of noughts as long as it needs to be, so
9852        // an AND against one writes three zero bytes rather than nothing.
9853        assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
9854        assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
9855
9856        // Every source missing is an empty result, and an empty result takes
9857        // the destination with it.
9858        f.run(&[b"SET", b"dest", b"x"]);
9859        assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
9860        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
9861    }
9862
9863    /// What `BITOP` says when it is asked for something it cannot do.
9864    #[test]
9865    fn bitop_names_the_operation_in_its_own_complaints() {
9866        let mut f = Fixture::new();
9867        f.run(&[b"SET", b"a", b"abc"]);
9868        assert_eq!(
9869            f.run(&[b"BITOP", b"nope", b"d", b"a"]),
9870            "-ERR syntax error\r\n"
9871        );
9872        assert_eq!(
9873            f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
9874            "-ERR BITOP NOT must be called with a single source key.\r\n"
9875        );
9876        for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
9877            assert_eq!(
9878                f.run(&[b"BITOP", op, b"d", b"a"]),
9879                format!(
9880                    "-ERR BITOP {} must be called with at least two source keys.\r\n",
9881                    String::from_utf8_lossy(op)
9882                )
9883            );
9884        }
9885        f.run(&[b"LPUSH", b"l", b"x"]);
9886        assert_eq!(
9887            f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
9888            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9889        );
9890    }
9891
9892    /// Packed fields, the three overflow policies and the `#` offset.
9893    #[test]
9894    fn bitfield_reads_and_writes_packed_fields() {
9895        let mut f = Fixture::new();
9896        assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
9897        assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
9898
9899        assert_eq!(
9900            f.run(&[
9901                b"BITFIELD",
9902                b"bf",
9903                b"INCRBY",
9904                b"u2",
9905                b"100",
9906                b"1",
9907                b"GET",
9908                b"u4",
9909                b"0"
9910            ]),
9911            "*2\r\n:1\r\n:0\r\n"
9912        );
9913        // The field at bit 100 is two bits wide, so it ends in the thirteenth
9914        // byte and the value grew to thirteen bytes to hold it.
9915        assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
9916
9917        // A `#` offset counts in fields rather than in bits.
9918        assert_eq!(
9919            f.run(&[
9920                b"BITFIELD",
9921                b"bf",
9922                b"SET",
9923                b"u8",
9924                b"#0",
9925                b"255",
9926                b"GET",
9927                b"u8",
9928                b"#0"
9929            ]),
9930            "*2\r\n:0\r\n:255\r\n"
9931        );
9932
9933        assert_eq!(
9934            f.run(&[
9935                b"BITFIELD",
9936                b"bf",
9937                b"OVERFLOW",
9938                b"SAT",
9939                b"INCRBY",
9940                b"i8",
9941                b"0",
9942                b"120",
9943                b"INCRBY",
9944                b"i8",
9945                b"0",
9946                b"120"
9947            ]),
9948            "*2\r\n:119\r\n:127\r\n"
9949        );
9950        assert_eq!(
9951            f.run(&[
9952                b"BITFIELD",
9953                b"bf2",
9954                b"OVERFLOW",
9955                b"FAIL",
9956                b"INCRBY",
9957                b"u2",
9958                b"0",
9959                b"5"
9960            ]),
9961            "*1\r\n$-1\r\n"
9962        );
9963        assert_eq!(
9964            f.run(&[
9965                b"BITFIELD",
9966                b"bf3",
9967                b"OVERFLOW",
9968                b"WRAP",
9969                b"INCRBY",
9970                b"u2",
9971                b"0",
9972                b"5"
9973            ]),
9974            "*1\r\n:1\r\n"
9975        );
9976        assert_eq!(
9977            f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
9978            "*1\r\n:4611686018427387904\r\n"
9979        );
9980    }
9981
9982    /// A bad subcommand anywhere in the line stops all of it.
9983    ///
9984    /// Redis checks the whole argument list before it runs any of it, so the
9985    /// `SET` in front of the bad type here never happens and the key it would
9986    /// have created is not there afterwards.
9987    #[test]
9988    fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
9989        let mut f = Fixture::new();
9990        let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
9991        assert_eq!(
9992            f.run(&[
9993                b"BITFIELD",
9994                b"bad",
9995                b"SET",
9996                b"u8",
9997                b"0",
9998                b"1",
9999                b"GET",
10000                b"u99",
10001                b"0"
10002            ]),
10003            bad_type
10004        );
10005        assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
10006        assert_eq!(
10007            f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
10008            bad_type
10009        );
10010        assert_eq!(
10011            f.run(&[b"BITFIELD", b"bad", b"GET"]),
10012            "-ERR syntax error\r\n"
10013        );
10014        assert_eq!(
10015            f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
10016            "-ERR syntax error\r\n"
10017        );
10018        assert_eq!(
10019            f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
10020            "-ERR syntax error\r\n"
10021        );
10022        assert_eq!(
10023            f.run(&[
10024                b"BITFIELD",
10025                b"bad",
10026                b"OVERFLOW",
10027                b"NOPE",
10028                b"GET",
10029                b"u8",
10030                b"0"
10031            ]),
10032            "-ERR Invalid OVERFLOW type specified\r\n"
10033        );
10034        assert_eq!(
10035            f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
10036            "-ERR value is not an integer or out of range\r\n"
10037        );
10038        for at in [&b"#-1"[..], b"abc"] {
10039            assert_eq!(
10040                f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
10041                "-ERR bit offset is not an integer or out of range\r\n"
10042            );
10043        }
10044    }
10045
10046    /// The read only twin reads, refuses to write, and creates nothing.
10047    #[test]
10048    fn bitfield_ro_answers_gets_and_refuses_the_rest() {
10049        let mut f = Fixture::new();
10050        f.run(&[b"SET", b"n", b"123"]);
10051        assert_eq!(
10052            f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
10053            "*1\r\n:49\r\n"
10054        );
10055        // A read does not unpack an int the way a write does.
10056        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
10057
10058        // An OVERFLOW word is allowed even though nothing here can overflow.
10059        assert_eq!(
10060            f.run(&[
10061                b"BITFIELD_RO",
10062                b"n",
10063                b"OVERFLOW",
10064                b"SAT",
10065                b"GET",
10066                b"u8",
10067                b"0"
10068            ]),
10069            "*1\r\n:49\r\n"
10070        );
10071        for sub in [&b"SET"[..], b"INCRBY"] {
10072            assert_eq!(
10073                f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
10074                "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
10075            );
10076        }
10077
10078        assert_eq!(
10079            f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
10080            "*1\r\n:0\r\n"
10081        );
10082        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10083    }
10084
10085    /// The offsets a bitmap command will not take.
10086    #[test]
10087    fn an_offset_off_the_end_of_the_world_is_refused() {
10088        let mut f = Fixture::new();
10089        let bad = "-ERR bit offset is not an integer or out of range\r\n";
10090        for arg in [&b"abc"[..], b"-1", b"4294967296"] {
10091            assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
10092            assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
10093        }
10094        for arg in [&b"2"[..], b"-1"] {
10095            assert_eq!(
10096                f.run(&[b"BITPOS", b"k", arg]),
10097                "-ERR The bit argument must be 1 or 0.\r\n"
10098            );
10099        }
10100        assert_eq!(
10101            f.run(&[b"BITPOS", b"k", b"abc"]),
10102            "-ERR value is not an integer or out of range\r\n"
10103        );
10104        assert_eq!(
10105            f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
10106            "-ERR value is not an integer or out of range\r\n"
10107        );
10108        let bad_bit = "-ERR bit is not an integer or out of range\r\n";
10109        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
10110        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
10111    }
10112
10113    /// Every one of the seven refuses a key that is not a string.
10114    #[test]
10115    fn every_bitmap_command_says_wrongtype() {
10116        let mut f = Fixture::new();
10117        f.run(&[b"LPUSH", b"l", b"x"]);
10118        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10119        let cases: &[&[&[u8]]] = &[
10120            &[b"SETBIT", b"l", b"0", b"1"],
10121            &[b"GETBIT", b"l", b"0"],
10122            &[b"BITCOUNT", b"l"],
10123            &[b"BITPOS", b"l", b"1"],
10124            &[b"BITOP", b"AND", b"d", b"l"],
10125            &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
10126            &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
10127        ];
10128        for case in cases {
10129            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10130        }
10131    }
10132
10133    // --------------------------------------------------------- hyperloglogs
10134
10135    #[test]
10136    fn a_sketch_is_added_to_and_counted() {
10137        let mut f = Fixture::new();
10138        // Creating the key counts as a change, even with nothing to add.
10139        assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
10140        assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
10141        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
10142        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
10143        // And it is a string, which is not an implementation detail: a client
10144        // can `GET` a sketch out of one server and `SET` it into another.
10145        assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
10146        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
10147
10148        assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
10149        assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
10150        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10151    }
10152
10153    #[test]
10154    fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
10155        let mut f = Fixture::new();
10156        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10157        // Not text, so it is compared as bytes.
10158        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";
10159        let mut reply = b"$27\r\n".to_vec();
10160        reply.extend_from_slice(want);
10161        reply.extend_from_slice(b"\r\n");
10162        assert_eq!(f.raw(&[b"GET", b"h"]), reply);
10163    }
10164
10165    #[test]
10166    fn counting_several_keys_counts_their_union() {
10167        let mut f = Fixture::new();
10168        f.run(&[b"PFADD", b"a", b"x", b"y"]);
10169        f.run(&[b"PFADD", b"b", b"y", b"z"]);
10170        assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
10171        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
10172        // A key that is not there is an empty sketch, not an error and not
10173        // something that gets created by being counted.
10174        assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
10175        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
10176        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10177    }
10178
10179    #[test]
10180    fn a_merge_keeps_what_the_destination_had() {
10181        let mut f = Fixture::new();
10182        f.run(&[b"PFADD", b"a", b"x", b"y"]);
10183        f.run(&[b"PFADD", b"b", b"z"]);
10184        assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
10185        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
10186        // The destination is one of the sources, so a second merge adds to it.
10187        f.run(&[b"PFADD", b"c", b"w"]);
10188        assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
10189        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
10190        // And with no sources it is a no-op that still answers OK and still
10191        // creates a destination that was not there.
10192        assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
10193        assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
10194    }
10195
10196    /// Not under Miri, and not for the number of commands: a dense sketch is
10197    /// sixteen thousand three hundred and eighty four registers and every
10198    /// command here walks all of them, so one `PFCOUNT` is more interpreted
10199    /// work than a hundred ordinary tests. The registers and the walking are in
10200    /// `yo-kv`, where fifteen tests of their own cover both encodings and where
10201    /// the interpreter does run over them. What is left here is the dispatch
10202    /// around it, which is the same dispatch every other command in this file
10203    /// goes through.
10204    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
10205    #[test]
10206    fn the_debug_forms_answer_four_different_shapes() {
10207        let mut f = Fixture::new();
10208        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10209        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
10210        assert_eq!(
10211            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10212            "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
10213        );
10214        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
10215        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
10216        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
10217        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
10218        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10219        // A dense sketch has no opcodes left to print.
10220        assert_eq!(
10221            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10222            "-ERR HLL encoding is not sparse\r\n"
10223        );
10224
10225        // All 16384 registers, of which three are not nought.
10226        let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
10227        assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
10228        assert_eq!(reply.matches(":0\r\n").count(), 16381);
10229        assert_eq!(reply.matches(":1\r\n").count(), 2);
10230        assert_eq!(reply.matches(":2\r\n").count(), 1);
10231
10232        assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
10233    }
10234
10235    #[test]
10236    fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
10237        let mut f = Fixture::new();
10238        f.run(&[b"SET", b"plain", b"not a sketch"]);
10239        let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
10240        assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
10241        assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
10242        assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
10243        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
10244
10245        // A key that is not a string at all gets the ordinary sentence, and a
10246        // destination that would have been written is not created.
10247        f.run(&[b"RPUSH", b"l", b"x"]);
10248        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10249        assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
10250        assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
10251        assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
10252        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10253        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
10254    }
10255
10256    #[test]
10257    fn pfdebug_has_its_own_complaints() {
10258        let mut f = Fixture::new();
10259        f.run(&[b"PFADD", b"h", b"a"]);
10260        // The word is quoted exactly as the client spelled it, and this is not
10261        // the "Try X HELP." sentence every other container command uses.
10262        assert_eq!(
10263            f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
10264            "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
10265        );
10266        // Where all three of the real commands take a missing key as empty.
10267        let gone = "-ERR The specified key does not exist\r\n";
10268        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
10269        assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
10270        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
10271        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
10272        assert_eq!(
10273            f.run(&[b"PFDEBUG"]),
10274            "-ERR wrong number of arguments for 'pfdebug' command\r\n"
10275        );
10276        assert_eq!(
10277            f.run(&[b"PFSELFTEST", b"x"]),
10278            "-ERR wrong number of arguments for 'pfselftest' command\r\n"
10279        );
10280    }
10281
10282    #[test]
10283    fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
10284        let mut f = Fixture::new();
10285        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10286        // The sketch with its last byte cut off, which is still a header and a
10287        // magic and is a run length encoding that stops short of register 16384.
10288        let reply = f.raw(&[b"GET", b"h"]);
10289        let short = reply[5..reply.len() - 3].to_vec();
10290        f.run(&[b"SET", b"h", &short]);
10291        assert_eq!(
10292            f.run(&[b"PFCOUNT", b"h"]),
10293            "-INVALIDOBJ Corrupted HLL object detected\r\n"
10294        );
10295    }
10296
10297    #[test]
10298    fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
10299        let mut f = Fixture::new();
10300        // One that stays sparse and one that has gone dense, since the payload
10301        // carries the bytes and the two encodings are different lengths.
10302        f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
10303        // Ten thousand elements is what takes a sketch dense on its own, and it
10304        // is ten thousand trips through dispatch, which is what Miri charges
10305        // for. There the same sketch is taken across by hand. What this test is
10306        // about is a dense payload surviving a round trip and the encoding is
10307        // dense either way: that a sketch converts when it fills up is what
10308        // `the_debug_forms_answer_four_different_shapes` is for.
10309        if cfg!(miri) {
10310            f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
10311            f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
10312        } else {
10313            for i in 0..10_000u32 {
10314                let ele = format!("e{i}");
10315                f.run(&[b"PFADD", b"big", ele.as_bytes()]);
10316            }
10317        }
10318        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
10319        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
10320
10321        for key in [&b"small"[..], b"big"] {
10322            let mut copy = key.to_vec();
10323            copy.push(b'2');
10324            let bytes = payload(&f.raw(&[b"DUMP", key]));
10325            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
10326            // The bytes, the encoding and the estimate all come back, which is
10327            // the whole of what byte compatibility across a round trip means.
10328            assert_eq!(f.raw(&[b"GET", &copy]), f.raw(&[b"GET", key]));
10329            assert_eq!(
10330                f.run(&[b"PFDEBUG", b"ENCODING", &copy]),
10331                f.run(&[b"PFDEBUG", b"ENCODING", key])
10332            );
10333            assert_eq!(f.run(&[b"PFCOUNT", &copy]), f.run(&[b"PFCOUNT", key]));
10334        }
10335        assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
10336        assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
10337    }
10338
10339    /// One RESP2 bulk string. The JSON replies are almost all one of these and
10340    /// the text inside them has quotes in it, so writing the frame out by hand
10341    /// buries the part of the assertion that matters.
10342    fn bulk(s: &str) -> String {
10343        format!("${}\r\n{s}\r\n", s.len())
10344    }
10345
10346    /// A RESP2 array of bulk strings, which is what most of the list replies
10347    /// are and what writing them out by hand in every assertion looks like.
10348    fn bulks(parts: &[&str]) -> String {
10349        let mut s = format!("*{}\r\n", parts.len());
10350        for p in parts {
10351            s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
10352        }
10353        s
10354    }
10355
10356    #[test]
10357    fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
10358        let mut f = Fixture::new();
10359        // Each element in turn goes at the head, so the last one sent is at the
10360        // front when it is over. That reads like a bug in the client and it is
10361        // what every Redis has always done.
10362        assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
10363        assert_eq!(
10364            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10365            bulks(&["c", "b", "a"])
10366        );
10367        assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
10368        assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
10369        assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
10370        assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
10371        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
10372        assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
10373    }
10374
10375    #[test]
10376    fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
10377        let mut f = Fixture::new();
10378        assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
10379        assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
10380        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10381        f.run(&[b"RPUSH", b"k", b"a"]);
10382        assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
10383        assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
10384        assert_eq!(
10385            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10386            bulks(&["z", "a", "y"])
10387        );
10388    }
10389
10390    /// The four ways a pop can come back with nothing, which are three
10391    /// different replies and a RESP2 client can tell all of them apart.
10392    #[test]
10393    fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
10394        let mut f = Fixture::new();
10395        assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
10396        assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
10397        assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
10398        assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
10399        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10400        // A count of zero against a list that is there is an empty array and
10401        // not a null array, which is the fourth answer.
10402        assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
10403        assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
10404        // More than there is takes what there is and the key goes with it.
10405        assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
10406        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10407    }
10408
10409    #[test]
10410    fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
10411        let mut f = Fixture::new();
10412        f.run(&[b"RPUSH", b"k", b"a"]);
10413        let range = "-ERR value is out of range, must be positive\r\n";
10414        assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
10415        assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
10416        assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
10417        // Redis calls this an arity error and not a syntax error, which is a
10418        // distinction it does not always make.
10419        assert_eq!(
10420            f.run(&[b"LPOP", b"k", b"1", b"2"]),
10421            "-ERR wrong number of arguments for 'lpop' command\r\n"
10422        );
10423        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10424    }
10425
10426    #[test]
10427    fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
10428        let mut f = Fixture::new();
10429        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10430        assert_eq!(
10431            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10432            bulks(&["a", "b", "c"])
10433        );
10434        assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
10435        assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
10436        assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
10437        assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
10438        assert_eq!(
10439            f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
10440            bulks(&["a", "b", "c"])
10441        );
10442        // A key that is not there is an empty range and not a nil, which is the
10443        // one place a list disagrees with a set.
10444        assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
10445        assert_eq!(
10446            f.run(&[b"LRANGE", b"k", b"a", b"b"]),
10447            "-ERR value is not an integer or out of range\r\n"
10448        );
10449    }
10450
10451    #[test]
10452    fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
10453        let mut f = Fixture::new();
10454        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10455        assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
10456        assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
10457        assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
10458        assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
10459        assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
10460        assert_eq!(
10461            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10462            bulks(&["a", "b", "z"])
10463        );
10464        // Both ways of missing are errors here rather than a nil, because a
10465        // list is never empty and there is nothing else the reply could be.
10466        assert_eq!(
10467            f.run(&[b"LSET", b"k", b"99", b"z"]),
10468            "-ERR index out of range\r\n"
10469        );
10470        assert_eq!(
10471            f.run(&[b"LSET", b"nope", b"0", b"z"]),
10472            "-ERR no such key\r\n"
10473        );
10474    }
10475
10476    #[test]
10477    fn linsert_says_three_things_with_one_signed_number() {
10478        let mut f = Fixture::new();
10479        // Zero for a key that is not there, which is not the same as minus one
10480        // for a pivot that is not in a list that is.
10481        assert_eq!(
10482            f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
10483            ":0\r\n"
10484        );
10485        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10486        assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
10487        assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
10488        assert_eq!(
10489            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10490            bulks(&["X", "a", "b", "Y"])
10491        );
10492        assert_eq!(
10493            f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
10494            ":-1\r\n"
10495        );
10496        assert_eq!(
10497            f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
10498            "-ERR syntax error\r\n"
10499        );
10500    }
10501
10502    #[test]
10503    fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
10504        let mut f = Fixture::new();
10505        f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
10506        assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
10507        assert_eq!(
10508            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10509            bulks(&["b", "c", "a"])
10510        );
10511        assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
10512        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10513        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
10514        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
10515        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10516        assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
10517    }
10518
10519    #[test]
10520    fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
10521        let mut f = Fixture::new();
10522        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
10523        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
10524        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10525        // `LTRIM k 1 0` is the documented way to empty a list, so it has to
10526        // leave `EXISTS` answering zero rather than leaving an empty one.
10527        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
10528        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10529        assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
10530    }
10531
10532    #[test]
10533    fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
10534        let mut f = Fixture::new();
10535        f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
10536        assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
10537        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
10538        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
10539        assert_eq!(
10540            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
10541            "*2\r\n:0\r\n:3\r\n"
10542        );
10543        assert_eq!(
10544            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
10545            "*3\r\n:6\r\n:3\r\n:0\r\n"
10546        );
10547        // MAXLEN counts elements looked at and not matches found, so three
10548        // stops after `a b c` and finds the one match in it.
10549        assert_eq!(
10550            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
10551            "*1\r\n:0\r\n"
10552        );
10553        // Nothing found is three different replies depending on how it was
10554        // asked and whether the key is there at all.
10555        assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
10556        assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
10557        assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
10558        assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
10559    }
10560
10561    #[test]
10562    fn lpos_words_its_three_mistakes_the_way_redis_does() {
10563        let mut f = Fixture::new();
10564        f.run(&[b"RPUSH", b"p", b"a"]);
10565        // The whole sentence and not a prefix, because the older wording of it
10566        // is still all over the internet and clients match on the text.
10567        assert_eq!(
10568            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
10569            "-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"
10570        );
10571        assert_eq!(
10572            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
10573            "-ERR COUNT can't be negative\r\n"
10574        );
10575        assert_eq!(
10576            f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
10577            "-ERR MAXLEN can't be negative\r\n"
10578        );
10579        assert_eq!(
10580            f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
10581            "-ERR syntax error\r\n"
10582        );
10583        assert_eq!(
10584            f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
10585            "-ERR syntax error\r\n"
10586        );
10587    }
10588
10589    #[test]
10590    fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
10591        let mut f = Fixture::new();
10592        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10593        assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
10594        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10595        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
10596        assert_eq!(
10597            f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
10598            "$1\r\na\r\n"
10599        );
10600        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
10601        // The same key twice is the documented way to rotate a list and falls
10602        // out of taking the element before deciding where to put it.
10603        f.run(&[b"DEL", b"r"]);
10604        f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
10605        assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
10606        assert_eq!(
10607            f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
10608            bulks(&["3", "1", "2"])
10609        );
10610        assert_eq!(
10611            f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
10612            "$-1\r\n"
10613        );
10614        assert_eq!(
10615            f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
10616            "-ERR syntax error\r\n"
10617        );
10618    }
10619
10620    #[test]
10621    fn a_move_checks_the_destination_before_it_takes_anything() {
10622        let mut f = Fixture::new();
10623        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10624        f.run(&[b"SET", b"str", b"v"]);
10625        assert_eq!(
10626            f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
10627            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10628        );
10629        // The element is still where it was, rather than having gone nowhere.
10630        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10631    }
10632
10633    #[test]
10634    fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
10635        // OBO is what you get from sending LMOVE that many times, BULK keeps
10636        // the source order. The two only differ when both ends are the same,
10637        // which is the whole reason the word exists.
10638        for (from, to, order, want) in [
10639            ("LEFT", "RIGHT", "OBO", ["a", "b"]),
10640            ("LEFT", "RIGHT", "BULK", ["a", "b"]),
10641            ("LEFT", "LEFT", "OBO", ["b", "a"]),
10642            ("LEFT", "LEFT", "BULK", ["a", "b"]),
10643            ("RIGHT", "LEFT", "OBO", ["d", "e"]),
10644            ("RIGHT", "LEFT", "BULK", ["d", "e"]),
10645            ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
10646            ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
10647        ] {
10648            let mut f = Fixture::new();
10649            f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
10650            let how = format!("{from} {to} {order}");
10651            let reply = f.run(&[
10652                b"LMOVEM",
10653                b"s",
10654                b"d",
10655                from.as_bytes(),
10656                to.as_bytes(),
10657                b"COUNT",
10658                b"2",
10659                order.as_bytes(),
10660            ]);
10661            assert_eq!(reply, bulks(&want), "the reply for {how}");
10662            assert_eq!(
10663                f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
10664                bulks(&want),
10665                "the destination for {how}"
10666            );
10667        }
10668    }
10669
10670    #[test]
10671    fn a_block_move_of_one_needs_no_count_at_all() {
10672        let mut f = Fixture::new();
10673        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10674        assert_eq!(
10675            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
10676            bulks(&["a"])
10677        );
10678        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
10679        // Six and seven arguments are neither of the two forms, so the
10680        // reference calls both of them a syntax error rather than guessing.
10681        assert_eq!(
10682            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
10683            "-ERR syntax error\r\n"
10684        );
10685        assert_eq!(
10686            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
10687            "-ERR syntax error\r\n"
10688        );
10689    }
10690
10691    #[test]
10692    fn a_block_move_with_exactly_takes_all_of_them_or_none() {
10693        let mut f = Fixture::new();
10694        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10695        // A null array and not a null bulk string, which `redis-cli` prints as
10696        // `(nil)` either way and only the raw wire tells apart. What it would
10697        // have sent is an array, so its nothing is an array's nothing.
10698        assert_eq!(
10699            f.run(&[
10700                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
10701            ]),
10702            "*-1\r\n"
10703        );
10704        assert_eq!(
10705            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10706            bulks(&["a", "b", "c"])
10707        );
10708        // COUNT takes what there is, and an emptied source goes away.
10709        assert_eq!(
10710            f.run(&[
10711                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
10712            ]),
10713            bulks(&["a", "b", "c"])
10714        );
10715        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
10716        assert_eq!(
10717            f.run(&[
10718                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10719            ]),
10720            "*-1\r\n"
10721        );
10722    }
10723
10724    #[test]
10725    fn a_block_move_onto_itself_rotates_by_the_count() {
10726        let mut f = Fixture::new();
10727        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10728        assert_eq!(
10729            f.run(&[
10730                b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
10731            ]),
10732            bulks(&["a", "b"])
10733        );
10734        assert_eq!(
10735            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10736            bulks(&["c", "a", "b"])
10737        );
10738    }
10739
10740    #[test]
10741    fn a_block_move_reads_the_count_before_the_ordering_word() {
10742        let mut f = Fixture::new();
10743        f.run(&[b"RPUSH", b"s", b"a", b"b"]);
10744        f.run(&[b"SET", b"str", b"v"]);
10745        let count = "-ERR count should be greater than 0\r\n";
10746        assert_eq!(
10747            f.run(&[
10748                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
10749            ]),
10750            count
10751        );
10752        assert_eq!(
10753            f.run(&[
10754                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
10755            ]),
10756            count
10757        );
10758        assert_eq!(
10759            f.run(&[
10760                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
10761            ]),
10762            "-ERR syntax error\r\n"
10763        );
10764        assert_eq!(
10765            f.run(&[
10766                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
10767            ]),
10768            "-ERR syntax error\r\n"
10769        );
10770        // Every argument is read before the keys are looked at, so a bad count
10771        // beats a wrong type even when the type is wrong on the source.
10772        assert_eq!(
10773            f.run(&[
10774                b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
10775            ]),
10776            count
10777        );
10778        assert_eq!(
10779            f.run(&[
10780                b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10781            ]),
10782            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10783        );
10784        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
10785    }
10786
10787    #[test]
10788    fn lmpop_answers_from_the_first_key_that_has_anything() {
10789        let mut f = Fixture::new();
10790        f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
10791        // The name of the key that answered comes back with the elements,
10792        // because the client cannot work out which one it was.
10793        assert_eq!(
10794            f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
10795            "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
10796        );
10797        assert_eq!(
10798            f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
10799            "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
10800        );
10801        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
10802        // A null array and not a null, even though what it stands in for is an
10803        // array holding a key name and then another array.
10804        assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
10805    }
10806
10807    #[test]
10808    fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
10809        let mut f = Fixture::new();
10810        f.run(&[b"RPUSH", b"k", b"a"]);
10811        assert_eq!(
10812            f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
10813            "-ERR numkeys should be greater than 0\r\n"
10814        );
10815        assert_eq!(
10816            f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
10817            "-ERR numkeys should be greater than 0\r\n"
10818        );
10819        assert_eq!(
10820            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
10821            "-ERR count should be greater than 0\r\n"
10822        );
10823        // A key count that eats the direction is a syntax error and not a
10824        // sentence about key counts, because the direction is simply not there.
10825        assert_eq!(
10826            f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
10827            "-ERR syntax error\r\n"
10828        );
10829        assert_eq!(
10830            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
10831            "-ERR syntax error\r\n"
10832        );
10833        assert_eq!(
10834            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
10835            "-ERR syntax error\r\n"
10836        );
10837        assert_eq!(
10838            f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
10839            "-ERR syntax error\r\n"
10840        );
10841        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10842    }
10843
10844    #[test]
10845    fn every_list_command_says_wrongtype_and_writes_nothing() {
10846        let mut f = Fixture::new();
10847        f.run(&[b"SET", b"str", b"v"]);
10848        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10849        for cmd in [
10850            &[b"LPUSH".as_slice(), b"str", b"a"][..],
10851            &[b"RPUSH", b"str", b"a"],
10852            &[b"LPUSHX", b"str", b"a"],
10853            &[b"RPUSHX", b"str", b"a"],
10854            &[b"LPOP", b"str"],
10855            &[b"LPOP", b"str", b"2"],
10856            &[b"RPOP", b"str"],
10857            &[b"LLEN", b"str"],
10858            &[b"LRANGE", b"str", b"0", b"-1"],
10859            &[b"LINDEX", b"str", b"0"],
10860            &[b"LSET", b"str", b"0", b"a"],
10861            &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
10862            &[b"LREM", b"str", b"0", b"a"],
10863            &[b"LTRIM", b"str", b"0", b"-1"],
10864            &[b"LPOS", b"str", b"a"],
10865            &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
10866            &[b"RPOPLPUSH", b"str", b"d"],
10867            &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
10868            &[b"LMPOP", b"1", b"str", b"LEFT"],
10869        ] {
10870            assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
10871        }
10872        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
10873        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10874    }
10875
10876    /// A timeout is not an integer and it is not an ordinary float either: the
10877    /// three sentences it can answer with are its own, and which one a given
10878    /// argument gets is not what reading the code would suggest.
10879    #[test]
10880    fn a_timeout_has_three_ways_of_being_wrong() {
10881        let mut f = Fixture::new();
10882        let not_float = "-ERR timeout is not a float or out of range\r\n";
10883        let range = "-ERR timeout is out of range\r\n";
10884        for (bad, want) in [
10885            (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
10886            (&[b"BLPOP", b"k", b"nan"], not_float),
10887            (&[b"BLPOP", b"k", b""], not_float),
10888            // Whitespace on either side, which `strtold` would take and Redis
10889            // does not.
10890            (&[b"BLPOP", b"k", b" 1"], not_float),
10891            (&[b"BLPOP", b"k", b"1 "], not_float),
10892            (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
10893            (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
10894            // These three parse, so they are not the not-a-float error, and all
10895            // three are further off than an i64 of milliseconds reaches.
10896            (&[b"BLPOP", b"k", b"1e400"], range),
10897            (&[b"BLPOP", b"k", b"inf"], range),
10898            (&[b"BLPOP", b"k", b"9999999999999999"], range),
10899            (&[b"BRPOP", b"k", b"abc"], not_float),
10900            (
10901                &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
10902                not_float,
10903            ),
10904            (
10905                &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
10906                "-ERR timeout is negative\r\n",
10907            ),
10908            (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
10909        ] {
10910            assert_eq!(f.run(bad), want, "for {bad:?}");
10911        }
10912    }
10913
10914    /// A timeout of exactly zero means no timeout, and there are two ways of
10915    /// writing exactly zero.
10916    #[test]
10917    fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
10918        let mut f = Fixture::new();
10919        for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
10920            let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
10921            assert_eq!(flow, Flow::Block, "for {timeout:?}");
10922            assert!(out.is_empty(), "for {timeout:?}");
10923        }
10924        // Positive, so it is a real deadline, and the deadline is this
10925        // millisecond. Nothing is written here either: the reply comes from the
10926        // sweep, which is the engine's and not this layer's.
10927        let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
10928        assert_eq!(flow, Flow::Block);
10929        assert!(out.is_empty());
10930    }
10931
10932    #[test]
10933    fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
10934        let mut f = Fixture::new();
10935        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10936
10937        // The one difference from LPOP: the reply names the key that answered,
10938        // which is what makes BLPOP over several keys usable.
10939        assert_eq!(
10940            f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
10941            (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
10942        );
10943        assert_eq!(
10944            f.run(&[b"BRPOP", b"L", b"0"]),
10945            "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
10946        );
10947        assert_eq!(
10948            f.run(&[
10949                b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
10950            ]),
10951            "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10952        );
10953        assert_eq!(
10954            f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10955            "$1\r\nd\r\n"
10956        );
10957        assert_eq!(
10958            f.run(&[b"EXISTS", b"L"]),
10959            ":0\r\n",
10960            "and the key went with it"
10961        );
10962        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
10963        // Onto itself, which is how a list is rotated and is a real thing to ask
10964        // a blocking move for.
10965        f.run(&[b"RPUSH", b"D", b"x"]);
10966        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
10967        assert_eq!(
10968            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10969            "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
10970        );
10971    }
10972
10973    #[test]
10974    fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
10975        let mut f = Fixture::new();
10976        f.run(&[b"RPUSH", b"k", b"a"]);
10977        for (bad, want) in [
10978            (
10979                &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
10980                "-ERR numkeys should be greater than 0\r\n",
10981            ),
10982            (
10983                &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
10984                "-ERR numkeys should be greater than 0\r\n",
10985            ),
10986            // Two keys named and one given, so the word that should have been
10987            // the direction is a key and there is no direction left.
10988            (
10989                &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
10990                "-ERR syntax error\r\n",
10991            ),
10992            (
10993                &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
10994                "-ERR syntax error\r\n",
10995            ),
10996            (
10997                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
10998                "-ERR syntax error\r\n",
10999            ),
11000            (
11001                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
11002                "-ERR syntax error\r\n",
11003            ),
11004            // A count that is not a number at all gets the same sentence a zero
11005            // or a negative one gets, rather than the usual one about integers.
11006            (
11007                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
11008                "-ERR count should be greater than 0\r\n",
11009            ),
11010            (
11011                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
11012                "-ERR count should be greater than 0\r\n",
11013            ),
11014        ] {
11015            assert_eq!(f.run(bad), want, "for {bad:?}");
11016        }
11017        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
11018    }
11019
11020    #[test]
11021    fn a_blocking_move_reads_its_directions_before_its_timeout() {
11022        let mut f = Fixture::new();
11023        // Both are wrong. Redis checks the directions first, so this is the
11024        // syntax error and not a complaint about the timeout.
11025        assert_eq!(
11026            f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
11027            "-ERR syntax error\r\n"
11028        );
11029        assert_eq!(
11030            f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
11031            "-ERR syntax error\r\n"
11032        );
11033    }
11034
11035    /// `BLMOVEM` answers exactly what `LMOVEM` answers when it does not have to
11036    /// wait, which is the same relationship every other command in this file has
11037    /// with the one it wraps.
11038    #[test]
11039    fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
11040        let mut f = Fixture::new();
11041        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
11042        assert_eq!(
11043            f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
11044            (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
11045        );
11046        assert_eq!(
11047            f.run(&[
11048                b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
11049            ]),
11050            bulks(&["e", "d"])
11051        );
11052        assert_eq!(
11053            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11054            bulks(&["a", "e", "d"])
11055        );
11056        // `EXACTLY` with enough there does not wait either.
11057        assert_eq!(
11058            f.run(&[
11059                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
11060            ]),
11061            bulks(&["b", "c"])
11062        );
11063        assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
11064    }
11065
11066    /// The one thing `BLMOVEM` decides differently from the other five: `COUNT`
11067    /// is ready as soon as there is anything and `EXACTLY` is not ready until the
11068    /// whole block has arrived.
11069    #[test]
11070    fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
11071        let mut f = Fixture::new();
11072        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11073        // Two there and three asked for. `COUNT` takes the two.
11074        assert_eq!(
11075            f.flow(&[
11076                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
11077            ]),
11078            (Flow::Continue, bulks(&["a", "b"]))
11079        );
11080
11081        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11082        // The same line with `EXACTLY` parks instead, and takes nothing on the
11083        // way past.
11084        assert_eq!(
11085            f.flow(&[
11086                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
11087            ])
11088            .0,
11089            Flow::Block
11090        );
11091        assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
11092    }
11093
11094    #[test]
11095    fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
11096        let mut f = Fixture::new();
11097        let syntax = "-ERR syntax error\r\n";
11098        // All three are wrong and the directions are read first.
11099        assert_eq!(
11100            f.run(&[
11101                b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
11102            ]),
11103            syntax
11104        );
11105        // Directions fine, timeout and count both wrong, so the timeout wins.
11106        assert_eq!(
11107            f.run(&[
11108                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
11109            ]),
11110            "-ERR timeout is not a float or out of range\r\n"
11111        );
11112        assert_eq!(
11113            f.run(&[
11114                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
11115            ]),
11116            "-ERR timeout is negative\r\n"
11117        );
11118        // And with the timeout fine, the count before the ordering word.
11119        assert_eq!(
11120            f.run(&[
11121                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
11122            ]),
11123            "-ERR count should be greater than 0\r\n"
11124        );
11125        assert_eq!(
11126            f.run(&[
11127                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
11128            ]),
11129            syntax
11130        );
11131        // Seven and eight arguments are neither of the two forms, the same way
11132        // six and seven are for `LMOVEM`.
11133        assert_eq!(
11134            f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
11135            syntax
11136        );
11137        assert_eq!(
11138            f.run(&[
11139                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
11140            ]),
11141            syntax
11142        );
11143    }
11144
11145    /// The four ways a blocking command sees a key of another type, and the one
11146    /// way it does not.
11147    #[test]
11148    fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
11149        let mut f = Fixture::new();
11150        f.run(&[b"SET", b"S", b"v"]);
11151        f.run(&[b"RPUSH", b"D", b"x"]);
11152        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11153
11154        assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
11155        // Every key is checked even when an earlier one would have blocked, so
11156        // an empty key in front of a string does not hide it.
11157        assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
11158        assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
11159        assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
11160        assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
11161        // The destination, which is only reached because the source has
11162        // something in it.
11163        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
11164        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
11165        assert_eq!(
11166            f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
11167            wrong
11168        );
11169        assert_eq!(
11170            f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
11171            wrong
11172        );
11173
11174        // And the one that does not: an empty source means the destination is
11175        // never looked at, so this waits rather than erroring, and on a real
11176        // server it times out.
11177        assert_eq!(
11178            f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11179                .0,
11180            Flow::Block
11181        );
11182        // `BLMOVEM` has a second way of not being ready, and it hides the
11183        // destination just as well: the source is a list with two elements in it
11184        // and `EXACTLY` wants three, so the string never gets looked at.
11185        assert_eq!(
11186            f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11187                .0,
11188            Flow::Block
11189        );
11190        f.run(&[b"RPUSH", b"E", b"1", b"2"]);
11191        assert_eq!(
11192            f.flow(&[
11193                b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
11194            ])
11195            .0,
11196            Flow::Block
11197        );
11198    }
11199
11200    /// The same churn the set and the string get, because a list that leaks a
11201    /// chunk per push looks exactly like one that does not until it has run for
11202    /// an afternoon.
11203    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
11204    #[cfg_attr(miri, ignore = "the volume is the claim")]
11205    #[test]
11206    fn churning_lists_does_not_grow_the_server() {
11207        let mut f = Fixture::new();
11208        let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
11209        let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
11210            .into_iter()
11211            .chain(vals.iter().map(Vec::as_slice))
11212            .collect();
11213
11214        f.run(&args);
11215        f.run(&[b"DEL", b"k"]);
11216        f.server.compact_step();
11217        let after_first = f.server.memory_bytes();
11218
11219        for _ in 0..200 {
11220            f.run(&args);
11221            f.run(&[b"LTRIM", b"k", b"1", b"0"]);
11222            f.server.compact_step();
11223        }
11224        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11225        assert!(
11226            f.server.memory_bytes() <= after_first * 2,
11227            "held {} after two hundred passes against {after_first} after one",
11228            f.server.memory_bytes()
11229        );
11230    }
11231
11232    // ------------------------------------------------------------ sorted set
11233
11234    #[test]
11235    fn a_sorted_set_takes_scores_and_gives_them_back() {
11236        let mut f = Fixture::new();
11237        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
11238        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
11239        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
11240        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
11241        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
11242        assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
11243        assert_eq!(
11244            f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
11245            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
11246        );
11247        assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
11248        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
11249        // The key goes when the last member does.
11250        assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
11251        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11252    }
11253
11254    #[test]
11255    fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
11256        let mut f = Fixture::new();
11257        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
11258        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
11259        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
11260        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
11261
11262        f.out = Out::new(Proto::Resp3);
11263        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
11264        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
11265        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
11266        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
11267    }
11268
11269    #[test]
11270    fn the_zadd_options_gate_what_gets_written() {
11271        let mut f = Fixture::new();
11272        f.run(&[b"ZADD", b"z", b"5", b"a"]);
11273        // NX leaves a member that is there alone, XX will not create one.
11274        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
11275        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
11276        assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
11277        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
11278        // GT and LT only move a score one way.
11279        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
11280        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
11281        assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
11282        // CH counts a moved score and plain ZADD does not.
11283        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
11284        assert_eq!(
11285            f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
11286            ":2\r\n"
11287        );
11288    }
11289
11290    #[test]
11291    fn zadd_incr_answers_a_score_or_nothing_at_all() {
11292        let mut f = Fixture::new();
11293        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
11294        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
11295        // A gate that refuses is the string nil, because the reply it stands in
11296        // for is a score.
11297        assert_eq!(
11298            f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
11299            "$-1\r\n"
11300        );
11301        assert_eq!(
11302            f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
11303            "$-1\r\n"
11304        );
11305        assert_eq!(
11306            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
11307            "$-1\r\n"
11308        );
11309        assert_eq!(
11310            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
11311            "$1\r\n8\r\n"
11312        );
11313        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
11314        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
11315    }
11316
11317    #[test]
11318    fn the_two_infinities_will_not_be_added_together() {
11319        let mut f = Fixture::new();
11320        f.run(&[b"ZADD", b"z", b"inf", b"m"]);
11321        let nan = "-ERR resulting score is not a number (NaN)\r\n";
11322        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
11323        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
11324        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
11325        // And a key made for an increment that then fails does not stay behind.
11326        assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
11327    }
11328
11329    #[test]
11330    fn zadd_says_its_mistakes_the_way_redis_says_them() {
11331        let mut f = Fixture::new();
11332        // The pairs are counted before the options are looked at, so this is a
11333        // syntax error about having none and not a complaint about NX and XX.
11334        assert_eq!(
11335            f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
11336            "-ERR syntax error\r\n"
11337        );
11338        assert_eq!(
11339            f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
11340            "-ERR XX and NX options at the same time are not compatible\r\n"
11341        );
11342        let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
11343        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
11344        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
11345        assert_eq!(
11346            f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
11347            "-ERR INCR option supports a single increment-element pair\r\n"
11348        );
11349        // An odd number of arguments after the options.
11350        assert_eq!(
11351            f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
11352            "-ERR syntax error\r\n"
11353        );
11354        // Every score is read before the first is stored.
11355        assert_eq!(
11356            f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
11357            "-ERR value is not a valid float\r\n"
11358        );
11359        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11360    }
11361
11362    #[test]
11363    fn a_rank_says_where_a_member_sits_from_either_end() {
11364        let mut f = Fixture::new();
11365        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11366        assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
11367        assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
11368        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
11369        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
11370        // WITHSCORE changes both shapes: the answer and the nothing.
11371        assert_eq!(
11372            f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
11373            "*2\r\n:1\r\n$1\r\n2\r\n"
11374        );
11375        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
11376        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
11377        assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
11378        // A bad option is a syntax error and one argument too many is an arity
11379        // error, which is Redis's split.
11380        assert_eq!(
11381            f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
11382            "-ERR syntax error\r\n"
11383        );
11384        assert_eq!(
11385            f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
11386            "-ERR wrong number of arguments for 'zrevrank' command\r\n"
11387        );
11388    }
11389
11390    #[test]
11391    fn the_two_counts_read_their_two_kinds_of_bound() {
11392        let mut f = Fixture::new();
11393        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11394        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
11395        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
11396        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
11397        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
11398        assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
11399        assert_eq!(
11400            f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
11401            "-ERR min or max is not a float\r\n"
11402        );
11403
11404        f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
11405        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
11406        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
11407        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
11408        // A bare member is not a bound, because a member can start with any
11409        // byte and there would be no way to say the bracket if it were optional.
11410        assert_eq!(
11411            f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
11412            "-ERR min or max not valid string range item\r\n"
11413        );
11414    }
11415
11416    /// The three ways `ZRANGE` can be asked for a window, forwards and back.
11417    ///
11418    /// Every byte in here was read off a real 8.10.1 rather than worked out,
11419    /// because the interesting part of this command is not what it selects, it
11420    /// is which of the two ends the client is expected to name first.
11421    #[test]
11422    fn one_range_command_selects_by_rank_or_score_or_name() {
11423        let mut f = Fixture::new();
11424        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11425        assert_eq!(
11426            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11427            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11428        );
11429        assert_eq!(
11430            f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
11431            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11432        );
11433        assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
11434        assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
11435        // REV over ranks reverses the walk and leaves the two arguments alone,
11436        // because a rank counts from the end the walk starts at.
11437        assert_eq!(
11438            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
11439            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11440        );
11441        assert_eq!(
11442            f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
11443            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11444        );
11445        // And REV over scores does swap them, since a bound does not count from
11446        // anywhere. This is the one line of the parse that tells the two apart.
11447        assert_eq!(
11448            f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
11449            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11450        );
11451        assert_eq!(
11452            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
11453            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11454        );
11455        assert_eq!(
11456            f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
11457            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11458        );
11459    }
11460
11461    /// The older spellings, which are the same six windows with the mode in the
11462    /// name and the high end named first on the three that go backwards.
11463    #[test]
11464    fn the_older_range_spellings_name_their_high_end_first() {
11465        let mut f = Fixture::new();
11466        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11467        assert_eq!(
11468            f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
11469            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11470        );
11471        assert_eq!(
11472            f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
11473            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11474        );
11475        assert_eq!(
11476            f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
11477            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11478        );
11479        assert_eq!(
11480            f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
11481            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11482        );
11483        // The two arguments the wrong way round is an empty answer and not an
11484        // error, which is what the swap being in the parse rather than in the
11485        // window buys.
11486        assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
11487        assert_eq!(
11488            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
11489            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
11490        );
11491        assert_eq!(
11492            f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
11493            "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
11494        );
11495        // BYSCORE, BYLEX and REV mean nothing to these, so they are not another
11496        // way of spelling the mode, they are a syntax error.
11497        for cmd in [
11498            &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
11499            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
11500            &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
11501        ] {
11502            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
11503        }
11504    }
11505
11506    /// `LIMIT` and `WITHSCORES`, which every one of these commands reads and
11507    /// only some of them accept.
11508    #[test]
11509    fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
11510        let mut f = Fixture::new();
11511        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11512        assert_eq!(
11513            f.run(&[
11514                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
11515            ]),
11516            "*1\r\n$1\r\nb\r\n"
11517        );
11518        // A negative offset skips past everything, a negative count is no bound.
11519        assert_eq!(
11520            f.run(&[
11521                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
11522            ]),
11523            "*0\r\n"
11524        );
11525        assert_eq!(
11526            f.run(&[
11527                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
11528            ]),
11529            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11530        );
11531        // The two options in either order, which falls out of the parse loop.
11532        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";
11533        assert_eq!(
11534            f.run(&[
11535                b"ZRANGEBYSCORE",
11536                b"z",
11537                b"1",
11538                b"3",
11539                b"WITHSCORES",
11540                b"LIMIT",
11541                b"0",
11542                b"2"
11543            ]),
11544            both
11545        );
11546        assert_eq!(
11547            f.run(&[
11548                b"ZRANGEBYSCORE",
11549                b"z",
11550                b"1",
11551                b"3",
11552                b"LIMIT",
11553                b"0",
11554                b"2",
11555                b"WITHSCORES"
11556            ]),
11557            both
11558        );
11559        // LIMIT on a range by rank is refused after the whole option list has
11560        // been read, so this complains about LIMIT and not about WITHSCORES.
11561        let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
11562        assert_eq!(
11563            f.run(&[
11564                b"ZREVRANGE",
11565                b"z",
11566                b"0",
11567                b"-1",
11568                b"WITHSCORES",
11569                b"LIMIT",
11570                b"0",
11571                b"1"
11572            ]),
11573            needs_by
11574        );
11575        assert_eq!(
11576            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
11577            needs_by
11578        );
11579        let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
11580        assert_eq!(
11581            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
11582            not_bylex
11583        );
11584        assert_eq!(
11585            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
11586            not_bylex
11587        );
11588        // Two modes at once, an option nobody knows, a LIMIT missing its count,
11589        // and the three number errors, which are three different sentences.
11590        for cmd in [
11591            &[
11592                b"ZRANGE".as_slice(),
11593                b"z",
11594                b"0",
11595                b"-1",
11596                b"BYSCORE",
11597                b"BYLEX",
11598            ][..],
11599            &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
11600            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
11601        ] {
11602            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11603        }
11604        assert_eq!(
11605            f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
11606            "-ERR min or max is not a float\r\n"
11607        );
11608        assert_eq!(
11609            f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
11610            "-ERR min or max not valid string range item\r\n"
11611        );
11612        assert_eq!(
11613            f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
11614            "-ERR value is not an integer or out of range\r\n"
11615        );
11616    }
11617
11618    /// `WITHSCORES` is the one place in this group where the two protocols
11619    /// disagree about the shape of the reply and not just the type of a value.
11620    #[test]
11621    fn withscores_nests_on_resp3_and_flattens_on_resp2() {
11622        let mut f = Fixture::new();
11623        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11624        assert_eq!(
11625            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11626            "*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"
11627        );
11628        f.out = Out::new(Proto::Resp3);
11629        assert_eq!(
11630            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11631            "*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"
11632        );
11633        assert_eq!(
11634            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11635            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11636        );
11637    }
11638
11639    /// The store form, which is the same parse with the destination in front.
11640    #[test]
11641    fn a_range_store_writes_the_window_into_another_key() {
11642        let mut f = Fixture::new();
11643        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11644        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
11645        // A window that selects nothing deletes the destination rather than
11646        // leaving an empty sorted set, because an empty one does not exist.
11647        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
11648        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11649        assert_eq!(
11650            f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
11651            ":2\r\n"
11652        );
11653        assert_eq!(
11654            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11655            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11656        );
11657        // The destination is allowed to be the source, because the result is
11658        // built whole before anything is written over.
11659        assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
11660        assert_eq!(
11661            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11662            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11663        );
11664        // It takes every option ZRANGE takes except WITHSCORES, which is a
11665        // plain syntax error here and not the sentence about BYLEX.
11666        assert_eq!(
11667            f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
11668            "-ERR syntax error\r\n"
11669        );
11670    }
11671
11672    /// The three removals, which are the read side's window with the walk
11673    /// turned into a removal and no options at all.
11674    #[test]
11675    fn the_three_removals_share_their_window_with_the_reads() {
11676        let mut f = Fixture::new();
11677        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11678        assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
11679        assert_eq!(
11680            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11681            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11682        );
11683        assert_eq!(
11684            f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
11685            ":1\r\n"
11686        );
11687        assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
11688        // The last member going takes the key with it.
11689        assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
11690        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11691        assert_eq!(
11692            f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
11693            ":0\r\n"
11694        );
11695        assert_eq!(
11696            f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
11697            "-ERR value is not an integer or out of range\r\n"
11698        );
11699    }
11700
11701    /// The algebra, which is one gather and three names for it.
11702    #[test]
11703    fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
11704        let mut f = Fixture::new();
11705        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11706        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11707        assert_eq!(
11708            f.run(&[b"ZUNION", b"2", b"z", b"y"]),
11709            "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
11710        );
11711        // The scores are added where a member is in both, and the answer comes
11712        // out in the order those combined scores put it in.
11713        assert_eq!(
11714            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
11715            "*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"
11716        );
11717        assert_eq!(
11718            f.run(&[
11719                b"ZUNION",
11720                b"2",
11721                b"z",
11722                b"y",
11723                b"WEIGHTS",
11724                b"2",
11725                b"3",
11726                b"WITHSCORES"
11727            ]),
11728            "*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"
11729        );
11730        assert_eq!(
11731            f.run(&[
11732                b"ZUNION",
11733                b"2",
11734                b"z",
11735                b"y",
11736                b"AGGREGATE",
11737                b"MIN",
11738                b"WITHSCORES"
11739            ]),
11740            "*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"
11741        );
11742        assert_eq!(
11743            f.run(&[
11744                b"ZUNION",
11745                b"2",
11746                b"z",
11747                b"y",
11748                b"AGGREGATE",
11749                b"MAX",
11750                b"WITHSCORES"
11751            ]),
11752            "*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"
11753        );
11754        assert_eq!(
11755            f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
11756            "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
11757        );
11758        assert_eq!(
11759            f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
11760            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
11761        );
11762        assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
11763        // A plain set is an input, and it behaves as a sorted set in which
11764        // every member scores one.
11765        f.run(&[b"SADD", b"p", b"a", b"d"]);
11766        assert_eq!(
11767            f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
11768            "*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"
11769        );
11770        // A difference never combines two scores, so it has nothing for either
11771        // of the two options to do and refuses both.
11772        for cmd in [
11773            &[
11774                b"ZDIFF".as_slice(),
11775                b"2",
11776                b"z",
11777                b"y",
11778                b"WEIGHTS",
11779                b"1",
11780                b"1",
11781            ][..],
11782            &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
11783        ] {
11784            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11785        }
11786    }
11787
11788    /// The count of keys, which is what lets a key be named `WEIGHTS`.
11789    #[test]
11790    fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
11791        let mut f = Fixture::new();
11792        f.run(&[b"ZADD", b"z", b"1", b"a"]);
11793        f.run(&[b"ZADD", b"y", b"2", b"b"]);
11794        // Redis names the command in this one, so each spelling says its own.
11795        assert_eq!(
11796            f.run(&[b"ZUNION", b"0", b"z"]),
11797            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11798        );
11799        assert_eq!(
11800            f.run(&[b"ZUNION", b"-1", b"z"]),
11801            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11802        );
11803        assert_eq!(
11804            f.run(&[b"ZINTERCARD", b"0", b"z"]),
11805            "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
11806        );
11807        // A count bigger than the line is a plain syntax error, which reads
11808        // oddly and is what Redis says.
11809        assert_eq!(
11810            f.run(&[b"ZUNION", b"3", b"z", b"y"]),
11811            "-ERR syntax error\r\n"
11812        );
11813        assert_eq!(
11814            f.run(&[b"ZUNION", b"x", b"z"]),
11815            "-ERR value is not an integer or out of range\r\n"
11816        );
11817        // A WEIGHTS list that is not one per key is a syntax error, and a
11818        // weight that is not a number gets a sentence of its own.
11819        assert_eq!(
11820            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
11821            "-ERR syntax error\r\n"
11822        );
11823        assert_eq!(
11824            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
11825            "-ERR weight value is not a float\r\n"
11826        );
11827        assert_eq!(
11828            f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
11829            "-ERR syntax error\r\n"
11830        );
11831    }
11832
11833    /// The three store forms, which answer a count and take no WITHSCORES.
11834    #[test]
11835    fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
11836        let mut f = Fixture::new();
11837        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11838        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11839        assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
11840        assert_eq!(
11841            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11842            "*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"
11843        );
11844        assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
11845        assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
11846        // An empty result deletes the destination rather than leaving an empty
11847        // sorted set, because an empty one does not exist.
11848        assert_eq!(
11849            f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
11850            ":0\r\n"
11851        );
11852        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11853        // The destination is allowed to name its own source.
11854        assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
11855        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
11856        for cmd in [
11857            &[
11858                b"ZUNIONSTORE".as_slice(),
11859                b"d",
11860                b"2",
11861                b"z",
11862                b"y",
11863                b"WITHSCORES",
11864            ][..],
11865            &[
11866                b"ZDIFFSTORE",
11867                b"d",
11868                b"2",
11869                b"z",
11870                b"y",
11871                b"WEIGHTS",
11872                b"1",
11873                b"1",
11874            ],
11875        ] {
11876            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11877        }
11878    }
11879
11880    /// `ZINTERCARD`, which counts without building anything.
11881    #[test]
11882    fn intercard_counts_and_stops_at_its_limit() {
11883        let mut f = Fixture::new();
11884        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11885        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
11886        assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
11887        // A limit of zero is no limit, which is Redis's reading of it.
11888        assert_eq!(
11889            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
11890            ":2\r\n"
11891        );
11892        assert_eq!(
11893            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
11894            ":1\r\n"
11895        );
11896        // A negative limit and a limit that is not a number at all get the same
11897        // sentence, which looks like a mistake in Redis and is copied as one.
11898        let bad = "-ERR LIMIT can't be negative\r\n";
11899        assert_eq!(
11900            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
11901            bad
11902        );
11903        assert_eq!(
11904            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
11905            bad
11906        );
11907        for cmd in [
11908            &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
11909            &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
11910            &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
11911        ] {
11912            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11913        }
11914    }
11915
11916    /// `ZRANDMEMBER`, which answers two different shapes out of one name.
11917    #[test]
11918    fn a_draw_answers_one_member_or_an_array_of_them() {
11919        let mut f = Fixture::new();
11920        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11921        // No count is one member or a nil, a count is an array that may be
11922        // empty, and those are two reply types the client has to tell apart.
11923        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
11924        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
11925        assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
11926        assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
11927        // A positive count draws without replacement, so a count over the size
11928        // answers the whole set and never a member twice.
11929        let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
11930        assert!(all.starts_with("*3\r\n"), "{all}");
11931        for m in ["a", "b", "c"] {
11932            assert!(all.contains(m), "{all}");
11933        }
11934        // A negative one draws with replacement and answers exactly as many as
11935        // it was asked for, whatever the size of the set.
11936        assert!(
11937            f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
11938            "five draws with replacement"
11939        );
11940        assert!(
11941            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
11942                .starts_with("*4\r\n"),
11943            "two pairs, flat on RESP2"
11944        );
11945        f.out = Out::new(Proto::Resp3);
11946        let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
11947        assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
11948        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
11949        f.out = Out::new(Proto::Resp2);
11950        assert_eq!(
11951            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
11952            "-ERR syntax error\r\n"
11953        );
11954        assert_eq!(
11955            f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
11956            "-ERR value is not an integer or out of range\r\n"
11957        );
11958    }
11959
11960    /// `ZSCAN`, and the one sorted set reply where a score is not a double.
11961    #[test]
11962    fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
11963        let mut f = Fixture::new();
11964        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11965        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";
11966        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11967        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
11968        assert_eq!(
11969            f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
11970            "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11971        );
11972        assert_eq!(
11973            f.run(&[b"ZSCAN", b"nokey", b"0"]),
11974            "*2\r\n$1\r\n0\r\n*0\r\n"
11975        );
11976        // A score stays a bulk string on RESP3, which is the one place the two
11977        // protocols agree about a score and everywhere else they do not.
11978        f.out = Out::new(Proto::Resp3);
11979        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11980        f.out = Out::new(Proto::Resp2);
11981        assert_eq!(
11982            f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
11983            "-ERR NOVALUES option can only be used in HSCAN\r\n"
11984        );
11985        assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
11986        assert_eq!(
11987            f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
11988            "-ERR syntax error\r\n"
11989        );
11990    }
11991
11992    /// The count is what decides the shape, and its value is not.
11993    #[test]
11994    fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
11995        let mut f = Fixture::new();
11996        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11997        // No count, so one flat pair, and the score is a bulk string on RESP2.
11998        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11999        assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
12000        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12001        // A count, so pairs, and on RESP2 they are flattened into one run.
12002        assert_eq!(
12003            f.run(&[b"ZPOPMIN", b"z", b"2"]),
12004            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
12005        );
12006        // An empty array rather than a null, which is where a sorted set pop and
12007        // a list pop part company, and the same answer a count of zero gives.
12008        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
12009        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
12010        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
12011        // The last member takes the key with it.
12012        assert_eq!(
12013            f.run(&[b"ZPOPMIN", b"z", b"9"]),
12014            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12015        );
12016        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12017
12018        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
12019        f.out = Out::new(Proto::Resp3);
12020        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
12021        assert_eq!(
12022            f.run(&[b"ZPOPMIN", b"z", b"1"]),
12023            "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
12024        );
12025        f.out = Out::new(Proto::Resp2);
12026        // Both of these are the range error rather than the usual sentence about
12027        // integers, which is the odd answer and so the one worth copying.
12028        let bad = "-ERR value is out of range, must be positive\r\n";
12029        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
12030        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
12031        assert_eq!(
12032            f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
12033            "-ERR syntax error\r\n"
12034        );
12035    }
12036
12037    /// `ZMPOP`, which is `LMPOP` with scores and the same parse.
12038    #[test]
12039    fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
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.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
12044            "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
12045        );
12046        // Nested on RESP2 as well, because the key name is already in front of
12047        // the pairs and there is nothing left to flatten into.
12048        assert_eq!(
12049            f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
12050            "*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"
12051        );
12052        // A null array and not a null, the same as LMPOP.
12053        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
12054        f.out = Out::new(Proto::Resp3);
12055        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
12056        f.out = Out::new(Proto::Resp2);
12057        let numkeys = "-ERR numkeys should be greater than 0\r\n";
12058        for bad in [
12059            &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
12060            &[b"ZMPOP", b"-1", b"z", b"MIN"],
12061            &[b"ZMPOP", b"x", b"z", b"MIN"],
12062        ] {
12063            assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
12064        }
12065        let count = "-ERR count should be greater than 0\r\n";
12066        for bad in [
12067            &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
12068            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
12069            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
12070        ] {
12071            assert_eq!(f.run(bad), count, "{:?}", bad[5]);
12072        }
12073        let syntax = "-ERR syntax error\r\n";
12074        for bad in [
12075            // Two keys named and one given, so the word that should have been
12076            // the direction is a key and there is no direction left.
12077            &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
12078            &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
12079            &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
12080            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
12081        ] {
12082            assert_eq!(f.run(bad), syntax, "{bad:?}");
12083        }
12084    }
12085
12086    /// The three that wait, when there is something there and they do not have
12087    /// to. `BZPOPMIN` is the one reply in the group that is three flat elements.
12088    #[test]
12089    fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
12090        let mut f = Fixture::new();
12091        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12092        assert_eq!(
12093            f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
12094            (
12095                Flow::Continue,
12096                "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
12097            )
12098        );
12099        assert_eq!(
12100            f.run(&[b"BZPOPMAX", b"z", b"0"]),
12101            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
12102        );
12103        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12104        assert_eq!(
12105            f.run(&[
12106                b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
12107            ]),
12108            "*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"
12109        );
12110        f.out = Out::new(Proto::Resp3);
12111        assert_eq!(
12112            f.run(&[b"BZPOPMIN", b"z", b"0"]),
12113            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
12114        );
12115        f.out = Out::new(Proto::Resp2);
12116        // Nothing to take, so the client is parked and nothing was written.
12117        assert_eq!(
12118            f.flow(&[b"BZPOPMIN", b"z", b"0"]),
12119            (Flow::Block, String::new())
12120        );
12121        assert_eq!(
12122            f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
12123            (Flow::Block, String::new())
12124        );
12125        // The timeout is read before the key count, so this complains about the
12126        // timeout and not about the count.
12127        assert_eq!(
12128            f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
12129            "-ERR timeout is not a float or out of range\r\n"
12130        );
12131        assert_eq!(
12132            f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
12133            "-ERR numkeys should be greater than 0\r\n"
12134        );
12135        assert_eq!(
12136            f.run(&[b"BZPOPMIN", b"z", b"-1"]),
12137            "-ERR timeout is negative\r\n"
12138        );
12139    }
12140
12141    /// A parked sorted set client is served by whatever puts a member under one
12142    /// of its keys, and is not served by something of another type landing
12143    /// there.
12144    #[test]
12145    fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
12146        let mut f = Fixture::new();
12147        assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
12148        assert_eq!(f.server.parked(), 1);
12149        // A string under the key is not what it asked for, so it stays parked
12150        // rather than being handed a WRONGTYPE on a command that was accepted.
12151        f.run(&[b"SET", b"z", b"v"]);
12152        let mut out = Out::new(Proto::Resp2);
12153        assert!(!f.server.serve_waiter(7, 0, &mut out));
12154        assert!(out.as_slice().is_empty());
12155        f.run(&[b"DEL", b"z"]);
12156        f.run(&[b"ZADD", b"z", b"5", b"m"]);
12157        assert!(f.server.serve_waiter(7, 0, &mut out));
12158        assert_eq!(
12159            core::str::from_utf8(out.as_slice()).expect("ascii"),
12160            "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
12161        );
12162        // And the member is gone, which is what makes a queue of workers on a
12163        // sorted set work at all.
12164        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12165    }
12166
12167    #[test]
12168    fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
12169        let mut f = Fixture::new();
12170        f.run(&[b"SET", b"s", b"v"]);
12171        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12172        for cmd in [
12173            &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
12174            &[b"ZINCRBY", b"s", b"1", b"a"],
12175            &[b"ZCARD", b"s"],
12176            &[b"ZSCORE", b"s", b"a"],
12177            &[b"ZMSCORE", b"s", b"a"],
12178            &[b"ZREM", b"s", b"a"],
12179            &[b"ZRANK", b"s", b"a"],
12180            &[b"ZREVRANK", b"s", b"a"],
12181            &[b"ZCOUNT", b"s", b"1", b"2"],
12182            &[b"ZLEXCOUNT", b"s", b"-", b"+"],
12183            &[b"ZRANGE", b"s", b"0", b"-1"],
12184            &[b"ZREVRANGE", b"s", b"0", b"-1"],
12185            &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
12186            &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
12187            &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
12188            &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
12189            &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
12190            &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
12191            &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
12192            &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
12193            &[b"ZUNION", b"1", b"s"],
12194            &[b"ZINTER", b"1", b"s"],
12195            &[b"ZDIFF", b"1", b"s"],
12196            &[b"ZUNIONSTORE", b"d", b"1", b"s"],
12197            &[b"ZINTERSTORE", b"d", b"1", b"s"],
12198            &[b"ZDIFFSTORE", b"d", b"1", b"s"],
12199            &[b"ZINTERCARD", b"1", b"s"],
12200            &[b"ZRANDMEMBER", b"s"],
12201            &[b"ZSCAN", b"s", b"0"],
12202            &[b"ZPOPMIN", b"s"],
12203            &[b"ZPOPMAX", b"s", b"2"],
12204            &[b"ZMPOP", b"1", b"s", b"MIN"],
12205            &[b"BZPOPMIN", b"s", b"0"],
12206            &[b"BZPOPMAX", b"s", b"0"],
12207            &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
12208        ] {
12209            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12210        }
12211        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
12212    }
12213
12214    /// The same churn the set, the string and the list get, because a sorted
12215    /// set that leaks a tree node per add looks exactly like one that does not
12216    /// until it has run for an afternoon.
12217    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
12218    #[cfg_attr(miri, ignore = "the volume is the claim")]
12219    #[test]
12220    fn churning_sorted_sets_does_not_grow_the_server() {
12221        let mut f = Fixture::new();
12222        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
12223        let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
12224        let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
12225        for i in 0..200 {
12226            args.push(&scores[i]);
12227            args.push(&members[i]);
12228        }
12229
12230        f.run(&args);
12231        f.run(&[b"DEL", b"z"]);
12232        f.server.compact_step();
12233        let after_first = f.server.memory_bytes();
12234
12235        for _ in 0..200 {
12236            f.run(&args);
12237            f.run(&[b"DEL", b"z"]);
12238            f.server.compact_step();
12239        }
12240        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
12241        assert!(
12242            f.server.memory_bytes() <= after_first * 2,
12243            "held {} after two hundred passes against {after_first} after one",
12244            f.server.memory_bytes()
12245        );
12246    }
12247
12248    // ------------------------------------------------------------------- geo
12249
12250    /// The three places every Redis geo example uses, and one more.
12251    ///
12252    /// Every reply this section asserts on came off a running 8.10.1 with these
12253    /// three loaded, byte for byte, including the number of digits in a
12254    /// coordinate and the four places on a distance.
12255    fn sicily(f: &mut Fixture) {
12256        f.run(&[
12257            b"GEOADD",
12258            b"Sicily",
12259            b"13.361389",
12260            b"38.115556",
12261            b"Palermo",
12262            b"15.087269",
12263            b"37.502669",
12264            b"Catania",
12265        ]);
12266        f.run(&[
12267            b"GEOADD",
12268            b"Sicily",
12269            b"13.583333",
12270            b"37.316667",
12271            b"Agrigento",
12272        ]);
12273    }
12274
12275    #[test]
12276    fn places_go_in_as_scores_and_come_back_as_positions() {
12277        let mut f = Fixture::new();
12278        assert_eq!(
12279            f.run(&[
12280                b"GEOADD",
12281                b"Sicily",
12282                b"13.361389",
12283                b"38.115556",
12284                b"Palermo",
12285                b"15.087269",
12286                b"37.502669",
12287                b"Catania"
12288            ]),
12289            ":2\r\n"
12290        );
12291        // A geo key is a sorted set and says so, which is not an implementation
12292        // detail either: a client removes a place with ZREM and counts them
12293        // with ZCARD, and the score is the number a real server stores.
12294        assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
12295        assert_eq!(
12296            f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
12297            "$16\r\n3479099956230698\r\n"
12298        );
12299        assert_eq!(
12300            f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
12301            "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
12302        );
12303        assert_eq!(
12304            f.run(&[
12305                b"GEOHASH",
12306                b"Sicily",
12307                b"Palermo",
12308                b"Catania",
12309                b"NonExisting"
12310            ]),
12311            "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
12312        );
12313        // A key that is not there is an empty one, and the two nulls are not
12314        // the same null: GEOPOS answers the array one and GEOHASH the string
12315        // one, which a RESP2 client can tell apart.
12316        assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
12317        assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
12318    }
12319
12320    #[test]
12321    fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
12322        let mut f = Fixture::new();
12323        sicily(&mut f);
12324        assert_eq!(
12325            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
12326            "$11\r\n166274.1516\r\n"
12327        );
12328        assert_eq!(
12329            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
12330            "$8\r\n166.2742\r\n"
12331        );
12332        assert_eq!(
12333            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
12334            "$8\r\n103.3182\r\n"
12335        );
12336        // A member that is not there and a key that is not there are the same
12337        // nil, and the unit is read before the key is looked up, so a bad unit
12338        // on a missing key is still an error.
12339        assert_eq!(
12340            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
12341            "$-1\r\n"
12342        );
12343        assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
12344        assert_eq!(
12345            f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
12346            "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
12347        );
12348        assert_eq!(
12349            f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
12350            "-ERR syntax error\r\n"
12351        );
12352    }
12353
12354    #[test]
12355    fn a_search_finds_what_is_inside_it_nearest_first() {
12356        let mut f = Fixture::new();
12357        sicily(&mut f);
12358        let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
12359        assert_eq!(
12360            f.run(&[
12361                b"GEOSEARCH",
12362                b"Sicily",
12363                b"FROMLONLAT",
12364                b"15",
12365                b"37",
12366                b"BYRADIUS",
12367                b"200",
12368                b"km",
12369                b"ASC"
12370            ]),
12371            all
12372        );
12373        // The older spelling of the same search, which is the same nine boxes
12374        // and the same order.
12375        assert_eq!(
12376            f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
12377            all
12378        );
12379        assert_eq!(
12380            f.run(&[
12381                b"GEORADIUS_RO",
12382                b"Sicily",
12383                b"15",
12384                b"37",
12385                b"200",
12386                b"km",
12387                b"ASC"
12388            ]),
12389            all
12390        );
12391        // A count with no ordering means the nearest ones, so DESC has to be
12392        // asked for to get the far end.
12393        assert_eq!(
12394            f.run(&[
12395                b"GEORADIUS",
12396                b"Sicily",
12397                b"15",
12398                b"37",
12399                b"200",
12400                b"km",
12401                b"DESC",
12402                b"COUNT",
12403                b"1"
12404            ]),
12405            "*1\r\n$7\r\nPalermo\r\n"
12406        );
12407        assert_eq!(
12408            f.run(&[
12409                b"GEORADIUS",
12410                b"Sicily",
12411                b"15",
12412                b"37",
12413                b"200",
12414                b"km",
12415                b"COUNT",
12416                b"1"
12417            ]),
12418            "*1\r\n$7\r\nCatania\r\n"
12419        );
12420        // Nothing inside a kilometre of that point, and nothing in a key that
12421        // is not there, and both are the empty array rather than an error.
12422        let empty = "*0\r\n";
12423        assert_eq!(
12424            f.run(&[
12425                b"GEOSEARCH",
12426                b"Sicily",
12427                b"FROMLONLAT",
12428                b"15",
12429                b"37",
12430                b"BYRADIUS",
12431                b"1",
12432                b"km"
12433            ]),
12434            empty
12435        );
12436        assert_eq!(
12437            f.run(&[
12438                b"GEOSEARCH",
12439                b"nokey",
12440                b"FROMLONLAT",
12441                b"15",
12442                b"37",
12443                b"BYRADIUS",
12444                b"1",
12445                b"km"
12446            ]),
12447            empty
12448        );
12449        assert_eq!(
12450            f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
12451            empty
12452        );
12453    }
12454
12455    #[test]
12456    fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
12457        let mut f = Fixture::new();
12458        sicily(&mut f);
12459        assert_eq!(
12460            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
12461            "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12462        );
12463        // The member itself is nothing away from itself, which is where the
12464        // fixed point writer's zero shows up on the wire.
12465        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";
12466        assert_eq!(
12467            f.run(&[
12468                b"GEORADIUSBYMEMBER_RO",
12469                b"Sicily",
12470                b"Agrigento",
12471                b"100",
12472                b"km",
12473                b"WITHDIST"
12474            ]),
12475            with_dist
12476        );
12477        assert_eq!(
12478            f.run(&[
12479                b"GEOSEARCH",
12480                b"Sicily",
12481                b"FROMMEMBER",
12482                b"Agrigento",
12483                b"BYRADIUS",
12484                b"100",
12485                b"km",
12486                b"ASC",
12487                b"WITHDIST"
12488            ]),
12489            with_dist
12490        );
12491        assert_eq!(
12492            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
12493            "-ERR could not decode requested zset member\r\n"
12494        );
12495    }
12496
12497    #[test]
12498    fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
12499        let mut f = Fixture::new();
12500        sicily(&mut f);
12501        // Three options asked for, so each result is a four element array of
12502        // the member, the distance, the hash and a pair. The order of the three
12503        // is Redis's and not the order they were written in the command.
12504        assert_eq!(
12505            f.run(&[
12506                b"GEOSEARCH",
12507                b"Sicily",
12508                b"FROMLONLAT",
12509                b"15",
12510                b"37",
12511                b"BYBOX",
12512                b"400",
12513                b"400",
12514                b"km",
12515                b"ASC",
12516                b"WITHCOORD",
12517                b"WITHDIST",
12518                b"WITHHASH"
12519            ]),
12520            "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
12521             $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
12522             *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
12523             $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
12524             *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
12525             $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
12526        );
12527    }
12528
12529    #[test]
12530    fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
12531        let mut f = Fixture::new();
12532        sicily(&mut f);
12533        let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
12534                      $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
12535                      $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
12536        assert_eq!(
12537            f.run(&[
12538                b"GEOSEARCHSTORE",
12539                b"dst",
12540                b"Sicily",
12541                b"FROMLONLAT",
12542                b"15",
12543                b"37",
12544                b"BYRADIUS",
12545                b"200",
12546                b"km",
12547                b"ASC"
12548            ]),
12549            ":3\r\n"
12550        );
12551        assert_eq!(
12552            f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
12553            hashes
12554        );
12555        // The same again through the older spelling, which stores the same
12556        // scores, so a key written by either is a geo key.
12557        assert_eq!(
12558            f.run(&[
12559                b"GEORADIUS",
12560                b"Sicily",
12561                b"15",
12562                b"37",
12563                b"200",
12564                b"km",
12565                b"STORE",
12566                b"dst3"
12567            ]),
12568            ":3\r\n"
12569        );
12570        assert_eq!(
12571            f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
12572            hashes
12573        );
12574        // STOREDIST stores the distance in the search unit instead, and those
12575        // are full doubles rather than the four places WITHDIST writes. The
12576        // numbers on the right are what 8.10.1 stored for this search, and they
12577        // are compared with a tolerance rather than byte for byte because the
12578        // last bit of a haversine is the platform's sin, cos and asin: this
12579        // machine and that one disagree in the sixteenth digit, and so do two
12580        // Redis builds. Everything a client actually reads back is four places
12581        // and is asserted exactly above.
12582        assert_eq!(
12583            f.run(&[
12584                b"GEOSEARCHSTORE",
12585                b"dst2",
12586                b"Sicily",
12587                b"FROMLONLAT",
12588                b"15",
12589                b"37",
12590                b"BYRADIUS",
12591                b"200",
12592                b"km",
12593                b"ASC",
12594                b"STOREDIST"
12595            ]),
12596            ":3\r\n"
12597        );
12598        for (member, want) in [
12599            ("Catania", 56.441_257_870_158_19),
12600            ("Agrigento", 130.423_487_067_147_14),
12601            ("Palermo", 190.442_429_847_757_92),
12602        ] {
12603            let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
12604            let got: f64 = reply
12605                .trim_start_matches(|c: char| c != '\n')
12606                .trim()
12607                .parse()
12608                .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
12609            assert!(
12610                (got - want).abs() < 1e-9,
12611                "{member} scored {got} not {want}"
12612            );
12613        }
12614        // The order they went in is the order the scores put them in, which is
12615        // the point of storing the distance rather than the hash.
12616        assert_eq!(
12617            f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
12618            "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12619        );
12620        // A search that finds nothing takes the destination with it rather than
12621        // leaving what was there, and a source key that is not there is a
12622        // search that finds nothing.
12623        assert_eq!(
12624            f.run(&[
12625                b"GEOSEARCHSTORE",
12626                b"dst",
12627                b"nokey",
12628                b"FROMLONLAT",
12629                b"15",
12630                b"37",
12631                b"BYRADIUS",
12632                b"200",
12633                b"km"
12634            ]),
12635            ":0\r\n"
12636        );
12637        assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
12638    }
12639
12640    #[test]
12641    fn the_gates_on_geoadd_are_the_ones_zadd_has() {
12642        let mut f = Fixture::new();
12643        sicily(&mut f);
12644        // XX on a member that is already where it is changes nothing, and NX on
12645        // one that is there refuses to move it.
12646        assert_eq!(
12647            f.run(&[
12648                b"GEOADD",
12649                b"Sicily",
12650                b"XX",
12651                b"CH",
12652                b"13.361389",
12653                b"38.115556",
12654                b"Palermo"
12655            ]),
12656            ":0\r\n"
12657        );
12658        assert_eq!(
12659            f.run(&[
12660                b"GEOADD",
12661                b"Sicily",
12662                b"NX",
12663                b"13.361389",
12664                b"38.9",
12665                b"Palermo"
12666            ]),
12667            ":0\r\n"
12668        );
12669        assert_eq!(
12670            f.run(&[
12671                b"GEOADD",
12672                b"Sicily",
12673                b"CH",
12674                b"13.361389",
12675                b"38.9",
12676                b"Palermo"
12677            ]),
12678            ":1\r\n"
12679        );
12680        // Out of range, and nothing is stored: the whole call is refused rather
12681        // than the good pairs going in and the bad one stopping it.
12682        assert_eq!(
12683            f.run(&[
12684                b"GEOADD",
12685                b"new",
12686                b"13.361389",
12687                b"38.115556",
12688                b"here",
12689                b"181",
12690                b"38",
12691                b"there"
12692            ]),
12693            "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
12694        );
12695        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
12696        assert_eq!(
12697            f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
12698            "-ERR value is not a valid float\r\n"
12699        );
12700        // The count of triples is checked before the two gates are, and a call
12701        // with no triples at all reaches the same sentence.
12702        assert_eq!(
12703            f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
12704            "-ERR syntax error\r\n"
12705        );
12706        assert_eq!(
12707            f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
12708            "-ERR syntax error\r\n"
12709        );
12710        assert_eq!(
12711            f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
12712            "-ERR syntax error\r\n"
12713        );
12714        assert_eq!(
12715            f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
12716            "-ERR wrong number of arguments for 'geoadd' command\r\n"
12717        );
12718    }
12719
12720    /// The sentences a search answers, which are its contract as much as the
12721    /// results are.
12722    #[test]
12723    fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
12724        let mut f = Fixture::new();
12725        sicily(&mut f);
12726        let cases: &[(&[&[u8]], &str)] = &[
12727            (
12728                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
12729                "-ERR need numeric radius\r\n",
12730            ),
12731            (
12732                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
12733                "-ERR radius cannot be negative\r\n",
12734            ),
12735            (
12736                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
12737                "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
12738            ),
12739            (
12740                &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
12741                "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
12742            ),
12743            (
12744                &[
12745                    b"GEOSEARCH",
12746                    b"Sicily",
12747                    b"FROMLONLAT",
12748                    b"15",
12749                    b"37",
12750                    b"BYBOX",
12751                    b"x",
12752                    b"1",
12753                    b"km",
12754                ],
12755                "-ERR need numeric width\r\n",
12756            ),
12757            (
12758                &[
12759                    b"GEOSEARCH",
12760                    b"Sicily",
12761                    b"FROMLONLAT",
12762                    b"15",
12763                    b"37",
12764                    b"BYBOX",
12765                    b"1",
12766                    b"y",
12767                    b"km",
12768                ],
12769                "-ERR need numeric height\r\n",
12770            ),
12771            (
12772                &[
12773                    b"GEOSEARCH",
12774                    b"Sicily",
12775                    b"FROMLONLAT",
12776                    b"15",
12777                    b"37",
12778                    b"BYBOX",
12779                    b"-1",
12780                    b"1",
12781                    b"km",
12782                ],
12783                "-ERR height or width cannot be negative\r\n",
12784            ),
12785            (
12786                &[
12787                    b"GEOSEARCH",
12788                    b"Sicily",
12789                    b"FROMLONLAT",
12790                    b"15",
12791                    b"37",
12792                    b"BYRADIUS",
12793                    b"1",
12794                    b"km",
12795                    b"ANY",
12796                ],
12797                "-ERR the ANY argument requires COUNT argument\r\n",
12798            ),
12799            (
12800                &[
12801                    b"GEOSEARCH",
12802                    b"Sicily",
12803                    b"FROMLONLAT",
12804                    b"15",
12805                    b"37",
12806                    b"BYRADIUS",
12807                    b"1",
12808                    b"km",
12809                    b"COUNT",
12810                    b"0",
12811                ],
12812                "-ERR COUNT must be > 0\r\n",
12813            ),
12814            (
12815                &[
12816                    b"GEOSEARCH",
12817                    b"Sicily",
12818                    b"BYRADIUS",
12819                    b"1",
12820                    b"km",
12821                    b"BYBOX",
12822                    b"1",
12823                    b"1",
12824                    b"km",
12825                ],
12826                "-ERR syntax error\r\n",
12827            ),
12828            (
12829                &[
12830                    b"GEOSEARCH",
12831                    b"Sicily",
12832                    b"FROMMEMBER",
12833                    b"Palermo",
12834                    b"FROMLONLAT",
12835                    b"1",
12836                    b"2",
12837                    b"BYRADIUS",
12838                    b"1",
12839                    b"km",
12840                ],
12841                "-ERR syntax error\r\n",
12842            ),
12843            // The two options a GEOSEARCH cannot leave out, each with its own
12844            // sentence, and the command quoted the way the client spelled it.
12845            (
12846                &[
12847                    b"geosearch",
12848                    b"Sicily",
12849                    b"BYRADIUS",
12850                    b"1",
12851                    b"km",
12852                    b"ASC",
12853                    b"WITHDIST",
12854                ],
12855                "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
12856            ),
12857            (
12858                &[
12859                    b"GEOSEARCH",
12860                    b"Sicily",
12861                    b"FROMLONLAT",
12862                    b"15",
12863                    b"37",
12864                    b"ASC",
12865                    b"WITHDIST",
12866                ],
12867                "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
12868            ),
12869            // A store cannot also be asked for the distance, and the two
12870            // families name themselves differently in the same sentence.
12871            (
12872                &[
12873                    b"GEOSEARCHSTORE",
12874                    b"d",
12875                    b"Sicily",
12876                    b"FROMLONLAT",
12877                    b"15",
12878                    b"37",
12879                    b"BYRADIUS",
12880                    b"1",
12881                    b"km",
12882                    b"WITHCOORD",
12883                ],
12884                "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12885            ),
12886            (
12887                &[
12888                    b"GEORADIUS",
12889                    b"Sicily",
12890                    b"15",
12891                    b"37",
12892                    b"1",
12893                    b"km",
12894                    b"WITHDIST",
12895                    b"STORE",
12896                    b"d",
12897                ],
12898                "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12899            ),
12900            // The read only forms have no store at all, so the word is a stray
12901            // one, and GEOSEARCH's STOREDIST is only a GEOSEARCHSTORE option.
12902            (
12903                &[
12904                    b"GEORADIUS_RO",
12905                    b"Sicily",
12906                    b"15",
12907                    b"37",
12908                    b"1",
12909                    b"km",
12910                    b"STORE",
12911                    b"d",
12912                ],
12913                "-ERR syntax error\r\n",
12914            ),
12915            (
12916                &[
12917                    b"GEOSEARCH",
12918                    b"Sicily",
12919                    b"FROMLONLAT",
12920                    b"15",
12921                    b"37",
12922                    b"BYRADIUS",
12923                    b"1",
12924                    b"km",
12925                    b"STOREDIST",
12926                ],
12927                "-ERR syntax error\r\n",
12928            ),
12929        ];
12930        for (parts, want) in cases {
12931            assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
12932        }
12933    }
12934
12935    /// A wrong type wins over a bad argument, because the key is looked up
12936    /// first, and every one of the ten says the same thing about it.
12937    #[test]
12938    fn every_geo_command_says_wrongtype() {
12939        let mut f = Fixture::new();
12940        f.run(&[b"SET", b"s", b"v"]);
12941        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12942        let cases: &[&[&[u8]]] = &[
12943            &[b"GEOADD", b"s", b"13", b"38", b"m"],
12944            &[b"GEOPOS", b"s", b"m"],
12945            &[b"GEOHASH", b"s", b"m"],
12946            &[b"GEODIST", b"s", b"a", b"b"],
12947            &[
12948                b"GEOSEARCH",
12949                b"s",
12950                b"FROMLONLAT",
12951                b"15",
12952                b"37",
12953                b"BYRADIUS",
12954                b"1",
12955                b"km",
12956            ],
12957            &[
12958                b"GEOSEARCHSTORE",
12959                b"d",
12960                b"s",
12961                b"FROMLONLAT",
12962                b"15",
12963                b"37",
12964                b"BYRADIUS",
12965                b"1",
12966                b"km",
12967            ],
12968            &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
12969            &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
12970            &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
12971            &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
12972        ];
12973        for case in cases {
12974            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
12975        }
12976        // And it wins over an argument that will not parse, which is the whole
12977        // reason the lookup comes first.
12978        assert_eq!(
12979            f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
12980            wrong
12981        );
12982    }
12983
12984    // ----------------------------------------------------------------- array
12985
12986    #[test]
12987    fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
12988        let mut f = Fixture::new();
12989        // Three consecutive positions from a high index, and the reply is how
12990        // many of them were empty before rather than how many were written.
12991        assert_eq!(
12992            f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
12993            ":3\r\n"
12994        );
12995        assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
12996        assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
12997        assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
12998        // A hole and a key that is not there are the same answer.
12999        assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
13000        assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
13001        assert_eq!(
13002            f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
13003            "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
13004        );
13005        // Scattered pairs in one command, last write wins within it.
13006        assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
13007        assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
13008    }
13009
13010    /// The two numbers an array reports are not the same number, and one of
13011    /// them does not fit a signed integer.
13012    #[test]
13013    fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
13014        let mut f = Fixture::new();
13015        assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
13016        assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
13017        f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
13018        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13019        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13020        // Deleting in the middle leaves the high water mark where it was.
13021        assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
13022        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13023        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
13024
13025        // The top of the space is addressable, and its length is a number with
13026        // bit sixty three set, so the reply has to be unsigned or it comes back
13027        // negative.
13028        f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
13029        assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
13030        assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
13031        // And one past it does not exist, so a write that would reach it fails
13032        // before any of it lands.
13033        assert_eq!(
13034            f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
13035            "-ERR array index overflow\r\n"
13036        );
13037        assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
13038    }
13039
13040    /// One reply per position and not one per element, which is the whole
13041    /// reason the range is capped.
13042    #[test]
13043    fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
13044        let mut f = Fixture::new();
13045        f.run(&[b"ARSET", b"a", b"1", b"x"]);
13046        assert_eq!(
13047            f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
13048            "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
13049        );
13050        // The two ends may come in either order, and the answer is reversed
13051        // rather than empty.
13052        assert_eq!(
13053            f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
13054            "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
13055        );
13056        // A key that is not there reads like an array of nothing but holes.
13057        assert_eq!(
13058            f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
13059            "*2\r\n$-1\r\n$-1\r\n"
13060        );
13061        // A range wider than a million positions is refused and not trimmed,
13062        // because against a missing key it is a request for as many nulls as
13063        // the range is wide.
13064        assert_eq!(
13065            f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
13066            "-ERR range exceeds maximum of 1000000 items\r\n"
13067        );
13068    }
13069
13070    /// Every index in the argument list is read before the key is touched, so
13071    /// a bad one at the end leaves nothing half written.
13072    #[test]
13073    fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
13074        let mut f = Fixture::new();
13075        assert_eq!(
13076            f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
13077            "-ERR invalid array index\r\n"
13078        );
13079        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13080        f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
13081        assert_eq!(
13082            f.run(&[b"ARDEL", b"a", b"0", b"01"]),
13083            "-ERR invalid array index\r\n"
13084        );
13085        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
13086        // An index is unsigned here, so the numbers a list would take are not
13087        // the last element, they are errors.
13088        assert_eq!(
13089            f.run(&[b"ARGET", b"a", b"-1"]),
13090            "-ERR invalid array index\r\n"
13091        );
13092        // And a pair list with an odd tail is an arity error rather than a
13093        // syntax one.
13094        assert_eq!(
13095            f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
13096            "-ERR wrong number of arguments for 'armset' command\r\n"
13097        );
13098        assert_eq!(
13099            f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
13100            "-ERR wrong number of arguments for 'ardelrange' command\r\n"
13101        );
13102    }
13103
13104    #[test]
13105    fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
13106        let mut f = Fixture::new();
13107        f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
13108        assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
13109        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13110        // Two ranges in one command, and the second one covers the whole space
13111        // without walking it.
13112        assert_eq!(
13113            f.run(&[
13114                b"ARDELRANGE",
13115                b"a",
13116                b"100",
13117                b"200",
13118                b"0",
13119                b"18446744073709551614"
13120            ]),
13121            ":2\r\n"
13122        );
13123        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13124        assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
13125        assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
13126    }
13127
13128    /// A value goes out as the bytes it came in as, whichever of the three ways
13129    /// the array found to store it.
13130    #[test]
13131    fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
13132        let mut f = Fixture::new();
13133        let long = vec![b'v'; 200];
13134        f.run(&[
13135            b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
13136            b"short", b"5", &long, b"6", b"-0",
13137        ]);
13138        // 42 is an integer, 007 is not one because it does not print back the
13139        // same, 3.5 survives a double and 3.14 does not, and the last two are a
13140        // word packed string and a blob.
13141        assert_eq!(
13142            f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
13143            format!(
13144                "*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",
13145                String::from_utf8_lossy(&long)
13146            )
13147        );
13148    }
13149
13150    #[test]
13151    fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
13152        let mut f = Fixture::new();
13153        f.run(&[b"ARSET", b"a", b"0", b"x"]);
13154        assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
13155        assert_eq!(
13156            f.run(&[b"OBJECT", b"ENCODING", b"a"]),
13157            "$12\r\nsliced-array\r\n"
13158        );
13159        // And it is a body like any other, so the key commands work on it.
13160        assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
13161        assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
13162        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
13163        assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
13164        assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
13165        assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
13166    }
13167
13168    #[test]
13169    fn every_array_command_refuses_a_key_holding_something_else() {
13170        let mut f = Fixture::new();
13171        f.run(&[b"SET", b"s", b"v"]);
13172        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13173        for cmd in [
13174            &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
13175            &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
13176            &[b"ARGET".as_ref(), b"s", b"0"][..],
13177            &[b"ARMGET".as_ref(), b"s", b"0"][..],
13178            &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
13179            &[b"ARLEN".as_ref(), b"s"][..],
13180            &[b"ARCOUNT".as_ref(), b"s"][..],
13181            &[b"ARDEL".as_ref(), b"s", b"0"][..],
13182            &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
13183            &[b"ARINSERT".as_ref(), b"s", b"x"][..],
13184            &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
13185            &[b"ARNEXT".as_ref(), b"s"][..],
13186            &[b"ARSEEK".as_ref(), b"s", b"1"][..],
13187            &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
13188            &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
13189            &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
13190            &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
13191            &[b"ARINFO".as_ref(), b"s"][..],
13192        ] {
13193            assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
13194        }
13195    }
13196
13197    /// Two of the array commands look the key up before they read the index and
13198    /// the rest read the index first, so the same broken argument gets two
13199    /// different errors depending on which command it went to.
13200    #[test]
13201    fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
13202        let mut f = Fixture::new();
13203        f.run(&[b"SET", b"s", b"v"]);
13204        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13205        let bad = "-ERR invalid array index\r\n";
13206        assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
13207        assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
13208        assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
13209        assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
13210        assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
13211        assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
13212        // And on a key that is an array the index is just an index.
13213        f.run(&[b"ARSET", b"a", b"0", b"x"]);
13214        assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
13215        assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
13216    }
13217
13218    #[test]
13219    fn an_append_follows_a_cursor_the_client_can_move() {
13220        let mut f = Fixture::new();
13221        assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
13222        assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
13223        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
13224        assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
13225        assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
13226
13227        // A seek says where the next one goes, and a missing key has no cursor
13228        // to move and is not created by the asking.
13229        assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
13230        assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
13231        assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
13232        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
13233        assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
13234        assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
13235        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
13236
13237        // The top of the space is the one index only ARSEEK will take, and it
13238        // leaves the cursor with nowhere to go.
13239        assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
13240        assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
13241        assert_eq!(
13242            f.run(&[b"ARINSERT", b"a", b"x"]),
13243            "-ERR insert index overflow\r\n"
13244        );
13245        assert_eq!(
13246            f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
13247            "-ERR invalid array index\r\n"
13248        );
13249    }
13250
13251    #[test]
13252    fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
13253        let mut f = Fixture::new();
13254        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
13255        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
13256        assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
13257        assert_eq!(
13258            f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
13259            "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
13260        );
13261        // Growing it after it has wrapped puts the survivors back in the order
13262        // they arrived, which is the whole point of paying for the rebuild.
13263        assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
13264        assert_eq!(
13265            f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
13266            "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
13267        );
13268        // The size is read before the key, so a bad one is a bad size wherever
13269        // it is sent.
13270        assert_eq!(
13271            f.run(&[b"ARRING", b"r", b"0", b"x"]),
13272            "-ERR size must be positive\r\n"
13273        );
13274        assert_eq!(
13275            f.run(&[b"ARRING", b"r", b"big", b"x"]),
13276            "-ERR invalid size\r\n"
13277        );
13278    }
13279
13280    #[test]
13281    fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
13282        let mut f = Fixture::new();
13283        assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
13284        f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
13285        assert_eq!(
13286            f.run(&[b"ARLASTITEMS", b"r", b"3"]),
13287            "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
13288        );
13289        assert_eq!(
13290            f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
13291            "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
13292        );
13293        assert_eq!(
13294            f.run(&[b"ARLASTITEMS", b"r", b"99"]),
13295            "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
13296            "more than there is gets what there is"
13297        );
13298        // Nothing asked for is an empty reply, and Redis answers that before it
13299        // has read the option or looked at the key.
13300        assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
13301        assert_eq!(
13302            f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
13303            "-ERR syntax error\r\n"
13304        );
13305        assert_eq!(
13306            f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
13307            "-ERR invalid COUNT\r\n"
13308        );
13309
13310        // With no cursor the tail of the array is the anchor, and a hole inside
13311        // the window is reported as one.
13312        f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
13313        assert_eq!(
13314            f.run(&[b"ARLASTITEMS", b"h", b"5"]),
13315            "*2\r\n$-1\r\n$1\r\nz\r\n"
13316        );
13317    }
13318
13319    #[test]
13320    fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
13321        let mut f = Fixture::new();
13322        assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
13323        f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
13324        // The whole index space, which ARGETRANGE refuses and this one answers
13325        // in three visits because holes cost nothing.
13326        assert_eq!(
13327            f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
13328            "*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"
13329        );
13330        assert_eq!(
13331            f.run(&[
13332                b"ARSCAN",
13333                b"a",
13334                b"18446744073709551614",
13335                b"0",
13336                b"LIMIT",
13337                b"1"
13338            ]),
13339            "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
13340        );
13341        assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
13342        assert_eq!(
13343            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
13344            "-ERR LIMIT must be positive\r\n"
13345        );
13346        assert_eq!(
13347            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
13348            "-ERR syntax error\r\n"
13349        );
13350        assert_eq!(
13351            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
13352            "-ERR wrong number of arguments for 'arscan' command\r\n"
13353        );
13354    }
13355
13356    #[test]
13357    fn a_grep_answers_the_indexes_whose_elements_match() {
13358        let mut f = Fixture::new();
13359        assert_eq!(
13360            f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
13361            "*0\r\n"
13362        );
13363        f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
13364
13365        // The two bounds take the ends of the array as well as an index, and a
13366        // reversed range is walked backwards the way ARSCAN walks one.
13367        assert_eq!(
13368            f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
13369            "*3\r\n:0\r\n:1\r\n:2\r\n"
13370        );
13371        assert_eq!(
13372            f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
13373            "*3\r\n:2\r\n:1\r\n:0\r\n"
13374        );
13375        assert_eq!(
13376            f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
13377            "*2\r\n:1\r\n:2\r\n"
13378        );
13379
13380        // One test each. NOCASE reaches all four of them and it may be written
13381        // after the pattern it applies to.
13382        assert_eq!(
13383            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
13384            "*1\r\n:0\r\n"
13385        );
13386        assert_eq!(
13387            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
13388            "*2\r\n:0\r\n:3\r\n"
13389        );
13390        assert_eq!(
13391            f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
13392            "*1\r\n:2\r\n"
13393        );
13394        assert_eq!(
13395            f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
13396            "*2\r\n:1\r\n:2\r\n"
13397        );
13398
13399        // OR is the default and AND has to be asked for, and either way the
13400        // last of a repeated option wins.
13401        let both: &[&[u8]] = &[
13402            b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
13403        ];
13404        assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
13405        assert_eq!(
13406            f.run(&[
13407                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
13408            ]),
13409            "*0\r\n"
13410        );
13411        assert_eq!(
13412            f.run(&[
13413                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
13414            ]),
13415            "*2\r\n:0\r\n:1\r\n"
13416        );
13417
13418        // WITHVALUES turns each hit into a pair, and LIMIT counts the hits and
13419        // not the positions it had to look at.
13420        assert_eq!(
13421            f.run(&[
13422                b"ARGREP",
13423                b"a",
13424                b"-",
13425                b"+",
13426                b"MATCH",
13427                b"a",
13428                b"WITHVALUES",
13429                b"LIMIT",
13430                b"2"
13431            ]),
13432            "*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"
13433        );
13434        assert_eq!(
13435            f.run(&[
13436                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
13437            ]),
13438            "*1\r\n:3\r\n"
13439        );
13440    }
13441
13442    /// Everything ARGREP refuses, in the order it refuses it.
13443    #[test]
13444    fn a_grep_reports_a_broken_command_the_way_redis_does() {
13445        let mut f = Fixture::new();
13446        f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
13447        let syntax = "-ERR syntax error\r\n";
13448
13449        // The bounds are read before the plan, so a bad index beats a bad
13450        // predicate whichever way round the two are written.
13451        assert_eq!(
13452            f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
13453            "-ERR invalid array index\r\n"
13454        );
13455        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
13456        // A keyword with nothing after it, and a command that asks for nothing.
13457        assert_eq!(
13458            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
13459            syntax
13460        );
13461        assert_eq!(
13462            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
13463            syntax
13464        );
13465        assert_eq!(
13466            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
13467            syntax,
13468            "a command with no predicate in it at all"
13469        );
13470        assert_eq!(
13471            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
13472            "-ERR LIMIT must be positive\r\n"
13473        );
13474        assert_eq!(
13475            f.run(&[
13476                b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
13477            ]),
13478            "-ERR value is not an integer or out of range\r\n"
13479        );
13480        assert_eq!(
13481            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
13482            "-ERR regular expression is empty\r\n"
13483        );
13484        assert_eq!(
13485            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
13486            "-ERR invalid regular expression: Missing ')'\r\n"
13487        );
13488        assert_eq!(
13489            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
13490            "-ERR regular expression backreferences are not supported\r\n"
13491        );
13492        // The arity is minus six, so a predicate keyword with no pattern after
13493        // it is short by one and never reaches the parser.
13494        let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
13495        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
13496        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
13497    }
13498
13499    #[test]
13500    fn an_op_reduces_a_range_to_one_number() {
13501        let mut f = Fixture::new();
13502        f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
13503        assert_eq!(
13504            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
13505            "$4\r\n-0.5\r\n"
13506        );
13507        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
13508        assert_eq!(
13509            f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
13510            "$3\r\n2.5\r\n"
13511        );
13512        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
13513        assert_eq!(
13514            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
13515            ":1\r\n"
13516        );
13517        // An aggregate is written with seventeen significant digits, which is
13518        // Redis's own choice and not what a score comes back as.
13519        f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
13520        assert_eq!(
13521            f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
13522            "$19\r\n0.30000000000000004\r\n"
13523        );
13524        assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
13525        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
13526
13527        // Nothing to work with is a null, and a missing key is a null for the
13528        // aggregates and a zero for the two that count.
13529        f.run(&[b"ARSET", b"w", b"0", b"word"]);
13530        assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
13531        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
13532        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
13533
13534        assert_eq!(
13535            f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
13536            "-ERR unknown operation\r\n"
13537        );
13538        assert_eq!(
13539            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
13540            "-ERR MATCH requires a value argument\r\n"
13541        );
13542        assert_eq!(
13543            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
13544            "-ERR wrong number of arguments for 'arop' command\r\n"
13545        );
13546    }
13547
13548    #[test]
13549    fn the_info_is_a_map_and_a_missing_key_is_an_error() {
13550        let mut f = Fixture::new();
13551        assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
13552        f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
13553        let short = f.run(&[b"ARINFO", b"a"]);
13554        assert!(
13555            short.starts_with("*14\r\n"),
13556            "seven pairs on RESP2: {short}"
13557        );
13558        assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
13559        assert!(
13560            short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
13561            "{short}"
13562        );
13563        assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
13564        let full = f.run(&[b"ARINFO", b"a", b"full"]);
13565        assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
13566        // Two values one apart are held sparsely, so the dense count is zero and
13567        // the two dense averages have nothing to average.
13568        assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
13569        assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
13570        assert!(
13571            full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
13572            "{full}"
13573        );
13574        assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
13575
13576        // On RESP3 the same reply is a map and the averages are doubles.
13577        let mut g = Fixture::new();
13578        g.run(&[b"HELLO", b"3"]);
13579        g.run(&[b"ARINSERT", b"a", b"x"]);
13580        let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
13581        assert!(map.starts_with("%12\r\n"), "{map}");
13582        assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
13583        assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
13584    }
13585
13586    #[test]
13587    fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
13588        let mut f = Fixture::new();
13589        // Whole numbers up to two to the sixty second come back as integers,
13590        // and past that the digit generator takes over and uses an exponent.
13591        for (score, want) in [
13592            ("3", "3"),
13593            ("3.5", "3.5"),
13594            ("0.3", "0.3"),
13595            ("1e30", "1e+30"),
13596            ("1e19", "1e+19"),
13597            ("1e-7", "1e-7"),
13598            ("0.000001", "0.000001"),
13599            ("4611686018427387904", "4611686018427387904"),
13600            ("-0", "-0"),
13601        ] {
13602            f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
13603            assert_eq!(
13604                f.run(&[b"ZSCORE", b"z", b"m"]),
13605                format!("${}\r\n{want}\r\n", want.len()),
13606                "score {score}"
13607            );
13608        }
13609
13610        // The same bytes on RESP3, where the reply is a double rather than a
13611        // bulk string.
13612        let mut g = Fixture::new();
13613        g.run(&[b"HELLO", b"3"]);
13614        g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
13615        assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
13616        // The two float increments are not this printer. They go through
13617        // ld2string in its human mode, which is a fixed point conversion with
13618        // the trailing zeros taken off, so they never write an exponent, and
13619        // they reply with a bulk string on both protocols.
13620        assert_eq!(
13621            g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
13622            "$31\r\n1000000000000000000000000000000\r\n"
13623        );
13624        assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
13625        assert_eq!(
13626            g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
13627            "$20\r\n10000000000000000000\r\n"
13628        );
13629    }
13630
13631    // ----------------------------------------------------------------- graph
13632
13633    #[test]
13634    fn a_node_comes_back_with_the_fields_it_went_in_with() {
13635        let mut f = Fixture::new();
13636        assert_eq!(
13637            f.run(&[
13638                b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
13639            ]),
13640            ":1\r\n"
13641        );
13642        // The year comes back as the four bytes that were sent and not as a
13643        // number, because every property is text and there is nothing on the
13644        // wire that says which of `1815` and `"1815"` the client meant. The
13645        // fields are in the document's order, which is sorted by name, because
13646        // that is what makes a field lookup a binary search.
13647        assert_eq!(
13648            f.run(&[b"G.NGET", b"social", b"ada"]),
13649            "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13650        );
13651        // A second write to the same id replaces the document and says so with
13652        // a zero, so an ingest can count what it created.
13653        assert_eq!(
13654            f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
13655            ":0\r\n"
13656        );
13657        assert_eq!(
13658            f.run(&[b"G.NGET", b"social", b"ada"]),
13659            "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
13660        );
13661        // A node with no properties is an empty map and not a null, which is
13662        // how a client tells an isolated node from one that is not there.
13663        assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
13664        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13665        assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
13666        assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
13667
13668        // A field with no value creates nothing, because the pairs are checked
13669        // before the key is touched.
13670        assert_eq!(
13671            f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
13672            "-ERR syntax error\r\n"
13673        );
13674        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
13675
13676        // On RESP3 the same reply is a map.
13677        let mut g = Fixture::new();
13678        g.run(&[b"HELLO", b"3"]);
13679        g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
13680        assert_eq!(
13681            g.run(&[b"G.NGET", b"social", b"ada"]),
13682            "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13683        );
13684    }
13685
13686    #[test]
13687    fn an_edge_creates_the_ends_it_needs() {
13688        let mut f = Fixture::new();
13689        assert_eq!(
13690            f.run(&[
13691                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
13692            ]),
13693            ":1\r\n"
13694        );
13695        // Neither end was written first and both are there, as empty nodes.
13696        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
13697        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13698        assert_eq!(
13699            f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
13700            "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
13701        );
13702        assert_eq!(
13703            f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
13704            "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
13705        );
13706        // The same pair under the same label again updates the edge rather than
13707        // making a second one.
13708        assert_eq!(
13709            f.run(&[
13710                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
13711            ]),
13712            ":0\r\n"
13713        );
13714        assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
13715        // A different label between the same pair is a different edge.
13716        assert_eq!(
13717            f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
13718            ":1\r\n"
13719        );
13720        assert_eq!(
13721            f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
13722            ":1\r\n"
13723        );
13724
13725        assert_eq!(
13726            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13727            ":1\r\n"
13728        );
13729        assert_eq!(
13730            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13731            ":0\r\n"
13732        );
13733        // A label nothing has used, an end that is not there, and a key that is
13734        // not there are all a zero rather than an error.
13735        assert_eq!(
13736            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
13737            ":0\r\n"
13738        );
13739        assert_eq!(
13740            f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
13741            ":0\r\n"
13742        );
13743        assert_eq!(
13744            f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
13745            ":0\r\n"
13746        );
13747    }
13748
13749    /// A run is paged the way `SCAN` is paged, so a client that can walk one
13750    /// can walk the other.
13751    #[test]
13752    fn a_hop_answers_a_cursor_and_a_page() {
13753        let mut f = Fixture::new();
13754        for i in 0..25u32 {
13755            let dst = format!("n{i}");
13756            f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
13757        }
13758        // Ten without being asked, and the cursor is where to carry on from.
13759        let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
13760        assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
13761
13762        let mut seen = 0;
13763        let mut cursor = String::from("0");
13764        loop {
13765            let page = f.run(&[
13766                b"G.OUT",
13767                b"social",
13768                b"hub",
13769                b"FOLLOWS",
13770                b"COUNT",
13771                b"7",
13772                b"CURSOR",
13773                cursor.as_bytes(),
13774            ]);
13775            let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
13776            cursor = head
13777                .rsplit("\r\n")
13778                .next()
13779                .expect("the cursor line")
13780                .to_string();
13781            seen += rest
13782                .split_once("\r\n")
13783                .expect("the page length")
13784                .0
13785                .parse::<usize>()
13786                .expect("a length");
13787            if cursor == "0" {
13788                break;
13789            }
13790        }
13791        assert_eq!(seen, 25, "every neighbour once across the pages");
13792
13793        // A cursor past the end is an empty page and not an error, and so is a
13794        // key or a label that is not there.
13795        assert_eq!(
13796            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
13797            "*2\r\n$1\r\n0\r\n*0\r\n"
13798        );
13799        assert_eq!(
13800            f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
13801            "*2\r\n$1\r\n0\r\n*0\r\n"
13802        );
13803        assert_eq!(
13804            f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
13805            "*2\r\n$1\r\n0\r\n*0\r\n"
13806        );
13807        assert_eq!(
13808            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
13809            "-ERR COUNT must be a positive integer\r\n"
13810        );
13811        assert_eq!(
13812            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
13813            "-ERR syntax error\r\n"
13814        );
13815    }
13816
13817    #[test]
13818    fn a_degree_counts_one_way_or_both() {
13819        let mut f = Fixture::new();
13820        f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
13821        f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
13822        f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
13823        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
13824        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
13825        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
13826        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
13827        assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
13828        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
13829        assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
13830        assert_eq!(
13831            f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
13832            "-ERR syntax error\r\n"
13833        );
13834    }
13835
13836    /// A walk answers which nodes it can reach and not by how many routes, so a
13837    /// node two ways out is in the frontier once.
13838    #[test]
13839    fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
13840        let mut f = Fixture::new();
13841        for (src, dst) in [
13842            ("ada", "grace"),
13843            ("ada", "alan"),
13844            ("grace", "edsger"),
13845            ("alan", "edsger"),
13846            ("edsger", "barbara"),
13847        ] {
13848            f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
13849        }
13850        // Two hops without being asked, the start left out, and edsger once
13851        // even though both of the first hop's nodes point at it.
13852        assert_eq!(
13853            f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
13854            "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
13855        );
13856        assert_eq!(
13857            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
13858            "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
13859        );
13860        let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
13861        assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
13862        assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
13863        // COUNT stops the walk rather than trimming what it found.
13864        assert_eq!(
13865            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
13866            "*1\r\n$5\r\ngrace\r\n"
13867        );
13868        // A node nothing leaves is an empty array and not an error.
13869        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
13870        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
13871        assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
13872        assert_eq!(
13873            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
13874            "-ERR DEPTH must be a positive integer\r\n"
13875        );
13876        assert_eq!(
13877            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
13878            "-ERR syntax error\r\n"
13879        );
13880    }
13881
13882    /// The two sided search, which is the whole reason `G.PATH` is a command
13883    /// and not something a client builds out of `G.OUT`.
13884    #[test]
13885    fn a_path_is_the_shortest_one_and_goes_over_any_label() {
13886        let mut f = Fixture::new();
13887        // A chain of six, and a shortcut that makes a shorter way round under a
13888        // second label so the search has to take either kind of hop.
13889        for i in 0..6u32 {
13890            let src = format!("n{i}");
13891            let dst = format!("n{}", i + 1);
13892            f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
13893        }
13894        assert_eq!(
13895            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13896            "*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"
13897        );
13898        f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
13899        assert_eq!(
13900            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13901            "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
13902        );
13903        // A node to itself is a path of one, and a depth too short to reach is
13904        // no path at all.
13905        assert_eq!(
13906            f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
13907            "*1\r\n$2\r\nn2\r\n"
13908        );
13909        assert_eq!(
13910            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
13911            "*0\r\n"
13912        );
13913        // Direction counts: the chain only goes one way.
13914        assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
13915        // An unreachable node, a node that is not there, and a key that is not
13916        // there are the same empty answer.
13917        f.run(&[b"G.NADD", b"road", b"island"]);
13918        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
13919        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
13920        assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
13921        assert_eq!(
13922            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
13923            "-ERR syntax error\r\n"
13924        );
13925    }
13926
13927    /// The point of the escape in the record tag: the keyspace owns a graph key
13928    /// the way it owns every other key, and none of these commands know a graph
13929    /// exists.
13930    #[test]
13931    fn the_keyspace_sees_a_graph_key_like_any_other() {
13932        let mut f = Fixture::new();
13933        f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
13934        assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
13935        assert_eq!(
13936            f.run(&[b"OBJECT", b"ENCODING", b"social"]),
13937            "$9\r\nadjacency\r\n"
13938        );
13939        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13940        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13941        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
13942        // A graph is counted against the server the way every other body is,
13943        // which is what `maxmemory` will read when this key is a million nodes.
13944        // There is no `MEMORY USAGE` command yet, so this asks the server.
13945        let held = f.server.memory_bytes();
13946        for i in 0..200u32 {
13947            let dst = format!("n{i}");
13948            f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
13949        }
13950        assert!(
13951            f.server.memory_bytes() > held,
13952            "two hundred edges cost something: {held} then {}",
13953            f.server.memory_bytes()
13954        );
13955        f.run(&[b"DEL", b"big"]);
13956
13957        // An expiry, then a rename, then a move to another database, all of
13958        // which are the keyspace moving a record it cannot look inside.
13959        assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
13960        assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
13961        assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
13962        assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
13963        assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
13964        f.run(&[b"SELECT", b"1"]);
13965        assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
13966
13967        assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
13968        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13969        f.run(&[b"G.NADD", b"g", b"n"]);
13970        assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
13971        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13972    }
13973
13974    /// Neither `COPY` nor `DUMP` has a byte shape for a graph, so both say so
13975    /// rather than answering the way they answer for a key that is not there.
13976    #[test]
13977    fn a_graph_cannot_be_copied_or_dumped() {
13978        let mut f = Fixture::new();
13979        f.run(&[b"G.NADD", b"social", b"ada"]);
13980        assert_eq!(
13981            f.run(&[b"COPY", b"social", b"other"]),
13982            "-ERR COPY is not supported for a graph\r\n"
13983        );
13984        assert_eq!(
13985            f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
13986            "-ERR COPY is not supported for a graph\r\n"
13987        );
13988        assert_eq!(
13989            f.run(&[b"DUMP", b"social"]),
13990            "-ERR DUMP is not supported for a graph\r\n"
13991        );
13992        // A refused copy leaves both keys exactly as they were.
13993        assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
13994    }
13995
13996    /// A graph key is a key, so the commands for the other types refuse it and
13997    /// the graph commands refuse theirs.
13998    #[test]
13999    fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
14000        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14001        let mut f = Fixture::new();
14002        f.run(&[b"G.NADD", b"social", b"ada"]);
14003        assert_eq!(f.run(&[b"GET", b"social"]), wrong);
14004        assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
14005        assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
14006
14007        f.run(&[b"SET", b"str", b"v"]);
14008        for cmd in [
14009            vec![b"G.NADD".as_ref(), b"str", b"n"],
14010            vec![b"G.NGET".as_ref(), b"str", b"n"],
14011            vec![b"G.NDEL".as_ref(), b"str", b"n"],
14012            vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
14013            vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
14014            vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
14015            vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
14016            vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
14017            vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
14018            vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
14019        ] {
14020            assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
14021        }
14022    }
14023
14024    /// Every other collection here takes its key with it when its last member
14025    /// goes, and a graph is no different.
14026    #[test]
14027    fn a_graph_goes_when_its_last_node_does() {
14028        let mut f = Fixture::new();
14029        f.run(&[
14030            b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
14031        ]);
14032        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
14033        // The node and the edges that hung off it are both gone.
14034        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
14035        assert_eq!(
14036            f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
14037            ":0\r\n"
14038        );
14039        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
14040        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
14041
14042        assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
14043        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
14044        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14045        assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
14046
14047        // The id the removed node had is not handed out again, so a client
14048        // holding an id from an earlier reply cannot have it mean another node.
14049        f.run(&[b"G.NADD", b"social", b"first"]);
14050        f.run(&[b"G.NADD", b"social", b"second"]);
14051        f.run(&[b"G.NDEL", b"social", b"first"]);
14052        f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
14053        assert_eq!(
14054            f.run(&[b"G.OUT", b"social", b"third", b"F"]),
14055            "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
14056        );
14057    }
14058
14059    // ------------------------------------------------------------------ json
14060
14061    /// The two path syntaxes answer different shapes, which is the thing a
14062    /// client is most likely to be broken by and so the thing to pin first.
14063    #[test]
14064    fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
14065        let mut f = Fixture::new();
14066        let doc = br#"{"a":1,"b":{"c":true}}"#;
14067        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
14068        // No path at all is the legacy root and not `$`, so the document comes
14069        // back as itself rather than wrapped.
14070        assert_eq!(
14071            f.run(&[b"JSON.GET", b"doc"]),
14072            bulk(r#"{"a":1,"b":{"c":true}}"#)
14073        );
14074        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
14075        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
14076        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
14077        // A path that matched nothing is an empty set on one syntax and an
14078        // error on the other, and the error does not quote the path.
14079        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
14080        assert_eq!(
14081            f.run(&[b"JSON.GET", b"doc", b".nope"]),
14082            "-ERR Path does not exist\r\n"
14083        );
14084        assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
14085        // The key is a document to the rest of the keyspace, under the name
14086        // RedisJSON registers, and every generic command works on it.
14087        assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
14088        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
14089        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
14090        assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
14091        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14092    }
14093
14094    /// The two error lines RedisJSON sends without a prefix in front of them.
14095    ///
14096    /// Every other error this server writes starts `ERR` or `WRONGTYPE`. These
14097    /// two do not, on a real server, and a differential harness compares the
14098    /// whole line.
14099    #[test]
14100    fn the_two_json_errors_that_carry_no_prefix() {
14101        let mut f = Fixture::new();
14102        f.run(&[b"SET", b"plain", b"x"]);
14103        let wrong = "-Existing key has wrong Redis type\r\n";
14104        assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
14105        assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
14106        assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
14107        assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
14108        assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
14109
14110        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
14111        // A wildcard that matched something writes to all of it. A wildcard
14112        // that matched nothing would have to invent a place, and that is the
14113        // other unprefixed line.
14114        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
14115        assert_eq!(
14116            f.run(&[b"JSON.GET", b"doc"]),
14117            bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
14118        );
14119        assert_eq!(
14120            f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
14121            "-Err wrong static path\r\n"
14122        );
14123    }
14124
14125    /// What `JSON.SET` does with a path that named nowhere.
14126    #[test]
14127    fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
14128        let mut f = Fixture::new();
14129        // A key that is not there can only be written whole.
14130        assert_eq!(
14131            f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
14132            "-ERR new objects must be created at the root\r\n"
14133        );
14134        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14135        // The root check comes before NX and XX, which is the order a real
14136        // server checks them in.
14137        assert_eq!(
14138            f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
14139            "-ERR new objects must be created at the root\r\n"
14140        );
14141        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
14142        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
14143
14144        f.run(&[
14145            b"JSON.SET",
14146            b"doc",
14147            b"$",
14148            br#"{"o":{},"arr":[1,2],"s":"x"}"#,
14149        ]);
14150        // One step past a container that is there is a place to write.
14151        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
14152        // One step past something that is not, or past something that is not an
14153        // object, is not an error and is not a write either.
14154        assert_eq!(
14155            f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
14156            "$-1\r\n"
14157        );
14158        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
14159        // An index past the end does not append. JSON.ARRAPPEND appends.
14160        assert_eq!(
14161            f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
14162            "-ERR array index out of range\r\n"
14163        );
14164        assert_eq!(
14165            f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
14166            "-ERR array index out of range\r\n"
14167        );
14168        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
14169        // NX on a path that is there and XX on a path that is not are both a
14170        // nil and neither changes anything.
14171        assert_eq!(
14172            f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
14173            "$-1\r\n"
14174        );
14175        assert_eq!(
14176            f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
14177            "$-1\r\n"
14178        );
14179        assert_eq!(
14180            f.run(&[b"JSON.GET", b"doc"]),
14181            bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
14182        );
14183        // Text that is not JSON is refused before the key is touched. The
14184        // line has no `ERR` in front of it, which is this command's and not
14185        // every command's, and is in D-37.
14186        assert!(
14187            f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
14188                .starts_with("-this is not the start of a value")
14189        );
14190        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
14191    }
14192
14193    /// `JSON.DEL`, `JSON.TYPE`, `JSON.TOGGLE` and `JSON.CLEAR`, each of which
14194    /// answers a count or a word rather than text.
14195    #[test]
14196    fn the_json_commands_that_do_not_answer_text() {
14197        let mut f = Fixture::new();
14198        let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
14199        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14200
14201        assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
14202        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
14203        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
14204        assert_eq!(
14205            f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
14206            format!("*1\r\n{}", bulk("integer"))
14207        );
14208        // The one place a legacy path that matched nothing is a nil rather than
14209        // an error, which lines up with a key that is not there.
14210        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
14211        assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
14212
14213        // A boolean flips and answers the value it now has, as an integer on
14214        // one syntax and as the word on the other.
14215        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
14216        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
14217        // Something that is not a boolean is a hole on one syntax and one
14218        // sentence covering both cases on the other.
14219        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
14220        assert_eq!(
14221            f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
14222            "-ERR Path does not exist or not a bool\r\n"
14223        );
14224        assert_eq!(
14225            f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
14226            "-ERR Path does not exist or not a bool\r\n"
14227        );
14228        assert_eq!(
14229            f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
14230            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14231        );
14232
14233        // Clearing empties containers and zeroes numbers and leaves everything
14234        // else alone, and counts only what it changed.
14235        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
14236        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
14237        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
14238        assert_eq!(
14239            f.run(&[b"JSON.GET", b"doc"]),
14240            bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
14241        );
14242
14243        // Deleting counts what it removed, and deleting the root is deleting
14244        // the key.
14245        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
14246        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
14247        // Deleting the last member of the root container deletes the key, the
14248        // same way popping the last element off a list does. It is a rule about
14249        // deleting and not about shape: a document written as an empty object
14250        // by JSON.SET stays, because nothing was removed from it.
14251        assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
14252        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
14253        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14254        assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
14255        assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
14256        assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
14257        assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
14258        assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
14259    }
14260
14261    /// `JSON.GET` with more than one path, and with a layout.
14262    ///
14263    /// The wrapper the reply is built in is laid out too, so what a path
14264    /// matched starts one level in for a single JSONPath and two for one of
14265    /// several, and getting that wrong is the kind of thing only a byte for
14266    /// byte comparison catches.
14267    #[test]
14268    fn json_get_lays_out_the_wrapper_it_builds() {
14269        let mut f = Fixture::new();
14270        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
14271
14272        assert_eq!(
14273            f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
14274            bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
14275        );
14276        // Legacy paths are not wrapped, even when there are several of them.
14277        assert_eq!(
14278            f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
14279            bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
14280        );
14281        let fmt: &[&[u8]] = &[b"INDENT", b"  ", b"NEWLINE", b"\n", b"SPACE", b" "];
14282        let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
14283        one.extend_from_slice(fmt);
14284        one.push(b"$.b");
14285        assert_eq!(
14286            f.run(&one),
14287            bulk("[\n  [\n    1,\n    {\n      \"c\": 2\n    }\n  ]\n]")
14288        );
14289        let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
14290        two.extend_from_slice(fmt);
14291        two.push(b"$.a");
14292        two.push(b"$.nope");
14293        assert_eq!(
14294            f.run(&two),
14295            bulk("{\n  \"$.a\": [\n    1\n  ],\n  \"$.nope\": []\n}")
14296        );
14297        // The options are read before the paths and in any order, and a
14298        // document with nothing to lay out is the same either way.
14299        let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
14300        root.push(b".a");
14301        assert_eq!(f.run(&root), bulk("1"));
14302    }
14303
14304    /// `JSON.MGET`, which is the only command here that reads more than one key
14305    /// and so the only one whose answer has holes in it.
14306    #[test]
14307    fn json_mget_answers_once_per_key_whatever_is_under_them() {
14308        let mut f = Fixture::new();
14309        f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
14310        f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
14311        f.run(&[b"SET", b"plain", b"x"]);
14312        assert_eq!(
14313            f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
14314            format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
14315        );
14316        // A key that is not there and a key holding something else are both a
14317        // hole rather than an error, the way MGET treats a hash.
14318        assert_eq!(
14319            f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
14320            format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
14321        );
14322        // A legacy path that matched nothing is a hole too, because one bad
14323        // answer should not lose the others.
14324        assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
14325    }
14326
14327    /// The four commands that ask how big something is, and the four different
14328    /// sets of answers they give for the same three failures.
14329    ///
14330    /// There is no pattern in this and there is no reading it off the
14331    /// documentation either. It was read off a running RedisJSON one line at a
14332    /// time, and it is written down here because the error text is what a client
14333    /// library branches on.
14334    #[test]
14335    fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
14336        let mut f = Fixture::new();
14337        let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
14338        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14339
14340        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
14341        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
14342        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
14343        assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
14344        assert_eq!(
14345            f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
14346            format!("*2\r\n{}{}", bulk("x"), bulk("y"))
14347        );
14348        // A JSONPath answers one entry per match and a hole for a match of the
14349        // wrong kind, which is the one shape all four agree on.
14350        assert_eq!(
14351            f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
14352            "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
14353        );
14354
14355        // A legacy path that matched nothing. Two of them are an error and two
14356        // of them are a nil, and the two errors do not use the same sentence.
14357        assert_eq!(
14358            f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
14359            "-ERR Path does not exist\r\n"
14360        );
14361        assert_eq!(
14362            f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
14363            "-ERR Path does not exist\r\n"
14364        );
14365        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
14366        // A nil bulk and not an empty array, even though the answer would have
14367        // been an array, which is what RedisJSON sends here too.
14368        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
14369        // The JSONPath spelling of the same question is an empty array, since
14370        // no match is not a failure on that syntax.
14371        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
14372
14373        // A legacy path that matched the wrong kind of value. Now two of them
14374        // are an ERR and two of them are a WRONGTYPE, and it is not the same
14375        // two.
14376        assert_eq!(
14377            f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
14378            "-ERR Path does not exist or not an array\r\n"
14379        );
14380        assert_eq!(
14381            f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
14382            "-ERR Path does not exist or not an object\r\n"
14383        );
14384        assert_eq!(
14385            f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
14386            "-WRONGTYPE wrong type of path value - expected object\r\n"
14387        );
14388        assert_eq!(
14389            f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
14390            "-WRONGTYPE wrong type of path value - expected string\r\n"
14391        );
14392
14393        // A key that is not there, where the two syntaxes swap over: the legacy
14394        // path is the quiet answer and the JSONPath is the error.
14395        assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
14396        assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
14397        assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
14398        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
14399        assert_eq!(
14400            f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
14401            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14402        );
14403        // Except this one, which answers about the path instead.
14404        assert_eq!(
14405            f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
14406            "-ERR Path does not exist or not an object\r\n"
14407        );
14408    }
14409
14410    /// `JSON.ARRAPPEND`, `JSON.ARRINSERT`, `JSON.ARRTRIM` and `JSON.ARRPOP`.
14411    ///
14412    /// The four of them share one error line for a path that named something
14413    /// that is not an array, and they disagree about what an index outside the
14414    /// array means: insert refuses it and the other two clamp.
14415    #[test]
14416    fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
14417        let mut f = Fixture::new();
14418        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
14419
14420        assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
14421        assert_eq!(
14422            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
14423            "*1\r\n:6\r\n"
14424        );
14425        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
14426
14427        // A negative index counts back from the end, and the end itself is a
14428        // place to insert at, so an insert at the length is an append.
14429        assert_eq!(
14430            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
14431            ":7\r\n"
14432        );
14433        assert_eq!(
14434            f.run(&[b"JSON.GET", b"doc", b".a"]),
14435            bulk("[1,2,3,4,5,0,6]")
14436        );
14437        assert_eq!(
14438            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
14439            ":8\r\n"
14440        );
14441        // One past the end is not, and neither is one before the front.
14442        assert_eq!(
14443            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
14444            "-ERR index out of bounds\r\n"
14445        );
14446        assert_eq!(
14447            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
14448            "-ERR index out of bounds\r\n"
14449        );
14450
14451        // Trim takes both ends inclusive and clamps both of them, so a start
14452        // past the end leaves an empty array rather than an error.
14453        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
14454        assert_eq!(
14455            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
14456            ":3\r\n"
14457        );
14458        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
14459        assert_eq!(
14460            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
14461            ":2\r\n"
14462        );
14463        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
14464        assert_eq!(
14465            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
14466            ":0\r\n"
14467        );
14468        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14469
14470        // Pop clamps as well, its default is the last element, and an empty
14471        // array pops a nil rather than failing.
14472        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
14473        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
14474        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
14475        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
14476        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
14477
14478        // One sentence covers a path that matched nothing and a path that
14479        // matched the wrong kind of value, for all four of them.
14480        for call in [
14481            &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
14482            &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
14483            &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
14484            &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
14485        ] {
14486            for path in [&b".n"[..], &b".nope"[..]] {
14487                let args: Vec<&[u8]> = call
14488                    .iter()
14489                    .map(|a| if *a == b"PATH" { path } else { *a })
14490                    .collect();
14491                assert_eq!(
14492                    f.run(&args),
14493                    "-ERR Path does not exist or not an array\r\n",
14494                    "{} {}",
14495                    String::from_utf8_lossy(call[0]),
14496                    String::from_utf8_lossy(path)
14497                );
14498            }
14499        }
14500
14501        // A key that is not there is the same sentence for all four, on either
14502        // syntax, and it is about the key and not about the path.
14503        assert_eq!(
14504            f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
14505            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14506        );
14507        assert_eq!(
14508            f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
14509            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14510        );
14511
14512        // The values are parsed before the key is touched, so text that is not
14513        // JSON leaves the document alone.
14514        // Text that is not JSON is refused before the key is touched, and
14515        // the line has no `ERR` in front of it, which is D-37.
14516        assert!(
14517            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
14518                .starts_with("-this is not the start of a value")
14519        );
14520        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14521    }
14522
14523    /// `JSON.ARRINSERT` refuses the whole command when any one of the arrays a
14524    /// path matched cannot take the index, which is D-36.
14525    ///
14526    /// RedisJSON walks the matches, inserts into each one it can, and returns
14527    /// the error on the first one it cannot, leaving the earlier inserts in the
14528    /// document. A write here is one list of edits applied together, so either
14529    /// all of them happen or none of them do.
14530    #[test]
14531    fn json_arrinsert_is_all_or_nothing_across_the_matches() {
14532        let mut f = Fixture::new();
14533        let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
14534        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14535        assert_eq!(
14536            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
14537            "-ERR index out of bounds\r\n"
14538        );
14539        assert_eq!(
14540            f.run(&[b"JSON.GET", b"doc"]),
14541            bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
14542        );
14543        // Every match can take the index, so every match gets it.
14544        assert_eq!(
14545            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
14546            "*3\r\n:4\r\n:3\r\n:2\r\n"
14547        );
14548        assert_eq!(
14549            f.run(&[b"JSON.GET", b"doc"]),
14550            bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
14551        );
14552    }
14553
14554    /// `JSON.ARRINDEX`, whose stop is exclusive and whose start clamps to the
14555    /// last element rather than to one past it.
14556    ///
14557    /// Both of those read like mistakes and both are what RedisJSON does. The
14558    /// start is the one that bites: a start of five into an array of four still
14559    /// looks at the fourth, so a search that should have run out of array comes
14560    /// back with an answer.
14561    #[test]
14562    fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
14563        let mut f = Fixture::new();
14564        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
14565
14566        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
14567        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
14568        assert_eq!(
14569            f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
14570            "*1\r\n:1\r\n"
14571        );
14572
14573        // Zero as the stop means the end rather than the front, so leaving it
14574        // off and passing it are the same thing.
14575        assert_eq!(
14576            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
14577            ":3\r\n"
14578        );
14579        // The stop is exclusive, so a stop of three does not look at index
14580        // three.
14581        assert_eq!(
14582            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
14583            ":-1\r\n"
14584        );
14585
14586        // The start clamps to the last element in both directions, which is why
14587        // a start of four, five or minus one all find the 1 at index three.
14588        for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
14589            assert_eq!(
14590                f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
14591                ":3\r\n",
14592                "{}",
14593                String::from_utf8_lossy(start)
14594            );
14595        }
14596        assert_eq!(
14597            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
14598            ":0\r\n"
14599        );
14600        // An empty array is the one case that comes back with nothing, since
14601        // the stop is zero and the loop never starts.
14602        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
14603        assert_eq!(
14604            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
14605            ":-1\r\n"
14606        );
14607
14608        // The comparison is structural rather than one of the encoded bytes,
14609        // because an object in a stored document holds its keys as intern table
14610        // ids where one parsed off the wire holds them as bytes.
14611        f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
14612        assert_eq!(
14613            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
14614            ":0\r\n"
14615        );
14616        assert_eq!(
14617            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
14618            ":1\r\n"
14619        );
14620        assert_eq!(
14621            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
14622            ":-1\r\n"
14623        );
14624
14625        // Its errors are a third set again: a missing legacy path is the short
14626        // sentence, the wrong kind of value is a WRONGTYPE, and a key that is
14627        // not there is about the path on either syntax.
14628        assert_eq!(
14629            f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
14630            "-ERR Path does not exist\r\n"
14631        );
14632        assert_eq!(
14633            f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
14634            "-WRONGTYPE wrong type of path value - expected array\r\n"
14635        );
14636        assert_eq!(
14637            f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
14638            "-ERR Path does not exist\r\n"
14639        );
14640        assert_eq!(
14641            f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
14642            "-ERR Path does not exist\r\n"
14643        );
14644    }
14645
14646    /// The number family answers text and keeps an integer an integer until
14647    /// something in the sum is not one.
14648    #[test]
14649    fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
14650        let mut f = Fixture::new();
14651        let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
14652        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14653
14654        // A legacy path answers the new value as JSON text in a bulk string,
14655        // not as a number, which is the shape all three of them use.
14656        assert_eq!(
14657            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
14658            bulk("9").as_str()
14659        );
14660        // A JSONPath answers a bulk string holding a JSON array.
14661        assert_eq!(
14662            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
14663            bulk("[11]").as_str()
14664        );
14665        // Two integers stay an integer and a double anywhere in it makes the
14666        // answer a double, which the document then holds.
14667        assert_eq!(
14668            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
14669            bulk("13.0").as_str()
14670        );
14671        assert_eq!(
14672            f.run(&[b"JSON.TYPE", b"doc", b".i"]),
14673            bulk("number").as_str()
14674        );
14675        assert_eq!(
14676            f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
14677            bulk("3.0").as_str()
14678        );
14679        assert_eq!(
14680            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
14681            bulk("-8").as_str()
14682        );
14683        // A power of a half is a square root, and the square root of a negative
14684        // number is the error that says the answer is not a number.
14685        f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
14686        assert_eq!(
14687            f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
14688            bulk("1.224744871391589").as_str()
14689        );
14690        assert_eq!(
14691            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
14692            "-ERR result is not a number\r\n"
14693        );
14694        // An integer answer that does not fit is refused rather than promoted,
14695        // and a negative exponent lands in the same error because there is no
14696        // integer answer to two to the minus one.
14697        f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
14698        assert_eq!(
14699            f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
14700            "-ERR numeric overflow\r\n"
14701        );
14702        f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
14703        assert_eq!(
14704            f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
14705            "-ERR numeric overflow\r\n"
14706        );
14707        // A double that leaves the finite numbers is the other error.
14708        f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
14709        assert_eq!(
14710            f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
14711            "-ERR result is not a number\r\n"
14712        );
14713
14714        // A match that is not a number is a null inside the array on a
14715        // JSONPath, and a legacy path that found no number at all is the error
14716        // with the module's own typo in it.
14717        assert_eq!(
14718            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
14719            bulk("[null]").as_str()
14720        );
14721        assert_eq!(
14722            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
14723            bulk("[]").as_str()
14724        );
14725        assert_eq!(
14726            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
14727            "-ERR Path does not exist or does not contains a number\r\n"
14728        );
14729        assert_eq!(
14730            f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
14731            "-ERR Path does not exist or does not contains a number\r\n"
14732        );
14733        // The operand is JSON and has to be a number. Valid JSON that is not
14734        // one is a line of its own, and it goes out without a prefix.
14735        assert_eq!(
14736            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
14737            "-bad input number\r\n"
14738        );
14739        assert_eq!(
14740            f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
14741            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14742        );
14743        assert_eq!(
14744            f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
14745            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14746        );
14747    }
14748
14749    /// `JSON.STRAPPEND` puts its path in the middle and makes it optional,
14750    /// which nothing else in the group does.
14751    #[test]
14752    fn json_strappend_reads_its_shape_off_the_argument_count() {
14753        let mut f = Fixture::new();
14754        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
14755
14756        assert_eq!(
14757            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
14758            ":3\r\n"
14759        );
14760        assert_eq!(
14761            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
14762            "*1\r\n:4\r\n"
14763        );
14764        // The length is in bytes and not in characters, so one two byte letter
14765        // takes it up by two.
14766        assert_eq!(
14767            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
14768            ":6\r\n"
14769        );
14770        // Three arguments means the value is the last one and the path is the
14771        // root, so this appends to a document that is a string on its own.
14772        f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
14773        assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
14774        assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
14775
14776        // The value is JSON and has to be a JSON string. A number is a
14777        // WRONGTYPE about a path value even though it was the value that was
14778        // wrong, which is the module's wording and not a slip here.
14779        assert_eq!(
14780            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
14781            "-WRONGTYPE wrong type of path value - expected string\r\n"
14782        );
14783        assert_eq!(
14784            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
14785            "*1\r\n$-1\r\n"
14786        );
14787        assert_eq!(
14788            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
14789            "-ERR Path does not exist or not a string\r\n"
14790        );
14791        assert_eq!(
14792            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
14793            "*0\r\n"
14794        );
14795        assert_eq!(
14796            f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
14797            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14798        );
14799    }
14800
14801    /// A legacy path can match more than one value, and which of them the one
14802    /// answer comes from is not the same choice twice.
14803    #[test]
14804    fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
14805        let mut f = Fixture::new();
14806        // Three arrays of one, two and three elements, which tells the first
14807        // match and the last match apart in a single command.
14808        let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
14809
14810        f.run(&[b"JSON.SET", b"doc", b"$", three]);
14811        assert_eq!(
14812            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14813            ":4\r\n"
14814        );
14815        f.run(&[b"JSON.SET", b"doc", b"$", three]);
14816        assert_eq!(
14817            f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
14818            ":2\r\n"
14819        );
14820        f.run(&[b"JSON.SET", b"doc", b"$", three]);
14821        assert_eq!(
14822            f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
14823            ":1\r\n"
14824        );
14825        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
14826        assert_eq!(
14827            f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
14828            bulk("1").as_str()
14829        );
14830        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
14831        assert_eq!(
14832            f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
14833            bulk("13").as_str()
14834        );
14835        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
14836        assert_eq!(
14837            f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
14838            ":4\r\n"
14839        );
14840        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
14841        assert_eq!(
14842            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14843            bulk("false").as_str()
14844        );
14845        // Every one of them wrote to all three matches, whichever one it chose
14846        // to answer about.
14847        assert_eq!(
14848            f.run(&[b"JSON.GET", b"doc", b".a"]),
14849            bulk("[false,true,false]").as_str()
14850        );
14851
14852        // A match of the wrong kind is skipped rather than being the answer, so
14853        // a path that found a string and then two arrays still answers.
14854        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
14855        assert_eq!(
14856            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14857            ":3\r\n"
14858        );
14859        assert_eq!(
14860            f.run(&[b"JSON.GET", b"doc", b".a"]),
14861            bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
14862        );
14863        // Nothing of the right kind anywhere is the error, and that is the only
14864        // case that is.
14865        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
14866        assert_eq!(
14867            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14868            "-ERR Path does not exist or not an array\r\n"
14869        );
14870        assert_eq!(
14871            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14872            "-ERR Path does not exist or not a bool\r\n"
14873        );
14874        // The one array that was there and had nothing in it is an answer and
14875        // not a skip, so the pop answers about it rather than about the array
14876        // after it.
14877        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
14878        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
14879        assert_eq!(
14880            f.run(&[b"JSON.GET", b"doc", b".a"]),
14881            bulk("[[],[2]]").as_str()
14882        );
14883    }
14884
14885    /// A path that matched a value and something inside that value writes to
14886    /// both, which is what `$..` and a nested wildcard are for.
14887    #[test]
14888    fn a_write_reaches_a_match_that_sits_inside_another_match() {
14889        let mut f = Fixture::new();
14890        let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
14891
14892        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14893        assert_eq!(
14894            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
14895            "*3\r\n:3\r\n:2\r\n:3\r\n"
14896        );
14897        assert_eq!(
14898            f.run(&[b"JSON.GET", b"doc", b"$"]),
14899            bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
14900        );
14901
14902        // The same for a trim, where the outer array keeps the two elements the
14903        // inner writes landed in.
14904        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14905        assert_eq!(
14906            f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
14907            "*3\r\n:1\r\n:1\r\n:1\r\n"
14908        );
14909        assert_eq!(
14910            f.run(&[b"JSON.GET", b"doc", b"$"]),
14911            bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
14912        );
14913
14914        // And for a number, where the first match is the object the outer array
14915        // holds and only the two inside it are numbers.
14916        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14917        assert_eq!(
14918            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
14919            bulk("[null,8,8]").as_str()
14920        );
14921    }
14922
14923    /// The value a write is given is looked at only once the path has found
14924    /// something of the right kind to use it on.
14925    #[test]
14926    fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
14927        let mut f = Fixture::new();
14928        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
14929
14930        // A string is not a number, so the path answers first and the `"x"` is
14931        // never looked at. Same for the value that is not JSON at all.
14932        assert_eq!(
14933            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
14934            bulk("[null]").as_str()
14935        );
14936        assert_eq!(
14937            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
14938            bulk("[null]").as_str()
14939        );
14940        assert_eq!(
14941            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
14942            bulk("[]").as_str()
14943        );
14944        assert_eq!(
14945            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
14946            "-ERR Path does not exist or does not contains a number\r\n"
14947        );
14948        // A number match anywhere and the value is looked at after all.
14949        assert_eq!(
14950            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
14951            "-bad input number\r\n"
14952        );
14953
14954        // JSON.STRAPPEND follows the same order with its own two answers.
14955        assert_eq!(
14956            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
14957            "*1\r\n$-1\r\n"
14958        );
14959        assert_eq!(
14960            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
14961            "-ERR Path does not exist or not a string\r\n"
14962        );
14963        assert_eq!(
14964            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
14965            "-WRONGTYPE wrong type of path value - expected string\r\n"
14966        );
14967
14968        // A key that is not there still comes before either of them.
14969        assert_eq!(
14970            f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
14971            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14972        );
14973        assert_eq!(
14974            f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
14975            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14976        );
14977    }
14978
14979    /// RFC 7386 in one test: a null deletes, everything else merges, and a
14980    /// patch that is not an object replaces what it lands on.
14981    #[test]
14982    fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
14983        let mut f = Fixture::new();
14984
14985        // A key that is not there is created at the root, nulls and all,
14986        // because a deletion with nothing to delete is still what the client
14987        // sent.
14988        assert_eq!(
14989            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
14990            "+OK\r\n"
14991        );
14992        assert_eq!(
14993            f.run(&[b"JSON.GET", b"doc", b"$"]),
14994            bulk(r#"[{"x":null,"y":1}]"#).as_str()
14995        );
14996
14997        // Onto something that is there, a null deletes the member of that name
14998        // and the rest is merged one level at a time.
14999        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
15000        assert_eq!(
15001            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
15002            "+OK\r\n"
15003        );
15004        assert_eq!(
15005            f.run(&[b"JSON.GET", b"doc", b"$"]),
15006            bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
15007        );
15008
15009        // A patch that is not an object replaces what it is merged onto.
15010        assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
15011        assert_eq!(
15012            f.run(&[b"JSON.GET", b"doc", b"$"]),
15013            bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
15014        );
15015
15016        // A patch object onto a value that is not an object starts from an
15017        // empty object, so this time the null has nothing to delete and is
15018        // dropped rather than stored.
15019        assert_eq!(
15020            f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
15021            "+OK\r\n"
15022        );
15023        assert_eq!(
15024            f.run(&[b"JSON.GET", b"doc", b"$"]),
15025            bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
15026        );
15027
15028        // A member one level past the end of the document is created and keeps
15029        // its nulls, two levels past it is a write that did not happen, and a
15030        // path that would have to invent where it goes is the unprefixed line.
15031        assert_eq!(
15032            f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
15033            "+OK\r\n"
15034        );
15035        assert_eq!(
15036            f.run(&[b"JSON.GET", b"doc", b"$.new"]),
15037            bulk(r#"[{"z":null}]"#).as_str()
15038        );
15039        assert_eq!(
15040            f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
15041            "$-1\r\n"
15042        );
15043        assert_eq!(
15044            f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
15045            "-Err wrong static path\r\n"
15046        );
15047
15048        // A wildcard merges every match.
15049        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
15050        assert_eq!(
15051            f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
15052            "+OK\r\n"
15053        );
15054        assert_eq!(
15055            f.run(&[b"JSON.GET", b"doc", b"$"]),
15056            bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
15057        );
15058
15059        // The three ways to get it wrong.
15060        assert_eq!(
15061            f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
15062            "-ERR syntax error\r\n"
15063        );
15064        assert_eq!(
15065            f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
15066            "-ERR new objects must be created at the root\r\n"
15067        );
15068        f.run(&[b"SET", b"str", b"x"]);
15069        assert_eq!(
15070            f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
15071            "-Existing key has wrong Redis type\r\n"
15072        );
15073    }
15074
15075    /// A descent is the one path that matches a value and something inside that
15076    /// same value, and the inner merge has to survive the outer one.
15077    #[test]
15078    fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
15079        let mut f = Fixture::new();
15080        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15081        assert_eq!(
15082            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
15083            "+OK\r\n"
15084        );
15085        // `a`, `a.b`, `c` and `c[0]` all match. `a.b` is merged first and `a` is
15086        // merged onto the result, so the `{"m":1}` written into `a.b` is still
15087        // there. Doing it the other way round would leave `{"a":{"b":1,"m":1}}`.
15088        assert_eq!(
15089            f.run(&[b"JSON.GET", b"doc", b"$"]),
15090            bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
15091        );
15092
15093        // A deletion down the same path, which is the case where the inner
15094        // merge empties the object the outer one then copies.
15095        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15096        assert_eq!(
15097            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
15098            "+OK\r\n"
15099        );
15100        assert_eq!(
15101            f.run(&[b"JSON.GET", b"doc", b"$"]),
15102            bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
15103        );
15104    }
15105
15106    /// A filter is a selector like any other, so every command that takes a path
15107    /// takes one, reads and writes alike.
15108    #[test]
15109    fn a_filter_path_reads_and_writes_the_members_it_keeps() {
15110        let mut f = Fixture::new();
15111        let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
15112        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15113
15114        assert_eq!(
15115            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
15116            bulk(r#"["a","c"]"#).as_str()
15117        );
15118        // `$` inside the expression is the document, so a member can be measured
15119        // against something that is not inside it.
15120        assert_eq!(
15121            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
15122            bulk(r#"["a","c"]"#).as_str()
15123        );
15124        // The legacy syntax takes one too, and answers the first match.
15125        assert_eq!(
15126            f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
15127            bulk(r#""a""#).as_str()
15128        );
15129        assert_eq!(
15130            f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
15131            "*1\r\n$6\r\nobject\r\n"
15132        );
15133
15134        // A write goes through it as far as a value that is already there. A
15135        // field that is not there yet has nowhere definite to go, which is the
15136        // same refusal a wildcard gets.
15137        assert_eq!(
15138            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
15139            bulk("[9,10]").as_str()
15140        );
15141        assert_eq!(
15142            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
15143            "+OK\r\n"
15144        );
15145        assert_eq!(
15146            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
15147            "-Err wrong static path\r\n"
15148        );
15149        assert_eq!(
15150            f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
15151            ":2\r\n"
15152        );
15153        assert_eq!(
15154            f.run(&[b"JSON.GET", b"doc", b"$"]),
15155            bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
15156        );
15157
15158        // A path that does not parse is refused before the document is read, so
15159        // a key that is not there answers the same way.
15160        assert!(
15161            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
15162                .starts_with("-ERR")
15163        );
15164        assert!(
15165            f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
15166                .starts_with("-ERR")
15167        );
15168    }
15169
15170    /// The operators past the comparisons, over the wire rather than in the
15171    /// parser's own tests, so that a client can reach all of them.
15172    #[test]
15173    fn a_filter_takes_the_membership_operators_and_the_methods_too() {
15174        let mut f = Fixture::new();
15175        let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
15176        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15177
15178        for (path, want) in [
15179            (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
15180            (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
15181            (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
15182            (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
15183            (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
15184            (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
15185            (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
15186            (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
15187            (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
15188            (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
15189            (b"$.box[?(@.n~)].t", "[]"),
15190            (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
15191            (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
15192            (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
15193            (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
15194        ] {
15195            assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
15196        }
15197
15198        // A write goes through one of these the same way it goes through a
15199        // comparison.
15200        assert_eq!(
15201            f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
15202            "+OK\r\n"
15203        );
15204        assert_eq!(
15205            f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
15206            bulk(r#"["b"]"#).as_str()
15207        );
15208    }
15209
15210    /// D-41. RedisJSON refuses this one, and which document it refuses is
15211    /// decided by how it happens to hold an array of numbers.
15212    #[test]
15213    fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
15214        let mut f = Fixture::new();
15215        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
15216        assert_eq!(
15217            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15218            "+OK\r\n"
15219        );
15220        assert_eq!(
15221            f.run(&[b"JSON.GET", b"doc", b"$"]),
15222            bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
15223        );
15224        // The same document with one element that is not an integer is the one
15225        // RedisJSON is happy with, and it goes the same way here.
15226        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
15227        assert_eq!(
15228            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15229            "+OK\r\n"
15230        );
15231        assert_eq!(
15232            f.run(&[b"JSON.GET", b"doc", b"$"]),
15233            bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
15234        );
15235    }
15236
15237    /// `JSON.MSET` checks what it can before it writes anything and skips the
15238    /// one thing it cannot, which is a path with nowhere to put its value.
15239    #[test]
15240    fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
15241        let mut f = Fixture::new();
15242        assert_eq!(
15243            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
15244            "+OK\r\n"
15245        );
15246        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
15247        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
15248
15249        // A repeated key takes the last write.
15250        assert_eq!(
15251            f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
15252            "+OK\r\n"
15253        );
15254        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
15255
15256        // A triple whose path names nowhere is skipped, the others are still
15257        // written and the reply turns into a nil. Both ways round, because a
15258        // loop that gave up at the first skip would agree with this on one
15259        // order and not on the other.
15260        f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
15261        assert_eq!(
15262            f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
15263            "$-1\r\n"
15264        );
15265        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
15266        assert_eq!(
15267            f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
15268            "$-1\r\n"
15269        );
15270        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15271
15272        // A value that is not JSON, a key holding something else and a path
15273        // that would have to create a document below its own root are all
15274        // checked before anything is written, so the good triple next to them
15275        // does not happen either.
15276        f.run(&[b"SET", b"str", b"x"]);
15277        assert_eq!(
15278            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
15279            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
15280        );
15281        assert_eq!(
15282            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
15283            "-Existing key has wrong Redis type\r\n"
15284        );
15285        assert_eq!(
15286            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
15287            "-ERR new objects must be created at the root\r\n"
15288        );
15289        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
15290
15291        // The two errors a path can be are checked up front as well, so the
15292        // triple before them is not written either. A wildcard that matched
15293        // nothing has nowhere to invent, and an index that is not in the array
15294        // is out of range, and both of them stop the whole command.
15295        assert_eq!(
15296            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
15297            "-Err wrong static path\r\n"
15298        );
15299        assert_eq!(
15300            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
15301            "-ERR array index out of range\r\n"
15302        );
15303        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15304
15305        // Every triple is worked out against the keyspace as the command found
15306        // it, so a second triple on the same key does not see the first one and
15307        // the last write is the one that stays.
15308        f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
15309        assert_eq!(
15310            f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
15311            "+OK\r\n"
15312        );
15313        assert_eq!(
15314            f.run(&[b"JSON.GET", b"c", b"$"]),
15315            bulk(r#"[{"n":3}]"#).as_str()
15316        );
15317
15318        // An argument count that is not a run of key, path and value is the
15319        // arity error rather than a syntax one.
15320        assert_eq!(
15321            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
15322            "-ERR wrong number of arguments for 'json.mset' command\r\n"
15323        );
15324    }
15325
15326    /// `JSON.RESP` hands back RESP types, and the marker element is what tells
15327    /// an empty array and an empty object apart.
15328    #[test]
15329    fn json_resp_answers_the_document_as_resp_types() {
15330        let mut f = Fixture::new();
15331        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
15332        assert_eq!(
15333            f.run(&[b"JSON.RESP", b"doc"]),
15334            "*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"
15335        );
15336        // A JSONPath wraps the same answer in one more array.
15337        assert_eq!(
15338            f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
15339            "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
15340        );
15341
15342        f.run(&[
15343            b"JSON.SET",
15344            b"doc",
15345            b"$",
15346            br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
15347        ]);
15348        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
15349        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
15350        // A double goes out as its text, so a client reads the same digits
15351        // `JSON.GET` would have given it.
15352        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
15353        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
15354        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
15355
15356        // A missing legacy path is an error, a missing JSONPath is an empty
15357        // array, and a key that is not there is a nil on either.
15358        assert_eq!(
15359            f.run(&[b"JSON.RESP", b"doc", b".nope"]),
15360            "-ERR Path does not exist\r\n"
15361        );
15362        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
15363        assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
15364        assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
15365    }
15366
15367    /// `JSON.DEBUG` answers a byte count that is this encoding's, so the test
15368    /// pins the shapes and that the two syntaxes agree rather than a number
15369    /// read off another server. That is D-42.
15370    #[test]
15371    fn json_debug_answers_a_byte_count_and_its_own_help() {
15372        let mut f = Fixture::new();
15373        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
15374        let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
15375        assert!(one.starts_with(':'), "{one}");
15376        assert_eq!(
15377            f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
15378            format!("*1\r\n{one}")
15379        );
15380        let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
15381        assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
15382
15383        // A key that is not there is a zero on a legacy path and an empty set
15384        // on a JSONPath, which is the one reader here that does not answer nil
15385        // for it.
15386        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
15387        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
15388        assert_eq!(
15389            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
15390            "-ERR Path does not exist\r\n"
15391        );
15392        assert_eq!(
15393            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
15394            "*0\r\n"
15395        );
15396
15397        assert_eq!(
15398            f.run(&[b"JSON.DEBUG", b"HELP"]),
15399            "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
15400             $34\r\nHELP                - this message\r\n"
15401        );
15402        assert_eq!(
15403            f.run(&[b"JSON.DEBUG", b"NOPE"]),
15404            "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
15405        );
15406        assert_eq!(
15407            f.run(&[b"JSON.DEBUG", b"MEMORY"]),
15408            "-ERR wrong number of arguments for 'json.debug' command\r\n"
15409        );
15410    }
15411
15412    // ---------------------------------------------------------------- vector
15413
15414    /// The first `VADD` fixes the dimension and every one after it has to
15415    /// agree, because there is no create command to say it earlier.
15416    #[test]
15417    fn the_first_vadd_decides_how_wide_the_set_is() {
15418        let mut f = Fixture::new();
15419        assert_eq!(
15420            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
15421            ":1\r\n"
15422        );
15423        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15424        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15425        // A second vector under the same name replaces it and says so with a
15426        // zero, so an ingest can count what it created.
15427        assert_eq!(
15428            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
15429            ":0\r\n"
15430        );
15431        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15432        // Three dimensions into a two dimensional set names both numbers, since
15433        // a client that gets this wrong needs to know which end is which.
15434        assert_eq!(
15435            f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
15436            "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
15437        );
15438        // A vector of zeros has no direction, and it is taken anyway and comes
15439        // back as the origin, because that is what a real server does with it.
15440        assert_eq!(
15441            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
15442            ":1\r\n"
15443        );
15444        assert_eq!(
15445            f.run(&[b"VEMB", b"v", b"nowhere"]),
15446            "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
15447        );
15448        // A set is made with one quantisation and keeps it, and a `VADD` that
15449        // names another is refused. Naming none names `Q8`, which is why this
15450        // set is a `Q8` one.
15451        assert_eq!(
15452            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
15453            "-ERR asked quantization mismatch with existing vector set\r\n"
15454        );
15455        // Nothing above created a key, and a set that never took a vector has
15456        // no dimension to report.
15457        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
15458        assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
15459        assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
15460    }
15461
15462    /// What a client sent comes back out, and what a client asked for is a
15463    /// similarity and not the distance underneath it.
15464    #[test]
15465    fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
15466        let mut f = Fixture::new();
15467        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
15468        // The set stored the direction and the length is multiplied back on the
15469        // way out, so this is `3 4` and not `0.6 0.8`. It is not quite `3 4`
15470        // either, because nobody named a quantisation and that means `Q8`: the
15471        // wider coordinate lands on a code exactly and the other one does not.
15472        // Both numbers are a real server's answers for the same input.
15473        assert_eq!(
15474            f.run(&[b"VEMB", b"v", b"a"]),
15475            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15476        );
15477        // NOQUANT is the way to ask for what went in to come back out.
15478        f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
15479        assert_eq!(
15480            f.run(&[b"VEMB", b"n", b"a"]),
15481            "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
15482        );
15483        // BIN keeps the signs and nothing else, and does not multiply the
15484        // length back on, since a sign has no length in it to scale.
15485        f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
15486        assert_eq!(
15487            f.run(&[b"VEMB", b"b", b"a"]),
15488            "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
15489        );
15490        assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
15491        assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
15492
15493        // On the axes, where the unit vector is exact and so is the dot
15494        // product, both ends of the scale come out exact: the same direction is
15495        // 1 and the opposite one is 0, with a right angle at a half.
15496        let mut f = Fixture::new();
15497        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
15498        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
15499        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
15500        assert_eq!(
15501            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
15502            "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
15503             $8\r\nopposite\r\n$1\r\n0\r\n"
15504        );
15505        // A search from an element leaves that element out, since it is always
15506        // its own nearest neighbour.
15507        assert_eq!(
15508            f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
15509            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15510        );
15511        // An element that is not there is an empty answer and not an error,
15512        // which is what a missing key gives too.
15513        assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
15514        assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
15515        // COUNT bounds it and TRUTH reads every vector rather than the codes,
15516        // which has to agree with the index on a set this small.
15517        assert_eq!(
15518            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
15519            "*1\r\n$6\r\nacross\r\n"
15520        );
15521        assert_eq!(
15522            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
15523            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15524        );
15525        // EF widens how much of the index is read and does not change how many
15526        // answers come back, so a wide search still returns what COUNT asked
15527        // for.
15528        assert_eq!(
15529            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
15530            "*1\r\n$6\r\nacross\r\n"
15531        );
15532
15533        // On RESP3 a scored search is a map, which is what the vector set
15534        // module replies and is not what ZRANGE does here.
15535        let mut g = Fixture::new();
15536        g.run(&[b"HELLO", b"3"]);
15537        g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15538        assert_eq!(
15539            g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
15540            "%1\r\n$4\r\neast\r\n,1\r\n"
15541        );
15542    }
15543
15544    /// The attribute pair, and the one reply that means two things.
15545    #[test]
15546    fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
15547        let mut f = Fixture::new();
15548        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15549        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15550        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
15551        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
15552        // Not parsed as JSON, because nothing reads into it yet and refusing a
15553        // write for a rule nothing enforces would be the wrong trade.
15554        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
15555        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
15556        // An empty string clears it, which is Redis's spelling of the removal.
15557        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
15558        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15559        // An element that is not there answers zero rather than being created,
15560        // since an attribute with no vector under it is not a thing this holds.
15561        assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
15562        assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
15563        assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
15564        // A null for an element with no attribute and a null for one that is
15565        // not there. VISMEMBER is how a client tells the two apart.
15566        assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
15567        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
15568        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
15569        assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
15570
15571        // WITHATTRIBS carries it alongside the answers.
15572        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15573        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15574        assert_eq!(
15575            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
15576            "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
15577        );
15578    }
15579
15580    /// The slot a removed element had is reused, and nothing that was beside it
15581    /// comes back with the next element to get it.
15582    #[test]
15583    fn vrem_takes_the_attribute_with_it() {
15584        let mut f = Fixture::new();
15585        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15586        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15587        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
15588        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
15589        assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
15590        // The key went with the last element, the way every other collection
15591        // here works.
15592        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15593
15594        // The next element is given the slot the removed one had, and it comes
15595        // with no attribute on it.
15596        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15597        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15598        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15599        f.run(&[b"VREM", b"v", b"east"]);
15600        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
15601        assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
15602    }
15603
15604    /// `VINFO` says what the index is before it says anything a client could
15605    /// mistake for a graph.
15606    #[test]
15607    fn vinfo_says_partition_first() {
15608        let mut f = Fixture::new();
15609        f.run(&[
15610            b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
15611        ]);
15612        f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
15613        let info = f.run(&[b"VINFO", b"v"]);
15614        assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
15615        // What the client asked for and not what happened to the tuning, which
15616        // is `10` section 7: M is recorded and changes nothing.
15617        assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
15618        assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
15619        assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
15620        // Nobody named a quantisation, so this set is a `Q8` one and every
15621        // element in it is stored that way.
15622        assert!(
15623            info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
15624            "{info}"
15625        );
15626        let mut f = Fixture::new();
15627        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
15628        assert!(
15629            f.run(&[b"VINFO", b"v"])
15630                .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
15631        );
15632        assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
15633    }
15634
15635    /// A set to read ranges of names out of.
15636    fn named() -> Fixture {
15637        let mut f = Fixture::new();
15638        for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
15639            .iter()
15640            .enumerate()
15641        {
15642            let x = (i + 1).to_string();
15643            f.run(&[
15644                b"VADD",
15645                b"r",
15646                b"VALUES",
15647                b"2",
15648                x.as_bytes(),
15649                b"1",
15650                name.as_bytes(),
15651            ]);
15652        }
15653        f
15654    }
15655
15656    /// `VRANGE` reads the names in the order bytes come in and pays no
15657    /// attention to where the vectors point.
15658    #[test]
15659    fn vrange_walks_the_names_and_not_the_vectors() {
15660        let mut f = named();
15661        assert_eq!(
15662            f.run(&[b"VRANGE", b"r", b"-", b"+"]),
15663            "*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"
15664        );
15665        assert_eq!(
15666            f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
15667            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
15668            "the high end is a name and not a prefix, so delta is past it"
15669        );
15670        assert_eq!(
15671            f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
15672            "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
15673        );
15674        assert_eq!(
15675            f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
15676            "*1\r\n$4\r\nbeta\r\n"
15677        );
15678        assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
15679        // Bytes and not letters, so an upper case name sorts before every lower
15680        // case one rather than beside its own spelling.
15681        f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
15682        assert_eq!(
15683            f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
15684            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15685        );
15686        assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
15687    }
15688
15689    /// The count cuts the answer after the range is decided, and zero is not
15690    /// the same as leaving it out.
15691    #[test]
15692    fn a_vrange_count_of_zero_asks_for_nothing() {
15693        let mut f = named();
15694        assert_eq!(
15695            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
15696            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15697        );
15698        assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
15699        assert!(
15700            f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
15701                .starts_with("*5\r\n"),
15702            "a negative count is no limit at all"
15703        );
15704    }
15705
15706    /// Both ends are read before either is placed, and the count is read before
15707    /// either end.
15708    #[test]
15709    fn vrange_says_which_end_it_could_not_read() {
15710        let mut f = named();
15711        assert_eq!(
15712            f.run(&[b"VRANGE", b"r", b"x", b"y"]),
15713            "-ERR invalid start range format\r\n"
15714        );
15715        assert_eq!(
15716            f.run(&[b"VRANGE", b"r", b"+", b"x"]),
15717            "-ERR invalid end range format\r\n",
15718            "the high end is spelled wrong, which is worth saying before the \
15719             low end being on the wrong side"
15720        );
15721        assert_eq!(
15722            f.run(&[b"VRANGE", b"r", b"+", b"-"]),
15723            "-ERR '-' can only be used as first argument, '+' only as second\r\n"
15724        );
15725        // A bracket with nothing after it is not the empty name here, though an
15726        // element really can be called that.
15727        assert_eq!(
15728            f.run(&[b"VRANGE", b"r", b"[", b"+"]),
15729            "-ERR invalid start range format\r\n"
15730        );
15731        assert_eq!(
15732            f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
15733            "-ERR invalid COUNT value\r\n"
15734        );
15735        assert_eq!(
15736            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
15737            "-ERR wrong number of arguments for 'VRANGE' command\r\n"
15738        );
15739        f.run(&[b"SET", b"s", b"x"]);
15740        assert!(
15741            f.run(&[b"VRANGE", b"s", b"-", b"+"])
15742                .starts_with("-WRONGTYPE")
15743        );
15744    }
15745
15746    /// The option that asks for something this index does not have says so
15747    /// rather than doing something else quietly.
15748    #[test]
15749    fn reduce_is_refused_and_not_ignored() {
15750        let mut f = Fixture::new();
15751        let reduce = f.run(&[
15752            b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
15753        ]);
15754        assert!(
15755            reduce.starts_with("-ERR REDUCE is not supported."),
15756            "{reduce}"
15757        );
15758        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15759    }
15760
15761    /// A filtered search answers with the nearest elements that match, and an
15762    /// expression that is not one is an error before the key is looked at.
15763    #[test]
15764    fn vsim_filter_reads_the_attributes() {
15765        let mut f = Fixture::new();
15766        for (name, x, y, attr) in [
15767            ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
15768            ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
15769            ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
15770            ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
15771        ] {
15772            f.run(&[
15773                b"VADD",
15774                b"v",
15775                b"VALUES",
15776                b"2",
15777                x.as_bytes(),
15778                y.as_bytes(),
15779                name.as_bytes(),
15780                b"SETATTR",
15781                attr.as_bytes(),
15782            ]);
15783        }
15784        // `b` is the nearest to the query and is the one the filter drops, so
15785        // this is the answer a filter applied afterwards would have got wrong.
15786        assert_eq!(
15787            f.run(&[
15788                b"VSIM",
15789                b"v",
15790                b"VALUES",
15791                b"2",
15792                b"9",
15793                b"1",
15794                b"COUNT",
15795                b"2",
15796                b"FILTER",
15797                b".lang == \"en\"",
15798            ]),
15799            "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
15800        );
15801        // A number is compared as a number, and the two halves of an `and` both
15802        // have to hold.
15803        assert_eq!(
15804            f.run(&[
15805                b"VSIM",
15806                b"v",
15807                b"VALUES",
15808                b"2",
15809                b"9",
15810                b"1",
15811                b"FILTER",
15812                b".lang == 'en' and .year > 1980",
15813            ]),
15814            "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
15815        );
15816        // A list, and a field an element does not have.
15817        assert_eq!(
15818            f.run(&[
15819                b"VSIM",
15820                b"v",
15821                b"VALUES",
15822                b"2",
15823                b"9",
15824                b"1",
15825                b"FILTER",
15826                b".lang in ['fr', 'de']",
15827            ]),
15828            "*1\r\n$1\r\nb\r\n"
15829        );
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"FILTER",
15839                b".rating > 3"
15840            ]),
15841            "*0\r\n"
15842        );
15843        // TRUTH measures every vector, and the filter still decides which ones
15844        // are measured.
15845        assert_eq!(
15846            f.run(&[
15847                b"VSIM",
15848                b"v",
15849                b"VALUES",
15850                b"2",
15851                b"9",
15852                b"1",
15853                b"TRUTH",
15854                b"FILTER",
15855                b".year < 1980",
15856            ]),
15857            "*1\r\n$1\r\nc\r\n"
15858        );
15859        // VSETATTR moves an element in and out of a filter, which means the tag
15860        // beside its code was rewritten and not just the string.
15861        f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
15862        assert_eq!(
15863            f.run(&[
15864                b"VSIM",
15865                b"v",
15866                b"VALUES",
15867                b"2",
15868                b"9",
15869                b"1",
15870                b"COUNT",
15871                b"1",
15872                b"FILTER",
15873                b".lang == \"en\"",
15874            ]),
15875            "*1\r\n$1\r\nb\r\n"
15876        );
15877        // And a VADD that replaces the vector keeps the attribute and the tag,
15878        // which is the same rewrite from the other end.
15879        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
15880        assert_eq!(
15881            f.run(&[
15882                b"VSIM",
15883                b"v",
15884                b"VALUES",
15885                b"2",
15886                b"9",
15887                b"1",
15888                b"COUNT",
15889                b"1",
15890                b"FILTER",
15891                b".lang == \"en\"",
15892            ]),
15893            "*1\r\n$1\r\nb\r\n"
15894        );
15895
15896        // The expression is parsed before the key is read, so a bad one is an
15897        // error whether or not the key is there.
15898        let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
15899        assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
15900        assert_eq!(
15901            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
15902            "-ERR invalid FILTER expression\r\n"
15903        );
15904        // FILTER-EF raises the effort rather than capping it, and zero is
15905        // Redis's word for no limit, so neither is an error.
15906        assert_eq!(
15907            f.run(&[
15908                b"VSIM",
15909                b"v",
15910                b"VALUES",
15911                b"2",
15912                b"9",
15913                b"1",
15914                b"COUNT",
15915                b"1",
15916                b"FILTER-EF",
15917                b"500",
15918                b"FILTER",
15919                b".lang == 'en'",
15920            ]),
15921            "*1\r\n$1\r\nb\r\n"
15922        );
15923        assert_eq!(
15924            f.run(&[
15925                b"VSIM",
15926                b"v",
15927                b"VALUES",
15928                b"2",
15929                b"9",
15930                b"1",
15931                b"COUNT",
15932                b"1",
15933                b"FILTER-EF",
15934                b"0"
15935            ]),
15936            "*1\r\n$1\r\nb\r\n"
15937        );
15938        assert_eq!(
15939            f.run(&[
15940                b"VSIM",
15941                b"v",
15942                b"VALUES",
15943                b"2",
15944                b"9",
15945                b"1",
15946                b"FILTER-EF",
15947                b"lots"
15948            ]),
15949            "-ERR EF must be a positive integer\r\n"
15950        );
15951    }
15952
15953    /// A vector set key is a key, so the keyspace owns it the way it owns every
15954    /// other one and none of those commands know what is inside it.
15955    #[test]
15956    fn the_keyspace_sees_a_vector_set_key_like_any_other() {
15957        let mut f = Fixture::new();
15958        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15959        assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
15960        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
15961        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
15962        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
15963        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
15964        assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
15965        assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
15966        assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
15967        assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
15968        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15969
15970        // And the wrong type is the wrong type in both directions.
15971        f.run(&[b"SET", b"s", b"1"]);
15972        assert_eq!(
15973            f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
15974            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15975        );
15976        assert_eq!(
15977            f.run(&[b"VCARD", b"s"]),
15978            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15979        );
15980        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15981        assert_eq!(
15982            f.run(&[b"GET", b"v"]),
15983            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15984        );
15985        // A graph and a vector set share the escape in the record tag and are
15986        // still two different types, which is the case the tag alone cannot
15987        // decide.
15988        f.run(&[b"G.NADD", b"social", b"ada"]);
15989        assert_eq!(
15990            f.run(&[b"VCARD", b"social"]),
15991            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15992        );
15993        assert_eq!(
15994            f.run(&[b"G.NGET", b"v", b"ada"]),
15995            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15996        );
15997    }
15998
15999    /// `VRANDMEMBER` is `SRANDMEMBER` over the element names, in both of its
16000    /// shapes, off the database's own generator.
16001    #[test]
16002    fn vrandmember_has_the_two_shapes_srandmember_has() {
16003        let mut f = Fixture::new();
16004        for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
16005            let x = (i + 1).to_string();
16006            f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
16007        }
16008        // One element is a bulk string and not an array of one.
16009        let one = f.run(&[b"VRANDMEMBER", b"v"]);
16010        assert!(one.starts_with("$1\r\n"), "{one}");
16011        // A positive count is distinct and stops at the size of the set.
16012        let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
16013        assert!(all.starts_with("*3\r\n"), "{all}");
16014        for name in ["a", "b", "c"] {
16015            assert!(all.contains(name), "{all} is missing {name}");
16016        }
16017        all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
16018        assert!(all.starts_with("*2\r\n"), "{all}");
16019        // A negative one draws that many and allows repeats.
16020        let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
16021        assert!(many.starts_with("*5\r\n"), "{many}");
16022        // A key that is not there answers the shape that was asked for.
16023        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
16024        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
16025    }
16026
16027    /// `VLINKS` answers about the index that is here rather than the graph that
16028    /// is not, which is D-2.
16029    #[test]
16030    fn vlinks_reports_one_layer_of_partition_neighbours() {
16031        let mut f = Fixture::new();
16032        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16033        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16034        // One layer deep, because the index is one layer deep, so a client
16035        // walking layers gets a short list and not a shape it cannot parse.
16036        assert_eq!(
16037            f.run(&[b"VLINKS", b"v", b"east"]),
16038            "*1\r\n*1\r\n$5\r\nnorth\r\n"
16039        );
16040        assert_eq!(
16041            f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
16042            "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
16043        );
16044        assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
16045        assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
16046    }
16047
16048    /// A vector arrives either as digits or as bytes, and the two have to mean
16049    /// the same thing.
16050    #[test]
16051    fn fp32_and_values_are_the_same_vector() {
16052        let mut f = Fixture::new();
16053        let mut blob = Vec::new();
16054        for x in [3.0f32, 4.0] {
16055            blob.extend_from_slice(&x.to_le_bytes());
16056        }
16057        assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
16058        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
16059        assert_eq!(
16060            f.run(&[b"VEMB", b"v", b"a"]),
16061            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
16062        );
16063        // RAW is the stored bytes and the numbers that turn them back into the
16064        // client's vector, which for `Q8` is a code a coordinate, the length the
16065        // vector arrived with and the scale the codes are measured against. The
16066        // name of the form is a simple string, which is a real server's shape,
16067        // and all four of these are a real server's answers.
16068        assert_eq!(
16069            f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
16070            "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
16071        );
16072        // A blob that is not a whole number of floats is not a vector.
16073        assert_eq!(
16074            f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
16075            "-ERR invalid vector specification\r\n"
16076        );
16077        // Neither is a count that promises more than arrived.
16078        assert_eq!(
16079            f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
16080            "-ERR syntax error\r\n"
16081        );
16082        assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
16083    }
16084
16085    // ----------------------------------------------------------------- bloom
16086
16087    /// The filter a client gets when it does not describe one, and the two
16088    /// answers an add can give.
16089    #[test]
16090    fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
16091        let mut f = Fixture::new();
16092        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
16093        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
16094        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
16095        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
16096        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
16097        // The defaults are the module's configs and not anything the command
16098        // said, which is 100 entries at a hundredth and a growth of 2.
16099        assert_eq!(
16100            f.run(&[b"BF.INFO", b"b"]),
16101            "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16102             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16103             +Expansion rate\r\n:2\r\n"
16104        );
16105        assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
16106        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
16107        // A key that is not there has no filter to report on, and answers two
16108        // different ways about it depending on which command asked.
16109        assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
16110        assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
16111    }
16112
16113    /// `BF.EXISTS` on a key holding something else answers a miss, and
16114    /// everything else in the family answers `WRONGTYPE`.
16115    ///
16116    /// The two halves of a check and set disagree about what that key is, which
16117    /// is the module's behaviour and not a decision taken here.
16118    #[test]
16119    fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
16120        let mut f = Fixture::new();
16121        f.run(&[b"SET", b"s", b"text"]);
16122        assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
16123        assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16124        for cmd in [
16125            vec![&b"BF.ADD"[..], b"s", b"x"],
16126            vec![&b"BF.MADD"[..], b"s", b"x"],
16127            vec![&b"BF.CARD"[..], b"s"],
16128            vec![&b"BF.INFO"[..], b"s"],
16129            vec![&b"BF.DEBUG"[..], b"s"],
16130            vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
16131        ] {
16132            let name = String::from_utf8_lossy(cmd[0]).into_owned();
16133            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16134        }
16135        // The arguments are read before the key is, so a reserve with a bad
16136        // error rate complains about the rate and never learns about the string.
16137        assert_eq!(
16138            f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
16139            "-ERR bad error rate\r\n"
16140        );
16141        assert!(
16142            f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
16143                .starts_with("-WRONGTYPE")
16144        );
16145    }
16146
16147    /// A chain grows by its expansion factor and each link is half as wrong as
16148    /// the one before, which is what makes the whole filter hold its rate.
16149    #[test]
16150    fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
16151        let mut f = Fixture::new();
16152        assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
16153        for i in 0..10u32 {
16154            assert_eq!(
16155                f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
16156                ":1\r\n"
16157            );
16158        }
16159        assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
16160        assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
16161        assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
16162        // Capacity is the sum of every link and not the number that was asked
16163        // for, so it is 10 and then 10 plus 20.
16164        assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
16165        assert_eq!(
16166            f.run(&[b"BF.DEBUG", b"g"]),
16167            "*3\r\n$7\r\nsize:11\r\n\
16168             $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
16169             $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
16170        );
16171
16172        // The same filter told not to grow fills instead.
16173        assert_eq!(
16174            f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
16175            "+OK\r\n"
16176        );
16177        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
16178        assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
16179        assert_eq!(
16180            f.run(&[b"BF.ADD", b"n", b"c"]),
16181            "-ERR non scaling filter is full\r\n"
16182        );
16183        // And an item that is already in it still answers, because membership
16184        // is checked before fullness.
16185        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
16186        // A filter that will not grow has no expansion rate to report, in
16187        // either of the two spellings that make one.
16188        assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
16189        f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
16190        assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
16191        // Asking for both at once is refused, which is one of the module's
16192        // errors that carries no prefix at all.
16193        assert_eq!(
16194            f.run(&[
16195                b"BF.RESERVE",
16196                b"q",
16197                b"0.01",
16198                b"2",
16199                b"NONSCALING",
16200                b"EXPANSION",
16201                b"2"
16202            ]),
16203            "-Nonscaling filters cannot expand\r\n"
16204        );
16205    }
16206
16207    /// A multi add stops where the filter did, so the reply can be shorter than
16208    /// the argument list.
16209    #[test]
16210    fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
16211        let mut f = Fixture::new();
16212        f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
16213        assert_eq!(
16214            f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
16215            "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
16216        );
16217        assert_eq!(
16218            f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
16219            "*2\r\n:1\r\n:0\r\n"
16220        );
16221    }
16222
16223    /// `BF.INSERT` describes a filter and fills it in one command, with its own
16224    /// spelling of every complaint.
16225    #[test]
16226    fn insert_is_a_reserve_and_a_madd_with_different_errors() {
16227        let mut f = Fixture::new();
16228        assert_eq!(
16229            f.run(&[
16230                b"BF.INSERT",
16231                b"i",
16232                b"CAPACITY",
16233                b"50",
16234                b"ERROR",
16235                b"0.001",
16236                b"ITEMS",
16237                b"a",
16238                b"b"
16239            ]),
16240            "*2\r\n:1\r\n:1\r\n"
16241        );
16242        assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
16243        // NOCREATE is the only way to add without making the key.
16244        assert_eq!(
16245            f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16246            "-ERR not found\r\n"
16247        );
16248        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16249        // The same mistakes as BF.RESERVE, in the sentences this command uses
16250        // for them, and one sentence where BF.RESERVE has two.
16251        assert_eq!(
16252            f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16253            "-Bad capacity\r\n"
16254        );
16255        assert_eq!(
16256            f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
16257            "-Bad error rate\r\n"
16258        );
16259        assert_eq!(
16260            f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
16261            "-Bad expansion\r\n"
16262        );
16263        // An option is matched on its first letter and not on the word, so a
16264        // token nobody meant as an option is one anyway if it starts with the
16265        // right letter. NOSUCHTHING is NONSCALING here, and the filter it
16266        // builds says so.
16267        assert_eq!(
16268            f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
16269            "*1\r\n:1\r\n"
16270        );
16271        assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
16272        // Only E and N need a second look, one for ERROR against EXPANSION and
16273        // the other for NOCREATE against NONSCALING, and both stop as soon as
16274        // they can tell the two apart.
16275        assert_eq!(
16276            f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
16277            "*1\r\n:1\r\n"
16278        );
16279        assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
16280        assert_eq!(
16281            f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
16282            "*1\r\n:1\r\n"
16283        );
16284        assert_eq!(
16285            f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
16286            "-ERR not found\r\n"
16287        );
16288        // A letter that starts nothing is the one case that is refused.
16289        assert_eq!(
16290            f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16291            "-Unknown argument received\r\n"
16292        );
16293        // Everything after ITEMS is an item, even when it spells an option.
16294        assert_eq!(
16295            f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16296            "*1\r\n:1\r\n"
16297        );
16298        // And ITEMS with nothing after it is the same as leaving it out.
16299        assert!(
16300            f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
16301                .contains("wrong number of arguments")
16302        );
16303    }
16304
16305    /// A filter dumped a chunk at a time and put back into another key is the
16306    /// same filter.
16307    #[test]
16308    fn a_dump_replays_into_a_filter_that_answers_the_same() {
16309        let mut f = Fixture::new();
16310        f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
16311        for i in 0..25u32 {
16312            f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
16313        }
16314        assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
16315
16316        // Iterator zero asks for the header and every one after it is a running
16317        // byte offset, and a chunk never spans two links.
16318        let mut iter = b"0".to_vec();
16319        let mut chunks = 0;
16320        loop {
16321            let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
16322            let text = String::from_utf8_lossy(&raw).into_owned();
16323            let next = text
16324                .split("\r\n")
16325                .nth(1)
16326                .and_then(|n| n.strip_prefix(':'))
16327                .expect("a two element reply of an iterator and a chunk")
16328                .to_owned();
16329            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16330            let data = &body[body
16331                .windows(2)
16332                .position(|w| w == b"\r\n")
16333                .expect("a length line")
16334                + 2..body.len() - 2];
16335            if next == "0" {
16336                assert!(data.is_empty(), "the last chunk is empty");
16337                break;
16338            }
16339            let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
16340            assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
16341            iter = next.into_bytes();
16342            chunks += 1;
16343        }
16344        assert_eq!(chunks, 3, "a header and one chunk per link");
16345
16346        assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
16347        assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
16348        for i in 0..25u32 {
16349            assert_eq!(
16350                f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
16351                ":1\r\n"
16352            );
16353        }
16354
16355        // A header on top of a filter is refused rather than merged, and so is
16356        // one that no filter wrote.
16357        assert_eq!(
16358            f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
16359            "-ERR received bad data\r\n"
16360        );
16361        assert_eq!(
16362            f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
16363            "-ERR received bad data\r\n"
16364        );
16365        // An offset past the end of the filter names itself.
16366        assert_eq!(
16367            f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
16368            "-ERR invalid offset - no link found\r\n"
16369        );
16370        assert_eq!(
16371            f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
16372            "-ERR Second argument must be numeric\r\n"
16373        );
16374        // The same complaint without the prefix on the way out, which is the
16375        // module's inconsistency and not a slip here.
16376        assert_eq!(
16377            f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
16378            "-Second argument must be numeric\r\n"
16379        );
16380    }
16381
16382    /// The argument checks, which have a sentence each and read numbers the way
16383    /// Redis reads them everywhere else.
16384    #[test]
16385    fn reserve_reads_its_numbers_the_way_string2ll_does() {
16386        let mut f = Fixture::new();
16387        for (args, want) in [
16388            (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
16389            (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
16390            (
16391                vec![&b"0"[..], b"10"],
16392                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16393            ),
16394            (
16395                vec![&b"1"[..], b"10"],
16396                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16397            ),
16398            (
16399                vec![&b"inf"[..], b"10"],
16400                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16401            ),
16402            (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
16403            (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
16404            (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
16405            (
16406                vec![&b"0.01"[..], b"0"],
16407                "-ERR capacity must be in the range [1, 1073741824]\r\n",
16408            ),
16409            (
16410                vec![&b"0.01"[..], b"1073741825"],
16411                "-ERR capacity must be in the range [1, 1073741824]\r\n",
16412            ),
16413        ] {
16414            let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
16415            cmd.extend(args.iter().copied());
16416            assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
16417        }
16418        assert_eq!(
16419            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
16420            "-ERR no expansion\r\n"
16421        );
16422        assert_eq!(
16423            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
16424            "-ERR bad expansion\r\n"
16425        );
16426        assert_eq!(
16427            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
16428            "-ERR expansion must be in the range [0, 32768]\r\n"
16429        );
16430        // Trailing rubbish after the capacity is ignored rather than refused.
16431        assert_eq!(
16432            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
16433            "+OK\r\n"
16434        );
16435        assert_eq!(
16436            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
16437            "-ERR item exists\r\n"
16438        );
16439        assert_eq!(
16440            f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
16441            "-Invalid information value\r\n"
16442        );
16443        assert!(
16444            f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
16445                .contains("wrong number of arguments")
16446        );
16447    }
16448
16449    /// The RESP3 shapes, which are where this family differs most from RESP2.
16450    #[test]
16451    fn the_bloom_family_answers_in_resp3_spelling_too() {
16452        let mut f = Fixture::new();
16453        f.out.set_proto(Proto::Resp3);
16454        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
16455        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
16456        assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
16457        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
16458        assert_eq!(
16459            f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
16460            "*2\r\n#t\r\n#f\r\n"
16461        );
16462        // The count stays an integer, because it counts rather than answers.
16463        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
16464        assert_eq!(
16465            f.run(&[b"BF.INFO", b"b"]),
16466            "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16467             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
16468             +Expansion rate\r\n:2\r\n"
16469        );
16470        // One field is a map of one here and a bare array of one on RESP2, so
16471        // this is the reply where the two protocols carry different facts.
16472        assert_eq!(
16473            f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
16474            "%1\r\n+Capacity\r\n:100\r\n"
16475        );
16476    }
16477
16478    // ---------------------------------------------------------------- cuckoo
16479
16480    /// A dump header, which is the four counts and the three widths a filter
16481    /// writes in front of its fingerprints.
16482    ///
16483    /// Written by hand rather than taken from a `CF.SCANDUMP`, because what the
16484    /// tests below want out of it is the states a filter cannot be put into
16485    /// from the wire.
16486    fn cf_header(
16487        items: u64,
16488        buckets: u64,
16489        deletes: u64,
16490        filters: u64,
16491        geometry: [u16; 3],
16492    ) -> Vec<u8> {
16493        let mut out = Vec::with_capacity(38);
16494        for n in [items, buckets, deletes, filters] {
16495            out.extend_from_slice(&n.to_le_bytes());
16496        }
16497        for n in geometry {
16498            out.extend_from_slice(&n.to_le_bytes());
16499        }
16500        out
16501    }
16502
16503    /// The filter a client gets when it does not describe one, and the thing a
16504    /// cuckoo filter does that a Bloom filter cannot, which is count copies and
16505    /// take them out again.
16506    #[test]
16507    fn cf_add_makes_the_filter_and_counts_the_copies() {
16508        let mut f = Fixture::new();
16509        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16510        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16511        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
16512        // The NX form is the one that looks first, which is why it is a command
16513        // of its own rather than an option.
16514        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
16515        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
16516        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
16517        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
16518        assert_eq!(
16519            f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
16520            "*2\r\n:1\r\n:0\r\n"
16521        );
16522        // The defaults are the module's configs: 1024 entries over buckets of
16523        // two, twenty kicks and a chain that grows by one.
16524        assert_eq!(
16525            f.run(&[b"CF.INFO", b"d"]),
16526            "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16527             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
16528             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
16529             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16530        );
16531        assert_eq!(
16532            f.run(&[b"CF.DEBUG", b"d"]),
16533            "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
16534             max_iterations:20 expansion:1\r\n"
16535        );
16536        assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
16537        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
16538
16539        // A delete takes one copy, so the same item goes twice and then stops.
16540        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16541        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
16542        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16543        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
16544        assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
16545
16546        // A key with no filter under it gets three different sentences and one
16547        // plain miss, depending on which command asked.
16548        assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
16549        assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
16550        assert_eq!(
16551            f.run(&[b"CF.COMPACT", b"gone"]),
16552            "-Cuckoo filter was not found\r\n"
16553        );
16554        assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
16555        // And `CF.COMPACT` is declared as taking any number of keys and takes
16556        // exactly one, which is the module's own arity being wrong rather than
16557        // this table's.
16558        assert!(
16559            f.run(&[b"CF.COMPACT", b"a", b"b"])
16560                .contains("wrong number of arguments")
16561        );
16562    }
16563
16564    /// The four that only read fingerprints treat a key holding something else
16565    /// as a key with no filter, and everything else answers `WRONGTYPE`.
16566    #[test]
16567    fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
16568        let mut f = Fixture::new();
16569        f.run(&[b"SET", b"s", b"text"]);
16570        assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
16571        assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16572        assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
16573        // `CF.DEL` writes and is still in that group, and `CF.COMPACT` writes
16574        // and is declared read only, so neither of the two halves of the family
16575        // is the same set as the flags say.
16576        assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
16577        assert_eq!(
16578            f.run(&[b"CF.COMPACT", b"s"]),
16579            "-Cuckoo filter was not found\r\n"
16580        );
16581        for cmd in [
16582            vec![&b"CF.ADD"[..], b"s", b"x"],
16583            vec![&b"CF.ADDNX"[..], b"s", b"x"],
16584            vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
16585            vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
16586            vec![&b"CF.INFO"[..], b"s"],
16587            vec![&b"CF.DEBUG"[..], b"s"],
16588            vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
16589            vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
16590            vec![&b"CF.RESERVE"[..], b"s", b"64"],
16591        ] {
16592            let name = String::from_utf8_lossy(cmd[0]).into_owned();
16593            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16594        }
16595    }
16596
16597    /// `CF.RESERVE` reads its options by name in an order of its own, and the
16598    /// first pair with a given name is the only one it looks at.
16599    #[test]
16600    fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
16601        let mut f = Fixture::new();
16602        assert_eq!(
16603            f.run(&[
16604                b"CF.RESERVE",
16605                b"r",
16606                b"64",
16607                b"BUCKETSIZE",
16608                b"1",
16609                b"MAXITERATIONS",
16610                b"7",
16611                b"EXPANSION",
16612                b"4"
16613            ]),
16614            "+OK\r\n"
16615        );
16616        assert_eq!(
16617            f.run(&[b"CF.DEBUG", b"r"]),
16618            "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
16619             max_iterations:7 expansion:4\r\n"
16620        );
16621        assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
16622
16623        assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
16624        assert_eq!(
16625            f.run(&[b"CF.RESERVE", b"q", b"1"]),
16626            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16627        );
16628        // The range is the bucket size's and not a constant, so a capacity that
16629        // was fine at two slots a bucket is not at four.
16630        assert_eq!(
16631            f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
16632            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16633        );
16634        assert_eq!(
16635            f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
16636            "+OK\r\n"
16637        );
16638
16639        // The capacity is checked last, so a command that is wrong twice
16640        // answers about the option. Which option it answers about is the order
16641        // the module looks for them in and not the order they were written, so
16642        // a bad kick budget wins over a bad bucket size wherever the two sit.
16643        assert_eq!(
16644            f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
16645            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16646        );
16647        assert_eq!(
16648            f.run(&[
16649                b"CF.RESERVE",
16650                b"q2",
16651                b"64",
16652                b"EXPANSION",
16653                b"xx",
16654                b"BUCKETSIZE",
16655                b"0"
16656            ]),
16657            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16658        );
16659        assert_eq!(
16660            f.run(&[
16661                b"CF.RESERVE",
16662                b"q2",
16663                b"64",
16664                b"MAXITERATIONS",
16665                b"0",
16666                b"BUCKETSIZE",
16667                b"0"
16668            ]),
16669            "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
16670        );
16671        // A second pair with a name that has already been read is not looked at
16672        // at all, so this one is a filter with buckets of one rather than an
16673        // error about a bucket size of zero.
16674        assert_eq!(
16675            f.run(&[
16676                b"CF.RESERVE",
16677                b"q3",
16678                b"64",
16679                b"BUCKETSIZE",
16680                b"1",
16681                b"BUCKETSIZE",
16682                b"0"
16683            ]),
16684            "+OK\r\n"
16685        );
16686        // A pair nobody knows is dropped, which is the opposite of what
16687        // `CF.INSERT` does with the same mistake.
16688        assert_eq!(
16689            f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
16690            "+OK\r\n"
16691        );
16692        assert_eq!(
16693            f.run(&[b"CF.DEBUG", b"q4"]),
16694            "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
16695             max_iterations:20 expansion:1\r\n"
16696        );
16697        // And an option with nothing after it leaves an odd number of them,
16698        // which is an arity error rather than a complaint about the option.
16699        assert!(
16700            f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
16701                .contains("wrong number of arguments")
16702        );
16703    }
16704
16705    /// `CF.INSERT` is a reserve and a multi add, with a grammar that agrees
16706    /// with `CF.RESERVE` about nothing.
16707    #[test]
16708    fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
16709        let mut f = Fixture::new();
16710        assert_eq!(
16711            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
16712            "*2\r\n:1\r\n:1\r\n"
16713        );
16714        assert_eq!(
16715            f.run(&[b"CF.DEBUG", b"i"]),
16716            "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
16717             max_iterations:20 expansion:1\r\n"
16718        );
16719        // The NX form has three answers rather than two, which is why it stays
16720        // integers on both protocols.
16721        assert_eq!(
16722            f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
16723            "*2\r\n:0\r\n:1\r\n"
16724        );
16725        assert_eq!(
16726            f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16727            "-ERR not found\r\n"
16728        );
16729        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16730
16731        assert_eq!(
16732            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16733            "-Bad capacity\r\n"
16734        );
16735        // The bucket size cannot be given here, so the range names the config
16736        // that holds it instead of the option `CF.RESERVE` names.
16737        assert_eq!(
16738            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
16739            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16740        );
16741        // Every occurrence is checked, which is where this differs from
16742        // `CF.RESERVE`: the second `CAPACITY` is an error even though the first
16743        // one is the one that would have been used.
16744        assert_eq!(
16745            f.run(&[
16746                b"CF.INSERT",
16747                b"i",
16748                b"CAPACITY",
16749                b"8",
16750                b"CAPACITY",
16751                b"2",
16752                b"ITEMS",
16753                b"a"
16754            ]),
16755            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16756        );
16757        // An option is one letter and not a word, so `NOSUCH` is `NOCREATE` and
16758        // `ITEMSXYZ` is `ITEMS`, and only a letter that starts nothing is
16759        // refused.
16760        assert_eq!(
16761            f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
16762            "*1\r\n:1\r\n"
16763        );
16764        assert_eq!(
16765            f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
16766            "*1\r\n:1\r\n"
16767        );
16768        assert_eq!(
16769            f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16770            "-Unknown argument received\r\n"
16771        );
16772        // Everything after ITEMS is an item, even when it spells an option.
16773        assert_eq!(
16774            f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16775            "*1\r\n:1\r\n"
16776        );
16777        // And the two ways of sending no items at all are the same complaint.
16778        assert!(
16779            f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
16780                .contains("wrong number of arguments")
16781        );
16782        assert!(
16783            f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
16784                .contains("wrong number of arguments")
16785        );
16786    }
16787
16788    /// The two walls a filter can hit, which say different things and are not
16789    /// the same wall.
16790    #[test]
16791    fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
16792        let mut f = Fixture::new();
16793        f.run(&[
16794            b"CF.RESERVE",
16795            b"s",
16796            b"4",
16797            b"BUCKETSIZE",
16798            b"1",
16799            b"EXPANSION",
16800            b"0",
16801        ]);
16802        for i in 0..4u32 {
16803            assert_eq!(
16804                f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
16805                ":1\r\n"
16806            );
16807        }
16808        assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
16809        assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
16810        // The add commands say it in a sentence and the insert commands say it
16811        // in the array, one value per item, and the array is never short.
16812        assert_eq!(
16813            f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
16814            "*2\r\n:-1\r\n:-1\r\n"
16815        );
16816        assert_eq!(
16817            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
16818            "*2\r\n:0\r\n:-1\r\n"
16819        );
16820
16821        // A chain that is allowed to grow stops for a different reason, and the
16822        // count it stops at is the filter limit rather than the room: this one
16823        // gives up with three slots free. Loading a chain that already has
16824        // every filter it is allowed shows why, since it refuses an item
16825        // straight into an empty one.
16826        let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
16827        assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
16828        assert_eq!(
16829            f.run(&[b"CF.ADD", b"g", b"q"]),
16830            "-Maximum expansions reached\r\n"
16831        );
16832        assert_eq!(
16833            f.run(&[b"CF.INFO", b"g"]),
16834            "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
16835             +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
16836             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
16837             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16838        );
16839    }
16840
16841    /// A filter dumped a chunk at a time and put back under another key is the
16842    /// same filter, and the headers that describe one nobody could build are
16843    /// refused on the way in.
16844    #[test]
16845    fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
16846        let mut f = Fixture::new();
16847        f.run(&[
16848            b"CF.RESERVE",
16849            b"src",
16850            b"8",
16851            b"BUCKETSIZE",
16852            b"2",
16853            b"EXPANSION",
16854            b"2",
16855        ]);
16856        for i in 0..40u32 {
16857            f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
16858        }
16859        // Position zero asks for the header and every one after it is a byte
16860        // offset across every filter laid end to end, and the walk ends on a
16861        // zero and a nil rather than an empty chunk.
16862        let mut pos = b"0".to_vec();
16863        let mut chunks = 0;
16864        loop {
16865            let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
16866            let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
16867            let next = head
16868                .split("\r\n")
16869                .nth(1)
16870                .and_then(|n| n.strip_prefix(':'))
16871                .expect("a two element reply of a position and a chunk")
16872                .to_owned();
16873            if next == "0" {
16874                assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
16875                break;
16876            }
16877            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16878            let at = body
16879                .windows(2)
16880                .position(|w| w == b"\r\n")
16881                .expect("a length line")
16882                + 2;
16883            let data = &body[at..body.len() - 2];
16884            assert_eq!(
16885                f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
16886                "+OK\r\n",
16887                "loading chunk {chunks}"
16888            );
16889            pos = next.into_bytes();
16890            chunks += 1;
16891        }
16892        assert!(chunks >= 2, "a header and at least one chunk");
16893
16894        assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
16895        assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
16896        for i in 0..40u32 {
16897            assert_eq!(
16898                f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
16899                ":1\r\n"
16900            );
16901        }
16902
16903        // A filter with nothing in it hands out no header at all, so a client
16904        // that dumps one has nothing to load back.
16905        f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
16906        assert_eq!(
16907            f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
16908            "*2\r\n:0\r\n$-1\r\n"
16909        );
16910
16911        // The positions this end will not take, which are not the same set at
16912        // both ends: a dump refuses a negative one and a load takes it as an
16913        // offset and fails to find anything there.
16914        assert_eq!(
16915            f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
16916            "-Invalid position\r\n"
16917        );
16918        assert_eq!(
16919            f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
16920            "-Invalid position\r\n"
16921        );
16922        assert_eq!(
16923            f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
16924            "-Invalid position\r\n"
16925        );
16926        assert_eq!(
16927            f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
16928            "-Couldn't load chunk!\r\n"
16929        );
16930        // A header on top of a filter is refused rather than merged.
16931        let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
16932        assert_eq!(
16933            f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
16934            "-ERR item exists\r\n"
16935        );
16936        // A chunk that is not the size of a header where a header should have
16937        // been is one sentence, and one that is the size of a header and
16938        // describes a filter nobody could build is another.
16939        assert_eq!(
16940            f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
16941            "-Invalid header\r\n"
16942        );
16943        for (why, bad) in [
16944            ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
16945            ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
16946            (
16947                "a bucket count that is not a power of two",
16948                cf_header(0, 3, 0, 1, [2, 20, 1]),
16949            ),
16950            ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
16951            ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
16952            (
16953                "a growth nobody could reach",
16954                cf_header(0, 8, 0, 1, [2, 20, 32769]),
16955            ),
16956            (
16957                "a chain that cannot grow and did",
16958                cf_header(0, 8, 0, 2, [2, 20, 0]),
16959            ),
16960            // The count is written in eight bytes and read into two, so a
16961            // number that is a multiple of the second arrives as none.
16962            (
16963                "a filter count that wraps",
16964                cf_header(0, 8, 0, 65_536, [2, 20, 1]),
16965            ),
16966        ] {
16967            assert_eq!(
16968                f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
16969                "-Couldn't create filter!\r\n",
16970                "{why}"
16971            );
16972        }
16973    }
16974
16975    /// The RESP3 shapes, which are where this family differs most from RESP2
16976    /// and where one of its answers stops being readable.
16977    #[test]
16978    fn the_cuckoo_family_answers_in_resp3_spelling_too() {
16979        let mut f = Fixture::new();
16980        f.out.set_proto(Proto::Resp3);
16981        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16982        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16983        assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
16984        assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
16985        assert_eq!(
16986            f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
16987            "*2\r\n#t\r\n#f\r\n"
16988        );
16989        assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
16990        assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
16991        // The count stays an integer, because it counts rather than answers.
16992        assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
16993        assert_eq!(
16994            f.run(&[b"CF.INFO", b"c"]),
16995            "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16996             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16997             +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
16998             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16999        );
17000
17001        // `CF.INSERT` writes a boolean per item here and an integer per item on
17002        // RESP2, and minus one has nowhere to go in a boolean, so a RESP3
17003        // client cannot tell an item that did not fit from one that is already
17004        // there. `CF.INSERTNX` keeps its integers for exactly that reason.
17005        f.run(&[
17006            b"CF.RESERVE",
17007            b"s",
17008            b"4",
17009            b"BUCKETSIZE",
17010            b"1",
17011            b"EXPANSION",
17012            b"0",
17013        ]);
17014        assert_eq!(
17015            f.run(&[
17016                b"CF.INSERT",
17017                b"s",
17018                b"ITEMS",
17019                b"a",
17020                b"b",
17021                b"c",
17022                b"d",
17023                b"e",
17024                b"f"
17025            ]),
17026            "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
17027        );
17028        assert_eq!(
17029            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
17030            "*2\r\n:0\r\n:-1\r\n"
17031        );
17032        assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
17033        // The end of a dump is a nil and not an empty chunk, which is one
17034        // underscore here and a negative length on RESP2.
17035        assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
17036    }
17037
17038    // ------------------------------------------------------------------- cms
17039
17040    /// A sketch is made from either end, and both constructors look at the key
17041    /// before they look at their arguments.
17042    #[test]
17043    fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
17044        let mut f = Fixture::new();
17045        assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
17046        assert_eq!(
17047            f.run(&[b"CMS.INFO", b"d"]),
17048            "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
17049        );
17050        assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
17051        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17052        // Two over the error rounded up, and the log of the probability over the
17053        // log of a half rounded up, which for these two is 200 by 6.
17054        assert_eq!(
17055            f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
17056            "+OK\r\n"
17057        );
17058        assert_eq!(
17059            f.run(&[b"CMS.INFO", b"p"]),
17060            "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
17061        );
17062        // The key is checked first, so a width of zero at a key that is already
17063        // there is about the key and not about the width.
17064        assert_eq!(
17065            f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
17066            "-CMS: key already exists\r\n"
17067        );
17068        assert_eq!(
17069            f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
17070            "-CMS: invalid width\r\n"
17071        );
17072        assert_eq!(
17073            f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
17074            "-CMS: invalid depth\r\n"
17075        );
17076        assert_eq!(
17077            f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
17078            "-CMS: invalid overestimation value\r\n"
17079        );
17080        assert_eq!(
17081            f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
17082            "-CMS: invalid prob value\r\n"
17083        );
17084        // A probability whose float conversion is zero has no depth, and a width
17085        // past a signed sixty four bit integer has no width, and both are the
17086        // same sentence.
17087        assert_eq!(
17088            f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
17089            "-CMS: invalid init arguments\r\n"
17090        );
17091        // And a sketch bigger than a gibibyte of counters is refused here where
17092        // the reference reserves address space nobody has touched, which is
17093        // D-47.
17094        assert_eq!(
17095            f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
17096            "-CMS: Insufficient memory to create the key\r\n"
17097        );
17098        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17099    }
17100
17101    /// Every pair is parsed before any of them lands, the counters saturate,
17102    /// and the count is a signed total of what was asked for.
17103    #[test]
17104    fn increments_are_parsed_whole_and_the_counters_saturate() {
17105        let mut f = Fixture::new();
17106        f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
17107        assert_eq!(
17108            f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
17109            "*2\r\n:3\r\n:4\r\n"
17110        );
17111        // An item that is incremented twice in one call sees its own first
17112        // increment in the reply to the second.
17113        assert_eq!(
17114            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
17115            "*2\r\n:4\r\n:5\r\n"
17116        );
17117        // A bad number anywhere means nothing at all is applied.
17118        assert_eq!(
17119            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
17120            "-CMS: Cannot parse number\r\n"
17121        );
17122        assert_eq!(
17123            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
17124            "-CMS: Number cannot be negative\r\n"
17125        );
17126        assert_eq!(
17127            f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
17128            "*2\r\n:5\r\n:4\r\n"
17129        );
17130        // The counters stop at four billion and the item that stopped says so in
17131        // its own slot while the one beside it answers a number.
17132        f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
17133        assert_eq!(
17134            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
17135            "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
17136        );
17137        assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
17138        // The count is what was asked for rather than what landed, and it is
17139        // signed, so a big enough total comes back negative.
17140        f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
17141        f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
17142        f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
17143        assert_eq!(
17144            f.run(&[b"CMS.INFO", b"w"]),
17145            "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
17146        );
17147        // An odd number of arguments after the key is an arity error and not a
17148        // syntax one.
17149        assert!(
17150            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
17151                .contains("wrong number of arguments")
17152        );
17153        assert_eq!(
17154            f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
17155            "-CMS: key does not exist\r\n"
17156        );
17157        assert_eq!(
17158            f.run(&[b"CMS.QUERY", b"nope", b"a"]),
17159            "-CMS: key does not exist\r\n"
17160        );
17161    }
17162
17163    /// A merge overwrites its destination, and it is worked out in full before
17164    /// any of it is written.
17165    #[test]
17166    fn a_merge_lands_whole_or_not_at_all() {
17167        let mut f = Fixture::new();
17168        for name in [&b"m1"[..], b"m2", b"dst"] {
17169            f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
17170        }
17171        f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
17172        f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
17173        assert_eq!(
17174            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17175            "+OK\r\n"
17176        );
17177        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17178        // Overwritten and not added to, so the same merge twice is the same
17179        // answer twice.
17180        assert_eq!(
17181            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17182            "+OK\r\n"
17183        );
17184        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17185        assert_eq!(
17186            f.run(&[
17187                b"CMS.MERGE",
17188                b"dst",
17189                b"2",
17190                b"m1",
17191                b"m2",
17192                b"WEIGHTS",
17193                b"2",
17194                b"3"
17195            ]),
17196            "+OK\r\n"
17197        );
17198        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17199        // A cell times a weight is checked wide rather than wrapped, so this is
17200        // a refusal and the destination is left exactly as it was.
17201        assert_eq!(
17202            f.run(&[
17203                b"CMS.MERGE",
17204                b"dst",
17205                b"1",
17206                b"m1",
17207                b"WEIGHTS",
17208                b"4611686018427387904"
17209            ]),
17210            "-CMS: MERGE overflow\r\n"
17211        );
17212        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17213        // The destination comes first, then the count, then the layout, then the
17214        // weights, then the sources one at a time.
17215        f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
17216        assert_eq!(
17217            f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
17218            "-CMS: key does not exist\r\n"
17219        );
17220        assert_eq!(
17221            f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
17222            "-CMS: Number of keys must be positive\r\n"
17223        );
17224        assert_eq!(
17225            f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
17226            "-CMS: wrong number of keys\r\n"
17227        );
17228        assert_eq!(
17229            f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
17230            "-CMS: wrong number of keys/weights\r\n"
17231        );
17232        assert_eq!(
17233            f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
17234            "-CMS: width/depth is not equal\r\n"
17235        );
17236        assert_eq!(
17237            f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
17238            "-CMS: key does not exist\r\n"
17239        );
17240    }
17241
17242    /// A key holding anything else is `WRONGTYPE` to all six, and a key holding
17243    /// a sketch is refused by the two commands that would have to serialise it.
17244    #[test]
17245    fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17246        let mut f = Fixture::new();
17247        f.run(&[b"SET", b"s", b"text"]);
17248        for cmd in [
17249            vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
17250            vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
17251            vec![&b"CMS.QUERY"[..], b"s", b"a"],
17252            vec![&b"CMS.INFO"[..], b"s"],
17253            vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
17254        ] {
17255            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17256            let reply = f.run(&cmd);
17257            // The two constructors see the key before anything else and say so
17258            // in the module's own words, and the rest are `WRONGTYPE`.
17259            assert!(
17260                reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
17261                "{name}: {reply}"
17262            );
17263        }
17264        f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
17265        // Redis refuses to copy a module key that has no copy callback, and
17266        // these are its words rather than ours. `DUMP` is the other half of
17267        // D-48: the reference has a payload for one of these and we do not.
17268        assert_eq!(
17269            f.run(&[b"COPY", b"c", b"c2"]),
17270            "-ERR not supported for this module key\r\n"
17271        );
17272        assert_eq!(
17273            f.run(&[b"DUMP", b"c"]),
17274            "-ERR DUMP is not supported for this module key\r\n"
17275        );
17276        // A graph is nobody's module and keeps its own sentence.
17277        f.run(&[b"G.NADD", b"g", b"a"]);
17278        assert_eq!(
17279            f.run(&[b"COPY", b"g", b"g2"]),
17280            "-ERR COPY is not supported for a graph\r\n"
17281        );
17282        assert_eq!(
17283            f.run(&[b"DUMP", b"g"]),
17284            "-ERR DUMP is not supported for a graph\r\n"
17285        );
17286        // Everything that does not need a byte shape works on a sketch key the
17287        // way it works on any other.
17288        assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
17289        assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
17290        assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
17291        assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
17292        assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
17293    }
17294
17295    // ------------------------------------------------------------------ topk
17296
17297    /// `TOPK.RESERVE` takes three arguments or six, and looks at the key before
17298    /// it looks at any of them.
17299    #[test]
17300    fn a_reserve_takes_three_arguments_or_six() {
17301        let mut f = Fixture::new();
17302        assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
17303        assert_eq!(
17304            f.run(&[b"TOPK.INFO", b"t"]),
17305            "*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"
17306        );
17307        // Four arguments and five are an arity error rather than a defaulted
17308        // depth or decay.
17309        for cmd in [
17310            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
17311            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
17312        ] {
17313            assert!(f.run(&cmd).contains("wrong number of arguments"));
17314        }
17315        assert_eq!(
17316            f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
17317            "+OK\r\n"
17318        );
17319        // The key is checked first, so a reserve with nothing else right at a
17320        // key that is taken still says the key is taken.
17321        assert_eq!(
17322            f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
17323            "-TopK: key already exists\r\n"
17324        );
17325        assert_eq!(
17326            f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
17327            "-TopK: invalid k\r\n"
17328        );
17329        assert_eq!(
17330            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
17331            "-TopK: invalid width\r\n"
17332        );
17333        assert_eq!(
17334            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
17335            "-TopK: invalid depth\r\n"
17336        );
17337        // Zero is out and one is in, which is the module's `> 0` and `<= 1`.
17338        assert_eq!(
17339            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
17340            "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
17341        );
17342        assert_eq!(
17343            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
17344            "+OK\r\n"
17345        );
17346        // Past the cap, with the one sentence in the family that has a prefix.
17347        assert_eq!(
17348            f.run(&[
17349                b"TOPK.RESERVE",
17350                b"w",
17351                b"1",
17352                b"4294967295",
17353                b"4294967295",
17354                b"0.9"
17355            ]),
17356            "-ERR Insufficient memory to create topk data structure\r\n"
17357        );
17358    }
17359
17360    /// What the sketch keeps, and the three ways of asking about it.
17361    #[test]
17362    fn the_kept_set_is_what_query_and_list_answer_from() {
17363        let mut f = Fixture::new();
17364        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
17365        // A null an item while there is room, then the name of whatever was
17366        // pushed out.
17367        assert_eq!(
17368            f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
17369            "*2\r\n$-1\r\n$-1\r\n"
17370        );
17371        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
17372        // Two slots are full and `c` arrives with a count of one, which is not
17373        // under the smallest kept count, so it takes that slot straight away.
17374        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
17375        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
17376        assert_eq!(
17377            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
17378            "*3\r\n:1\r\n:0\r\n:1\r\n"
17379        );
17380        // The table still counts what the kept set let go of.
17381        assert_eq!(
17382            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17383            "*3\r\n:11\r\n:1\r\n:6\r\n"
17384        );
17385        assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
17386        assert_eq!(
17387            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
17388            "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
17389        );
17390        // Any prefix of the keyword turns the counts on, the empty string
17391        // included, and only a longer word or a different one is refused.
17392        assert_eq!(
17393            f.run(&[b"TOPK.LIST", b"t", b"w"]),
17394            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17395        );
17396        assert_eq!(
17397            f.run(&[b"TOPK.LIST", b"t", b""]),
17398            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17399        );
17400        assert_eq!(
17401            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
17402            "-WITHCOUNT keyword expected\r\n"
17403        );
17404        // And the keyword is looked at before the key, so a missing key with a
17405        // bad keyword complains about the keyword.
17406        assert_eq!(
17407            f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
17408            "-WITHCOUNT keyword expected\r\n"
17409        );
17410        assert_eq!(
17411            f.run(&[b"TOPK.LIST", b"missing"]),
17412            "-TopK: key does not exist\r\n"
17413        );
17414        // An item counted zero times is kept and not listed.
17415        f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
17416        assert_eq!(
17417            f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
17418            "*1\r\n$-1\r\n"
17419        );
17420        assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
17421        assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
17422    }
17423
17424    /// `TOPK.INCRBY` applies as it goes, so a bad increment leaves everything
17425    /// before it counted, and the reply counts what it wrote.
17426    #[test]
17427    fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
17428        let mut f = Fixture::new();
17429        f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
17430        // Three pairs, the middle one bad: two elements come back, one of them
17431        // the error, and the array header says two rather than three. That last
17432        // part is D-51 and it is why a client here stays in step.
17433        assert_eq!(
17434            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
17435            format!(
17436                "*2\r\n$-1\r\n-{}\r\n",
17437                "TopK: increment must be an integer greater or equal to 0                            and smaller or equal to 100,000"
17438            )
17439        );
17440        assert_eq!(
17441            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17442            "*3\r\n:3\r\n:0\r\n:0\r\n"
17443        );
17444        // A hundred thousand is in and one more is out.
17445        assert_eq!(
17446            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
17447            "*1\r\n$-1\r\n"
17448        );
17449        assert!(
17450            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
17451                .contains("smaller or equal to 100,000")
17452        );
17453        // Pairs have to be pairs.
17454        assert!(
17455            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
17456                .contains("wrong number of arguments")
17457        );
17458        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
17459    }
17460
17461    /// The RESP3 shapes, which are the two the protocols disagree about.
17462    #[test]
17463    fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
17464        let mut f = Fixture::new();
17465        f.run(&[b"HELLO", b"3"]);
17466        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
17467        f.run(&[b"TOPK.ADD", b"t", b"a"]);
17468        assert_eq!(
17469            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
17470            "*2\r\n#t\r\n#f\r\n"
17471        );
17472        // The count stays an integer on both protocols.
17473        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
17474        assert_eq!(
17475            f.run(&[b"TOPK.INFO", b"t"]),
17476            "%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"
17477        );
17478        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
17479    }
17480
17481    /// A top k key answers the module sentences the other sketch families
17482    /// answer, and its own word for its type.
17483    #[test]
17484    fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17485        let mut f = Fixture::new();
17486        f.run(&[b"SET", b"s", b"text"]);
17487        for cmd in [
17488            vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
17489            vec![&b"TOPK.ADD"[..], b"s", b"a"],
17490            vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
17491            vec![&b"TOPK.QUERY"[..], b"s", b"a"],
17492            vec![&b"TOPK.COUNT"[..], b"s", b"a"],
17493            vec![&b"TOPK.LIST"[..], b"s"],
17494            vec![&b"TOPK.INFO"[..], b"s"],
17495        ] {
17496            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17497            let reply = f.run(&cmd);
17498            assert!(
17499                reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
17500                "{name}: {reply}"
17501            );
17502        }
17503        f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
17504        assert_eq!(
17505            f.run(&[b"COPY", b"t", b"t2"]),
17506            "-ERR not supported for this module key\r\n"
17507        );
17508        assert_eq!(
17509            f.run(&[b"DUMP", b"t"]),
17510            "-ERR DUMP is not supported for this module key\r\n"
17511        );
17512        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17513        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17514        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17515        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
17516        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17517        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17518        // Every one of the six that is not the constructor says the same thing
17519        // about a key that is not there.
17520        assert_eq!(
17521            f.run(&[b"TOPK.INFO", b"t3"]),
17522            "-TopK: key does not exist\r\n"
17523        );
17524    }
17525
17526    // --------------------------------------------------------------- tdigest
17527
17528    /// `TDIGEST.CREATE` takes two arguments or four, and the keyword search is a
17529    /// search rather than a lookup.
17530    #[test]
17531    fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
17532        let mut f = Fixture::new();
17533        assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
17534        // A hundred is the default and the capacity is six times it plus ten.
17535        assert_eq!(
17536            f.run(&[b"TDIGEST.INFO", b"t"]),
17537            "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
17538             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
17539             +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
17540        );
17541        assert_eq!(
17542            f.run(&[b"TDIGEST.CREATE", b"t"]),
17543            "-ERR T-Digest: key already exists\r\n"
17544        );
17545        // Three arguments is an arity error and not a missing keyword.
17546        assert!(
17547            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
17548                .contains("wrong number of arguments")
17549        );
17550        assert_eq!(
17551            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
17552            "+OK\r\n"
17553        );
17554        assert_eq!(
17555            f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
17556            "+OK\r\n"
17557        );
17558        // The word is looked for across both trailing arguments and the number
17559        // is then read out of the last one whatever was found, so this looks for
17560        // a number inside the word `COMPRESSION` and does not find one.
17561        assert_eq!(
17562            f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
17563            "-ERR T-Digest: error parsing compression parameter\r\n"
17564        );
17565        assert_eq!(
17566            f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
17567            "-ERR T-Digest: wrong keyword\r\n"
17568        );
17569        assert_eq!(
17570            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
17571            "-ERR T-Digest: error parsing compression parameter\r\n"
17572        );
17573        assert_eq!(
17574            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
17575            "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
17576        );
17577        // The reference's own ceiling, which is where the capacity stops fitting
17578        // in an int, and one past it.
17579        assert_eq!(
17580            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
17581            "-ERR T-Digest: allocation failed\r\n"
17582        );
17583        // And ours, which is a gibibyte of centroids and is D-52.
17584        assert_eq!(
17585            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
17586            "-ERR T-Digest: allocation failed\r\n"
17587        );
17588        // The key is checked before the arguments, so a bad compression at a key
17589        // that is already a digest still says the key is taken.
17590        assert_eq!(
17591            f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
17592            "-ERR T-Digest: key already exists\r\n"
17593        );
17594    }
17595
17596    /// The four samples every note about this family is written against, and the
17597    /// answers a real 8.10.1 gives for them.
17598    #[test]
17599    fn the_quantile_family_answers_what_the_module_answers() {
17600        let mut f = Fixture::new();
17601        f.run(&[b"TDIGEST.CREATE", b"s"]);
17602        assert_eq!(
17603            f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
17604            "+OK\r\n"
17605        );
17606        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
17607        assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
17608        // The cdf of a sample is the weight below it plus half its own.
17609        assert_eq!(
17610            f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
17611            "*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"
17612        );
17613        assert_eq!(
17614            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
17615            "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
17616        );
17617        // Out of order, the walk restarts, and 0.5 answers 3 either way while
17618        // the two after it are read from the front again.
17619        assert_eq!(
17620            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
17621            "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
17622        );
17623        assert_eq!(
17624            f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17625            "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
17626        );
17627        assert_eq!(
17628            f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17629            "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
17630        );
17631        assert_eq!(
17632            f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
17633            "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
17634        );
17635        assert_eq!(
17636            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
17637            "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
17638        );
17639        assert_eq!(
17640            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
17641            "$3\r\n2.5\r\n"
17642        );
17643        assert_eq!(
17644            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
17645            "$3\r\n2.5\r\n"
17646        );
17647        // The ranges, which are separate sentences from the parse failures.
17648        assert_eq!(
17649            f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
17650            "-ERR T-Digest: quantile should be in [0,1]\r\n"
17651        );
17652        assert_eq!(
17653            f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
17654            "-ERR T-Digest: error parsing quantile\r\n"
17655        );
17656        assert_eq!(
17657            f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
17658            "-ERR T-Digest: error parsing cdf\r\n"
17659        );
17660        assert_eq!(
17661            f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
17662            "-ERR T-Digest: error parsing value\r\n"
17663        );
17664        assert_eq!(
17665            f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
17666            "-ERR T-Digest: rank needs to be non negative\r\n"
17667        );
17668        assert_eq!(
17669            f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
17670            "-ERR T-Digest: error parsing rank\r\n"
17671        );
17672        // Both cuts have their own parse sentence and share the range one, and
17673        // equal cuts are refused rather than answering nothing.
17674        assert_eq!(
17675            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
17676            "-ERR T-Digest: error parsing low_cut_percentile\r\n"
17677        );
17678        assert_eq!(
17679            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
17680            "-ERR T-Digest: error parsing high_cut_percentile\r\n"
17681        );
17682        assert_eq!(
17683            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
17684            "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
17685        );
17686        assert_eq!(
17687            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
17688            "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
17689        );
17690    }
17691
17692    /// An empty digest answers every question, and answers most of them with
17693    /// something that is not a number.
17694    #[test]
17695    fn an_empty_digest_has_an_answer_for_everything() {
17696        let mut f = Fixture::new();
17697        f.run(&[b"TDIGEST.CREATE", b"e"]);
17698        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17699        assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
17700        assert_eq!(
17701            f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
17702            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17703        );
17704        assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
17705        assert_eq!(
17706            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
17707            "$3\r\nnan\r\n"
17708        );
17709        // Minus two, which is a number no rank on a digest with samples in it
17710        // can ever be.
17711        assert_eq!(
17712            f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
17713            "*2\r\n:-2\r\n:-2\r\n"
17714        );
17715        assert_eq!(
17716            f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
17717            "*2\r\n:-2\r\n:-2\r\n"
17718        );
17719        assert_eq!(
17720            f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
17721            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17722        );
17723        // A reset puts a digest with samples back into exactly this state.
17724        f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
17725        assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
17726        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17727        // Down to the compression count, so a reset digest and a fresh one of
17728        // the same compression report the same nine numbers.
17729        f.run(&[b"TDIGEST.CREATE", b"e2"]);
17730        assert_eq!(
17731            f.run(&[b"TDIGEST.INFO", b"e"]),
17732            f.run(&[b"TDIGEST.INFO", b"e2"])
17733        );
17734    }
17735
17736    /// The double parser is Redis's and not this engine's, and the two disagree
17737    /// at both ends of the range.
17738    #[test]
17739    fn a_sample_is_read_the_way_redis_reads_a_double() {
17740        let mut f = Fixture::new();
17741        f.run(&[b"TDIGEST.CREATE", b"a"]);
17742        // Overflow and underflow are parse failures rather than an infinity and
17743        // a zero, which is where this parts company with the rest of the engine.
17744        for bad in [
17745            &b"nan"[..],
17746            b"1e400",
17747            b"-1e400",
17748            b"1e309",
17749            b"1e-400",
17750            b"",
17751            b" 1",
17752            b"1 ",
17753            b"1e",
17754            b"--1",
17755        ] {
17756            assert_eq!(
17757                f.run(&[b"TDIGEST.ADD", b"a", bad]),
17758                "-ERR T-Digest: error parsing val parameter\r\n",
17759                "{}",
17760                String::from_utf8_lossy(bad)
17761            );
17762        }
17763        // An infinity spelled out parses and is then refused for being one, with
17764        // a different sentence.
17765        for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
17766            assert_eq!(
17767                f.run(&[b"TDIGEST.ADD", b"a", word]),
17768                "-ERR T-Digest: val parameter needs to be a finite number\r\n",
17769                "{}",
17770                String::from_utf8_lossy(word)
17771            );
17772        }
17773        // These all parse: hex, a bare point either side, and the smallest
17774        // subnormal the reference will take.
17775        for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
17776            assert_eq!(
17777                f.run(&[b"TDIGEST.ADD", b"a", good]),
17778                "+OK\r\n",
17779                "{}",
17780                String::from_utf8_lossy(good)
17781            );
17782        }
17783        // Nothing landed from the failures, so six samples is what there is.
17784        assert!(
17785            f.run(&[b"TDIGEST.INFO", b"a"])
17786                .contains("Observations\r\n:6\r\n")
17787        );
17788        // Every value is parsed before any is added, so this whole command is a
17789        // no op.
17790        assert_eq!(
17791            f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
17792            "-ERR T-Digest: error parsing val parameter\r\n"
17793        );
17794        assert!(
17795            f.run(&[b"TDIGEST.INFO", b"a"])
17796                .contains("Observations\r\n:6\r\n")
17797        );
17798    }
17799
17800    /// What a merge does to its destination, to its inputs and to the buffer
17801    /// split `TDIGEST.INFO` reports.
17802    #[test]
17803    fn a_merge_sweeps_the_destination_between_its_inputs() {
17804        let mut f = Fixture::new();
17805        f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
17806        f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
17807        f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
17808        f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
17809        assert_eq!(
17810            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
17811            "+OK\r\n"
17812        );
17813        // The destination did not exist, so the compression is the largest of
17814        // the inputs. The three from the first input were swept in before the
17815        // three from the second arrived, which is the one visible effect of the
17816        // reference folding one input at a time.
17817        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17818        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17819        assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
17820        assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
17821        assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
17822        assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
17823        assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
17824        // Reading a source sweeps it too, so a merge writes to keys it only
17825        // reads from.
17826        assert!(
17827            f.run(&[b"TDIGEST.INFO", b"m1"])
17828                .contains("Merged nodes\r\n:3\r\n")
17829        );
17830        // Without OVERRIDE the destination joins its own inputs, so this takes
17831        // it to nine observations and keeps its own compression.
17832        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
17833        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17834        assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
17835        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17836        // With OVERRIDE the old destination is dropped and the compression goes
17837        // back to the largest of the inputs.
17838        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
17839        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17840        assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
17841        assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
17842        // And COMPRESSION beats both.
17843        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
17844        assert!(
17845            f.run(&[b"TDIGEST.INFO", b"d"])
17846                .contains("Compression\r\n:500\r\n")
17847        );
17848        // Naming the destination as a source folds it in twice.
17849        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
17850        assert!(
17851            f.run(&[b"TDIGEST.INFO", b"d"])
17852                .contains("Observations\r\n:12\r\n")
17853        );
17854        // The arguments, in the order the reference checks them.
17855        assert_eq!(
17856            f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
17857            "-ERR T-Digest: error parsing numkeys\r\n"
17858        );
17859        assert_eq!(
17860            f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
17861            "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
17862        );
17863        assert!(
17864            f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
17865                .contains("wrong number of arguments")
17866        );
17867        assert!(
17868            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
17869                .contains("wrong number of arguments")
17870        );
17871        assert_eq!(
17872            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
17873            "-ERR T-Digest: wrong keyword\r\n"
17874        );
17875        // A source that is not there stops the whole thing, and the destination
17876        // is left as it was.
17877        assert_eq!(
17878            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
17879            "-ERR T-Digest: key does not exist\r\n"
17880        );
17881        assert!(
17882            f.run(&[b"TDIGEST.INFO", b"d"])
17883                .contains("Observations\r\n:12\r\n")
17884        );
17885        // A destination that is not there and is also named as a source is the
17886        // same sentence rather than an empty merge.
17887        assert_eq!(
17888            f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
17889            "-ERR T-Digest: key does not exist\r\n"
17890        );
17891    }
17892
17893    /// The RESP3 shapes, which are the two the protocols disagree about.
17894    #[test]
17895    fn a_digest_answers_doubles_and_a_map_on_resp3() {
17896        let mut f = Fixture::new();
17897        f.run(&[b"HELLO", b"3"]);
17898        f.run(&[b"TDIGEST.CREATE", b"s"]);
17899        f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
17900        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
17901        assert_eq!(
17902            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
17903            "*2\r\n,1\r\n,4\r\n"
17904        );
17905        assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
17906        // The two infinities and the NaN go out as the bare words.
17907        assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
17908        assert_eq!(
17909            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
17910            "*1\r\n,-inf\r\n"
17911        );
17912        f.run(&[b"TDIGEST.CREATE", b"e"]);
17913        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
17914        // The ranks stay integers on both protocols.
17915        assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
17916        // Every question above swept the buffer in, so the four samples are all
17917        // merged by now and the compression count says it happened once.
17918        assert_eq!(
17919            f.run(&[b"TDIGEST.INFO", b"s"]),
17920            "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
17921             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
17922             +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
17923        );
17924    }
17925
17926    /// A t digest key answers the module sentences the other sketch families
17927    /// answer, and its own word for its type.
17928    #[test]
17929    fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
17930        let mut f = Fixture::new();
17931        f.run(&[b"SET", b"s", b"text"]);
17932        for cmd in [
17933            vec![&b"TDIGEST.CREATE"[..], b"s"],
17934            vec![&b"TDIGEST.RESET"[..], b"s"],
17935            vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
17936            vec![&b"TDIGEST.MIN"[..], b"s"],
17937            vec![&b"TDIGEST.MAX"[..], b"s"],
17938            vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
17939            vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
17940            vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
17941            vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
17942            vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
17943            vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
17944            vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
17945            vec![&b"TDIGEST.INFO"[..], b"s"],
17946        ] {
17947            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17948            let reply = f.run(&cmd);
17949            assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
17950        }
17951        // The merge checks its destination the same way, and its sources too.
17952        f.run(&[b"TDIGEST.CREATE", b"t"]);
17953        assert!(
17954            f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
17955                .starts_with("-WRONGTYPE")
17956        );
17957        assert!(
17958            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
17959                .starts_with("-WRONGTYPE")
17960        );
17961        assert_eq!(
17962            f.run(&[b"COPY", b"t", b"t2"]),
17963            "-ERR not supported for this module key\r\n"
17964        );
17965        assert_eq!(
17966            f.run(&[b"DUMP", b"t"]),
17967            "-ERR DUMP is not supported for this module key\r\n"
17968        );
17969        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17970        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17971        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17972        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
17973        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17974        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17975        // An empty digest is still a key, so the twelve that are not the
17976        // constructor all say the same thing once it is gone.
17977        assert_eq!(
17978            f.run(&[b"TDIGEST.INFO", b"t3"]),
17979            "-ERR T-Digest: key does not exist\r\n"
17980        );
17981        // The key is looked at before the arguments, so a bad argument at a key
17982        // that is not there still says the key is not there.
17983        assert_eq!(
17984            f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
17985            "-ERR T-Digest: key does not exist\r\n"
17986        );
17987    }
17988
17989    // -------------------------------------------------------------------- ts
17990
17991    /// A `TS.INFO` reply with the memory usage taken out of it.
17992    ///
17993    /// That number is what a series costs here rather than what one costs in the
17994    /// module, which is D-53, and it moves whenever the layout of a chunk does.
17995    /// Everything either side of it is the wire contract and is worth pinning
17996    /// down exactly, so the tests below check the whole reply with the one
17997    /// number lifted out.
17998    fn without_memory(reply: &str) -> String {
17999        let head = "+memoryUsage\r\n:";
18000        let at = reply.find(head).expect("every TS.INFO reports memory");
18001        let rest = &reply[at + head.len()..];
18002        let end = rest.find("\r\n").expect("and it is a whole number");
18003        format!("{}{}", &reply[..at + head.len()], &rest[end..])
18004    }
18005
18006    /// A series is made empty and still says it has a chunk, and the options are
18007    /// read before the key is looked at.
18008    #[test]
18009    fn a_series_is_made_empty_and_reports_on_itself() {
18010        let mut f = Fixture::new();
18011        assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
18012        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18013        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
18014        // Fourteen fields, so twenty eight elements. An empty series reports one
18015        // chunk and zero at both ends, and neither the chunk type nor the
18016        // duplicate policy is ever a nil.
18017        assert_eq!(
18018            without_memory(&f.run(&[b"TS.INFO", b"t"])),
18019            "*28\r\n\
18020             +totalSamples\r\n:0\r\n\
18021             +memoryUsage\r\n:\r\n\
18022             +firstTimestamp\r\n:0\r\n\
18023             +lastTimestamp\r\n:0\r\n\
18024             +retentionTime\r\n:0\r\n\
18025             +chunkCount\r\n:1\r\n\
18026             +chunkSize\r\n:4096\r\n\
18027             +chunkType\r\n+compressed\r\n\
18028             +duplicatePolicy\r\n+block\r\n\
18029             +labels\r\n*0\r\n\
18030             +sourceKey\r\n$-1\r\n\
18031             +rules\r\n*0\r\n\
18032             +ignoreMaxTimeDiff\r\n:0\r\n\
18033             +ignoreMaxValDiff\r\n$1\r\n0\r\n"
18034        );
18035        // A key that is already there is about the key whatever it holds, and
18036        // the existence is what is checked rather than the type.
18037        assert_eq!(
18038            f.run(&[b"TS.CREATE", b"t"]),
18039            "-ERR TSDB: key already exists\r\n"
18040        );
18041        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18042        assert_eq!(
18043            f.run(&[b"TS.CREATE", b"str"]),
18044            "-ERR TSDB: key already exists\r\n"
18045        );
18046        // But the arguments are read first, so a bad one at a key that is there
18047        // answers about the argument.
18048        assert_eq!(
18049            f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
18050            "-ERR TSDB: Couldn't parse RETENTION\r\n"
18051        );
18052        // The seven that will not make a series say WRONGTYPE about a key
18053        // holding something else, where the two that would say a sentence.
18054        // The word is inside the sentence and not in front of it, because the
18055        // module writes its own error text and Redis puts ERR on the front of
18056        // anything a module writes.
18057        let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
18058        assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
18059        assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
18060        assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
18061        assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
18062        assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
18063        assert_eq!(
18064            f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
18065            "-ERR TSDB: the key is not a TSDB key\r\n"
18066        );
18067        // And the ones that will not make one say so about a key that is gone.
18068        assert_eq!(
18069            f.run(&[b"TS.INFO", b"nope"]),
18070            "-ERR TSDB: the key does not exist\r\n"
18071        );
18072        assert_eq!(
18073            f.run(&[b"TS.GET", b"nope"]),
18074            "-ERR TSDB: the key does not exist\r\n"
18075        );
18076        assert_eq!(
18077            f.run(&[b"TS.ALTER", b"nope"]),
18078            "-ERR TSDB: the key does not exist\r\n"
18079        );
18080        assert_eq!(
18081            f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
18082            "-ERR TSDB: the key does not exist\r\n"
18083        );
18084    }
18085
18086    /// Every option word, including the ones that are wrong, and the scan that
18087    /// finds them.
18088    #[test]
18089    fn the_options_are_a_keyword_scan_and_not_a_grammar() {
18090        let mut f = Fixture::new();
18091        assert_eq!(
18092            f.run(&[
18093                b"TS.CREATE",
18094                b"t",
18095                b"RETENTION",
18096                b"5000",
18097                b"ENCODING",
18098                b"UNCOMPRESSED",
18099                b"CHUNK_SIZE",
18100                b"128",
18101                b"DUPLICATE_POLICY",
18102                b"LAST",
18103                b"IGNORE",
18104                b"10",
18105                b"0.5",
18106                b"LABELS",
18107                b"room",
18108                b"kitchen"
18109            ]),
18110            "+OK\r\n"
18111        );
18112        let info = f.run(&[b"TS.INFO", b"t"]);
18113        assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
18114        assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
18115        assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
18116        assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
18117        assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
18118        // A plain double here, where a sample value out of TS.GET is the
18119        // shortest digits that read back as the same number.
18120        assert!(
18121            info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
18122            "{info}"
18123        );
18124        assert!(
18125            info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
18126            "{info}"
18127        );
18128
18129        // A word that is not an option is read past rather than refused.
18130        assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
18131        // LABELS eats everything after it in pairs, and the later scans still
18132        // look inside what it ate, so this sets a retention and stores a label
18133        // called RETENTION at the same time.
18134        assert_eq!(
18135            f.run(&[
18136                b"TS.CREATE",
18137                b"g",
18138                b"LABELS",
18139                b"a",
18140                b"b",
18141                b"RETENTION",
18142                b"5"
18143            ]),
18144            "+OK\r\n"
18145        );
18146        let greedy = f.run(&[b"TS.INFO", b"g"]);
18147        assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
18148        assert!(
18149            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"),
18150            "{greedy}"
18151        );
18152
18153        // Every way an option can be wrong, in the order the module reads them.
18154        assert_eq!(
18155            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
18156            "-ERR TSDB: Couldn't parse LABELS\r\n"
18157        );
18158        assert_eq!(
18159            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
18160            "-ERR TSDB: Couldn't parse LABELS\r\n"
18161        );
18162        assert_eq!(
18163            f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
18164            "-ERR TSDB: Couldn't parse RETENTION\r\n"
18165        );
18166        // A retention below zero is one of the two the module writes with no
18167        // ERR in front of it, where one that is not a number gets one.
18168        assert_eq!(
18169            f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
18170            "-TSDB: Couldn't parse RETENTION\r\n"
18171        );
18172        assert_eq!(
18173            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
18174            "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
18175        );
18176        assert_eq!(
18177            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
18178            "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
18179        );
18180        assert_eq!(
18181            f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
18182            "-ERR TSDB: unknown ENCODING parameter\r\n"
18183        );
18184        // And an ENCODING with nothing behind it is an arity error where every
18185        // other keyword in the same spot is a sentence.
18186        assert!(
18187            f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
18188                .contains("wrong number of arguments for 'ts.create' command")
18189        );
18190        assert_eq!(
18191            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
18192            "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
18193        );
18194        assert_eq!(
18195            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
18196            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18197        );
18198        assert_eq!(
18199            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
18200            "-ERR TSDB: Couldn't parse IGNORE\r\n"
18201        );
18202        assert_eq!(
18203            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
18204            "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
18205        );
18206        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
18207
18208        // An alter changes what was named and leaves the rest alone, and reads
18209        // an encoding only far enough to refuse a bad one.
18210        assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
18211        let after = f.run(&[b"TS.INFO", b"t"]);
18212        assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
18213        assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
18214        assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
18215        assert_eq!(
18216            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
18217            "-ERR TSDB: unknown ENCODING parameter\r\n"
18218        );
18219        // An encoding it does take is still not applied.
18220        assert_eq!(
18221            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
18222            "+OK\r\n"
18223        );
18224        assert!(
18225            f.run(&[b"TS.INFO", b"t"])
18226                .contains("+chunkType\r\n+uncompressed\r\n")
18227        );
18228    }
18229
18230    /// Samples go in, come back out and are refused for the reasons the module
18231    /// refuses them.
18232    #[test]
18233    fn samples_land_where_they_are_put_and_the_newest_comes_back() {
18234        let mut f = Fixture::new();
18235        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
18236        // The series was made on the way in.
18237        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18238        assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
18239        // A sample value goes out as a simple string of the shortest digits
18240        // that read back as the same number.
18241        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
18242        assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
18243        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
18244        // An empty series has no newest sample and answers an empty array
18245        // rather than a nil.
18246        assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
18247        assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
18248
18249        // The value is read before the key, so a bad one against a key holding
18250        // a string is about the value.
18251        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18252        assert_eq!(
18253            f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
18254            "-ERR TSDB: invalid value\r\n"
18255        );
18256        // The grammar is tighter than the one a number argument usually gets:
18257        // no leading plus, no bare fraction, no infinity and nothing that does
18258        // not fit.
18259        for bad in [
18260            &b".5"[..],
18261            b"1.",
18262            b"+1",
18263            b" 1",
18264            b"0x10",
18265            b"inf",
18266            b"1e400",
18267            b"--1",
18268            b"1e",
18269        ] {
18270            assert_eq!(
18271                f.run(&[b"TS.ADD", b"v", b"1", bad]),
18272                "-ERR TSDB: invalid value\r\n",
18273                "{}",
18274                String::from_utf8_lossy(bad)
18275            );
18276        }
18277        // And a reading that is not a number is one of three words.
18278        assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
18279
18280        // A timestamp that is not a number, and one that is and is below zero,
18281        // are two different sentences.
18282        assert_eq!(
18283            f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
18284            "-ERR TSDB: invalid timestamp\r\n"
18285        );
18286        assert_eq!(
18287            f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
18288            "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
18289        );
18290
18291        // A repeated timestamp is blocked by default, and ON_DUPLICATE on the
18292        // command beats what the series was told.
18293        assert_eq!(
18294            f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
18295            "-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"
18296        );
18297        assert_eq!(
18298            f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
18299            ":300\r\n"
18300        );
18301        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
18302        // ON_DUPLICATE is only read when the key was already there, which is
18303        // why a policy word that is not a policy passes on a fresh key.
18304        assert_eq!(
18305            f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
18306            ":1\r\n"
18307        );
18308        assert_eq!(
18309            f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
18310            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18311        );
18312
18313        // Retention is exact and it is checked before anything else happens, so
18314        // a sample landing behind the window is refused rather than trimmed.
18315        assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
18316        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
18317        assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
18318        assert_eq!(
18319            f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
18320            "-ERR TSDB: Timestamp is older than retention\r\n"
18321        );
18322        // And the window trims as it moves.
18323        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
18324        assert!(
18325            f.run(&[b"TS.INFO", b"r"])
18326                .contains("+totalSamples\r\n:1\r\n")
18327        );
18328
18329        // An ignore window drops a sample close enough to the newest one to be
18330        // uninteresting, and answers the newest timestamp so a client can tell.
18331        assert_eq!(
18332            f.run(&[
18333                b"TS.CREATE",
18334                b"i",
18335                b"DUPLICATE_POLICY",
18336                b"LAST",
18337                b"IGNORE",
18338                b"10",
18339                b"0.5"
18340            ]),
18341            "+OK\r\n"
18342        );
18343        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
18344        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
18345        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
18346    }
18347
18348    /// Every triple in a `TS.MADD` is answered on its own, and none of them
18349    /// makes a series.
18350    #[test]
18351    fn a_madd_answers_each_triple_and_creates_nothing() {
18352        let mut f = Fixture::new();
18353        assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
18354        assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
18355        assert_eq!(
18356            f.run(&[
18357                b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
18358            ]),
18359            "*3\r\n:100\r\n:100\r\n:200\r\n"
18360        );
18361        // A key that is not a series is an error in its own slot and the ones
18362        // after it still land.
18363        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18364        assert_eq!(
18365            f.run(&[
18366                b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
18367            ]),
18368            "*3\r\n\
18369             -ERR TSDB: the key is not a TSDB key\r\n\
18370             -ERR TSDB: the key is not a TSDB key\r\n\
18371             :300\r\n"
18372        );
18373        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
18374        // A bad value and a bad timestamp are answered in their slots too.
18375        assert_eq!(
18376            f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
18377            "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
18378        );
18379        // And a list that is not made of triples is an arity error.
18380        assert!(
18381            f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
18382                .contains("wrong number of arguments for 'ts.madd' command")
18383        );
18384    }
18385
18386    /// The two increments, which only ever write forwards.
18387    #[test]
18388    fn an_increment_walks_the_newest_value_up_and_down() {
18389        let mut f = Fixture::new();
18390        assert_eq!(
18391            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18392            ":100\r\n"
18393        );
18394        assert_eq!(
18395            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18396            ":100\r\n"
18397        );
18398        // Two on one timestamp add up rather than collide, because the sample
18399        // goes in under the last policy whatever the series says.
18400        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
18401        assert_eq!(
18402            f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
18403            ":200\r\n"
18404        );
18405        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
18406        // A timestamp behind the newest sample is the other of the two errors
18407        // the module writes with no ERR in front of it.
18408        assert_eq!(
18409            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
18410            "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
18411        );
18412        // The increment goes through the ordinary number reader, so it takes
18413        // what a sample value will not and refuses a NaN that a sample value
18414        // takes.
18415        assert_eq!(
18416            f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
18417            ":1\r\n"
18418        );
18419        assert_eq!(
18420            f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
18421            ":1\r\n"
18422        );
18423        assert_eq!(
18424            f.run(&[b"TS.INCRBY", b"t", b"nan"]),
18425            "-ERR TSDB: invalid increase/decrease value\r\n"
18426        );
18427        assert_eq!(
18428            f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
18429            "-ERR TSDB: invalid increase/decrease value\r\n"
18430        );
18431        // A key holding something else is WRONGTYPE and is answered before the
18432        // number is looked at.
18433        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18434        assert_eq!(
18435            f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
18436            "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18437        );
18438        // A TIMESTAMP keyword with nothing behind it is about the timestamp.
18439        // The reference reads one past the end of its own arguments here and
18440        // answers whatever was in that memory, so there is nothing to copy and
18441        // this answers the same thing every time.
18442        assert_eq!(
18443            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
18444            "-ERR TSDB: invalid timestamp\r\n"
18445        );
18446        // And one behind a LABELS is a label name rather than the keyword, so
18447        // this lands at the clock rather than at 5.
18448        assert_eq!(
18449            f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
18450            format!(":{}\r\n", f.server.now_ms())
18451        );
18452        // Adding to a series whose newest value is not a number has no answer.
18453        assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
18454        assert_eq!(
18455            f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
18456            "-ERR TSDB: cannot increment/decrement NaN value\r\n"
18457        );
18458    }
18459
18460    /// Deleting a span, both ends included.
18461    #[test]
18462    fn deleting_takes_out_a_span_and_answers_how_many_went() {
18463        let mut f = Fixture::new();
18464        for at in [b"100".as_slice(), b"200", b"300", b"400"] {
18465            f.run(&[b"TS.ADD", b"t", at, b"1"]);
18466        }
18467        assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
18468        assert!(
18469            f.run(&[b"TS.INFO", b"t"])
18470                .contains("+totalSamples\r\n:2\r\n")
18471        );
18472        // Ends the wrong way round take nothing out rather than being an error.
18473        assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
18474        // The two open ends.
18475        assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
18476        // A series everything has been deleted from keeps its chunk and reports
18477        // zero at both ends again.
18478        let empty = f.run(&[b"TS.INFO", b"t"]);
18479        assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
18480        assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
18481        assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
18482        assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
18483        assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
18484        // The two ends have their own sentences.
18485        assert_eq!(
18486            f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
18487            "-ERR TSDB: wrong fromTimestamp\r\n"
18488        );
18489        assert_eq!(
18490            f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
18491            "-ERR TSDB: wrong toTimestamp\r\n"
18492        );
18493        assert_eq!(
18494            f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
18495            "-ERR TSDB: wrong fromTimestamp\r\n"
18496        );
18497    }
18498
18499    /// What RESP3 changes, which is the two places a number is written and the
18500    /// shape of `TS.INFO`.
18501    #[test]
18502    fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
18503        let mut f = Fixture::new();
18504        f.out = Out::new(Proto::Resp3);
18505        assert_eq!(
18506            f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
18507            "+OK\r\n"
18508        );
18509        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
18510        // A double rather than the simple string RESP2 gets.
18511        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
18512        assert_eq!(
18513            without_memory(&f.run(&[b"TS.INFO", b"t"])),
18514            "%14\r\n\
18515             +totalSamples\r\n:1\r\n\
18516             +memoryUsage\r\n:\r\n\
18517             +firstTimestamp\r\n:100\r\n\
18518             +lastTimestamp\r\n:100\r\n\
18519             +retentionTime\r\n:0\r\n\
18520             +chunkCount\r\n:1\r\n\
18521             +chunkSize\r\n:4096\r\n\
18522             +chunkType\r\n+compressed\r\n\
18523             +duplicatePolicy\r\n+block\r\n\
18524             +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18525             +sourceKey\r\n_\r\n\
18526             +rules\r\n%0\r\n\
18527             +ignoreMaxTimeDiff\r\n:0\r\n\
18528             +ignoreMaxValDiff\r\n,0\r\n"
18529        );
18530    }
18531
18532    /// Reading a span back, both ways round, with the two ends and the three
18533    /// things that trim what comes out.
18534    #[test]
18535    fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
18536        let mut f = Fixture::new();
18537        for (at, v) in [
18538            (b"100".as_slice(), b"1".as_slice()),
18539            (b"200", b"2"),
18540            (b"300", b"3"),
18541            (b"400", b"4"),
18542        ] {
18543            f.run(&[b"TS.ADD", b"t", at, v]);
18544        }
18545        assert_eq!(
18546            f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
18547            "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
18548             *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
18549        );
18550        // Both ends are included.
18551        assert_eq!(
18552            f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
18553            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18554        );
18555        // Backwards, and the count takes from the front of what comes out, so
18556        // backwards it takes the newest.
18557        assert_eq!(
18558            f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
18559            "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
18560        );
18561        // Ends the wrong way round are empty rather than an error.
18562        assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
18563        // The two filters.
18564        assert_eq!(
18565            f.run(&[
18566                b"TS.RANGE",
18567                b"t",
18568                b"-",
18569                b"+",
18570                b"FILTER_BY_VALUE",
18571                b"2",
18572                b"3"
18573            ]),
18574            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18575        );
18576        assert_eq!(
18577            f.run(&[
18578                b"TS.RANGE",
18579                b"t",
18580                b"-",
18581                b"+",
18582                b"FILTER_BY_TS",
18583                b"100",
18584                b"400"
18585            ]),
18586            "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
18587        );
18588        // A word that is not an option is ignored wherever it sits.
18589        assert_eq!(
18590            f.run(&[
18591                b"TS.RANGE",
18592                b"t",
18593                b"-",
18594                b"+",
18595                b"ZZZ",
18596                b"FILTER_BY_TS",
18597                b"400"
18598            ]),
18599            "*1\r\n*2\r\n:400\r\n+4\r\n"
18600        );
18601        // `LATEST` means nothing until there is a compaction rule to follow.
18602        assert_eq!(
18603            f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
18604            "*1\r\n*2\r\n:100\r\n+1\r\n"
18605        );
18606    }
18607
18608    /// The bucketing, which is one column a reduction and a flat row.
18609    #[test]
18610    fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
18611        let mut f = Fixture::new();
18612        for (at, v) in [
18613            (b"100".as_slice(), b"1".as_slice()),
18614            (b"200", b"2"),
18615            (b"300", b"3"),
18616            (b"400", b"4"),
18617        ] {
18618            f.run(&[b"TS.ADD", b"t", at, v]);
18619        }
18620        assert_eq!(
18621            f.run(&[
18622                b"TS.RANGE",
18623                b"t",
18624                b"-",
18625                b"+",
18626                b"AGGREGATION",
18627                b"avg",
18628                b"200"
18629            ]),
18630            "*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"
18631        );
18632        // Three reductions is a row of four and not a row of two with a nested
18633        // three in it.
18634        assert_eq!(
18635            f.run(&[
18636                b"TS.RANGE",
18637                b"t",
18638                b"-",
18639                b"+",
18640                b"AGGREGATION",
18641                b"min,max,count",
18642                b"200"
18643            ]),
18644            "*3\r\n\
18645             *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
18646             *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
18647             *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
18648        );
18649        // The timestamp a bucket is reported under.
18650        assert_eq!(
18651            f.run(&[
18652                b"TS.RANGE",
18653                b"t",
18654                b"-",
18655                b"+",
18656                b"AGGREGATION",
18657                b"avg",
18658                b"200",
18659                b"BUCKETTIMESTAMP",
18660                b"+"
18661            ]),
18662            "*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"
18663        );
18664        // An alignment moves where the bucket edges land.
18665        assert_eq!(
18666            f.run(&[
18667                b"TS.RANGE",
18668                b"t",
18669                b"100",
18670                b"400",
18671                b"ALIGN",
18672                b"100",
18673                b"AGGREGATION",
18674                b"sum",
18675                b"200"
18676            ]),
18677            "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
18678        );
18679        // A `COUNT` sitting where the reduction name belongs is that name, and
18680        // the scan for a real one starts again two words later.
18681        assert_eq!(
18682            f.run(&[
18683                b"TS.RANGE",
18684                b"t",
18685                b"-",
18686                b"+",
18687                b"AGGREGATION",
18688                b"count",
18689                b"200"
18690            ]),
18691            "*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"
18692        );
18693        assert_eq!(
18694            f.run(&[
18695                b"TS.RANGE",
18696                b"t",
18697                b"-",
18698                b"+",
18699                b"AGGREGATION",
18700                b"count",
18701                b"200",
18702                b"COUNT",
18703                b"1"
18704            ]),
18705            "*1\r\n*2\r\n:0\r\n+1\r\n"
18706        );
18707    }
18708
18709    /// `EMPTY` fills the gaps between readings and nothing else, and `last`
18710    /// carries two different things depending on which kind of empty it is.
18711    #[test]
18712    fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
18713        let mut f = Fixture::new();
18714        for (at, v) in [
18715            (b"0".as_slice(), b"1".as_slice()),
18716            (b"100", b"2"),
18717            (b"500", b"nan"),
18718            (b"600", b"3"),
18719        ] {
18720            f.run(&[b"TS.ADD", b"g", at, v]);
18721        }
18722        // Without `EMPTY` the buckets with nothing in them are not there at all,
18723        // and neither is the one holding only a reading that is not a number.
18724        assert_eq!(
18725            f.run(&[
18726                b"TS.RANGE",
18727                b"g",
18728                b"-",
18729                b"+",
18730                b"AGGREGATION",
18731                b"avg",
18732                b"100"
18733            ]),
18734            "*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"
18735        );
18736        // The sum of nothing is zero rather than not a number.
18737        assert_eq!(
18738            f.run(&[
18739                b"TS.RANGE",
18740                b"g",
18741                b"-",
18742                b"+",
18743                b"AGGREGATION",
18744                b"sum",
18745                b"100",
18746                b"EMPTY"
18747            ]),
18748            "*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\
18749             *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\
18750             *2\r\n:600\r\n+3\r\n"
18751        );
18752        // Buckets 200 through 400 have no readings at all and carry the reading
18753        // before the gap either way round. Bucket 500 has a reading that is not
18754        // a number, so it carries whatever the bucket before it in the reading
18755        // direction answered, which is 2 forwards and 3 backwards.
18756        assert_eq!(
18757            f.run(&[
18758                b"TS.RANGE",
18759                b"g",
18760                b"-",
18761                b"+",
18762                b"AGGREGATION",
18763                b"last",
18764                b"100",
18765                b"EMPTY"
18766            ]),
18767            "*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\
18768             *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\
18769             *2\r\n:600\r\n+3\r\n"
18770        );
18771        assert_eq!(
18772            f.run(&[
18773                b"TS.REVRANGE",
18774                b"g",
18775                b"-",
18776                b"+",
18777                b"AGGREGATION",
18778                b"last",
18779                b"100",
18780                b"EMPTY"
18781            ]),
18782            "*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\
18783             *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\
18784             *2\r\n:0\r\n+1\r\n"
18785        );
18786        // And a window that opens on that bucket has nothing in range before it
18787        // to carry, so it answers not a number.
18788        assert_eq!(
18789            f.run(&[
18790                b"TS.RANGE",
18791                b"g",
18792                b"500",
18793                b"600",
18794                b"AGGREGATION",
18795                b"last",
18796                b"100",
18797                b"EMPTY"
18798            ]),
18799            "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
18800        );
18801    }
18802
18803    /// The sentences a read answers when its options do not add up, which are
18804    /// the module's own word for word.
18805    #[test]
18806    fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
18807        let mut f = Fixture::new();
18808        f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
18809        f.run(&[b"SET", b"str", b"x"]);
18810        let cases: &[(&[&[u8]], &str)] = &[
18811            (
18812                &[b"TS.RANGE", b"t"],
18813                "-ERR wrong number of arguments for 'ts.range' command\r\n",
18814            ),
18815            // The key is resolved before a single option is read.
18816            (
18817                &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
18818                "-ERR TSDB: the key does not exist\r\n",
18819            ),
18820            (
18821                &[b"TS.RANGE", b"str", b"-", b"+"],
18822                "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
18823            ),
18824            (
18825                &[b"TS.RANGE", b"t", b"abc", b"+"],
18826                "-ERR TSDB: wrong fromTimestamp\r\n",
18827            ),
18828            (
18829                &[b"TS.RANGE", b"t", b"-", b"abc"],
18830                "-ERR TSDB: wrong toTimestamp\r\n",
18831            ),
18832            (
18833                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
18834                "-ERR TSDB: COUNT argument is missing\r\n",
18835            ),
18836            (
18837                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
18838                "-ERR TSDB: Couldn't parse COUNT\r\n",
18839            ),
18840            (
18841                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
18842                "-ERR TSDB: Invalid COUNT value\r\n",
18843            ),
18844            (
18845                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
18846                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18847            ),
18848            (
18849                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
18850                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18851            ),
18852            (
18853                &[
18854                    b"TS.RANGE",
18855                    b"t",
18856                    b"-",
18857                    b"+",
18858                    b"AGGREGATION",
18859                    b"nope",
18860                    b"100",
18861                ],
18862                "-ERR TSDB: Unknown aggregation type\r\n",
18863            ),
18864            (
18865                &[
18866                    b"TS.RANGE",
18867                    b"t",
18868                    b"-",
18869                    b"+",
18870                    b"AGGREGATION",
18871                    b"avg,,min",
18872                    b"100",
18873                ],
18874                "-ERR TSDB: Empty aggregation type in list\r\n",
18875            ),
18876            // The list of names is read before the width is looked at.
18877            (
18878                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
18879                "-ERR TSDB: Unknown aggregation type\r\n",
18880            ),
18881            (
18882                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
18883                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18884            ),
18885            (
18886                &[
18887                    b"TS.RANGE",
18888                    b"t",
18889                    b"-",
18890                    b"+",
18891                    b"AGGREGATION",
18892                    b"avg",
18893                    b"100",
18894                    b"X",
18895                    b"EMPTY",
18896                ],
18897                "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
18898            ),
18899            (
18900                &[
18901                    b"TS.RANGE",
18902                    b"t",
18903                    b"-",
18904                    b"+",
18905                    b"AGGREGATION",
18906                    b"avg",
18907                    b"100",
18908                    b"BUCKETTIMESTAMP",
18909                    b"z",
18910                ],
18911                "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
18912            ),
18913            (
18914                &[
18915                    b"TS.RANGE",
18916                    b"t",
18917                    b"-",
18918                    b"+",
18919                    b"AGGREGATION",
18920                    b"avg",
18921                    b"100",
18922                    b"X",
18923                    b"Y",
18924                    b"BUCKETTIMESTAMP",
18925                    b"-",
18926                ],
18927                "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
18928                 AGGREGATION flag\r\n",
18929            ),
18930            (
18931                &[
18932                    b"TS.RANGE",
18933                    b"t",
18934                    b"-",
18935                    b"+",
18936                    b"ALIGN",
18937                    b"z",
18938                    b"AGGREGATION",
18939                    b"avg",
18940                    b"100",
18941                ],
18942                "-ERR TSDB: unknown ALIGN parameter\r\n",
18943            ),
18944            (
18945                &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
18946                "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
18947            ),
18948            (
18949                &[
18950                    b"TS.RANGE",
18951                    b"t",
18952                    b"-",
18953                    b"+",
18954                    b"ALIGN",
18955                    b"-",
18956                    b"AGGREGATION",
18957                    b"avg",
18958                    b"100",
18959                ],
18960                "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
18961            ),
18962            (
18963                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
18964                "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
18965            ),
18966            (
18967                &[
18968                    b"TS.RANGE",
18969                    b"t",
18970                    b"-",
18971                    b"+",
18972                    b"FILTER_BY_VALUE",
18973                    b"x",
18974                    b"2",
18975                ],
18976                "-ERR TSDB: Couldn't parse MIN\r\n",
18977            ),
18978            (
18979                &[
18980                    b"TS.RANGE",
18981                    b"t",
18982                    b"-",
18983                    b"+",
18984                    b"FILTER_BY_VALUE",
18985                    b"1",
18986                    b"y",
18987                ],
18988                "-ERR TSDB: Couldn't parse MAX\r\n",
18989            ),
18990            (
18991                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
18992                "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
18993            ),
18994        ];
18995        for (argv, want) in cases {
18996            let got = f.run(argv);
18997            assert_eq!(&got, want, "{:?}", argv.last());
18998        }
18999        // The one sentence here that is yo's own rather than the module's, which
19000        // is D-54. A read that would build more rows than yo will build is
19001        // refused instead of attempted.
19002        f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
19003        f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
19004        assert_eq!(
19005            f.run(&[
19006                b"TS.RANGE",
19007                b"wide",
19008                b"-",
19009                b"+",
19010                b"AGGREGATION",
19011                b"avg",
19012                b"1",
19013                b"EMPTY"
19014            ]),
19015            "-ERR TSDB: the requested range holds too many empty buckets\r\n"
19016        );
19017    }
19018
19019    /// What RESP3 changes on a read, which is only how a number is written.
19020    #[test]
19021    fn resp3_writes_a_read_value_as_a_double() {
19022        let mut f = Fixture::new();
19023        f.out = Out::new(Proto::Resp3);
19024        for (at, v) in [
19025            (b"0".as_slice(), b"1".as_slice()),
19026            (b"100", b"2"),
19027            (b"500", b"nan"),
19028            (b"600", b"3"),
19029        ] {
19030            f.run(&[b"TS.ADD", b"g", at, v]);
19031        }
19032        assert_eq!(
19033            f.run(&[
19034                b"TS.RANGE",
19035                b"g",
19036                b"0",
19037                b"100",
19038                b"AGGREGATION",
19039                b"avg,min",
19040                b"200"
19041            ]),
19042            "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
19043        );
19044        assert_eq!(
19045            f.run(&[
19046                b"TS.RANGE",
19047                b"g",
19048                b"500",
19049                b"600",
19050                b"AGGREGATION",
19051                b"last",
19052                b"100",
19053                b"EMPTY"
19054            ]),
19055            "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
19056        );
19057    }
19058
19059    /// Two series with an overlap and a gap each, plus a third holding nothing,
19060    /// which is what the joined reads are measured against.
19061    fn joined() -> Fixture {
19062        let mut f = Fixture::new();
19063        f.run(&[b"TS.CREATE", b"z"]);
19064        for (at, v) in [
19065            (b"10".as_slice(), b"1".as_slice()),
19066            (b"20", b"2"),
19067            (b"40", b"4"),
19068            (b"50", b"5"),
19069        ] {
19070            f.run(&[b"TS.ADD", b"x", at, v]);
19071        }
19072        for (at, v) in [
19073            (b"20".as_slice(), b"20".as_slice()),
19074            (b"30", b"30"),
19075            (b"50", b"50"),
19076            (b"60", b"60"),
19077        ] {
19078            f.run(&[b"TS.ADD", b"y", at, v]);
19079        }
19080        f
19081    }
19082
19083    /// The joined read lines its keys up on the timestamp and writes a row as
19084    /// the timestamp and then a nested array of the columns, which is the one
19085    /// shape in the family that is not the flat pair.
19086    #[test]
19087    fn an_nrange_joins_its_keys_on_the_timestamp() {
19088        let mut f = joined();
19089        // One key still nests, so the shape does not depend on the count.
19090        assert_eq!(
19091            f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
19092            "*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\
19093             *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
19094        );
19095        // A key with no reading where another key has one writes NaN there.
19096        assert_eq!(
19097            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
19098            "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
19099             *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19100             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19101             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19102             *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
19103             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19104        );
19105        // A series holding nothing is a column of NaN and never a row of its
19106        // own, and the same key twice answers twice.
19107        assert_eq!(
19108            f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
19109            "*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"
19110        );
19111        assert_eq!(
19112            f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
19113            "*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"
19114        );
19115        // COUNT is applied to the joined rows and not to each key, so backwards
19116        // it gives the newest joined row rather than the newest of each.
19117        assert_eq!(
19118            f.run(&[
19119                b"TS.NREVRANGE",
19120                b"2",
19121                b"x",
19122                b"y",
19123                b"-",
19124                b"+",
19125                b"COUNT",
19126                b"1"
19127            ]),
19128            "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19129        );
19130        assert_eq!(
19131            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
19132            "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
19133        );
19134        // The two sample filters are settled a key at a time, before the join.
19135        assert_eq!(
19136            f.run(&[
19137                b"TS.NRANGE",
19138                b"2",
19139                b"x",
19140                b"y",
19141                b"-",
19142                b"+",
19143                b"FILTER_BY_VALUE",
19144                b"2",
19145                b"30"
19146            ]),
19147            "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19148             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19149             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19150             *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
19151        );
19152    }
19153
19154    /// The aggregation on a joined read names one reduction a key and then the
19155    /// one bucket width, and each name may be a comma list, so a row can be
19156    /// wider than the key count.
19157    #[test]
19158    fn an_nrange_aggregation_names_one_reduction_a_key() {
19159        let mut f = joined();
19160        assert_eq!(
19161            f.run(&[
19162                b"TS.NRANGE",
19163                b"2",
19164                b"x",
19165                b"y",
19166                b"-",
19167                b"+",
19168                b"AGGREGATION",
19169                b"sum",
19170                b"sum",
19171                b"20"
19172            ]),
19173            "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
19174             *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
19175             *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
19176             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19177        );
19178        // A comma list on the first key widens the row to three columns.
19179        assert_eq!(
19180            f.run(&[
19181                b"TS.NRANGE",
19182                b"2",
19183                b"x",
19184                b"y",
19185                b"-",
19186                b"+",
19187                b"AGGREGATION",
19188                b"sum,count",
19189                b"avg",
19190                b"20"
19191            ]),
19192            "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
19193             *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
19194             *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
19195             *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
19196        );
19197        // Everything behind the width moves along with it, so BUCKETTIMESTAMP
19198        // sits one or two past the width whatever the key count is.
19199        assert_eq!(
19200            f.run(&[
19201                b"TS.NRANGE",
19202                b"2",
19203                b"x",
19204                b"y",
19205                b"-",
19206                b"+",
19207                b"AGGREGATION",
19208                b"avg",
19209                b"sum",
19210                b"100",
19211                b"EMPTY",
19212                b"BUCKETTIMESTAMP",
19213                b"end"
19214            ]),
19215            "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
19216        );
19217        // A COUNT landing in one of the name slots is a reduction name and not
19218        // the keyword, and the read then has no count at all.
19219        assert_eq!(
19220            f.run(&[
19221                b"TS.NRANGE",
19222                b"2",
19223                b"x",
19224                b"y",
19225                b"-",
19226                b"+",
19227                b"AGGREGATION",
19228                b"avg",
19229                b"COUNT",
19230                b"100"
19231            ]),
19232            "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
19233        );
19234    }
19235
19236    /// The sentences a joined read answers when it does not add up, which are
19237    /// the module's own and come out in the module's own order.
19238    #[test]
19239    fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
19240        let mut f = joined();
19241        f.run(&[b"SET", b"str", b"hi"]);
19242        let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
19243        let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
19244                       must be equal to numkeys\r\n";
19245        let cases: &[(&[&[u8]], &str)] = &[
19246            (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
19247            (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
19248            (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
19249            // Not enough words behind the count for the keys and both ends of
19250            // the span, which is an arity error however many keys were named.
19251            (
19252                &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
19253                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19254            ),
19255            (
19256                &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
19257                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19258            ),
19259            // The reduction names are read before the two ends of the span,
19260            // which no other option is.
19261            (
19262                &[
19263                    b"TS.NRANGE",
19264                    b"2",
19265                    b"x",
19266                    b"y",
19267                    b"abc",
19268                    b"+",
19269                    b"AGGREGATION",
19270                    b"nope",
19271                    b"sum",
19272                    b"100",
19273                ],
19274                "-ERR TSDB: Unknown aggregation type\r\n",
19275            ),
19276            (
19277                &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
19278                "-ERR TSDB: wrong fromTimestamp\r\n",
19279            ),
19280            (
19281                &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
19282                "-ERR TSDB: wrong toTimestamp\r\n",
19283            ),
19284            // A name slot that is missing or holds a number is the count
19285            // sentence, and a width slot that is itself a reduction name is
19286            // that sentence as well.
19287            (
19288                &[
19289                    b"TS.NRANGE",
19290                    b"2",
19291                    b"x",
19292                    b"y",
19293                    b"-",
19294                    b"+",
19295                    b"AGGREGATION",
19296                    b"avg",
19297                ],
19298                numkeys,
19299            ),
19300            (
19301                &[
19302                    b"TS.NRANGE",
19303                    b"2",
19304                    b"x",
19305                    b"y",
19306                    b"-",
19307                    b"+",
19308                    b"AGGREGATION",
19309                    b"100",
19310                    b"sum",
19311                    b"100",
19312                ],
19313                numkeys,
19314            ),
19315            (
19316                &[
19317                    b"TS.NRANGE",
19318                    b"2",
19319                    b"x",
19320                    b"y",
19321                    b"-",
19322                    b"+",
19323                    b"AGGREGATION",
19324                    b"avg",
19325                    b"sum",
19326                    b"sum",
19327                    b"100",
19328                ],
19329                numkeys,
19330            ),
19331            (
19332                &[
19333                    b"TS.NRANGE",
19334                    b"2",
19335                    b"x",
19336                    b"y",
19337                    b"-",
19338                    b"+",
19339                    b"AGGREGATION",
19340                    b"avg",
19341                    b"sum",
19342                    b"abc",
19343                ],
19344                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19345            ),
19346            (
19347                &[
19348                    b"TS.NRANGE",
19349                    b"2",
19350                    b"x",
19351                    b"y",
19352                    b"-",
19353                    b"+",
19354                    b"AGGREGATION",
19355                    b"avg",
19356                    b"sum",
19357                    b"0",
19358                ],
19359                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19360            ),
19361            // With one key none of that applies and the plain parser runs, so a
19362            // lone width is a missing width rather than a count mismatch.
19363            (
19364                &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
19365                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19366            ),
19367            (
19368                &[
19369                    b"TS.NRANGE",
19370                    b"1",
19371                    b"x",
19372                    b"-",
19373                    b"+",
19374                    b"AGGREGATION",
19375                    b"100",
19376                    b"200",
19377                ],
19378                "-ERR TSDB: Unknown aggregation type\r\n",
19379            ),
19380            // The keys come last and in the order they were named.
19381            (
19382                &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
19383                "-ERR TSDB: the key does not exist\r\n",
19384            ),
19385            (
19386                &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
19387                "-ERR WRONGTYPE Operation against a key \
19388                 holding the wrong kind of value\r\n",
19389            ),
19390        ];
19391        for (argv, want) in cases {
19392            let got = f.run(argv);
19393            assert_eq!(&got, want, "{argv:?}");
19394        }
19395    }
19396
19397    /// `TS.READ`, which is a key, one timestamp and everything from there on.
19398    #[test]
19399    fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
19400        let mut f = joined();
19401        assert_eq!(
19402            f.run(&[b"TS.READ", b"x", b"-"]),
19403            "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
19404             *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19405        );
19406        // A plus is the last sample on its own, and a timestamp between two
19407        // samples starts at the one behind it.
19408        assert_eq!(
19409            f.run(&[b"TS.READ", b"x", b"+"]),
19410            "*1\r\n*2\r\n:50\r\n+5\r\n"
19411        );
19412        assert_eq!(
19413            f.run(&[b"TS.READ", b"x", b"25"]),
19414            "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19415        );
19416        // Past the end, a series holding nothing and a key that is not there
19417        // are all the empty array rather than an error.
19418        assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
19419        assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
19420        assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
19421        assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
19422        // The timestamp refusal goes out with nothing in front of it, and a key
19423        // holding something else answers the bare WRONGTYPE rather than the
19424        // module's prefixed one, both unlike the rest of the family.
19425        assert_eq!(
19426            f.run(&[b"TS.READ", b"x", b"abc"]),
19427            "-TSDB: invalid timestamp\r\n"
19428        );
19429        assert_eq!(
19430            f.run(&[b"TS.READ", b"x", b"-1"]),
19431            "-TSDB: invalid timestamp\r\n"
19432        );
19433        f.run(&[b"SET", b"str", b"hi"]);
19434        assert_eq!(
19435            f.run(&[b"TS.READ", b"str", b"-"]),
19436            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19437        );
19438        // Anything other than exactly three words is an arity error, so there
19439        // is nowhere to put an option even though the table says minus three.
19440        assert_eq!(
19441            f.run(&[b"TS.READ", b"x"]),
19442            "-ERR wrong number of arguments for 'ts.read' command\r\n"
19443        );
19444        assert_eq!(
19445            f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
19446            "-ERR wrong number of arguments for 'ts.read' command\r\n"
19447        );
19448    }
19449
19450    /// The keys of a joined read sit behind a count, so `COMMAND GETKEYS` has
19451    /// to read the count to find them.
19452    #[test]
19453    fn getkeys_reads_the_count_of_a_joined_read() {
19454        let mut f = Fixture::new();
19455        assert_eq!(
19456            f.run(&[
19457                b"COMMAND",
19458                b"GETKEYS",
19459                b"TS.NRANGE",
19460                b"2",
19461                b"a",
19462                b"b",
19463                b"-",
19464                b"+"
19465            ]),
19466            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
19467        );
19468        assert_eq!(
19469            f.run(&[
19470                b"COMMAND",
19471                b"GETKEYS",
19472                b"TS.NREVRANGE",
19473                b"1",
19474                b"a",
19475                b"-",
19476                b"+"
19477            ]),
19478            "*1\r\n$1\r\na\r\n"
19479        );
19480        // A count of zero, or one too large for the words that follow it, is
19481        // the server's own refusal and not the module's.
19482        for n in [b"0".as_slice(), b"9", b"abc"] {
19483            assert_eq!(
19484                f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
19485                "-ERR Invalid arguments specified for command\r\n"
19486            );
19487        }
19488    }
19489
19490    /// The five series every test of the label surface works against.
19491    fn labelled() -> Fixture {
19492        let mut f = Fixture::new();
19493        f.run(&[
19494            b"TS.CREATE",
19495            b"a",
19496            b"LABELS",
19497            b"room",
19498            b"kitchen",
19499            b"x",
19500            b"1",
19501        ]);
19502        f.run(&[
19503            b"TS.CREATE",
19504            b"b",
19505            b"LABELS",
19506            b"room",
19507            b"bedroom",
19508            b"x",
19509            b"2",
19510        ]);
19511        f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
19512        f.run(&[b"TS.CREATE", b"d"]);
19513        f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
19514        f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
19515        f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
19516        f
19517    }
19518
19519    /// The filter grammar, which is four steps and a `strtok` rather than a
19520    /// grammar, and which every command that searches on labels shares.
19521    #[test]
19522    fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
19523        let mut f = labelled();
19524        let cases: &[(&[&[u8]], &str)] = &[
19525            // The plain forms, and the order the answer comes back in, which is
19526            // by key name and not by anything the series remembers.
19527            (
19528                &[b"TS.QUERYINDEX", b"room=kitchen"],
19529                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19530            ),
19531            (
19532                &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
19533                "*1\r\n$1\r\na\r\n",
19534            ),
19535            (
19536                &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
19537                "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
19538            ),
19539            // An empty list still counts as something that says which series to
19540            // take, it just never takes any.
19541            (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
19542            // Absent and present, neither of which stands on its own.
19543            (
19544                &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
19545                "*1\r\n$1\r\nc\r\n",
19546            ),
19547            (
19548                &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
19549                "*1\r\n$1\r\na\r\n",
19550            ),
19551            (
19552                &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
19553                "-ERR TSDB: please provide at least one matcher\r\n",
19554            ),
19555            // A run of separators is one separator and everything past the
19556            // second field is dropped, so all three of these ask one question.
19557            (
19558                &[b"TS.QUERYINDEX", b"room==kitchen"],
19559                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19560            ),
19561            (
19562                &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
19563                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19564            ),
19565            (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
19566            // A bracket is only a list when it sits straight behind the
19567            // separator, and then the label in front of it has to be there.
19568            (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
19569            (
19570                &[b"TS.QUERYINDEX", b"=(1)"],
19571                "-ERR TSDB: failed parsing labels\r\n",
19572            ),
19573            (
19574                &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
19575                "-ERR TSDB: failed parsing labels\r\n",
19576            ),
19577            (
19578                &[b"TS.QUERYINDEX", b"room=(kitchen"],
19579                "-ERR TSDB: failed parsing labels\r\n",
19580            ),
19581            (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
19582            (
19583                &[b"TS.QUERYINDEX", b"nonsense"],
19584                "-ERR TSDB: failed parsing labels\r\n",
19585            ),
19586            // Nothing here says which series to take.
19587            (
19588                &[b"TS.QUERYINDEX", b"room!=kitchen"],
19589                "-ERR TSDB: please provide at least one matcher\r\n",
19590            ),
19591            // Names and values are both compared byte for byte.
19592            (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
19593            (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
19594            (
19595                &[b"TS.QUERYINDEX"],
19596                "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
19597            ),
19598        ];
19599        for (argv, want) in cases {
19600            let got = f.run(argv);
19601            assert_eq!(&got, want, "{:?}", argv.last());
19602        }
19603    }
19604
19605    /// `TS.QUERYLABELS`, whose filter is the one that is allowed to be missing.
19606    #[test]
19607    fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
19608        let mut f = labelled();
19609        let cases: &[(&[&[u8]], &str)] = &[
19610            (
19611                &[b"TS.QUERYLABELS", b"LABELS"],
19612                "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19613            ),
19614            (
19615                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
19616                "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19617            ),
19618            (
19619                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19620                "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19621            ),
19622            // The series wearing `r` twice contributes the smaller of the two
19623            // here, which is not the one it was written down as first.
19624            (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
19625            (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
19626            (
19627                &[b"TS.QUERYLABELS", b"VALUES"],
19628                "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
19629            ),
19630            (
19631                &[b"TS.QUERYLABELS", b"ZZZ"],
19632                "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
19633            ),
19634            (
19635                &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
19636                "-ERR TSDB: unknown argument, expected FILTER\r\n",
19637            ),
19638            (
19639                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
19640                "-ERR TSDB: FILTER given with no filter expressions\r\n",
19641            ),
19642            // With no filter at all every series is taken, which is why the
19643            // first case here answers about `r` as well. A filter that is there
19644            // still has to say which series to take.
19645            (
19646                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
19647                "-ERR TSDB: please provide at least one matcher\r\n",
19648            ),
19649            (
19650                &[
19651                    b"TS.QUERYLABELS",
19652                    b"LABELS",
19653                    b"FILTER",
19654                    b"room=kitchen",
19655                    b"x=",
19656                ],
19657                "*1\r\n$4\r\nroom\r\n",
19658            ),
19659        ];
19660        for (argv, want) in cases {
19661            let got = f.run(argv);
19662            assert_eq!(&got, want, "{:?}", argv.last());
19663        }
19664    }
19665
19666    /// `TS.MGET`, the newest sample of every series a filter takes, and the two
19667    /// ways of asking for the labels back alongside it.
19668    #[test]
19669    fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
19670        let mut f = labelled();
19671        let cases: &[(&[&[u8]], &str)] = &[
19672            // A series with no samples writes an empty array where the sample
19673            // goes rather than dropping out of the reply.
19674            (
19675                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19676                "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
19677                 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
19678            ),
19679            (
19680                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19681                "*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\
19682                 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
19683                 *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",
19684            ),
19685            // A selected label the series does not wear is a nil, not a gap.
19686            (
19687                &[
19688                    b"TS.MGET",
19689                    b"SELECTED_LABELS",
19690                    b"x",
19691                    b"FILTER",
19692                    b"room=kitchen",
19693                ],
19694                "*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\
19695                 *2\r\n:100\r\n+1.5\r\n\
19696                 *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",
19697            ),
19698            // The other half of the duplicated name rule. This one takes the
19699            // first written down where `TS.QUERYLABELS` takes the smallest.
19700            (
19701                &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
19702                "*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",
19703            ),
19704            (
19705                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19706                "*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\
19707                 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19708            ),
19709            // A word that is not an option is ignored, but a missing `FILTER`
19710            // is an arity error whatever else was written.
19711            (
19712                &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
19713                "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
19714            ),
19715            (
19716                &[b"TS.MGET", b"a", b"b", b"c"],
19717                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19718            ),
19719            (
19720                &[b"TS.MGET", b"FILTER"],
19721                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19722            ),
19723            // Both keyword checks happen before the filter is read, and the two
19724            // sentences spell the second keyword without its `ED`.
19725            (
19726                &[
19727                    b"TS.MGET",
19728                    b"WITHLABELS",
19729                    b"SELECTED_LABELS",
19730                    b"x",
19731                    b"FILTER",
19732                    b"bad",
19733                ],
19734                "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
19735            ),
19736            (
19737                &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
19738                "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
19739            ),
19740        ];
19741        for (argv, want) in cases {
19742            let got = f.run(argv);
19743            assert_eq!(&got, want, "{:?}", argv.last());
19744        }
19745    }
19746
19747    /// What RESP3 changes across the label surface, which is a set where there
19748    /// was an array and a map where there was a pair of them.
19749    #[test]
19750    fn resp3_writes_the_label_surface_as_sets_and_maps() {
19751        let mut f = labelled();
19752        f.out = Out::new(Proto::Resp3);
19753        let cases: &[(&[&[u8]], &str)] = &[
19754            (
19755                &[b"TS.QUERYINDEX", b"room=kitchen"],
19756                "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
19757            ),
19758            (
19759                &[b"TS.QUERYLABELS", b"LABELS"],
19760                "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19761            ),
19762            (
19763                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19764                "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19765            ),
19766            // The key stops being the first of three and becomes the map key,
19767            // and the labels stop being pairs and become a map of their own.
19768            (
19769                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19770                "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
19771                 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
19772            ),
19773            (
19774                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19775                "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19776                 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
19777                 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
19778            ),
19779            (
19780                &[
19781                    b"TS.MGET",
19782                    b"SELECTED_LABELS",
19783                    b"x",
19784                    b"FILTER",
19785                    b"room=kitchen",
19786                ],
19787                "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
19788                 *2\r\n:100\r\n,1.5\r\n\
19789                 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
19790            ),
19791            // A map with a name in it twice, which is what a series wearing one
19792            // label name twice turns into.
19793            (
19794                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19795                "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
19796                 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19797            ),
19798        ];
19799        for (argv, want) in cases {
19800            let got = f.run(argv);
19801            assert_eq!(&got, want, "{:?}", argv.last());
19802        }
19803    }
19804
19805    /// The same five series with enough samples in them for a group to have
19806    /// something to fold.
19807    fn spanned() -> Fixture {
19808        let mut f = labelled();
19809        f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
19810        f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
19811        f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
19812        f
19813    }
19814
19815    /// A span read out of every series a filter takes, with and without a group
19816    /// over the top of it.
19817    #[test]
19818    fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
19819        let mut f = spanned();
19820        let cases: &[(&[&[u8]], &str)] = &[
19821            (
19822                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
19823                "*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\
19824                 *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",
19825            ),
19826            // Newest first is applied to each series before anything else sees
19827            // the rows.
19828            (
19829                &[
19830                    b"TS.MREVRANGE",
19831                    b"-",
19832                    b"+",
19833                    b"WITHLABELS",
19834                    b"FILTER",
19835                    b"room=kitchen",
19836                ],
19837                "*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\
19838                 *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\
19839                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19840                 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
19841            ),
19842            // A label a series does not wear comes back against a nil rather
19843            // than being left out.
19844            (
19845                &[
19846                    b"TS.MRANGE",
19847                    b"-",
19848                    b"+",
19849                    b"SELECTED_LABELS",
19850                    b"x",
19851                    b"FILTER",
19852                    b"room=kitchen",
19853                ],
19854                "*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\
19855                 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
19856                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
19857                 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
19858            ),
19859            // The fold: 100 is in both series and adds up, the other two are in
19860            // one each and are still rows.
19861            (
19862                &[
19863                    b"TS.MRANGE",
19864                    b"-",
19865                    b"+",
19866                    b"FILTER",
19867                    b"room=kitchen",
19868                    b"GROUPBY",
19869                    b"room",
19870                    b"REDUCE",
19871                    b"sum",
19872                ],
19873                "*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\
19874                 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
19875            ),
19876            // RESP2 has nowhere to put the reducer and the member keys, so a
19877            // group wearing labels writes them as two more labels.
19878            (
19879                &[
19880                    b"TS.MRANGE",
19881                    b"-",
19882                    b"+",
19883                    b"WITHLABELS",
19884                    b"FILTER",
19885                    b"room=kitchen",
19886                    b"GROUPBY",
19887                    b"room",
19888                    b"REDUCE",
19889                    b"max",
19890                ],
19891                "*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\
19892                 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
19893                 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
19894                 *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",
19895            ),
19896            // A count is applied to each member and then again to the fold.
19897            (
19898                &[
19899                    b"TS.MREVRANGE",
19900                    b"-",
19901                    b"+",
19902                    b"COUNT",
19903                    b"1",
19904                    b"FILTER",
19905                    b"room=kitchen",
19906                    b"GROUPBY",
19907                    b"room",
19908                    b"REDUCE",
19909                    b"count",
19910                ],
19911                "*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",
19912            ),
19913            // Nothing wears the label, so nothing is in any group.
19914            (
19915                &[
19916                    b"TS.MRANGE",
19917                    b"-",
19918                    b"+",
19919                    b"FILTER",
19920                    b"room=kitchen",
19921                    b"GROUPBY",
19922                    b"nope",
19923                    b"REDUCE",
19924                    b"sum",
19925                ],
19926                "*0\r\n",
19927            ),
19928            (
19929                &[
19930                    b"TS.MRANGE",
19931                    b"-",
19932                    b"+",
19933                    b"AGGREGATION",
19934                    b"sum,avg",
19935                    b"100",
19936                    b"FILTER",
19937                    b"room=bedroom",
19938                ],
19939                "*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",
19940            ),
19941            // The errors, in the order they are looked for.
19942            (
19943                &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
19944                "-ERR TSDB: missing FILTER argument\r\n",
19945            ),
19946            (
19947                &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
19948                "-ERR TSDB: missing labels for filter argument\r\n",
19949            ),
19950            (
19951                &[
19952                    b"TS.MRANGE",
19953                    b"-",
19954                    b"+",
19955                    b"GROUPBY",
19956                    b"room",
19957                    b"REDUCE",
19958                    b"sum",
19959                    b"FILTER",
19960                    b"room=kitchen",
19961                ],
19962                "-ERR TSDB: GROUPBY should always come after filter\r\n",
19963            ),
19964            // The group is four words from the end here, so the length is what
19965            // is wrong with it.
19966            (
19967                &[
19968                    b"TS.MRANGE",
19969                    b"-",
19970                    b"+",
19971                    b"FILTER",
19972                    b"room=kitchen",
19973                    b"GROUPBY",
19974                    b"room",
19975                    b"REDUCE",
19976                    b"sum",
19977                    b"x",
19978                ],
19979                "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
19980            ),
19981            // And here it is not, so its words are filters and answer first.
19982            (
19983                &[
19984                    b"TS.MRANGE",
19985                    b"-",
19986                    b"+",
19987                    b"FILTER",
19988                    b"nope",
19989                    b"GROUPBY",
19990                    b"room",
19991                    b"REDUCE",
19992                    b"sum",
19993                    b"x",
19994                ],
19995                "-ERR TSDB: failed parsing labels\r\n",
19996            ),
19997            (
19998                &[
19999                    b"TS.MRANGE",
20000                    b"-",
20001                    b"+",
20002                    b"FILTER",
20003                    b"room=kitchen",
20004                    b"GROUPBY",
20005                    b"room",
20006                    b"REDUCE",
20007                    b"twa",
20008                ],
20009                "-ERR TSDB: Invalid reducer type\r\n",
20010            ),
20011            (
20012                &[
20013                    b"TS.MRANGE",
20014                    b"-",
20015                    b"+",
20016                    b"AGGREGATION",
20017                    b"sum,avg",
20018                    b"100",
20019                    b"FILTER",
20020                    b"room=kitchen",
20021                    b"GROUPBY",
20022                    b"room",
20023                    b"REDUCE",
20024                    b"sum",
20025                ],
20026                "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
20027            ),
20028            // The label list ends at a keyword, so this is a `COUNT` with a
20029            // `FILTER` where its number should be.
20030            (
20031                &[
20032                    b"TS.MRANGE",
20033                    b"-",
20034                    b"+",
20035                    b"SELECTED_LABELS",
20036                    b"COUNT",
20037                    b"FILTER",
20038                    b"room=kitchen",
20039                ],
20040                "-ERR TSDB: Couldn't parse COUNT\r\n",
20041            ),
20042        ];
20043        for (argv, want) in cases {
20044            let got = f.run(argv);
20045            assert_eq!(&got, want, "{argv:?}");
20046        }
20047    }
20048
20049    /// The multi key reads on RESP3, where the key becomes a map key and the
20050    /// reducer and the member keys become fields of their own.
20051    #[test]
20052    fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
20053        let mut f = spanned();
20054        f.out = Out::new(Proto::Resp3);
20055        let cases: &[(&[&[u8]], &str)] = &[
20056            (
20057                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
20058                "%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\
20059                 *1\r\n*2\r\n:200\r\n,2\r\n",
20060            ),
20061            // The reductions a read asked for, which RESP2 has no room for at
20062            // all and which is empty on a read that asked for none.
20063            (
20064                &[
20065                    b"TS.MRANGE",
20066                    b"-",
20067                    b"+",
20068                    b"AGGREGATION",
20069                    b"sum,avg",
20070                    b"100",
20071                    b"FILTER",
20072                    b"room=bedroom",
20073                ],
20074                "%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\
20075                 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
20076            ),
20077            (
20078                &[
20079                    b"TS.MRANGE",
20080                    b"-",
20081                    b"+",
20082                    b"FILTER",
20083                    b"room=kitchen",
20084                    b"GROUPBY",
20085                    b"room",
20086                    b"REDUCE",
20087                    b"sum",
20088                ],
20089                "%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\
20090                 $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\
20091                 *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",
20092            ),
20093            // The labels hold only the pair the group was made on, because the
20094            // reducer and the sources have somewhere else to go.
20095            (
20096                &[
20097                    b"TS.MRANGE",
20098                    b"-",
20099                    b"+",
20100                    b"WITHLABELS",
20101                    b"FILTER",
20102                    b"room=kitchen",
20103                    b"GROUPBY",
20104                    b"room",
20105                    b"REDUCE",
20106                    b"max",
20107                ],
20108                "%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\
20109                 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
20110                 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
20111                 *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",
20112            ),
20113            (
20114                &[
20115                    b"TS.MRANGE",
20116                    b"-",
20117                    b"+",
20118                    b"FILTER",
20119                    b"room=kitchen",
20120                    b"GROUPBY",
20121                    b"nope",
20122                    b"REDUCE",
20123                    b"sum",
20124                ],
20125                "%0\r\n",
20126            ),
20127        ];
20128        for (argv, want) in cases {
20129            let got = f.run(argv);
20130            assert_eq!(&got, want, "{argv:?}");
20131        }
20132    }
20133
20134    /// `TS.CREATERULE`, whose refusals come in an order of their own.
20135    #[test]
20136    fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
20137        let mut f = Fixture::new();
20138        f.run(&[b"TS.CREATE", b"src"]);
20139        f.run(&[b"TS.CREATE", b"dst"]);
20140        f.run(&[b"SET", b"plain", b"v"]);
20141        let cases: &[(&[&[u8]], &str)] = &[
20142            // The width is read before the reduction, the reduction before the
20143            // width being above zero, and all three before either key is looked
20144            // at, so a command that is wrong twice complains about the first.
20145            (
20146                &[
20147                    b"TS.CREATERULE",
20148                    b"src",
20149                    b"dst",
20150                    b"AGGREGATION",
20151                    b"nope",
20152                    b"x",
20153                ],
20154                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
20155            ),
20156            (
20157                &[
20158                    b"TS.CREATERULE",
20159                    b"src",
20160                    b"dst",
20161                    b"AGGREGATION",
20162                    b"nope",
20163                    b"10",
20164                ],
20165                "-ERR TSDB: Unknown aggregation type\r\n",
20166            ),
20167            (
20168                &[
20169                    b"TS.CREATERULE",
20170                    b"src",
20171                    b"dst",
20172                    b"AGGREGATION",
20173                    b"avg",
20174                    b"0",
20175                ],
20176                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
20177            ),
20178            (
20179                &[
20180                    b"TS.CREATERULE",
20181                    b"src",
20182                    b"dst",
20183                    b"AGGREGATION",
20184                    b"avg",
20185                    b"10",
20186                    b"x",
20187                ],
20188                "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
20189            ),
20190            (
20191                &[
20192                    b"TS.CREATERULE",
20193                    b"src",
20194                    b"src",
20195                    b"AGGREGATION",
20196                    b"avg",
20197                    b"10",
20198                ],
20199                "-ERR TSDB: the source key and destination key should be different\r\n",
20200            ),
20201            // A key holding something else answers the same as a key that is not
20202            // there at all, because the source is looked up first and neither of
20203            // them is a series.
20204            (
20205                &[
20206                    b"TS.CREATERULE",
20207                    b"nope",
20208                    b"plain",
20209                    b"AGGREGATION",
20210                    b"avg",
20211                    b"10",
20212                ],
20213                "-ERR TSDB: the key does not exist\r\n",
20214            ),
20215            (
20216                &[
20217                    b"TS.CREATERULE",
20218                    b"src",
20219                    b"nope",
20220                    b"AGGREGATION",
20221                    b"avg",
20222                    b"10",
20223                ],
20224                "-ERR TSDB: the key does not exist\r\n",
20225            ),
20226            // A keyword other than AGGREGATION is an arity error rather than a
20227            // syntax one, because the arity is all that is checked.
20228            (
20229                &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
20230                "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
20231            ),
20232            (
20233                &[
20234                    b"TS.CREATERULE",
20235                    b"src",
20236                    b"dst",
20237                    b"AGGREGATION",
20238                    b"avg",
20239                    b"10",
20240                ],
20241                "+OK\r\n",
20242            ),
20243            // The link is now in place, so the same rule again is refused from
20244            // the destination's end.
20245            (
20246                &[
20247                    b"TS.CREATERULE",
20248                    b"src",
20249                    b"dst",
20250                    b"AGGREGATION",
20251                    b"avg",
20252                    b"10",
20253                ],
20254                "-ERR TSDB: the destination key already has a src rule\r\n",
20255            ),
20256            // A source that is already someone's destination, and a destination
20257            // that is already someone's source, are two different sentences.
20258            (
20259                &[
20260                    b"TS.CREATERULE",
20261                    b"dst",
20262                    b"src",
20263                    b"AGGREGATION",
20264                    b"avg",
20265                    b"10",
20266                ],
20267                "-ERR TSDB: the source key already has a source rule\r\n",
20268            ),
20269            (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
20270            (
20271                &[b"TS.DELETERULE", b"src", b"dst"],
20272                "-ERR TSDB: compaction rule does not exist\r\n",
20273            ),
20274            // The source is looked up and the destination is not, so a missing
20275            // destination is a missing rule and a missing source is a missing
20276            // key, which is the other way round from `TS.CREATERULE`.
20277            (
20278                &[b"TS.DELETERULE", b"src", b"nope"],
20279                "-ERR TSDB: compaction rule does not exist\r\n",
20280            ),
20281            (
20282                &[b"TS.DELETERULE", b"nope", b"dst"],
20283                "-ERR TSDB: the key does not exist\r\n",
20284            ),
20285        ];
20286        for (argv, want) in cases {
20287            let got = f.run(argv);
20288            assert_eq!(&got, want, "{argv:?}");
20289        }
20290    }
20291
20292    /// What a rule writes, which is every bucket but the one it is filling.
20293    #[test]
20294    fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
20295        let mut f = Fixture::new();
20296        f.run(&[b"TS.CREATE", b"src"]);
20297        f.run(&[b"TS.CREATE", b"dst"]);
20298        // The readings written before the rule was made are not folded, so the
20299        // destination is still empty after the first two.
20300        f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
20301        f.run(&[
20302            b"TS.CREATERULE",
20303            b"src",
20304            b"dst",
20305            b"AGGREGATION",
20306            b"sum",
20307            b"100",
20308        ]);
20309        f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
20310        assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
20311        // The bucket the rule is filling holds only what it was given, so it is
20312        // 2 rather than 3, and it is written when a reading lands past it.
20313        assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
20314        f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
20315        assert_eq!(
20316            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20317            "*1\r\n*2\r\n:0\r\n+2\r\n"
20318        );
20319        // A reading into a bucket that has already been written works that
20320        // bucket out again over everything the source now holds.
20321        f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
20322        assert_eq!(
20323            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20324            "*1\r\n*2\r\n:0\r\n+11\r\n"
20325        );
20326        // Deleting from the source works the buckets it touched out again and
20327        // reopens the newest one, so `LATEST` starts from the whole bucket.
20328        assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
20329        assert_eq!(
20330            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20331            "*1\r\n*2\r\n:0\r\n+8\r\n"
20332        );
20333        assert_eq!(
20334            f.run(&[b"TS.GET", b"dst", b"LATEST"]),
20335            "*2\r\n:100\r\n+4\r\n"
20336        );
20337        // The link shows on both ends, and dropping either key takes it down.
20338        assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
20339        f.run(&[b"DEL", b"dst"]);
20340        assert_eq!(
20341            f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
20342            "-ERR TSDB: compaction rule does not exist\r\n"
20343        );
20344    }
20345
20346    /// The three shapes an `XADD` id can take, and the one rule behind all of
20347    /// them.
20348    #[test]
20349    fn xadd_ids_only_ever_go_up() {
20350        let mut f = Fixture::new();
20351        // A bare millisecond is that millisecond and sequence zero.
20352        assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
20353        // And `5-*` is the next free sequence inside it.
20354        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
20355        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
20356        assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
20357        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20358
20359        assert!(
20360            f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
20361                .contains("equal or smaller")
20362        );
20363        assert!(
20364            f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
20365                .contains("must be greater than 0-0")
20366        );
20367        assert!(
20368            f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
20369                .contains("Invalid stream ID")
20370        );
20371        // The pairs have to be pairs, and Redis calls an odd one an arity error
20372        // rather than a syntax error even though the table has already passed.
20373        assert!(
20374            f.run(&[b"XADD", b"s", b"*", b"a"])
20375                .contains("wrong number of arguments")
20376        );
20377
20378        // `NOMKSTREAM` on a key that is not there is a null and not a zero, so a
20379        // producer can tell nobody is consuming this yet from the write landed.
20380        assert_eq!(
20381            f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
20382            "$-1\r\n"
20383        );
20384        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20385        assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
20386        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
20387    }
20388
20389    /// The trim options, which are three keywords that disagree about how many
20390    /// arguments they take.
20391    #[test]
20392    fn trimming_reads_its_options_the_way_redis_does() {
20393        let mut f = Fixture::new();
20394        for i in 1..=10u32 {
20395            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20396        }
20397        assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
20398        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20399        assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
20400        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20401
20402        // One argument after the keyword and the `~` is read as the threshold,
20403        // which is what a real server does and is the reason this is a number
20404        // complaint and not a syntax one.
20405        assert!(
20406            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
20407                .contains("not an integer")
20408        );
20409        assert!(
20410            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
20411                .contains("MAXLEN argument must be >= 0")
20412        );
20413        // The strategy check runs before the approximation check, so a LIMIT
20414        // with neither is told about the missing strategy.
20415        assert!(
20416            f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
20417                .contains("without specifying a trimming strategy")
20418        );
20419        assert!(
20420            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
20421                .contains("without the special ~ option")
20422        );
20423        assert!(
20424            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
20425                .contains("at the same time are not compatible")
20426        );
20427        // NOMKSTREAM is XADD's and XTRIM does not take it.
20428        assert!(
20429            f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
20430                .contains("syntax error")
20431        );
20432        assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
20433    }
20434
20435    /// `XRANGE`, whose two kinds of nothing are the thing worth pinning.
20436    #[test]
20437    fn xrange_looks_the_key_up_before_it_reads_the_count() {
20438        let mut f = Fixture::new();
20439        f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
20440        f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
20441
20442        assert_eq!(
20443            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20444            "*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\
20445             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20446        );
20447        assert_eq!(
20448            f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
20449            "*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"
20450        );
20451        // The exclusive bound is stepped after the missing sequence is filled
20452        // in, so `(6` is `6-` and the largest sequence there is, minus one, and
20453        // `6-1` is still in the range.
20454        assert_eq!(
20455            f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
20456            "*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\
20457             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20458        );
20459        assert_eq!(
20460            f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
20461            "*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"
20462        );
20463        assert!(
20464            f.run(&[b"XRANGE", b"s", b"(-", b"+"])
20465                .contains("Invalid stream ID")
20466        );
20467
20468        // The two kinds of nothing. A key that is not there is an empty array
20469        // and a key that is there with a count of zero is a null array, because
20470        // the lookup happens first.
20471        assert_eq!(
20472            f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
20473            "*0\r\n"
20474        );
20475        assert_eq!(
20476            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
20477            "*-1\r\n"
20478        );
20479        f.run(&[b"SET", b"str", b"v"]);
20480        assert!(
20481            f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
20482                .starts_with("-WRONGTYPE")
20483        );
20484        // The count is read in a loop, so the last one wins.
20485        assert_eq!(
20486            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
20487            "*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"
20488        );
20489    }
20490
20491    /// `XDEL` and `XACK` check every id before they touch any of them.
20492    #[test]
20493    fn a_bad_id_late_in_the_list_stops_the_whole_command() {
20494        let mut f = Fixture::new();
20495        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20496        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20497        assert!(
20498            f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
20499                .contains("Invalid stream ID")
20500        );
20501        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20502        assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
20503        assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
20504        assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
20505        assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
20506    }
20507
20508    /// `XGROUP`, and the two different complaints it makes about arguments.
20509    #[test]
20510    fn xgroup_has_an_arity_per_subcommand() {
20511        let mut f = Fixture::new();
20512        assert!(
20513            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20514                .contains("requires the key")
20515        );
20516        assert_eq!(
20517            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
20518            "+OK\r\n"
20519        );
20520        // A second CREATE is BUSYGROUP and not an ordinary error, because a
20521        // client racing another one to make a group branches on the prefix.
20522        assert!(
20523            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20524                .starts_with("-BUSYGROUP")
20525        );
20526        assert_eq!(
20527            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20528            ":1\r\n"
20529        );
20530        assert_eq!(
20531            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20532            ":0\r\n"
20533        );
20534        assert_eq!(
20535            f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
20536            ":0\r\n"
20537        );
20538
20539        // Below the subcommand's own arity is an arity error naming the pair.
20540        let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
20541        assert!(
20542            short.contains("wrong number of arguments for 'xgroup|destroy' command"),
20543            "{short}"
20544        );
20545        // At or above it in a shape the handler will not take is the other one.
20546        let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
20547        assert!(
20548            odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
20549            "{odd}"
20550        );
20551        assert!(
20552            f.run(&[b"XGROUP", b"NOSUCH", b"s"])
20553                .contains("Try XGROUP HELP")
20554        );
20555
20556        assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
20557        assert!(
20558            f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
20559                .starts_with("-NOGROUP")
20560        );
20561        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
20562        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
20563        assert!(
20564            f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
20565                .contains("requires the key")
20566        );
20567    }
20568
20569    /// A group read, an acknowledgement, and what is left in between.
20570    #[test]
20571    fn xreadgroup_hands_out_and_xack_takes_back() {
20572        let mut f = Fixture::new();
20573        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20574        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20575        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20576
20577        let first = f.run(&[
20578            b"XREADGROUP",
20579            b"GROUP",
20580            b"g",
20581            b"c1",
20582            b"COUNT",
20583            b"1",
20584            b"STREAMS",
20585            b"s",
20586            b">",
20587        ]);
20588        assert_eq!(
20589            first,
20590            "*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"
20591        );
20592        // A history read names its stream even with nothing to show, which is
20593        // the difference between it and a `>` read that found nothing.
20594        assert_eq!(
20595            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
20596            "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
20597        );
20598        assert_eq!(
20599            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20600            "*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"
20601        );
20602
20603        assert_eq!(
20604            f.run(&[b"XPENDING", b"s", b"g"]),
20605            "*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"
20606        );
20607        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
20608        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
20609        // Empty is four nulls and not a zero with three empty things.
20610        assert_eq!(
20611            f.run(&[b"XPENDING", b"s", b"g"]),
20612            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
20613        );
20614
20615        // A history read of an entry that has since been deleted is the id with
20616        // a null beside it, so the consumer can still acknowledge it.
20617        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20618        f.run(&[b"XDEL", b"s", b"2-1"]);
20619        assert_eq!(
20620            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20621            "*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"
20622        );
20623
20624        // The group lookup runs before the id parse, so a `+` at a stream with
20625        // no such group is told about the group and not about the id.
20626        assert!(
20627            f.run(&[
20628                b"XREADGROUP",
20629                b"GROUP",
20630                b"nope",
20631                b"c",
20632                b"STREAMS",
20633                b"s",
20634                b"+"
20635            ])
20636            .starts_with("-NOGROUP")
20637        );
20638        assert!(
20639            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
20640                .contains("meaningless in the context of XREADGROUP")
20641        );
20642        assert!(
20643            f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
20644                .contains("only supported by XREADGROUP")
20645        );
20646        assert!(
20647            f.run(&[
20648                b"XREADGROUP",
20649                b"GROUP",
20650                b"g",
20651                b"c",
20652                b"STREAMS",
20653                b"s",
20654                b"a",
20655                b"b"
20656            ])
20657            .contains("Unbalanced 'xreadgroup' list of streams")
20658        );
20659    }
20660
20661    /// `XREAD` without `BLOCK`, which answers now and takes nothing for an
20662    /// answer.
20663    #[test]
20664    fn xread_with_no_block_writes_the_null_itself() {
20665        let mut f = Fixture::new();
20666        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20667        assert_eq!(
20668            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
20669            "*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"
20670        );
20671        // Nothing new is a null array and not an empty one, and a stream with
20672        // nothing new is left out rather than sent with an empty list.
20673        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
20674        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
20675        f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
20676        assert_eq!(
20677            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
20678            "*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"
20679        );
20680        // `$` is the last id, so nothing that is already there comes back.
20681        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
20682        // And `+` is the last entry, whatever COUNT says.
20683        assert_eq!(
20684            f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
20685            "*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"
20686        );
20687        // A count of zero means unlimited here, which is the opposite of what it
20688        // means to XRANGE.
20689        assert_eq!(
20690            f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
20691            "*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"
20692        );
20693        // Milliseconds as a whole number, where BLPOP takes seconds as a float.
20694        assert!(
20695            f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
20696                .contains("not an integer")
20697        );
20698        assert!(
20699            f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
20700                .contains("timeout is negative")
20701        );
20702        assert!(
20703            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
20704                .contains("Unbalanced 'xread' list of streams")
20705        );
20706    }
20707
20708    /// A blocked reader, and the two ways it stops being blocked.
20709    #[test]
20710    fn a_blocked_xread_wakes_on_the_next_entry() {
20711        let mut f = Fixture::new();
20712        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20713        let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20714        assert_eq!(flow, Flow::Block);
20715        assert!(reply.is_empty());
20716
20717        // Everybody parked on the stream gets the entry, because a read takes
20718        // nothing away. That is the difference between this and BLPOP. Two
20719        // clients rather than one twice, since a client that is waiting is not
20720        // reading and cannot block again.
20721        f.session = Session::new(8);
20722        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20723        assert_eq!(flow, Flow::Block);
20724        assert_eq!(f.server.parked(), 2);
20725
20726        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20727        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";
20728        for client in [7, 8] {
20729            let mut out = Out::new(Proto::Resp2);
20730            assert!(f.server.serve_waiter(client, 0, &mut out));
20731            assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
20732        }
20733
20734        // And a deadline that runs out is a null array, the same as a plain
20735        // XREAD that found nothing.
20736        f.server.forget_waiters(7);
20737        f.server.forget_waiters(8);
20738        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
20739        assert_eq!(flow, Flow::Block);
20740        let mut out = Out::new(Proto::Resp2);
20741        assert!(!f.server.serve_waiter(8, 0, &mut out));
20742        assert!(out.as_slice().is_empty());
20743        assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
20744        assert_eq!(
20745            core::str::from_utf8(out.as_slice()).expect("ascii"),
20746            "*-1\r\n"
20747        );
20748    }
20749
20750    /// A blocked group reader whose group is destroyed under it.
20751    #[test]
20752    fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
20753        let mut f = Fixture::new();
20754        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20755        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
20756        let (flow, _) = f.flow(&[
20757            b"XREADGROUP",
20758            b"GROUP",
20759            b"g",
20760            b"c",
20761            b"BLOCK",
20762            b"0",
20763            b"STREAMS",
20764            b"s",
20765            b">",
20766        ]);
20767        assert_eq!(flow, Flow::Block);
20768
20769        f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
20770        let mut out = Out::new(Proto::Resp2);
20771        assert!(f.server.serve_waiter(7, 0, &mut out));
20772        // The ordinary sentence and not a special one about having been parked,
20773        // which is what a running 8.10 sends.
20774        assert_eq!(
20775            core::str::from_utf8(out.as_slice()).expect("ascii"),
20776            "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
20777        );
20778    }
20779
20780    /// `XCLAIM`, whose argument shape is the odd one in the group.
20781    #[test]
20782    fn xclaim_reads_ids_until_one_will_not_parse() {
20783        let mut f = Fixture::new();
20784        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20785        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20786        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20787        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20788
20789        // Everything after the first argument that is not an id is an option, so
20790        // a `-` is an unrecognised option and not a bad id.
20791        assert!(
20792            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
20793                .contains("Unrecognized XCLAIM option '-'")
20794        );
20795        assert_eq!(
20796            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
20797            "*1\r\n$3\r\n1-1\r\n"
20798        );
20799        // An id that is pending but whose entry has gone is an empty answer, and
20800        // it leaves the pending list on the way past.
20801        f.run(&[b"XDEL", b"s", b"2-1"]);
20802        assert_eq!(
20803            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
20804            "*0\r\n"
20805        );
20806        assert!(
20807            f.run(&[b"XPENDING", b"s", b"g"])
20808                .starts_with("*4\r\n:1\r\n")
20809        );
20810        assert!(
20811            f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
20812                .starts_with("-NOGROUP")
20813        );
20814        assert!(
20815            f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
20816                .contains("Invalid min-idle-time argument for XCLAIM")
20817        );
20818    }
20819
20820    /// `XAUTOCLAIM`, and the third value nobody expects.
20821    #[test]
20822    fn xautoclaim_reports_what_it_dropped() {
20823        let mut f = Fixture::new();
20824        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20825        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20826        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20827        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20828        f.run(&[b"XDEL", b"s", b"1-1"]);
20829
20830        // The cursor, what was claimed, and what was dropped for no longer being
20831        // in the stream. The third one is what makes a sweep converge.
20832        assert_eq!(
20833            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
20834            "*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"
20835        );
20836        assert!(
20837            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
20838                .contains("COUNT must be > 0")
20839        );
20840        assert!(
20841            f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
20842                .starts_with("-NOGROUP")
20843        );
20844    }
20845
20846    /// `XDELEX`, which is `XDEL` with a say in what the groups keep.
20847    #[test]
20848    fn xdelex_answers_one_integer_an_id() {
20849        let mut f = Fixture::new();
20850        for i in 1..=4 {
20851            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20852        }
20853        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20854        f.run(&[
20855            b"XREADGROUP",
20856            b"GROUP",
20857            b"g",
20858            b"c",
20859            b"COUNT",
20860            b"2",
20861            b"STREAMS",
20862            b"s",
20863            b">",
20864        ]);
20865
20866        // One means gone and minus one means it was not there to start with.
20867        assert_eq!(
20868            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
20869            "*2\r\n:1\r\n:-1\r\n"
20870        );
20871        // `KEEPREF` leaves the pending entry behind, so the group still counts
20872        // the one it was handed even though the entry has gone.
20873        assert!(
20874            f.run(&[b"XPENDING", b"s", b"g"])
20875                .starts_with("*4\r\n:2\r\n")
20876        );
20877        // `DELREF` takes it out of every pending list on the way past.
20878        assert_eq!(
20879            f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
20880            "*1\r\n:1\r\n"
20881        );
20882        // `1-1` is still in the list, because the delete before it said KEEPREF.
20883        assert_eq!(
20884            f.run(&[b"XPENDING", b"s", b"g"]),
20885            "*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"
20886        );
20887
20888        // Two means somebody still wants it, and the question is wider than the
20889        // name: the group's bookmark is at `2-1`, so `4-1` is above it and is
20890        // refused even though no consumer has ever been handed it.
20891        assert_eq!(
20892            f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
20893            "*2\r\n:2\r\n:2\r\n"
20894        );
20895
20896        // A key that is not there answers minus ones without reading the IDs.
20897        assert_eq!(
20898            f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
20899            "*2\r\n:-1\r\n:-1\r\n"
20900        );
20901        // A key that is there validates every ID before deleting any of them.
20902        assert!(
20903            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
20904                .starts_with("-ERR Invalid stream ID")
20905        );
20906        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20907
20908        assert!(
20909            f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
20910                .contains("Number of IDs must be a positive integer")
20911        );
20912        assert!(
20913            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
20914                .contains("The `numids` parameter must match the number of arguments")
20915        );
20916        // The condition is one word, so a second one is a syntax error, and so
20917        // is one ID more than the count promised.
20918        assert!(
20919            f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
20920                .starts_with("-ERR syntax error")
20921        );
20922        assert!(
20923            f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
20924                .starts_with("-ERR syntax error")
20925        );
20926        // The key is looked up first, so the wrong type beats the syntax.
20927        f.run(&[b"SET", b"str", b"v"]);
20928        assert!(
20929            f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
20930                .starts_with("-WRONGTYPE")
20931        );
20932    }
20933
20934    /// `XACKDEL`, whose reply is about the pending list and not about the log.
20935    #[test]
20936    fn xackdel_reports_what_the_group_was_holding() {
20937        let mut f = Fixture::new();
20938        for i in 1..=3 {
20939            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20940        }
20941        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20942        f.run(&[
20943            b"XREADGROUP",
20944            b"GROUP",
20945            b"g",
20946            b"c",
20947            b"COUNT",
20948            b"1",
20949            b"STREAMS",
20950            b"s",
20951            b">",
20952        ]);
20953
20954        // Minus one is not about the stream: `2-1` is sitting there unread and
20955        // still answers minus one, because the group was not holding it. It also
20956        // stays, since only an ID that was acknowledged is deleted.
20957        assert_eq!(
20958            f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
20959            "*2\r\n:1\r\n:-1\r\n"
20960        );
20961        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20962
20963        // A missing group is minus one an ID and not a NOGROUP.
20964        assert_eq!(
20965            f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
20966            "*1\r\n:-1\r\n"
20967        );
20968        assert_eq!(
20969            f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
20970            "*1\r\n:-1\r\n"
20971        );
20972
20973        // The acknowledgement happens whatever the condition says, so an ACKED
20974        // that answers two has still emptied the pending list.
20975        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
20976        f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
20977        assert_eq!(
20978            f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
20979            "*1\r\n:2\r\n"
20980        );
20981        assert_eq!(
20982            f.run(&[b"XPENDING", b"s", b"g"]),
20983            "*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"
20984        );
20985        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20986    }
20987
20988    /// `XNACK`, which hands an entry back to nobody.
20989    #[test]
20990    fn xnack_releases_an_entry_for_the_next_claim() {
20991        let mut f = Fixture::new();
20992        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20993        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20994        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20995        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20996        // Twice, so the delivery count is two and the words have something to
20997        // do with it.
20998        f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
20999
21000        assert_eq!(
21001            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
21002            ":1\r\n"
21003        );
21004        // No owner, no idle time, and the count left where it was. A released
21005        // entry reads as idle for longer than any min-idle-time, which is what
21006        // puts it at the front of the next claim.
21007        assert_eq!(
21008            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
21009            "*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"
21010        );
21011        // The consumer no longer holds it, so a filtered XPENDING skips it.
21012        assert_eq!(
21013            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21014            "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
21015        );
21016        // The bookmark did not move, so a `>` read will not hand it out again.
21017        assert_eq!(
21018            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
21019            "*-1\r\n"
21020        );
21021        // A claim at any min-idle-time takes it.
21022        assert_eq!(
21023            f.run(&[
21024                b"XAUTOCLAIM",
21025                b"s",
21026                b"g",
21027                b"c2",
21028                b"99999999",
21029                b"-",
21030                b"JUSTID"
21031            ]),
21032            "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
21033        );
21034
21035        // `SILENT` takes one off the count rather than putting it back to zero,
21036        // which only shows on an entry that has been handed out more than once.
21037        // It was delivered and then claimed, so it is on two and goes to one.
21038        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21039        assert!(
21040            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21041                .contains(":-1\r\n:1\r\n")
21042        );
21043        // And it stops at zero rather than wrapping.
21044        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21045        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21046        assert!(
21047            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21048                .contains(":-1\r\n:0\r\n")
21049        );
21050        // `FATAL` puts it at the ceiling, and `RETRYCOUNT` wins over the word.
21051        f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
21052        assert!(
21053            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21054                .contains(":9223372036854775807\r\n")
21055        );
21056        f.run(&[
21057            b"XNACK",
21058            b"s",
21059            b"g",
21060            b"FATAL",
21061            b"IDS",
21062            b"1",
21063            b"1-1",
21064            b"RETRYCOUNT",
21065            b"3",
21066        ]);
21067        assert!(
21068            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21069                .contains(":-1\r\n:3\r\n")
21070        );
21071
21072        // Releasing something the group is not holding is zero, and `FORCE`
21073        // makes the pending entry rather than answering zero. A forced entry
21074        // starts at zero, since there was no earlier count to keep.
21075        f.run(&[b"XACK", b"s", b"g", b"2-1"]);
21076        assert_eq!(
21077            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
21078            ":0\r\n"
21079        );
21080        assert_eq!(
21081            f.run(&[
21082                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
21083            ]),
21084            ":1\r\n"
21085        );
21086        assert!(
21087            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21088                .contains(":-1\r\n:0\r\n")
21089        );
21090        // `FORCE` on an ID the stream does not have is still zero.
21091        assert_eq!(
21092            f.run(&[
21093                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
21094            ]),
21095            ":0\r\n"
21096        );
21097
21098        // The group is looked up before the mode word, and it raises rather
21099        // than answering per ID the way the two delete commands do.
21100        assert_eq!(
21101            f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
21102            "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
21103        );
21104        assert!(
21105            f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
21106                .starts_with("-ERR")
21107        );
21108        // Its own sentences, which are not the ones XDELEX uses.
21109        assert!(
21110            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
21111                .contains("numids must be a positive integer")
21112        );
21113        assert!(
21114            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
21115                .contains("number of IDs doesn't match numids")
21116        );
21117        // Everything past the counted IDs is an option, so one too many is an
21118        // option nobody recognises and not a count that does not add up.
21119        assert!(
21120            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
21121                .contains("Unrecognized XNACK option '2-1'")
21122        );
21123    }
21124
21125    /// `XINFO`, which is where the shape of the storage shows through.
21126    #[test]
21127    fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
21128        let mut f = Fixture::new();
21129        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21130        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21131        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21132        f.run(&[
21133            b"XREADGROUP",
21134            b"GROUP",
21135            b"g",
21136            b"c1",
21137            b"COUNT",
21138            b"1",
21139            b"STREAMS",
21140            b"s",
21141            b">",
21142        ]);
21143
21144        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21145        // Ten pairs, since the six idempotency fields have nothing behind them
21146        // here and a zero would claim they had. That is D-27.
21147        assert!(info.starts_with("*20\r\n"), "{info}");
21148        assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
21149        assert!(
21150            info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
21151            "{info}"
21152        );
21153        assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
21154        assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
21155
21156        let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
21157        assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
21158        assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
21159        assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
21160        assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
21161
21162        // A consumer that has never been given anything reports minus one for
21163        // inactive rather than the moment it turned up, which is what tells a
21164        // worker that is stuck from one that has nothing to do.
21165        f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
21166        let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
21167        assert!(consumers.starts_with("*2\r\n"), "{consumers}");
21168        assert!(
21169            consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
21170            "{consumers}"
21171        );
21172        // And in name order, which the storage does not hold them in.
21173        let c1 = consumers.find("c1").unwrap();
21174        let c2 = consumers.find("c2").unwrap();
21175        assert!(c1 < c2, "{consumers}");
21176
21177        let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
21178        assert!(full.starts_with("*18\r\n"), "{full}");
21179        assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
21180        assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
21181
21182        assert!(
21183            f.run(&[b"XINFO", b"STREAM", b"missing"])
21184                .contains("no such key")
21185        );
21186        assert!(
21187            f.run(&[b"XINFO", b"GROUPS", b"missing"])
21188                .contains("no such key")
21189        );
21190        assert!(
21191            f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
21192                .starts_with("-NOGROUP")
21193        );
21194        assert!(
21195            f.run(&[b"XINFO", b"NOSUCH", b"s"])
21196                .contains("Try XINFO HELP")
21197        );
21198        assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
21199        assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
21200    }
21201
21202    /// `XPENDING`'s long form, which reads its arguments by counting them.
21203    #[test]
21204    fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
21205        let mut f = Fixture::new();
21206        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21207        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21208        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21209
21210        let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
21211        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");
21212        assert_eq!(
21213            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21214            "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
21215        );
21216        // A consumer nobody has heard of holds nothing rather than erroring.
21217        assert_eq!(
21218            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
21219            "*0\r\n"
21220        );
21221        assert_eq!(
21222            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
21223            list
21224        );
21225        // IDLE is only read at position three.
21226        assert!(
21227            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
21228                .contains("syntax error")
21229        );
21230        assert!(
21231            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
21232                .contains("syntax error")
21233        );
21234        assert_eq!(
21235            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
21236            "*0\r\n"
21237        );
21238        assert!(
21239            f.run(&[b"XPENDING", b"missing", b"g"])
21240                .starts_with("-NOGROUP")
21241        );
21242    }
21243
21244    /// `XSETID`, which is three counters and two refusals.
21245    #[test]
21246    fn xsetid_will_not_go_below_what_is_there() {
21247        let mut f = Fixture::new();
21248        f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
21249        assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
21250        assert_eq!(
21251            f.run(&[
21252                b"XSETID",
21253                b"s",
21254                b"10-1",
21255                b"ENTRIESADDED",
21256                b"7",
21257                b"MAXDELETEDID",
21258                b"9-1"
21259            ]),
21260            "+OK\r\n"
21261        );
21262        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21263        assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
21264        assert!(
21265            info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
21266            "{info}"
21267        );
21268
21269        assert!(
21270            f.run(&[b"XSETID", b"s", b"1-1"])
21271                .contains("smaller than the target stream top item")
21272        );
21273        assert!(
21274            f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
21275                .contains("entries_added must be positive")
21276        );
21277        assert!(
21278            f.run(&[b"XSETID", b"missing", b"1-1"])
21279                .contains("no such key")
21280        );
21281    }
21282
21283    /// RESP3, where the two reads answer a map and the entries stay an array.
21284    #[test]
21285    fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
21286        let mut f = Fixture::new();
21287        f.run(&[b"HELLO", b"3"]);
21288        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21289        // A map header and then the key and the entries side by side, with no
21290        // two element array wrapping the pair.
21291        assert_eq!(
21292            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21293            "%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"
21294        );
21295        // The fields are still one flat array and not a map, which is Redis's
21296        // shape and is what every consumer written before RESP3 expects.
21297        assert_eq!(
21298            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
21299            "*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"
21300        );
21301        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
21302    }
21303
21304    /// A store to migrate values into, so a test can watch the inversion.
21305    ///
21306    /// A vector rather than a file for the same reason the tier's own tests use
21307    /// one: the file work has not attached a real store yet, and what this is
21308    /// checking is the policy above the store rather than the store.
21309    struct Mem {
21310        blobs: Vec<Vec<u8>>,
21311    }
21312
21313    impl yo_kv::cold::Blocks for Mem {
21314        fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
21315            self.blobs.push(bytes.to_vec());
21316            Ok(yo_common::Addr::new(
21317                yo_common::Space::Log,
21318                (self.blobs.len() - 1) as u64,
21319            ))
21320        }
21321
21322        fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
21323            self.blobs
21324                .get(at.offset() as usize)
21325                .map(Vec::as_slice)
21326                .ok_or_else(|| {
21327                    yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
21328                })
21329        }
21330
21331        fn bytes(&self) -> u64 {
21332            self.blobs.iter().map(|b| b.len() as u64).sum()
21333        }
21334    }
21335
21336    /// A server holding several segments of strings, with somewhere to put them.
21337    ///
21338    /// Answers the fixture and what it was holding when it stopped filling.
21339    /// The three tests that call this are the ones Miri is not run over.
21340    ///
21341    /// What they are about is the regime a database is in once the arena has
21342    /// several segments, and a segment is two megabytes, so there is no smaller
21343    /// version of the question: twenty four thousand keys is already the least
21344    /// that gets there. Interpreted, each of them sat for over forty minutes
21345    /// and was still going. The arena's own segment handling is interpreted in
21346    /// full in its own crate, and the policy these three check is ordinary
21347    /// bookkeeping with no unsafe block anywhere in it.
21348    fn filled(attach: bool) -> (Fixture, usize) {
21349        let mut f = Fixture::new();
21350        if attach {
21351            f.server
21352                .striped(0)
21353                .hold_stripe(0)
21354                .attach(Box::new(Mem { blobs: Vec::new() }));
21355        }
21356        let val = vec![b'v'; 256];
21357        for i in 0..24000u32 {
21358            let k = format!("key:{i:08}");
21359            f.run(&[b"SET", k.as_bytes(), &val]);
21360        }
21361        let full = f.server.memory_bytes();
21362        assert!(full > 3 * 1024 * 1024, "the arena is several segments");
21363        (f, full)
21364    }
21365
21366    /// Write until the server is under `limit` or the writes run out.
21367    ///
21368    /// The same shape the eviction test uses. A memory limit is enforced in
21369    /// front of a command, so nothing happens until something is written, and
21370    /// the budget means one command does not do the whole job.
21371    fn press(f: &mut Fixture, limit: usize) {
21372        let val = vec![b'v'; 256];
21373        for i in 0..3000u32 {
21374            let k = format!("new:{i:08}");
21375            assert_eq!(
21376                f.run(&[b"SET", k.as_bytes(), &val]),
21377                "+OK\r\n",
21378                "write {i} was refused"
21379            );
21380            f.server.refresh_memory();
21381            if f.server.memory_bytes() <= limit {
21382                return;
21383            }
21384        }
21385        panic!(
21386            "it never got under: {} against {limit}",
21387            f.server.memory_bytes()
21388        );
21389    }
21390
21391    #[test]
21392    fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
21393        let mut f = Fixture::new();
21394        assert_eq!(
21395            f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21396            "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
21397            "no limit is the default"
21398        );
21399        // The same memory value parser `maxmemory` uses, and the same trap in
21400        // it, plus the one spelling that means no limit at all.
21401        for (typed, bytes) in [
21402            (&b"0"[..], "0"),
21403            (b"1024", "1024"),
21404            (b"1k", "1000"),
21405            (b"1gb", "1073741824"),
21406            (b"-1", "-1"),
21407        ] {
21408            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
21409            assert_eq!(
21410                f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21411                format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
21412                "set {}",
21413                String::from_utf8_lossy(typed)
21414            );
21415        }
21416        for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
21417            assert_eq!(
21418                f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
21419                "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
21420                "refused {}",
21421                String::from_utf8_lossy(bad)
21422            );
21423        }
21424        // Nothing is attached, so the answer to a memory limit is still Redis's.
21425        let info = f.run(&[b"INFO", b"memory"]);
21426        assert!(info.contains("maxstore:-1"), "{info}");
21427        assert!(info.contains("yo_memory_regime:evict"), "{info}");
21428        assert!(info.contains("yo_store_bytes:0"), "{info}");
21429    }
21430
21431    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21432    #[test]
21433    fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
21434        // The inversion. The same pressure that makes a Redis server throw keys
21435        // away makes this one move values to the file, and afterwards every key
21436        // is still there and still answers with what was stored in it.
21437        let (mut f, full) = filled(true);
21438        let keys = f.run(&[b"DBSIZE"]);
21439        assert!(
21440            f.run(&[b"INFO", b"memory"])
21441                .contains("yo_memory_regime:migrate"),
21442            "a database with somewhere to put values migrates"
21443        );
21444
21445        let limit = full - 2 * 1024 * 1024;
21446        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21447        f.run(&[
21448            b"CONFIG",
21449            b"SET",
21450            b"maxmemory",
21451            limit.to_string().as_bytes(),
21452        ]);
21453        press(&mut f, limit);
21454
21455        assert!(
21456            f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21457            "nothing was thrown away"
21458        );
21459        let after: usize = f.run(&[b"DBSIZE"])[1..]
21460            .trim_end()
21461            .parse()
21462            .expect("a count");
21463        let before: usize = keys[1..].trim_end().parse().expect("a count");
21464        assert!(after > before, "the keys that came in are all still here");
21465        assert!(
21466            f.server.store_bytes() > 0,
21467            "and what came out of memory went to the file"
21468        );
21469        // And the values read back, which is the part that makes it a migration
21470        // rather than a loss.
21471        let val = format!("$256\r\n{}\r\n", "v".repeat(256));
21472        assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
21473        assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
21474    }
21475
21476    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21477    #[test]
21478    fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
21479        // The documented setting for a drop in cache. A file that may hold
21480        // nothing cannot be migrated to, so eviction is all that is left, and
21481        // the server behaves exactly as it did before any of this existed.
21482        let (mut f, full) = filled(true);
21483        f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
21484        assert!(
21485            f.run(&[b"INFO", b"memory"])
21486                .contains("yo_memory_regime:evict"),
21487            "nothing may go to the file"
21488        );
21489
21490        let limit = full - 2 * 1024 * 1024;
21491        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21492        f.run(&[
21493            b"CONFIG",
21494            b"SET",
21495            b"maxmemory",
21496            limit.to_string().as_bytes(),
21497        ]);
21498        press(&mut f, limit);
21499
21500        assert!(
21501            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21502            "keys were thrown away, which is what was asked for"
21503        );
21504        assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
21505    }
21506
21507    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21508    #[test]
21509    fn a_full_file_goes_back_to_evicting() {
21510        // A storage limit reached is a storage limit, and eviction is the right
21511        // answer to one. The budget here is a few kilobytes, so the first round
21512        // of migration fills it and everything after that is evicted.
21513        let (mut f, full) = filled(true);
21514        f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
21515        let limit = full - 2 * 1024 * 1024;
21516        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21517        f.run(&[
21518            b"CONFIG",
21519            b"SET",
21520            b"maxmemory",
21521            limit.to_string().as_bytes(),
21522        ]);
21523        press(&mut f, limit);
21524
21525        assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
21526        assert!(
21527            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21528            "and then it started evicting"
21529        );
21530        assert!(
21531            f.run(&[b"INFO", b"memory"])
21532                .contains("yo_memory_regime:evict"),
21533            "and it says so"
21534        );
21535    }
21536    // ------------------------------------------------------------- stripes
21537
21538    /// Every string command, run twice: once on a database that is one keyspace
21539    /// and once on a database that is eight, with the same commands in the same
21540    /// order and the replies compared byte for byte.
21541    ///
21542    /// This is the whole claim the striping rests on. A key belongs to one
21543    /// stripe and to no other, so the answer to a command cannot depend on how
21544    /// many stripes there are, and the way to check that is to ask the same
21545    /// question of two servers that differ in nothing else.
21546    ///
21547    /// The keys are chosen to land on different stripes rather than to look
21548    /// tidy. `MSET a 1 b 2 c 3` over eight stripes is only a test of anything if
21549    /// those three keys are not all on the same one, and at eight stripes three
21550    /// keys land together about one time in fifty.
21551    #[test]
21552    fn the_string_group_answers_the_same_however_many_stripes_there_are() {
21553        let script: &[&[&[u8]]] = &[
21554            // The single key commands, which are the ones that get handed one
21555            // stripe at the dispatch site.
21556            &[b"SET", b"k1", b"v1"],
21557            &[b"SET", b"k2", b"v2"],
21558            &[b"GET", b"k1"],
21559            &[b"GET", b"nothing"],
21560            &[b"GETSET", b"k1", b"v1b"],
21561            &[b"SETNX", b"k1", b"no"],
21562            &[b"SETNX", b"k3", b"yes"],
21563            &[b"APPEND", b"k3", b"!"],
21564            &[b"STRLEN", b"k3"],
21565            &[b"SETRANGE", b"k3", b"1", b"XY"],
21566            &[b"GETRANGE", b"k3", b"0", b"-1"],
21567            &[b"INCR", b"n1"],
21568            &[b"INCRBY", b"n1", b"41"],
21569            &[b"DECRBY", b"n1", b"2"],
21570            &[b"INCRBYFLOAT", b"f1", b"1.5"],
21571            &[b"SETEX", b"e1", b"100", b"v"],
21572            &[b"PSETEX", b"e2", b"100000", b"v"],
21573            &[b"GETEX", b"e1", b"PERSIST"],
21574            &[b"GETDEL", b"k2"],
21575            &[b"GET", b"k2"],
21576            &[b"DIGEST", b"k1"],
21577            &[b"DELEX", b"k3"],
21578            // The five that name more than one key, which are the ones that
21579            // cannot be handed one stripe at all.
21580            &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
21581            &[b"MGET", b"a", b"b", b"c", b"missing"],
21582            &[b"MSETNX", b"d", b"4", b"e", b"5"],
21583            &[b"MSETNX", b"e", b"6", b"f", b"7"],
21584            &[b"MGET", b"d", b"e", b"f"],
21585            &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
21586            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
21587            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
21588            &[b"MGET", b"g", b"h"],
21589            &[b"SET", b"s1", b"ohmytext"],
21590            &[b"SET", b"s2", b"mynewtext"],
21591            &[b"LCS", b"s1", b"s2"],
21592            &[b"LCS", b"s1", b"s2", b"LEN"],
21593            &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
21594            &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
21595            &[b"LCS", b"s1", b"gone"],
21596            // And the errors, which have to be the same errors.
21597            &[b"MSET", b"odd"],
21598            &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
21599            &[b"MGET"],
21600        ];
21601
21602        let mut one = Fixture::new();
21603        let mut many = Fixture::striped(8);
21604        for parts in script {
21605            let a = one.run(parts);
21606            let b = many.run(parts);
21607            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21608        }
21609    }
21610
21611    /// The keys of an `MSET` really do end up on different stripes.
21612    ///
21613    /// Without this the test above could pass on a server whose stripe number
21614    /// happened to be a constant, which is a striped database in name only.
21615    #[test]
21616    fn a_striped_database_spreads_the_keys_it_is_given() {
21617        let mut f = Fixture::striped(8);
21618        for i in 0..256 {
21619            let key = format!("key:{i}");
21620            f.run(&[b"SET", key.as_bytes(), b"v"]);
21621        }
21622        assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
21623    }
21624
21625    /// A wrong type stops an `MGET` no more than it does on one stripe: the key
21626    /// that is not a string comes back nil and the rest of the reply is intact.
21627    #[test]
21628    fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
21629        let mut one = Fixture::new();
21630        let mut many = Fixture::striped(8);
21631        for f in [&mut one, &mut many] {
21632            f.run(&[b"SET", b"str", b"v"]);
21633            // Planted rather than pushed. `RPUSH` belongs to the list group,
21634            // which has not been taught about stripes yet and would refuse the
21635            // wide server. What is under test is what `MGET` does when it walks
21636            // onto a key that is not a string, and that does not care how the
21637            // key got there.
21638            f.server
21639                .striped(0)
21640                .hold(b"list")
21641                .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
21642                .expect("a new list");
21643        }
21644        assert_eq!(
21645            one.run(&[b"MGET", b"str", b"list", b"gone"]),
21646            many.run(&[b"MGET", b"str", b"list", b"gone"])
21647        );
21648    }
21649
21650    /// The same claim for the keyspace group, and the same way of checking it.
21651    ///
21652    /// `SORT` is not in the script because it is the one command in that file
21653    /// that has not been taught about stripes, and `SCAN`, `KEYS` and
21654    /// `RANDOMKEY` are not in it either, because those three do not promise an
21655    /// order and comparing two replies byte for byte would be asserting one.
21656    /// They get tests of their own below.
21657    #[test]
21658    fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
21659        let script: &[&[&[u8]]] = &[
21660            &[b"SET", b"k1", b"v1"],
21661            &[b"SET", b"k2", b"v2"],
21662            &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
21663            &[b"TYPE", b"k1"],
21664            &[b"TYPE", b"gone"],
21665            &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
21666            &[b"EXPIRE", b"k1", b"100"],
21667            &[b"TTL", b"k1"],
21668            &[b"EXPIRE", b"k1", b"200", b"NX"],
21669            &[b"PERSIST", b"k1"],
21670            &[b"TTL", b"k1"],
21671            &[b"PEXPIREAT", b"k2", b"1900000000000"],
21672            &[b"EXPIRETIME", b"k2"],
21673            &[b"PEXPIRETIME", b"k2"],
21674            &[b"PERSIST", b"k2"],
21675            &[b"OBJECT", b"ENCODING", b"k1"],
21676            &[b"OBJECT", b"REFCOUNT", b"k1"],
21677            &[b"OBJECT", b"IDLETIME", b"k1"],
21678            &[b"OBJECT", b"FREQ", b"k1"],
21679            &[b"OBJECT", b"ENCODING", b"gone"],
21680            &[b"OBJECT", b"HELP"],
21681            &[b"RENAME", b"k1", b"k9"],
21682            &[b"GET", b"k9"],
21683            &[b"RENAME", b"gone", b"x"],
21684            &[b"RENAMENX", b"k9", b"k2"],
21685            &[b"RENAMENX", b"k9", b"k8"],
21686            &[b"GET", b"k8"],
21687            &[b"COPY", b"k8", b"c1"],
21688            &[b"COPY", b"k8", b"c1"],
21689            &[b"COPY", b"k8", b"c1", b"REPLACE"],
21690            &[b"COPY", b"k8", b"k8"],
21691            &[b"COPY", b"gone", b"c2"],
21692            &[b"COPY", b"k8", b"k8", b"DB", b"1"],
21693            &[b"COPY", b"k8", b"c9", b"DB", b"9"],
21694            &[b"MOVE", b"c1", b"1"],
21695            &[b"MOVE", b"c1", b"1"],
21696            &[b"MOVE", b"k8", b"0"],
21697            &[b"DEL", b"k2", b"gone"],
21698            &[b"UNLINK", b"k8", b"k8"],
21699            &[b"DBSIZE"],
21700        ];
21701
21702        let mut one = Fixture::new();
21703        let mut many = Fixture::striped(8);
21704        for parts in script {
21705            let a = one.run(parts);
21706            let b = many.run(parts);
21707            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21708        }
21709
21710        // `RESTORE` needs bytes a client would have got from a `DUMP`, so the
21711        // payload is taken from the store rather than parsed back out of a
21712        // reply that is not text. Both servers dump the same key and the bytes
21713        // are the same bytes, which is the first half of what is being checked
21714        // here.
21715        for f in [&mut one, &mut many] {
21716            f.run(&[b"SET", b"d1", b"payload"]);
21717            let payload = f
21718                .server
21719                .striped(0)
21720                .hold(b"d1")
21721                .dump(b"d1")
21722                .expect("a key that is there");
21723            assert!(
21724                f.run(&[b"DUMP", b"d1"])
21725                    .starts_with(&format!("${}", payload.len())),
21726                "a payload of the length the store gave"
21727            );
21728            assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
21729            assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
21730            assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
21731            assert_eq!(
21732                f.run(&[b"RESTORE", b"d2", b"0", &payload]),
21733                "-BUSYKEY Target key name already exists.\r\n"
21734            );
21735            assert_eq!(
21736                f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
21737                "-ERR DUMP payload version or checksum are wrong\r\n"
21738            );
21739        }
21740    }
21741
21742    /// A `SCAN` of a database of eight stripes comes back with all of it.
21743    ///
21744    /// The cursor is the thing under test. It has to carry the stripe as well
21745    /// as the place in it, so a client that stops at one stripe and comes back
21746    /// carries on in that stripe and not at the top of the database, and the
21747    /// walk has to end once rather than eight times.
21748    #[test]
21749    fn a_scan_of_a_striped_database_walks_all_of_it() {
21750        // Eight stripes and a COUNT of ten, so eighty keys is already more than
21751        // one page on every stripe and the cursor has to carry which stripe it
21752        // was on, which is the thing being checked.
21753        let n = if cfg!(miri) { 80 } else { 500 };
21754        let mut f = Fixture::striped(8);
21755        for i in 0..n {
21756            let key = format!("key:{i}");
21757            f.run(&[b"SET", key.as_bytes(), b"v"]);
21758        }
21759
21760        let mut seen = Vec::new();
21761        let mut cursor = "0".to_owned();
21762        let mut calls = 0;
21763        loop {
21764            let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
21765            let (next, keys) = scan_reply(&reply);
21766            seen.extend(keys);
21767            cursor = next;
21768            calls += 1;
21769            assert!(calls < 5_000, "a scan that will not finish");
21770            if cursor == "0" {
21771                break;
21772            }
21773        }
21774        seen.sort();
21775        assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
21776        assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
21777
21778        // And the options still work when the walk is over several stripes,
21779        // since a `MATCH` is applied to keys a stripe handed up and a `TYPE` is
21780        // applied by each stripe on the way.
21781        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
21782        let (_, keys) = scan_reply(&reply);
21783        assert_eq!(keys.len(), 10, "key:40 through key:49");
21784        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
21785        let (_, keys) = scan_reply(&reply);
21786        assert!(keys.is_empty(), "nothing here is a list");
21787    }
21788
21789    /// `RANDOMKEY` on a striped database answers a key from any of the stripes.
21790    ///
21791    /// The draw picks the stripe first, so the thing that can go wrong is that
21792    /// it always picks the same one, and two hundred draws over eight stripes
21793    /// would make that obvious.
21794    #[test]
21795    fn a_random_key_can_come_from_any_stripe() {
21796        let mut f = Fixture::striped(8);
21797        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
21798        for i in 0..200 {
21799            let key = format!("key:{i}");
21800            f.run(&[b"SET", key.as_bytes(), b"v"]);
21801        }
21802        let mut homes = std::collections::HashSet::new();
21803        for _ in 0..200 {
21804            let got = f.run(&[b"RANDOMKEY"]);
21805            let key = got.split("\r\n").nth(1).expect("a key").to_owned();
21806            assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
21807            homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
21808        }
21809        assert_eq!(homes.len(), 8, "some stripe was never drawn from");
21810    }
21811
21812    /// Two keys that are not on the same stripe, which is what `RENAME` and
21813    /// `COPY` have to cope with and what a test has to arrange rather than
21814    /// hope for.
21815    fn apart(f: &mut Fixture, src: &str) -> String {
21816        let home = f.server.striped(0).stripe_of(src.as_bytes());
21817        for i in 0..1_000 {
21818            let dst = format!("dst:{i}");
21819            if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
21820                return dst;
21821            }
21822        }
21823        panic!("eight stripes and a thousand keys all landed in one place");
21824    }
21825
21826    /// A rename whose two keys are on two stripes moves the value, the deadline
21827    /// and, for a collection, the body itself.
21828    #[test]
21829    fn a_rename_across_stripes_takes_everything_with_it() {
21830        let mut f = Fixture::striped(8);
21831        let dst = apart(&mut f, "src");
21832        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21833
21834        f.run(&[b"SET", src, b"v"]);
21835        f.run(&[b"EXPIRE", src, b"100"]);
21836        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21837        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
21838        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
21839        assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
21840
21841        // A list, because a string lives in its record and a collection lives
21842        // in a slab, and the second of those is the one that can be left
21843        // behind. Planted through the store, since the list group has not been
21844        // taught about stripes yet.
21845        f.server
21846            .striped(0)
21847            .hold(src)
21848            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21849            .expect("a new list");
21850        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21851        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
21852        assert_eq!(
21853            f.server.striped(0).hold(dst).llen(dst).expect("a list"),
21854            2,
21855            "the members are on the stripe the key moved to"
21856        );
21857
21858        // And `RENAMENX` still refuses a destination that is taken, which is
21859        // the one answer the cross stripe path has to work out for itself.
21860        f.run(&[b"SET", src, b"v"]);
21861        assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
21862        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
21863        assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
21864    }
21865
21866    /// And a copy across two stripes leaves both keys behind it.
21867    #[test]
21868    fn a_copy_across_stripes_leaves_the_source_where_it_was() {
21869        let mut f = Fixture::striped(8);
21870        let dst = apart(&mut f, "src");
21871        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21872
21873        f.run(&[b"SET", src, b"v"]);
21874        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21875        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
21876        assert_eq!(
21877            f.run(&[b"COPY", src, dst]),
21878            ":0\r\n",
21879            "the destination is taken"
21880        );
21881        f.run(&[b"SET", src, b"w"]);
21882        assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
21883        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
21884
21885        // A collection is cloned rather than moved, so both keys have a body of
21886        // their own afterwards and writing to one does not show up in the
21887        // other.
21888        f.run(&[b"DEL", src, dst]);
21889        f.server
21890            .striped(0)
21891            .hold(src)
21892            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21893            .expect("a new list");
21894        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21895        f.server
21896            .striped(0)
21897            .hold(src)
21898            .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
21899            .expect("a list that is there");
21900        assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
21901        assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
21902    }
21903
21904    /// Every bitmap command, on one stripe and on eight, replies compared byte
21905    /// for byte.
21906    ///
21907    /// `BITOP` is the one that names more than one key and it is where the work
21908    /// went. The rest are single key commands that now find their own stripe,
21909    /// and they are here because the cheapest way to be sure the routing is
21910    /// right is to ask.
21911    #[test]
21912    fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
21913        let script: &[&[&[u8]]] = &[
21914            &[b"SET", b"k1", b"foobar"],
21915            &[b"SETBIT", b"b1", b"7", b"1"],
21916            &[b"SETBIT", b"b1", b"7", b"0"],
21917            &[b"GETBIT", b"k1", b"6"],
21918            &[b"GETBIT", b"k1", b"100"],
21919            &[b"BITCOUNT", b"k1"],
21920            &[b"BITCOUNT", b"k1", b"0", b"0"],
21921            &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
21922            &[b"BITPOS", b"k1", b"1"],
21923            &[b"BITPOS", b"k1", b"0", b"2"],
21924            &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
21925            &[
21926                b"BITFIELD",
21927                b"bf",
21928                b"SET",
21929                b"u8",
21930                b"0",
21931                b"255",
21932                b"GET",
21933                b"u8",
21934                b"0",
21935            ],
21936            &[
21937                b"BITFIELD",
21938                b"bf",
21939                b"OVERFLOW",
21940                b"SAT",
21941                b"INCRBY",
21942                b"u8",
21943                b"0",
21944                b"10",
21945            ],
21946            &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
21947            // The multi key one, over sources that are not on one stripe unless
21948            // eight stripes have folded into one.
21949            &[b"SET", b"s1", b"abc"],
21950            &[b"SET", b"s2", b"abd"],
21951            &[b"SET", b"s3", b"a"],
21952            &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
21953            &[b"GET", b"d1"],
21954            &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
21955            &[b"GET", b"d2"],
21956            &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
21957            &[b"STRLEN", b"d3"],
21958            &[b"BITOP", b"NOT", b"d4", b"s1"],
21959            &[b"STRLEN", b"d4"],
21960            &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
21961            &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
21962            &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
21963            &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
21964            // A source that is not there reads as empty, and a result with
21965            // nothing in it deletes the destination rather than writing one.
21966            &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
21967            &[b"EXISTS", b"d1"],
21968            &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
21969            &[b"GET", b"d9"],
21970            // And the errors, which have to be the same errors. The key that
21971            // is not a string is planted below rather than pushed here, since
21972            // the list group has not been taught about stripes yet.
21973            &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
21974            &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
21975            &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
21976            &[b"BITOP", b"DIFF", b"d1", b"s1"],
21977            &[b"BITOP", b"NOPE", b"d1", b"s1"],
21978            &[b"BITCOUNT", b"list"],
21979            &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
21980        ];
21981
21982        let mut one = Fixture::new();
21983        let mut many = Fixture::striped(8);
21984        for f in [&mut one, &mut many] {
21985            plant_list(f, b"list");
21986        }
21987        for parts in script {
21988            let a = one.run(parts);
21989            let b = many.run(parts);
21990            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21991        }
21992    }
21993
21994    /// A list under `key`, put there through the store.
21995    ///
21996    /// What a test does when it wants a key of the wrong type on a striped
21997    /// server, because the command that would make one is in a group that has
21998    /// not been taught about stripes yet.
21999    fn plant_list(f: &mut Fixture, key: &[u8]) {
22000        f.server
22001            .striped(0)
22002            .hold(key)
22003            .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
22004            .expect("a new list");
22005    }
22006
22007    /// A `BITOP` whose keys are on two stripes reads both of them.
22008    ///
22009    /// The test above spreads its keys by hashing and would still pass if one
22010    /// stripe were doing all the work, since the answers would be the same. This
22011    /// one puts the destination and the two sources where they are known not to
22012    /// share a stripe.
22013    #[test]
22014    fn a_bitop_across_stripes_reads_every_source() {
22015        let mut f = Fixture::striped(8);
22016        let other = apart(&mut f, "src");
22017        let (src, far) = (b"src".as_slice(), other.as_bytes());
22018        assert_ne!(
22019            f.server.striped(0).stripe_of(src),
22020            f.server.striped(0).stripe_of(far),
22021            "the two keys are the point of the test"
22022        );
22023
22024        f.run(&[b"SET", src, b"abc"]);
22025        f.run(&[b"SET", far, b"abd"]);
22026        assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
22027        assert_eq!(
22028            f.run(&[b"GET", far]),
22029            "$3\r\nab`\r\n",
22030            "a destination that is also a source"
22031        );
22032        f.run(&[b"SET", far, b"abd"]);
22033        assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
22034        assert_eq!(
22035            f.run(&[b"GET", src]),
22036            "$3\r\n\0\0\x07\r\n",
22037            "and the other way round"
22038        );
22039
22040        // A result of nothing deletes a destination on whatever stripe it is
22041        // on, and a source of the wrong type is refused before anything is
22042        // written.
22043        f.run(&[b"SET", src, b"abc"]);
22044        f.run(&[b"DEL", far]);
22045        assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
22046        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
22047        f.run(&[b"SET", src, b"abc"]);
22048        f.run(&[b"DEL", far]);
22049        plant_list(&mut f, far);
22050        assert_eq!(
22051            f.run(&[b"BITOP", b"OR", b"out", src, far]),
22052            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22053        );
22054        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
22055    }
22056
22057    /// Every HyperLogLog command, on one stripe and on eight.
22058    ///
22059    /// Not under Miri, for the reason on
22060    /// `the_debug_forms_answer_four_different_shapes`, and twice over here
22061    /// because the script is run against both shapes of server.
22062    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
22063    #[test]
22064    fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
22065        let script: &[&[&[u8]]] = &[
22066            &[b"PFADD", b"h1", b"a", b"b", b"c"],
22067            &[b"PFADD", b"h1", b"a"],
22068            &[b"PFADD", b"h2"],
22069            &[b"PFADD", b"h2", b"c", b"d", b"e"],
22070            &[b"PFCOUNT", b"h1"],
22071            &[b"PFCOUNT", b"h2"],
22072            &[b"PFCOUNT", b"missing"],
22073            // The two that name more than one key.
22074            &[b"PFCOUNT", b"h1", b"h2"],
22075            &[b"PFCOUNT", b"h1", b"missing"],
22076            &[b"PFMERGE", b"m", b"h1", b"h2"],
22077            &[b"PFCOUNT", b"m"],
22078            &[b"STRLEN", b"m"],
22079            &[b"PFMERGE", b"m"],
22080            &[b"PFCOUNT", b"m"],
22081            &[b"PFMERGE", b"m2", b"missing"],
22082            &[b"PFCOUNT", b"m2"],
22083            // The debugging ones, which are single key and change what they
22084            // look at.
22085            &[b"PFDEBUG", b"ENCODING", b"h1"],
22086            &[b"PFDEBUG", b"DECODE", b"h1"],
22087            &[b"PFDEBUG", b"TODENSE", b"h1"],
22088            &[b"PFDEBUG", b"ENCODING", b"h1"],
22089            &[b"PFDEBUG", b"TODENSE", b"h1"],
22090            &[b"PFCOUNT", b"h1", b"h2"],
22091            &[b"PFSELFTEST"],
22092            // And the errors.
22093            &[b"SET", b"plain", b"not a sketch at all"],
22094            &[b"PFADD", b"plain", b"a"],
22095            &[b"PFCOUNT", b"plain"],
22096            &[b"PFCOUNT", b"h1", b"plain"],
22097            &[b"PFMERGE", b"plain", b"h1"],
22098            &[b"PFMERGE", b"m", b"plain"],
22099            &[b"PFDEBUG", b"ENCODING", b"gone"],
22100            &[b"PFDEBUG", b"NOPE", b"h1"],
22101        ];
22102
22103        let mut one = Fixture::new();
22104        let mut many = Fixture::striped(8);
22105        for parts in script {
22106            let a = one.run(parts);
22107            let b = many.run(parts);
22108            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22109        }
22110    }
22111
22112    /// Every set command, on one stripe and on eight.
22113    ///
22114    /// The commands that answer members answer them in whatever order the set
22115    /// or the table they were built in holds them, so those replies are
22116    /// compared as sets. Everything else is compared byte for byte. Two servers
22117    /// agreeing on the order would be a fact about the tables and not about the
22118    /// answer, and asserting it would make this test fail for a reason nobody
22119    /// cares about.
22120    #[test]
22121    fn the_set_group_answers_the_same_however_many_stripes_there_are() {
22122        const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
22123        let script: &[&[&[u8]]] = &[
22124            &[b"SADD", b"s1", b"a", b"b", b"c"],
22125            &[b"SADD", b"s1", b"a"],
22126            &[b"SADD", b"s2", b"b", b"c", b"d"],
22127            &[b"SADD", b"ints", b"1", b"2", b"3"],
22128            &[b"SCARD", b"s1"],
22129            &[b"SISMEMBER", b"s1", b"a"],
22130            &[b"SISMEMBER", b"s1", b"z"],
22131            &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
22132            &[b"SMEMBERS", b"s1"],
22133            &[b"SREM", b"s1", b"c"],
22134            &[b"SADD", b"s1", b"c"],
22135            &[b"SSCAN", b"s1", b"0"],
22136            &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
22137            // The two draws, on a set of one member, which is the only shape
22138            // whose answer two servers have to agree on.
22139            &[b"SADD", b"one", b"m"],
22140            &[b"SRANDMEMBER", b"one"],
22141            &[b"SRANDMEMBER", b"one", b"-3"],
22142            &[b"SRANDMEMBER", b"gone"],
22143            &[b"SPOP", b"one"],
22144            &[b"SPOP", b"one"],
22145            &[b"SPOP", b"gone", b"2"],
22146            // The one that names two keys.
22147            &[b"SMOVE", b"s1", b"s2", b"a"],
22148            &[b"SMOVE", b"s1", b"s2", b"zzz"],
22149            &[b"SMOVE", b"gone", b"s2", b"a"],
22150            &[b"SMEMBERS", b"s1"],
22151            &[b"SMEMBERS", b"s2"],
22152            // The algebra.
22153            &[b"SINTER", b"s1", b"s2"],
22154            &[b"SUNION", b"s1", b"s2"],
22155            &[b"SDIFF", b"s2", b"s1"],
22156            &[b"SINTER", b"s1", b"gone"],
22157            &[b"SUNION", b"s1", b"gone"],
22158            &[b"SDIFF", b"gone", b"s1"],
22159            &[b"SINTER", b"ints", b"s1"],
22160            &[b"SINTERCARD", b"2", b"s1", b"s2"],
22161            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
22162            &[b"SUNIONCARD", b"2", b"s1", b"s2"],
22163            &[b"SDIFFCARD", b"2", b"s2", b"s1"],
22164            &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
22165            &[b"SMEMBERS", b"d1"],
22166            &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
22167            &[b"SCARD", b"d2"],
22168            &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
22169            &[b"SCARD", b"d3"],
22170            // An empty result deletes the destination rather than storing a
22171            // set with nothing in it.
22172            &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
22173            &[b"EXISTS", b"d4"],
22174            // And a destination that is also a source.
22175            &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
22176            &[b"SCARD", b"s2"],
22177            // The errors, which have to be the same errors.
22178            &[b"SET", b"str", b"v"],
22179            &[b"SADD", b"str", b"a"],
22180            &[b"SINTER", b"s1", b"str"],
22181            &[b"SINTERSTORE", b"d5", b"s1", b"str"],
22182            &[b"EXISTS", b"d5"],
22183            &[b"SMOVE", b"str", b"s2", b"a"],
22184            &[b"SMOVE", b"s1", b"str", b"b"],
22185            &[b"SMOVE", b"gone", b"str", b"b"],
22186            &[b"SINTERCARD", b"0", b"s1"],
22187            &[b"SINTERCARD", b"3", b"s1", b"s2"],
22188            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
22189            &[b"SPOP", b"s1", b"-1"],
22190        ];
22191
22192        let mut one = Fixture::new();
22193        let mut many = Fixture::striped(8);
22194        for parts in script {
22195            let a = one.run(parts);
22196            let b = many.run(parts);
22197            let name = String::from_utf8_lossy(parts[0]).to_uppercase();
22198            if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
22199                assert_eq!(sorted(&a), sorted(&b), "{name}");
22200            } else {
22201                assert_eq!(a, b, "{name}");
22202            }
22203        }
22204    }
22205
22206    /// The algebra over sets that are known to be on different stripes.
22207    #[test]
22208    fn a_set_operation_across_stripes_reads_every_set() {
22209        let mut f = Fixture::striped(8);
22210        let second = apart(&mut f, "s1");
22211        let third = apart(&mut f, &second);
22212        let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
22213
22214        f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
22215        f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
22216        assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
22217        assert_eq!(
22218            sorted(&f.run(&[b"SUNION", s1, s2])),
22219            ["a", "b", "c", "d"],
22220            "a union of two stripes is both of them"
22221        );
22222        assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
22223        assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
22224        assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
22225        assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
22226
22227        // A destination on a third stripe, and then one that is also a source.
22228        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
22229        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
22230        assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
22231        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
22232        assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
22233        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
22234
22235        // An empty result deletes a destination wherever it is, and a key of
22236        // the wrong type stops the command before the destination is touched.
22237        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
22238        assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
22239        f.run(&[b"SET", s3, b"v"]);
22240        assert_eq!(
22241            f.run(&[b"SINTER", s1, s3]),
22242            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22243        );
22244        assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
22245    }
22246
22247    /// An `SMOVE` whose two keys are on two stripes.
22248    #[test]
22249    fn a_move_across_stripes_takes_the_member_with_it() {
22250        let mut f = Fixture::striped(8);
22251        let other = apart(&mut f, "src");
22252        let (src, dst) = (b"src".as_slice(), other.as_bytes());
22253
22254        f.run(&[b"SADD", src, b"a", b"b"]);
22255        f.run(&[b"SADD", dst, b"c"]);
22256        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
22257        assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
22258        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
22259        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
22260
22261        // A destination that is not there is created on its own stripe, and a
22262        // source that loses its last member is deleted from its own.
22263        f.run(&[b"DEL", dst]);
22264        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
22265        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
22266        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
22267
22268        // And a source that is not there answers zero without ever asking what
22269        // the destination holds, which is Redis's order and not the obvious
22270        // one.
22271        f.run(&[b"SET", dst, b"v"]);
22272        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
22273        f.run(&[b"SADD", src, b"b"]);
22274        assert_eq!(
22275            f.run(&[b"SMOVE", src, dst, b"b"]),
22276            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22277        );
22278    }
22279
22280    /// A count and a merge over sketches that are known to be on two stripes.
22281    #[test]
22282    fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
22283        let mut f = Fixture::striped(8);
22284        let other = apart(&mut f, "src");
22285        let (src, far) = (b"src".as_slice(), other.as_bytes());
22286
22287        for i in 0..150 {
22288            let ele = format!("e:{i}");
22289            f.run(&[b"PFADD", src, ele.as_bytes()]);
22290        }
22291        for i in 150..200 {
22292            let ele = format!("e:{i}");
22293            f.run(&[b"PFADD", far, ele.as_bytes()]);
22294        }
22295        // The three numbers a real server gives for these elements, which are
22296        // the numbers the single stripe tests in the keyspace crate check too.
22297        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
22298        assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
22299        assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
22300
22301        // A merge whose destination is on a third stripe, and then one that
22302        // writes into a source.
22303        let dest = apart(&mut f, &other);
22304        assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
22305        assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
22306        assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
22307        assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
22308        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
22309    }
22310
22311    /// Every sorted set command, on one stripe and on eight.
22312    ///
22313    /// Every reply here is compared byte for byte, unlike the set group, because
22314    /// a sorted set answers in rank order and members sharing a score come out
22315    /// in the order of their bytes. There is nothing left for the table the
22316    /// answer was built in to decide.
22317    #[test]
22318    fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
22319        let script: &[&[&[u8]]] = &[
22320            &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
22321            &[b"ZADD", b"z1", b"NX", b"9", b"a"],
22322            &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
22323            &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
22324            &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
22325            &[b"ZINCRBY", b"z1", b"1.5", b"b"],
22326            &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
22327            &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
22328            &[b"ZADD", b"one", b"1", b"m"],
22329            &[b"ZCARD", b"z1"],
22330            &[b"ZCARD", b"gone"],
22331            &[b"ZSCORE", b"z1", b"a"],
22332            &[b"ZSCORE", b"z1", b"zz"],
22333            &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
22334            &[b"ZRANK", b"z1", b"c"],
22335            &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
22336            &[b"ZREVRANK", b"z1", b"c"],
22337            &[b"ZRANK", b"z1", b"gone"],
22338            &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
22339            &[b"ZCOUNT", b"z1", b"(1", b"3"],
22340            &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
22341            // The range commands, which are one parse and one walk.
22342            &[b"ZRANGE", b"z1", b"0", b"-1"],
22343            &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
22344            &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
22345            &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
22346            &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
22347            &[b"ZREVRANGE", b"z1", b"0", b"-1"],
22348            &[
22349                b"ZRANGEBYSCORE",
22350                b"z1",
22351                b"-inf",
22352                b"+inf",
22353                b"LIMIT",
22354                b"1",
22355                b"1",
22356            ],
22357            &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
22358            &[b"ZSCAN", b"z1", b"0"],
22359            &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
22360            // The draw, on a sorted set of one member, which is the only shape
22361            // whose answer two servers have to agree on.
22362            &[b"ZRANDMEMBER", b"one"],
22363            &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
22364            &[b"ZRANDMEMBER", b"gone"],
22365            // The one that copies a window into another key.
22366            &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
22367            &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
22368            &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
22369            &[b"EXISTS", b"d0"],
22370            // The algebra, in both its shapes.
22371            &[b"ZUNION", b"2", b"z1", b"z2"],
22372            &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
22373            &[
22374                b"ZUNION",
22375                b"2",
22376                b"z1",
22377                b"z2",
22378                b"WEIGHTS",
22379                b"2",
22380                b"3",
22381                b"AGGREGATE",
22382                b"MAX",
22383                b"WITHSCORES",
22384            ],
22385            &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
22386            &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
22387            &[b"ZDIFF", b"2", b"gone", b"z1"],
22388            &[b"ZINTERCARD", b"2", b"z1", b"z2"],
22389            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
22390            &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
22391            &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
22392            &[
22393                b"ZINTERSTORE",
22394                b"d2",
22395                b"2",
22396                b"z1",
22397                b"z2",
22398                b"AGGREGATE",
22399                b"MIN",
22400            ],
22401            &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
22402            &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
22403            &[b"ZCARD", b"d3"],
22404            // An empty result deletes the destination rather than storing a
22405            // sorted set with nothing in it.
22406            &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
22407            &[b"EXISTS", b"d4"],
22408            // A plain set is a sorted set where every score is one, so it is a
22409            // legal input to all of these.
22410            &[b"SADD", b"plain", b"a", b"x"],
22411            &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
22412            &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
22413            // And a destination that is also a source.
22414            &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
22415            &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
22416            // The three removals and the two pops.
22417            &[b"ZREM", b"d5", b"x", b"nothere"],
22418            &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
22419            &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
22420            &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
22421            &[b"ZPOPMIN", b"z1"],
22422            &[b"ZPOPMAX", b"z1", b"2"],
22423            &[b"ZPOPMIN", b"gone"],
22424            &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
22425            &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
22426            // The errors, which have to be the same errors.
22427            &[b"SET", b"str", b"v"],
22428            &[b"ZADD", b"str", b"1", b"a"],
22429            &[b"ZSCORE", b"str", b"a"],
22430            &[b"ZADD", b"z1", b"nan", b"a"],
22431            &[b"ZUNION", b"2", b"z1", b"str"],
22432            &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
22433            &[b"EXISTS", b"d6"],
22434            &[b"ZINTERCARD", b"0", b"z1"],
22435            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
22436            &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
22437            &[b"ZMPOP", b"1", b"str", b"MIN"],
22438            &[b"ZPOPMIN", b"z1", b"-1"],
22439        ];
22440
22441        let mut one = Fixture::new();
22442        let mut many = Fixture::striped(8);
22443        for parts in script {
22444            let a = one.run(parts);
22445            let b = many.run(parts);
22446            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22447        }
22448    }
22449
22450    /// The algebra over sorted sets that are known to be on different stripes.
22451    #[test]
22452    fn a_sorted_set_operation_across_stripes_reads_every_input() {
22453        let mut f = Fixture::striped(8);
22454        let second = apart(&mut f, "z1");
22455        let third = apart(&mut f, &second);
22456        let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
22457
22458        f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
22459        f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
22460        // a is 1, c is 4, b is 2 and 3 added together, which is the order they
22461        // come out in and the answer that says both stripes were read.
22462        assert_eq!(
22463            f.run(&[b"ZUNION", b"2", z1, z2]),
22464            "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
22465        );
22466        assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
22467        assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
22468        assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
22469        assert_eq!(
22470            f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
22471            ":1\r\n"
22472        );
22473
22474        // A destination on a third stripe, and the weights and the aggregate
22475        // reaching every input.
22476        assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
22477        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
22478        assert_eq!(
22479            f.run(&[
22480                b"ZUNIONSTORE",
22481                z3,
22482                b"2",
22483                z1,
22484                z2,
22485                b"WEIGHTS",
22486                b"2",
22487                b"3",
22488                b"AGGREGATE",
22489                b"MAX"
22490            ]),
22491            ":3\r\n"
22492        );
22493        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
22494        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
22495        assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
22496        assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
22497        assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
22498
22499        // A pop over keys on several stripes takes from the first one that has
22500        // anything, which is what makes the order of the keys matter.
22501        let popped = format!(
22502            "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
22503            second.len()
22504        );
22505        assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
22506        f.run(&[b"ZADD", z2, b"3", b"b"]);
22507
22508        // An empty result deletes a destination wherever it is, and an input of
22509        // the wrong type stops the command before the destination is touched.
22510        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
22511        assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
22512        f.run(&[b"SET", z3, b"v"]);
22513        assert_eq!(
22514            f.run(&[b"ZUNION", b"2", z1, z3]),
22515            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22516        );
22517        assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
22518
22519        // And a destination that is also a source works across stripes for the
22520        // reason it works on one: the whole result is built before anything is
22521        // written.
22522        assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
22523        assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
22524        assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
22525    }
22526
22527    /// A `ZRANGESTORE` whose two keys are on two stripes.
22528    #[test]
22529    fn a_range_store_across_stripes_copies_the_window() {
22530        let mut f = Fixture::striped(8);
22531        let other = apart(&mut f, "src");
22532        let third = apart(&mut f, &other);
22533        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22534
22535        f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
22536        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
22537        assert_eq!(
22538            f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
22539            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
22540        );
22541        assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
22542
22543        // A window walked backwards takes the other end of the sorted set and
22544        // still stores what it took in score order.
22545        assert_eq!(
22546            f.run(&[
22547                b"ZRANGESTORE",
22548                dst,
22549                src,
22550                b"+inf",
22551                b"-inf",
22552                b"BYSCORE",
22553                b"REV",
22554                b"LIMIT",
22555                b"0",
22556                b"2"
22557            ]),
22558            ":2\r\n"
22559        );
22560        assert_eq!(
22561            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
22562            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22563        );
22564
22565        // An empty window deletes the destination on its own stripe, and a
22566        // source of the wrong type is refused before the destination is touched.
22567        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
22568        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
22569        f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
22570        f.run(&[b"SET", plain, b"v"]);
22571        assert_eq!(
22572            f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
22573            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22574        );
22575        assert_eq!(
22576            f.run(&[b"ZCARD", dst]),
22577            ":3\r\n",
22578            "and left the destination"
22579        );
22580    }
22581
22582    /// Every list command, on one stripe and on eight.
22583    ///
22584    /// The blocking six are in here too, both when they can be answered on the
22585    /// spot and when they cannot, since a command that parks its client writes
22586    /// nothing at all and two servers have to agree about that as much as they
22587    /// agree about a reply.
22588    #[test]
22589    fn the_list_group_answers_the_same_however_many_stripes_there_are() {
22590        let script: &[&[&[u8]]] = &[
22591            &[b"RPUSH", b"l1", b"a", b"b", b"c"],
22592            &[b"LPUSH", b"l1", b"z"],
22593            &[b"RPUSHX", b"l1", b"d"],
22594            &[b"LPUSHX", b"gone", b"x"],
22595            &[b"RPUSHX", b"gone", b"x"],
22596            &[b"LLEN", b"l1"],
22597            &[b"LLEN", b"gone"],
22598            &[b"LRANGE", b"l1", b"0", b"-1"],
22599            &[b"LRANGE", b"l1", b"1", b"2"],
22600            &[b"LRANGE", b"l1", b"5", b"9"],
22601            &[b"LINDEX", b"l1", b"0"],
22602            &[b"LINDEX", b"l1", b"-1"],
22603            &[b"LINDEX", b"l1", b"99"],
22604            &[b"LSET", b"l1", b"0", b"y"],
22605            &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
22606            &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
22607            &[b"LPOS", b"l1", b"b"],
22608            &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
22609            &[b"LPOS", b"l1", b"nothere"],
22610            &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
22611            &[b"LREM", b"l1", b"1", b"aa"],
22612            &[b"LTRIM", b"l1", b"0", b"3"],
22613            &[b"LRANGE", b"l1", b"0", b"-1"],
22614            &[b"LPOP", b"l1"],
22615            &[b"RPOP", b"l1"],
22616            &[b"LPOP", b"l1", b"2"],
22617            &[b"LPOP", b"gone"],
22618            &[b"LPOP", b"gone", b"2"],
22619            &[b"EXISTS", b"l1"],
22620            // The ones that name two keys, and the one that takes a block of
22621            // elements rather than the one on the end.
22622            &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
22623            &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
22624            &[b"RPOPLPUSH", b"src", b"dst"],
22625            &[b"LRANGE", b"dst", b"0", b"-1"],
22626            &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
22627            &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
22628            &[
22629                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
22630            ],
22631            &[
22632                b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
22633            ],
22634            &[b"LRANGE", b"dst", b"0", b"-1"],
22635            &[
22636                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
22637            ],
22638            &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
22639            &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
22640            &[b"LMPOP", b"1", b"gone", b"LEFT"],
22641            // The blocking ones, first with something there to answer them and
22642            // then with nothing, which parks the client and writes nothing.
22643            &[b"RPUSH", b"q", b"a", b"b", b"c"],
22644            &[b"BLPOP", b"gone", b"q", b"0"],
22645            &[b"BRPOP", b"q", b"0"],
22646            &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
22647            &[b"RPUSH", b"q", b"x", b"y", b"z"],
22648            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22649            &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
22650            &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22651            &[b"BLPOP", b"q", b"0"],
22652            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22653            // The errors, which have to be the same errors.
22654            &[b"SET", b"plain", b"v"],
22655            &[b"LPUSH", b"plain", b"a"],
22656            &[b"LLEN", b"plain"],
22657            &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
22658            &[b"LRANGE", b"dst", b"0", b"-1"],
22659            &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
22660            &[b"LSET", b"gone", b"0", b"v"],
22661            &[b"LSET", b"dst", b"99", b"v"],
22662            &[b"LPOP", b"dst", b"-1"],
22663            &[b"LMPOP", b"0", b"dst", b"LEFT"],
22664            &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
22665        ];
22666
22667        let mut one = Fixture::new();
22668        let mut many = Fixture::striped(8);
22669        for parts in script {
22670            let a = one.run(parts);
22671            let b = many.run(parts);
22672            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22673        }
22674    }
22675
22676    /// An `LMOVE` and an `LMOVEM` whose two keys are on two stripes.
22677    #[test]
22678    fn a_list_move_across_stripes_takes_the_elements_with_it() {
22679        let mut f = Fixture::striped(8);
22680        let other = apart(&mut f, "src");
22681        let third = apart(&mut f, &other);
22682        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22683
22684        f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
22685        assert_eq!(
22686            f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
22687            "$1\r\na\r\n"
22688        );
22689        assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
22690        assert_eq!(
22691            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22692            "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
22693            "one went on each end of the destination"
22694        );
22695        assert_eq!(
22696            f.run(&[b"LRANGE", src, b"0", b"-1"]),
22697            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22698        );
22699
22700        // A block of them, which under BULK arrives in the order it left.
22701        assert_eq!(
22702            f.run(&[
22703                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
22704            ]),
22705            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22706        );
22707        assert_eq!(
22708            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22709            "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
22710        );
22711        assert_eq!(
22712            f.run(&[b"EXISTS", src]),
22713            ":0\r\n",
22714            "and the source is gone with its last element"
22715        );
22716
22717        // An `EXACTLY` the source cannot fill moves nothing, and a source that
22718        // is not there at all is the two kinds of nothing the two commands have.
22719        f.run(&[b"RPUSH", src, b"e", b"f"]);
22720        assert_eq!(
22721            f.run(&[
22722                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
22723            ]),
22724            "*-1\r\n"
22725        );
22726        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
22727        assert_eq!(
22728            f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
22729            "$-1\r\n"
22730        );
22731        assert_eq!(
22732            f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
22733            "*-1\r\n"
22734        );
22735
22736        // A destination of the wrong type is refused before anything is taken,
22737        // which is the order that matters most here, since an element already
22738        // out of the source would have nowhere to go back to.
22739        f.run(&[b"SET", plain, b"v"]);
22740        assert_eq!(
22741            f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
22742            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22743        );
22744        assert_eq!(
22745            f.run(&[b"LLEN", src]),
22746            ":2\r\n",
22747            "and left the source alone"
22748        );
22749        assert_eq!(
22750            f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
22751            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22752        );
22753        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
22754    }
22755
22756    /// A parked client served by a push that landed on another stripe.
22757    ///
22758    /// A waiter remembers the database and not the stripe, which is the point:
22759    /// serving it runs the same attempt the command ran, and the attempt finds
22760    /// the stripe each of its keys is on for itself.
22761    #[test]
22762    fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
22763        let mut f = Fixture::striped(8);
22764        let other = apart(&mut f, "q");
22765        let (q, far) = (b"q".as_slice(), other.as_bytes());
22766
22767        assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
22768        assert_eq!(f.server.parked(), 1);
22769        f.run(&[b"RPUSH", far, b"v"]);
22770        let mut out = Out::new(Proto::Resp2);
22771        assert!(f.server.serve_waiter(7, 0, &mut out));
22772        let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
22773        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22774        assert_eq!(
22775            f.run(&[b"EXISTS", far]),
22776            ":0\r\n",
22777            "and it took the element with it"
22778        );
22779
22780        // And a move across two stripes is served the same way, by the push
22781        // that fills its source.
22782        f.server.forget_waiters(7);
22783        assert_eq!(
22784            f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
22785            Flow::Block
22786        );
22787        f.run(&[b"RPUSH", q, b"w"]);
22788        let mut out = Out::new(Proto::Resp2);
22789        assert!(f.server.serve_waiter(7, 0, &mut out));
22790        assert_eq!(
22791            core::str::from_utf8(out.as_slice()).expect("ascii"),
22792            "$1\r\nw\r\n"
22793        );
22794        assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
22795    }
22796
22797    /// Every stream command, on one stripe and on eight.
22798    ///
22799    /// Every ID is written out rather than left to the clock, so the two servers
22800    /// are being compared on what they store and not on how long the test took
22801    /// to get from one of them to the other.
22802    #[test]
22803    fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
22804        let script: &[&[&[u8]]] = &[
22805            &[b"XADD", b"s", b"1-1", b"a", b"1"],
22806            &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
22807            &[b"XADD", b"s", b"3-1", b"d", b"4"],
22808            &[b"XADD", b"s", b"1-1", b"e", b"5"],
22809            &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
22810            &[b"XLEN", b"s"],
22811            &[b"XLEN", b"gone"],
22812            &[b"XRANGE", b"s", b"-", b"+"],
22813            &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
22814            &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
22815            &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
22816            &[b"XREVRANGE", b"s", b"+", b"-"],
22817            &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
22818            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
22819            &[b"XREAD", b"STREAMS", b"s", b"$"],
22820            // The groups, which is where most of the state is.
22821            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22822            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22823            &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
22824            &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
22825            &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
22826            &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
22827            &[
22828                b"XREADGROUP",
22829                b"GROUP",
22830                b"g",
22831                b"c1",
22832                b"COUNT",
22833                b"1",
22834                b"STREAMS",
22835                b"s",
22836                b"0",
22837            ],
22838            &[
22839                b"XREADGROUP",
22840                b"GROUP",
22841                b"nope",
22842                b"c1",
22843                b"STREAMS",
22844                b"s",
22845                b">",
22846            ],
22847            &[b"XPENDING", b"s", b"g"],
22848            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
22849            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
22850            &[b"XPENDING", b"s", b"nope"],
22851            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
22852            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
22853            &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
22854            &[b"XACK", b"s", b"g", b"1-1"],
22855            &[b"XACK", b"s", b"g", b"1-1"],
22856            &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
22857            &[b"XPENDING", b"s", b"g"],
22858            &[b"XINFO", b"STREAM", b"s"],
22859            &[b"XINFO", b"GROUPS", b"s"],
22860            &[b"XINFO", b"CONSUMERS", b"s", b"g"],
22861            &[b"XINFO", b"STREAM", b"gone"],
22862            // Deleting, trimming and moving the ID on.
22863            &[b"XDEL", b"s", b"3-1"],
22864            &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
22865            &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
22866            &[b"XADD", b"s", b"9-1", b"z", b"9"],
22867            &[b"XTRIM", b"s", b"MAXLEN", b"1"],
22868            &[b"XTRIM", b"s", b"MINID", b"9"],
22869            &[b"XSETID", b"s", b"99-1"],
22870            &[b"XSETID", b"s", b"1-1"],
22871            &[b"XLEN", b"s"],
22872            &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
22873            &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
22874            &[b"XGROUP", b"DESTROY", b"s", b"g"],
22875            &[b"XGROUP", b"DESTROY", b"s", b"g"],
22876            // And the errors.
22877            &[b"SET", b"plain", b"v"],
22878            &[b"XADD", b"plain", b"1-1", b"a", b"1"],
22879            &[b"XLEN", b"plain"],
22880            &[b"XREAD", b"STREAMS", b"plain", b"0"],
22881            &[b"XRANGE", b"s", b"bogus", b"+"],
22882            &[b"XADD", b"s", b"1-1", b"a"],
22883            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
22884            &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
22885        ];
22886
22887        let mut one = Fixture::new();
22888        let mut many = Fixture::striped(8);
22889        for parts in script {
22890            let a = one.run(parts);
22891            let b = many.run(parts);
22892            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22893        }
22894    }
22895
22896    /// An `XREAD` and an `XREADGROUP` naming two keys on two stripes.
22897    ///
22898    /// Nothing is shared between the two streams, so the only thing this can go
22899    /// wrong at is looking both of them up, which is exactly what a read that
22900    /// held one database and walked it would get wrong.
22901    #[test]
22902    fn a_stream_read_across_stripes_reads_every_key() {
22903        let mut f = Fixture::striped(8);
22904        let other = apart(&mut f, "s1");
22905        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22906
22907        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22908        f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
22909        let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
22910        assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
22911        assert!(got.contains("1-1"), "the first one is in there: {got}");
22912        assert!(got.contains("2-1"), "and so is the second: {got}");
22913
22914        // A group read looks its group up on every key before it reads any of
22915        // them, so a group that is missing on the far key stops the near one.
22916        f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
22917        let got = f.run(&[
22918            b"XREADGROUP",
22919            b"GROUP",
22920            b"g",
22921            b"c",
22922            b"STREAMS",
22923            s1,
22924            s2,
22925            b">",
22926            b">",
22927        ]);
22928        assert!(got.starts_with("-NOGROUP"), "{got}");
22929        assert_eq!(
22930            f.run(&[b"XPENDING", s1, b"g"]),
22931            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
22932            "and read nothing from the key that did have the group"
22933        );
22934
22935        f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
22936        let got = f.run(&[
22937            b"XREADGROUP",
22938            b"GROUP",
22939            b"g",
22940            b"c",
22941            b"STREAMS",
22942            s1,
22943            s2,
22944            b">",
22945            b">",
22946        ]);
22947        assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
22948    }
22949
22950    /// A client parked on an `XREAD` woken by an entry on another stripe.
22951    #[test]
22952    fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
22953        let mut f = Fixture::striped(8);
22954        let other = apart(&mut f, "s1");
22955        let (s1, far) = (b"s1".as_slice(), other.as_bytes());
22956        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22957        f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
22958
22959        assert_eq!(
22960            f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
22961                .0,
22962            Flow::Block
22963        );
22964        f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
22965        let mut out = Out::new(Proto::Resp2);
22966        assert!(f.server.serve_waiter(7, 0, &mut out));
22967        let want = format!(
22968            "*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",
22969            other.len()
22970        );
22971        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22972    }
22973
22974    /// Every JSON command, on one stripe and on eight.
22975    #[test]
22976    fn the_json_group_answers_the_same_however_many_stripes_there_are() {
22977        let script: &[&[&[u8]]] = &[
22978            &[
22979                b"JSON.SET",
22980                b"d",
22981                b"$",
22982                br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
22983            ],
22984            &[b"JSON.SET", b"d", b"$.a", b"2"],
22985            &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
22986            &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
22987            &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
22988            &[b"JSON.GET", b"d"],
22989            &[b"JSON.GET", b"d", b"$.b"],
22990            &[b"JSON.GET", b"gone", b"$"],
22991            &[b"JSON.TYPE", b"d", b"$.b"],
22992            &[b"JSON.TYPE", b"d", b"$.s"],
22993            &[b"JSON.TOGGLE", b"d", b"$.t"],
22994            &[b"JSON.ARRLEN", b"d", b"$.b"],
22995            &[b"JSON.OBJLEN", b"d", b"$"],
22996            &[b"JSON.OBJKEYS", b"d", b"$"],
22997            &[b"JSON.STRLEN", b"d", b"$.s"],
22998            &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
22999            &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
23000            &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
23001            &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
23002            &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
23003            &[b"JSON.ARRPOP", b"d", b"$.b"],
23004            &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
23005            &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
23006            &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
23007            &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
23008            &[b"JSON.RESP", b"d", b"$.b"],
23009            &[b"JSON.DEBUG", b"MEMORY", b"d"],
23010            &[b"JSON.CLEAR", b"d", b"$.b"],
23011            &[b"JSON.DEL", b"d", b"$.m"],
23012            &[b"JSON.FORGET", b"d", b"$.nothere"],
23013            // The two that name more than one key.
23014            &[
23015                b"JSON.MSET",
23016                b"m1",
23017                b"$",
23018                b"1",
23019                b"m2",
23020                b"$",
23021                b"2",
23022                b"m3",
23023                b"$",
23024                b"3",
23025            ],
23026            &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
23027            &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
23028            &[b"JSON.GET", b"m1", b"$"],
23029            &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
23030            &[b"JSON.GET", b"m2", b"$"],
23031            // And the errors.
23032            &[b"SET", b"plain", b"v"],
23033            &[b"JSON.GET", b"plain", b"$"],
23034            &[b"JSON.SET", b"plain", b"$", b"1"],
23035            &[b"JSON.MGET", b"m1", b"plain", b"$"],
23036            &[b"JSON.SET", b"d", b"$.b", b"["],
23037            &[b"JSON.DEL", b"plain"],
23038        ];
23039
23040        let mut one = Fixture::new();
23041        let mut many = Fixture::striped(8);
23042        for parts in script {
23043            let a = one.run(parts);
23044            let b = many.run(parts);
23045            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23046        }
23047    }
23048
23049    /// A `JSON.MSET` and a `JSON.MGET` whose keys are on several stripes.
23050    ///
23051    /// `JSON.MSET` works every triple out against the keyspace as it was before
23052    /// the command and writes nothing until all of them are known to work, so
23053    /// the thing to check is that a triple that cannot be written stops the
23054    /// ones on other stripes as well as the ones on its own.
23055    #[test]
23056    fn a_json_multi_write_across_stripes_reaches_every_key() {
23057        let mut f = Fixture::striped(8);
23058        let second = apart(&mut f, "m1");
23059        let third = apart(&mut f, &second);
23060        let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
23061
23062        assert_eq!(
23063            f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
23064            "+OK\r\n"
23065        );
23066        assert_eq!(
23067            f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
23068            "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
23069        );
23070
23071        // A value that is not JSON is refused before anything is written, and
23072        // the key on the far stripe keeps what it had.
23073        assert_eq!(
23074            f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
23075            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
23076        );
23077        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
23078
23079        // A path that names nowhere is not an error. That triple is skipped,
23080        // the ones on the other stripes are still written, and the reply is a
23081        // nil rather than OK.
23082        assert_eq!(
23083            f.run(&[
23084                b"JSON.MSET",
23085                m1,
23086                b"$",
23087                b"9",
23088                m2,
23089                b"$.deep",
23090                b"9",
23091                m3,
23092                b"$",
23093                b"7"
23094            ]),
23095            "$-1\r\n"
23096        );
23097        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
23098        assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
23099        assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
23100    }
23101
23102    /// Every geospatial command, on one stripe and on eight.
23103    #[test]
23104    fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
23105        let script: &[&[&[u8]]] = &[
23106            &[
23107                b"GEOADD",
23108                b"g",
23109                b"13.361389",
23110                b"38.115556",
23111                b"palermo",
23112                b"15.087269",
23113                b"37.502669",
23114                b"catania",
23115            ],
23116            &[
23117                b"GEOADD",
23118                b"g",
23119                b"NX",
23120                b"13.361389",
23121                b"38.115556",
23122                b"palermo",
23123            ],
23124            &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
23125            &[b"GEOPOS", b"g", b"palermo", b"nothere"],
23126            &[b"GEOHASH", b"g", b"palermo", b"catania"],
23127            &[b"GEODIST", b"g", b"palermo", b"catania"],
23128            &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
23129            &[b"GEODIST", b"g", b"palermo", b"nothere"],
23130            &[
23131                b"GEOSEARCH",
23132                b"g",
23133                b"FROMLONLAT",
23134                b"15",
23135                b"37",
23136                b"BYRADIUS",
23137                b"200",
23138                b"KM",
23139                b"ASC",
23140                b"WITHCOORD",
23141                b"WITHDIST",
23142                b"WITHHASH",
23143            ],
23144            &[
23145                b"GEOSEARCH",
23146                b"g",
23147                b"FROMMEMBER",
23148                b"palermo",
23149                b"BYBOX",
23150                b"400",
23151                b"400",
23152                b"KM",
23153                b"DESC",
23154            ],
23155            &[
23156                b"GEORADIUS",
23157                b"g",
23158                b"15",
23159                b"37",
23160                b"200",
23161                b"KM",
23162                b"COUNT",
23163                b"1",
23164            ],
23165            &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
23166            &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
23167            &[
23168                b"GEOSEARCHSTORE",
23169                b"dst",
23170                b"g",
23171                b"FROMLONLAT",
23172                b"15",
23173                b"37",
23174                b"BYRADIUS",
23175                b"200",
23176                b"KM",
23177            ],
23178            &[b"ZRANGE", b"dst", b"0", b"-1"],
23179            &[
23180                b"GEOSEARCHSTORE",
23181                b"dst",
23182                b"g",
23183                b"FROMLONLAT",
23184                b"15",
23185                b"37",
23186                b"BYRADIUS",
23187                b"1",
23188                b"M",
23189                b"STOREDIST",
23190            ],
23191            &[b"EXISTS", b"dst"],
23192            &[
23193                b"GEORADIUS",
23194                b"g",
23195                b"15",
23196                b"37",
23197                b"200",
23198                b"KM",
23199                b"STORE",
23200                b"dst",
23201            ],
23202            &[b"ZCARD", b"dst"],
23203            // And the errors.
23204            &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
23205            &[b"SET", b"plain", b"v"],
23206            &[b"GEOPOS", b"plain", b"a"],
23207            &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
23208            &[
23209                b"GEOSEARCHSTORE",
23210                b"dst",
23211                b"g",
23212                b"FROMLONLAT",
23213                b"15",
23214                b"37",
23215                b"BYRADIUS",
23216                b"200",
23217                b"KM",
23218                b"WITHCOORD",
23219            ],
23220        ];
23221
23222        let mut one = Fixture::new();
23223        let mut many = Fixture::striped(8);
23224        for parts in script {
23225            let a = one.run(parts);
23226            let b = many.run(parts);
23227            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23228        }
23229    }
23230
23231    /// A `GEOSEARCHSTORE` whose two keys are on two stripes.
23232    #[test]
23233    fn a_geo_search_store_across_stripes_writes_what_it_found() {
23234        let mut f = Fixture::striped(8);
23235        let other = apart(&mut f, "g");
23236        let third = apart(&mut f, &other);
23237        let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
23238
23239        f.run(&[
23240            b"GEOADD",
23241            g,
23242            b"13.361389",
23243            b"38.115556",
23244            b"palermo",
23245            b"15.087269",
23246            b"37.502669",
23247            b"catania",
23248        ]);
23249        assert_eq!(
23250            f.run(&[
23251                b"GEOSEARCHSTORE",
23252                dst,
23253                g,
23254                b"FROMLONLAT",
23255                b"15",
23256                b"37",
23257                b"BYRADIUS",
23258                b"200",
23259                b"KM",
23260                b"ASC",
23261            ]),
23262            ":2\r\n"
23263        );
23264        assert_eq!(
23265            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23266            "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
23267            "the geohash is the score, so the order is not the search order"
23268        );
23269        assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
23270
23271        // `STOREDIST` stores the distance in the unit the search was asked in,
23272        // which is the destination stripe's sorted set and not the source's.
23273        assert_eq!(
23274            f.run(&[
23275                b"GEOSEARCHSTORE",
23276                dst,
23277                g,
23278                b"FROMMEMBER",
23279                b"palermo",
23280                b"BYRADIUS",
23281                b"200",
23282                b"KM",
23283                b"STOREDIST",
23284            ]),
23285            ":2\r\n"
23286        );
23287        assert_eq!(
23288            f.run(&[b"ZSCORE", dst, b"palermo"]),
23289            "$1\r\n0\r\n",
23290            "the centre is nought away from itself"
23291        );
23292
23293        // A search that found nothing deletes the destination on its own
23294        // stripe, and a source of the wrong type is refused with the
23295        // destination left alone.
23296        assert_eq!(
23297            f.run(&[
23298                b"GEOSEARCHSTORE",
23299                dst,
23300                g,
23301                b"FROMLONLAT",
23302                b"0",
23303                b"0",
23304                b"BYRADIUS",
23305                b"1",
23306                b"M",
23307            ]),
23308            ":0\r\n"
23309        );
23310        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23311        f.run(&[
23312            b"GEOSEARCHSTORE",
23313            dst,
23314            g,
23315            b"FROMLONLAT",
23316            b"15",
23317            b"37",
23318            b"BYRADIUS",
23319            b"200",
23320            b"KM",
23321        ]);
23322        f.run(&[b"SET", plain, b"v"]);
23323        assert_eq!(
23324            f.run(&[
23325                b"GEOSEARCHSTORE",
23326                dst,
23327                plain,
23328                b"FROMLONLAT",
23329                b"15",
23330                b"37",
23331                b"BYRADIUS",
23332                b"200",
23333                b"KM",
23334            ]),
23335            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23336        );
23337        assert_eq!(
23338            f.run(&[b"ZCARD", dst]),
23339            ":2\r\n",
23340            "and left the destination"
23341        );
23342    }
23343
23344    /// Every time series command, on one stripe and on eight.
23345    ///
23346    /// Every timestamp is written out rather than left to the clock, so the two
23347    /// servers are compared on the samples they hold and not on how long the
23348    /// test took to get from one of them to the other.
23349    #[test]
23350    fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
23351        let script: &[&[&[u8]]] = &[
23352            &[
23353                b"TS.CREATE",
23354                b"ts:a",
23355                b"LABELS",
23356                b"sensor",
23357                b"a",
23358                b"room",
23359                b"1",
23360            ],
23361            &[b"TS.CREATE", b"ts:a"],
23362            &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
23363            &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
23364            &[
23365                b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
23366            ],
23367            &[
23368                b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
23369            ],
23370            &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
23371            &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
23372            &[b"TS.GET", b"ts:a"],
23373            &[b"TS.GET", b"gone"],
23374            &[b"TS.RANGE", b"ts:a", b"-", b"+"],
23375            &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
23376            &[
23377                b"TS.RANGE",
23378                b"ts:a",
23379                b"-",
23380                b"+",
23381                b"AGGREGATION",
23382                b"avg",
23383                b"2000",
23384            ],
23385            &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
23386            &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23387            &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23388            &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
23389            &[b"TS.READ", b"ts:a", b"0"],
23390            &[b"TS.READ", b"ts:a", b"+"],
23391            // The filters, which are the ones that have to walk every stripe.
23392            &[b"TS.QUERYINDEX", b"sensor=a"],
23393            &[b"TS.QUERYINDEX", b"room=1"],
23394            &[b"TS.QUERYINDEX", b"room=9"],
23395            &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
23396            &[
23397                b"TS.QUERYLABELS",
23398                b"VALUES",
23399                b"sensor",
23400                b"FILTER",
23401                b"room=1",
23402            ],
23403            &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
23404            &[
23405                b"TS.MGET",
23406                b"SELECTED_LABELS",
23407                b"sensor",
23408                b"FILTER",
23409                b"sensor=a",
23410            ],
23411            &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
23412            &[
23413                b"TS.MREVRANGE",
23414                b"-",
23415                b"+",
23416                b"WITHLABELS",
23417                b"FILTER",
23418                b"sensor=a",
23419            ],
23420            &[
23421                b"TS.MRANGE",
23422                b"-",
23423                b"+",
23424                b"FILTER",
23425                b"room=1",
23426                b"GROUPBY",
23427                b"room",
23428                b"REDUCE",
23429                b"max",
23430            ],
23431            &[b"TS.INFO", b"ts:a"],
23432            // And a rule, which is the one thing here that names two keys.
23433            &[
23434                b"TS.CREATERULE",
23435                b"ts:a",
23436                b"ts:down",
23437                b"AGGREGATION",
23438                b"avg",
23439                b"1000",
23440            ],
23441            &[b"TS.CREATE", b"ts:down"],
23442            &[
23443                b"TS.CREATERULE",
23444                b"ts:a",
23445                b"ts:down",
23446                b"AGGREGATION",
23447                b"avg",
23448                b"1000",
23449            ],
23450            &[b"TS.ADD", b"ts:a", b"5000", b"4"],
23451            &[b"TS.ADD", b"ts:a", b"6000", b"5"],
23452            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23453            &[b"TS.GET", b"ts:down", b"LATEST"],
23454            &[b"TS.INFO", b"ts:down"],
23455            &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
23456            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23457            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23458            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23459            &[b"TS.DEL", b"ts:a", b"0", b"1000"],
23460            // And the errors.
23461            &[b"SET", b"plain", b"v"],
23462            &[b"TS.ADD", b"plain", b"1", b"1"],
23463            &[b"TS.GET", b"plain"],
23464            &[b"TS.READ", b"plain", b"0"],
23465            &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
23466            &[b"TS.RANGE", b"gone", b"-", b"+"],
23467            &[b"TS.INFO", b"gone"],
23468        ];
23469
23470        let mut one = Fixture::new();
23471        let mut many = Fixture::striped(8);
23472        for parts in script {
23473            let a = one.run(parts);
23474            let b = many.run(parts);
23475            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23476        }
23477    }
23478
23479    /// A compaction rule whose two ends are on two stripes.
23480    ///
23481    /// This is the one thing in the family that walks from a key to another key,
23482    /// and it walks it in both directions: a sample on the source closes a
23483    /// bucket on the destination, a `LATEST` read on the destination folds the
23484    /// bucket the source is still filling, and a delete on the source rewrites
23485    /// what the destination already held. The same script is run against a
23486    /// server one stripe wide, where the two keys share a store, and against one
23487    /// eight stripes wide, where they do not.
23488    #[test]
23489    fn a_compaction_rule_across_stripes_reaches_both_ends() {
23490        let mut many = Fixture::striped(8);
23491        let other = apart(&mut many, "src");
23492        let (src, dst) = (b"src".as_slice(), other.as_bytes());
23493        let mut one = Fixture::new();
23494        let mut both = |parts: &[&[u8]]| {
23495            let a = one.run(parts);
23496            let b = many.run(parts);
23497            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23498            a
23499        };
23500
23501        both(&[b"TS.CREATE", src]);
23502        both(&[b"TS.CREATE", dst]);
23503        assert_eq!(
23504            both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
23505            "+OK\r\n"
23506        );
23507        both(&[b"TS.ADD", src, b"1000", b"1"]);
23508        both(&[b"TS.ADD", src, b"1500", b"3"]);
23509        // The bucket the source is filling is not written down yet, and asking
23510        // for it works it out off the source.
23511        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23512        let open = both(&[b"TS.GET", dst, b"LATEST"]);
23513        assert!(open.contains(":1000"), "the open bucket is folded: {open}");
23514
23515        // A sample past the bucket closes it, which is the write that has to
23516        // land on the other stripe.
23517        both(&[b"TS.ADD", src, b"2000", b"5"]);
23518        let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
23519        assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
23520        assert!(got.contains(":1000"), "{got}");
23521
23522        // And a delete on the source takes it away again.
23523        both(&[b"TS.DEL", src, b"1000", b"1999"]);
23524        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23525
23526        // Both ends still know about each other, and the link comes apart from
23527        // the source.
23528        assert!(
23529            both(&[b"TS.INFO", dst]).contains("src"),
23530            "the source is named"
23531        );
23532        assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
23533        assert_eq!(
23534            both(&[b"TS.DELETERULE", src, dst]),
23535            "-ERR TSDB: compaction rule does not exist\r\n"
23536        );
23537    }
23538
23539    /// A label filter takes the series it names wherever they landed.
23540    #[test]
23541    fn a_label_query_across_stripes_finds_every_series() {
23542        let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
23543        let mut many = Fixture::striped(8);
23544        let mut homes: Vec<usize> = names
23545            .iter()
23546            .map(|name| many.server.striped(0).stripe_of(name))
23547            .collect();
23548        homes.sort_unstable();
23549        homes.dedup();
23550        assert!(homes.len() > 1, "the six keys are not all on one stripe");
23551
23552        let mut one = Fixture::new();
23553        let mut both = |parts: &[&[u8]]| {
23554            let a = one.run(parts);
23555            let b = many.run(parts);
23556            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23557            a
23558        };
23559        for name in &names {
23560            both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
23561            both(&[b"TS.ADD", name, b"1000", b"1"]);
23562        }
23563
23564        let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
23565        assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
23566        assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23567        assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23568        assert_eq!(
23569            both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
23570            "*1\r\n$4\r\nroom\r\n"
23571        );
23572    }
23573
23574    /// Every hash command, and the field import beside it, on one stripe and on
23575    /// eight.
23576    ///
23577    /// `HRANDFIELD` with a count draws from the stripe's own generator and two
23578    /// stripes do not draw the same numbers, so the only draw here is off a hash
23579    /// holding one field, where every generator gives the same answer.
23580    #[test]
23581    fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
23582        let script: &[&[&[u8]]] = &[
23583            &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
23584            &[b"HMSET", b"h", b"c", b"3"],
23585            &[b"HSETNX", b"h", b"a", b"9"],
23586            &[b"HSETNX", b"h", b"d", b"4"],
23587            &[b"HGET", b"h", b"a"],
23588            &[b"HGET", b"h", b"nope"],
23589            &[b"HMGET", b"h", b"a", b"nope"],
23590            &[b"HLEN", b"h"],
23591            &[b"HEXISTS", b"h", b"a"],
23592            &[b"HSTRLEN", b"h", b"a"],
23593            &[b"HGETALL", b"h"],
23594            &[b"HKEYS", b"h"],
23595            &[b"HVALS", b"h"],
23596            &[b"HINCRBY", b"h", b"a", b"5"],
23597            &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
23598            &[b"HSCAN", b"h", b"0"],
23599            &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
23600            &[b"HSCAN", b"h", b"0", b"NOVALUES"],
23601            &[b"HDEL", b"h", b"d"],
23602            &[b"HSET", b"one", b"f", b"v"],
23603            &[b"HRANDFIELD", b"one"],
23604            &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
23605            // The field deadlines.
23606            &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
23607            &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
23608            &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
23609            &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23610            &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23611            &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
23612            &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
23613            &[b"HGET", b"h", b"b"],
23614            // The three that came later and word everything their own way.
23615            &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
23616            &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
23617            &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
23618            &[b"HGET", b"h", b"e"],
23619            // And the import, whose key is the third word.
23620            &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
23621            &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
23622            &[b"HGETALL", b"imp"],
23623            &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
23624            &[b"HIMPORT", b"DISCARD", b"fs"],
23625            // And the errors.
23626            &[b"SET", b"plain", b"v"],
23627            &[b"HSET", b"plain", b"a", b"1"],
23628            &[b"HGETALL", b"plain"],
23629            &[b"HGET", b"gone", b"a"],
23630            &[b"HINCRBY", b"h", b"a", b"nan"],
23631        ];
23632
23633        let mut one = Fixture::new();
23634        let mut many = Fixture::striped(8);
23635        // The field deadlines are absolute milliseconds worked out from the
23636        // clock, so both servers are put on the same one rather than left to
23637        // read the wall a moment apart.
23638        one.server.set_clock_ms(1_700_000_000_000);
23639        many.server.set_clock_ms(1_700_000_000_000);
23640        for parts in script {
23641            let a = one.run(parts);
23642            let b = many.run(parts);
23643            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23644        }
23645    }
23646
23647    /// Every array command, on one stripe and on eight.
23648    #[test]
23649    fn the_array_group_answers_the_same_however_many_stripes_there_are() {
23650        let script: &[&[&[u8]]] = &[
23651            &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
23652            &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
23653            &[b"ARGET", b"a", b"1"],
23654            &[b"ARGET", b"a", b"99"],
23655            &[b"ARMGET", b"a", b"0", b"5", b"99"],
23656            &[b"ARGETRANGE", b"a", b"0", b"7"],
23657            &[b"ARLEN", b"a"],
23658            &[b"ARCOUNT", b"a"],
23659            &[b"ARINSERT", b"a", b"m", b"n"],
23660            &[b"ARSCAN", b"a", b"0", b"20"],
23661            &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
23662            &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
23663            &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
23664            &[b"ARLASTITEMS", b"a", b"2"],
23665            &[b"ARLASTITEMS", b"a", b"2", b"REV"],
23666            &[b"ARNEXT", b"a"],
23667            &[b"ARSEEK", b"a", b"3"],
23668            &[b"AROP", b"a", b"0", b"20", b"USED"],
23669            &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
23670            &[b"ARINFO", b"a"],
23671            &[b"ARINFO", b"a", b"FULL"],
23672            &[b"ARDEL", b"a", b"0"],
23673            &[b"ARDELRANGE", b"a", b"1", b"2"],
23674            &[b"ARCOUNT", b"a"],
23675            &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
23676            &[b"ARGETRANGE", b"r", b"0", b"9"],
23677            // And the errors.
23678            &[b"SET", b"plain", b"v"],
23679            &[b"ARGET", b"plain", b"0"],
23680            &[b"ARSET", b"plain", b"0", b"v"],
23681            &[b"ARGET", b"gone", b"0"],
23682            &[b"ARSET", b"a", b"bad", b"v"],
23683        ];
23684
23685        let mut one = Fixture::new();
23686        let mut many = Fixture::striped(8);
23687        for parts in script {
23688            let a = one.run(parts);
23689            let b = many.run(parts);
23690            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23691        }
23692    }
23693
23694    /// Every graph and vector set command, on one stripe and on eight.
23695    ///
23696    /// `VRANDMEMBER` is not in here for the reason `HRANDFIELD` with a count is
23697    /// not: it draws from the stripe's generator, and the stripes do not share
23698    /// one.
23699    #[test]
23700    fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
23701        let script: &[&[&[u8]]] = &[
23702            &[b"G.NADD", b"g", b"n1", b"name", b"one"],
23703            &[b"G.NADD", b"g", b"n2", b"name", b"two"],
23704            &[b"G.NADD", b"g", b"n3"],
23705            &[b"G.NGET", b"g", b"n1"],
23706            &[b"G.NGET", b"g", b"gone"],
23707            &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
23708            &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
23709            &[b"G.OUT", b"g", b"n1", b"knows"],
23710            &[b"G.IN", b"g", b"n2", b"knows"],
23711            &[b"G.DEG", b"g", b"n1", b"knows"],
23712            &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
23713            &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
23714            &[b"G.PATH", b"g", b"n1", b"n3"],
23715            &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
23716            &[b"G.NDEL", b"g", b"n3"],
23717            &[b"G.NGET", b"g", b"n3"],
23718            // The vector set, which is one index under one key.
23719            &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
23720            &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
23721            &[b"VCARD", b"v"],
23722            &[b"VDIM", b"v"],
23723            &[b"VEMB", b"v", b"e1"],
23724            &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
23725            &[b"VSIM", b"v", b"ELE", b"e1"],
23726            &[b"VISMEMBER", b"v", b"e1"],
23727            &[b"VISMEMBER", b"v", b"gone"],
23728            &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
23729            &[b"VGETATTR", b"v", b"e1"],
23730            &[b"VRANGE", b"v", b"-", b"+"],
23731            &[b"VLINKS", b"v", b"e1"],
23732            &[b"VINFO", b"v"],
23733            &[b"VREM", b"v", b"e2"],
23734            &[b"VCARD", b"v"],
23735            // And the errors.
23736            &[b"SET", b"plain", b"v"],
23737            &[b"G.NGET", b"plain", b"n1"],
23738            &[b"VCARD", b"plain"],
23739            &[b"G.NADD", b"gone2", b"n"],
23740            &[b"VEMB", b"gone3", b"e"],
23741        ];
23742
23743        let mut one = Fixture::new();
23744        let mut many = Fixture::striped(8);
23745        for parts in script {
23746            let a = one.run(parts);
23747            let b = many.run(parts);
23748            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23749        }
23750    }
23751
23752    /// Every bloom filter, cuckoo filter, count min sketch, top k and t digest
23753    /// command, on one stripe and on eight.
23754    #[test]
23755    fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
23756        let script: &[&[&[u8]]] = &[
23757            // The bloom filter.
23758            &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
23759            &[b"BF.ADD", b"bf", b"a"],
23760            &[b"BF.ADD", b"bf", b"a"],
23761            &[b"BF.MADD", b"bf", b"b", b"c"],
23762            &[b"BF.EXISTS", b"bf", b"a"],
23763            &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
23764            &[b"BF.CARD", b"bf"],
23765            &[b"BF.INFO", b"bf"],
23766            &[b"BF.INFO", b"bf", b"CAPACITY"],
23767            &[b"BF.DEBUG", b"bf"],
23768            &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
23769            &[b"BF.EXISTS", b"made", b"x"],
23770            &[b"BF.SCANDUMP", b"bf", b"0"],
23771            // The cuckoo filter.
23772            &[b"CF.RESERVE", b"cf", b"100"],
23773            &[b"CF.ADD", b"cf", b"a"],
23774            &[b"CF.ADDNX", b"cf", b"a"],
23775            &[b"CF.COUNT", b"cf", b"a"],
23776            &[b"CF.EXISTS", b"cf", b"a"],
23777            &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
23778            &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
23779            &[b"CF.DEL", b"cf", b"a"],
23780            &[b"CF.COMPACT", b"cf"],
23781            &[b"CF.INFO", b"cf"],
23782            &[b"CF.DEBUG", b"cf"],
23783            &[b"CF.SCANDUMP", b"cf", b"0"],
23784            // The count min sketch.
23785            &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
23786            &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
23787            &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
23788            &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
23789            &[b"CMS.INFO", b"cms"],
23790            // The top k sketch.
23791            &[b"TOPK.RESERVE", b"tk", b"3"],
23792            &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
23793            &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
23794            &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
23795            &[b"TOPK.COUNT", b"tk", b"a", b"c"],
23796            &[b"TOPK.LIST", b"tk"],
23797            &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
23798            &[b"TOPK.INFO", b"tk"],
23799            // The t digest.
23800            &[b"TDIGEST.CREATE", b"td"],
23801            &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
23802            &[b"TDIGEST.MIN", b"td"],
23803            &[b"TDIGEST.MAX", b"td"],
23804            &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
23805            &[b"TDIGEST.CDF", b"td", b"3"],
23806            &[b"TDIGEST.RANK", b"td", b"3"],
23807            &[b"TDIGEST.REVRANK", b"td", b"3"],
23808            &[b"TDIGEST.BYRANK", b"td", b"0"],
23809            &[b"TDIGEST.BYREVRANK", b"td", b"0"],
23810            &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
23811            &[b"TDIGEST.INFO", b"td"],
23812            &[b"TDIGEST.RESET", b"td"],
23813            &[b"TDIGEST.MIN", b"td"],
23814            // And the errors.
23815            &[b"SET", b"plain", b"v"],
23816            &[b"BF.ADD", b"plain", b"a"],
23817            &[b"CF.ADD", b"plain", b"a"],
23818            &[b"CMS.QUERY", b"plain", b"a"],
23819            &[b"TOPK.ADD", b"plain", b"a"],
23820            &[b"TDIGEST.ADD", b"plain", b"1"],
23821            &[b"CMS.INFO", b"gone"],
23822            &[b"TOPK.INFO", b"gone"],
23823            &[b"TDIGEST.INFO", b"gone"],
23824        ];
23825
23826        let mut one = Fixture::new();
23827        let mut many = Fixture::striped(8);
23828        for parts in script {
23829            let a = one.run(parts);
23830            let b = many.run(parts);
23831            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23832        }
23833    }
23834
23835    /// The two sketch merges, with their sources on stripes of their own.
23836    ///
23837    /// These are the only two commands in the ten groups that name more than one
23838    /// key, and both read a run of sources and write a destination, so both go
23839    /// wrong in the same way if a merge holds one store and looks every source up
23840    /// in it.
23841    #[test]
23842    fn a_sketch_merge_across_stripes_reads_every_source() {
23843        let mut many = Fixture::striped(8);
23844        let other = apart(&mut many, "s1");
23845        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
23846        let mut one = Fixture::new();
23847        let mut both = |parts: &[&[u8]]| {
23848            let a = one.run(parts);
23849            let b = many.run(parts);
23850            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23851            a
23852        };
23853
23854        // The count min sketch. The destination has to be the sources' shape,
23855        // and it is named first, so all three keys are read before anything is
23856        // written.
23857        for key in [b"cd".as_slice(), s1, s2] {
23858            both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
23859        }
23860        both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
23861        both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
23862        assert_eq!(
23863            both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
23864            "+OK\r\n",
23865            "the merge took both sources"
23866        );
23867        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
23868        // And with weights, which are read against the sources in order.
23869        both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
23870        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23871        // A source that is not a sketch is answered before anything is written.
23872        both(&[b"SET", b"plain", b"v"]);
23873        assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
23874        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23875
23876        // The t digest, which builds its destination and then puts it in place.
23877        // The two source keys are used again here, so what they held goes first.
23878        both(&[b"FLUSHALL"]);
23879        both(&[b"TDIGEST.CREATE", b"td"]);
23880        both(&[b"TDIGEST.CREATE", s1]);
23881        both(&[b"TDIGEST.CREATE", s2]);
23882        both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
23883        both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
23884        assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
23885        assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
23886        assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
23887    }
23888
23889    /// Every shape of `SORT`, on one stripe and on eight.
23890    ///
23891    /// The key it sorts, the keys a `BY` names, the keys a `GET` names and the
23892    /// destination are four different names and nothing lines them up, so on
23893    /// eight stripes this script is reading and writing all over the database
23894    /// while on one it is doing what it always did.
23895    #[test]
23896    fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
23897        let script: &[&[&[u8]]] = &[
23898            &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
23899            &[b"SORT", b"l"],
23900            &[b"SORT", b"l", b"DESC"],
23901            &[b"SORT", b"l", b"ALPHA"],
23902            &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
23903            &[b"SORT_RO", b"l"],
23904            // A weight per element, so the order comes off keys the command
23905            // never named.
23906            &[
23907                b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
23908            ],
23909            &[b"SORT", b"l", b"BY", b"w_*"],
23910            &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
23911            &[b"DEL", b"w_2"],
23912            &[b"SORT", b"l", b"BY", b"w_*"],
23913            // And the answer off another set of keys again, with `#` mixed in
23914            // so the rows are not all lookups.
23915            &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
23916            &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
23917            // A pattern that reaches into a hash, which is another key again.
23918            &[b"HSET", b"h_1", b"f", b"9"],
23919            &[b"HSET", b"h_2", b"f", b"8"],
23920            &[b"HSET", b"h_3", b"f", b"7"],
23921            &[b"HSET", b"h_10", b"f", b"6"],
23922            &[b"SORT", b"l", b"BY", b"h_*->f"],
23923            &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
23924            // The destination, which is a fourth place to land.
23925            &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
23926            &[b"LRANGE", b"out", b"0", b"-1"],
23927            &[b"SORT", b"l", b"STORE", b"l"],
23928            &[b"LRANGE", b"l", b"0", b"-1"],
23929            // An empty result takes the destination away rather than leaving a
23930            // list of nothing behind.
23931            &[b"SORT", b"missing", b"STORE", b"out"],
23932            &[b"EXISTS", b"out"],
23933            // A set and a sorted set sort the same way a list does, and a set
23934            // written to a destination is sorted even when nothing asked.
23935            &[b"SADD", b"s", b"c", b"a", b"b"],
23936            &[b"SORT", b"s", b"ALPHA"],
23937            &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
23938            &[b"LRANGE", b"out", b"0", b"-1"],
23939            &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
23940            &[b"SORT", b"z", b"BY", b"nosort"],
23941            &[b"SORT", b"z", b"ALPHA", b"DESC"],
23942            // And the two ways it refuses: a key of the wrong type, and an
23943            // element that is not a number under a numeric sort.
23944            &[b"SET", b"str", b"v"],
23945            &[b"SORT", b"str"],
23946            &[b"RPUSH", b"words", b"one", b"two"],
23947            &[b"SORT", b"words"],
23948            &[b"SORT_RO", b"l", b"STORE", b"out"],
23949        ];
23950
23951        let mut one = Fixture::new();
23952        let mut many = Fixture::striped(8);
23953        for parts in script {
23954            let a = one.run(parts);
23955            let b = many.run(parts);
23956            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23957        }
23958    }
23959
23960    /// One `SORT` whose four kinds of key are on stripes of their own.
23961    ///
23962    /// The script above spreads keys around by writing enough of them, and this
23963    /// one checks the spread rather than trusting it: the list, the weight key
23964    /// for one of its elements and the destination are asserted to be in three
23965    /// places before the command runs.
23966    #[test]
23967    fn a_sort_across_stripes_reads_every_pattern_key() {
23968        let mut f = Fixture::striped(8);
23969        let out = apart(&mut f, "l");
23970        let (list, dest) = (b"l".as_slice(), out.as_bytes());
23971
23972        f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
23973        f.run(&[
23974            b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
23975        ]);
23976        f.run(&[
23977            b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
23978        ]);
23979
23980        // The weights are four keys and they are not all in one place, which is
23981        // the thing that would go unnoticed if the command held a stripe.
23982        let db = f.server.striped(0);
23983        let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
23984            .iter()
23985            .map(|k| db.stripe_of(k.as_slice()))
23986            .collect();
23987        assert!(
23988            weights.iter().any(|s| *s != weights[0]),
23989            "the four weight keys all landed on one stripe, so this proves nothing"
23990        );
23991
23992        assert_eq!(
23993            f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
23994            "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
23995            "the order came off the weights and the answer off the data keys"
23996        );
23997        assert_eq!(
23998            f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
23999            ":4\r\n"
24000        );
24001        assert_eq!(
24002            f.run(&[b"LRANGE", dest, b"0", b"-1"]),
24003            "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
24004            "the destination is on a stripe of its own and got the whole answer"
24005        );
24006    }
24007
24008    /// A `CONFIG SET` reaches every stripe, so where a key landed does not
24009    /// decide what shape it is stored in.
24010    ///
24011    /// This is the setting that would go wrong quietly. A stripe that kept the
24012    /// old ladder would hold the same hash in a different encoding from the
24013    /// stripe next to it, and the only thing that would ever say so is
24014    /// `OBJECT ENCODING`, which is why the check is on that.
24015    #[test]
24016    fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
24017        let mut f = Fixture::striped(8);
24018        let other = apart(&mut f, "h");
24019        let (first, second) = (b"h".as_slice(), other.as_bytes());
24020
24021        assert_eq!(
24022            f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
24023            "+OK\r\n"
24024        );
24025        assert_eq!(
24026            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
24027            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
24028            "the read comes off one stripe and has to answer for all of them"
24029        );
24030        for key in [first, second] {
24031            f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
24032            assert_eq!(
24033                f.run(&[b"OBJECT", b"ENCODING", key]),
24034                "$8\r\nlistpack\r\n",
24035                "two fields is still under the ladder"
24036            );
24037            f.run(&[b"HSET", key, b"c", b"3"]);
24038            assert_eq!(
24039                f.run(&[b"OBJECT", b"ENCODING", key]),
24040                "$9\r\nhashtable\r\n",
24041                "three fields is over it, on whichever stripe the key is on"
24042            );
24043        }
24044
24045        // And the policy, which every stripe has to agree about for the same
24046        // reason: an eviction draws from one stripe at a time.
24047        assert_eq!(
24048            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
24049            "+OK\r\n"
24050        );
24051        let db = f.server.striped(0);
24052        assert!(
24053            (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
24054            "a stripe kept the old policy"
24055        );
24056    }
24057
24058    /// What an index holds, as the two numbers `FT.INFO` reports about it.
24059    ///
24060    /// Read off the registry rather than parsed back out of an `FT.INFO` reply,
24061    /// because the reply is thirty odd fields and these two are the ones the
24062    /// keyspace hook moves.
24063    fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
24064        let search = f.server.search.lock();
24065        let index = search.named(name).expect("the index is there");
24066        (index.held.docs.len(), index.held.docs.last())
24067    }
24068
24069    /// A hash written under an index's prefix reaches it, and one written
24070    /// outside the prefix does not.
24071    #[test]
24072    fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
24073        let mut f = Fixture::new();
24074        f.run(&[
24075            b"FT.CREATE",
24076            b"ix",
24077            b"PREFIX",
24078            b"1",
24079            b"p:",
24080            b"SCHEMA",
24081            b"t",
24082            b"TEXT",
24083        ]);
24084        f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
24085        assert_eq!(held(&f, b"ix"), (1, 1));
24086        f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
24087        assert_eq!(held(&f, b"ix"), (1, 1));
24088
24089        // Every field of the key and not the one the command named, since a
24090        // document is read from nothing every time.
24091        f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
24092        f.run(&[b"HDEL", b"p:1", b"u"]);
24093        assert_eq!(held(&f, b"ix"), (1, 3));
24094        let search = f.server.search.lock();
24095        let index = search.named(b"ix").expect("there");
24096        assert_eq!(index.held.docs.id(b"p:1"), Some(3));
24097    }
24098
24099    /// A fresh index reads the keys that were already there, and walks past a
24100    /// key of the wrong type without counting a failure.
24101    #[test]
24102    fn a_fresh_index_reads_the_keys_that_were_already_there() {
24103        let mut f = Fixture::new();
24104        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24105        f.run(&[b"SET", b"p:str", b"not a hash"]);
24106        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24107        f.run(&[
24108            b"FT.CREATE",
24109            b"ix",
24110            b"PREFIX",
24111            b"1",
24112            b"p:",
24113            b"SCHEMA",
24114            b"t",
24115            b"TEXT",
24116        ]);
24117
24118        assert_eq!(held(&f, b"ix"), (1, 1));
24119        let search = f.server.search.lock();
24120        let index = search.named(b"ix").expect("there");
24121        assert_eq!(index.trouble.whole().failures(), 0);
24122    }
24123
24124    /// `SKIPINITIALSCAN` leaves what was there alone, and a later write to one
24125    /// of those keys still lands.
24126    #[test]
24127    fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
24128        let mut f = Fixture::new();
24129        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24130        f.run(&[
24131            b"FT.CREATE",
24132            b"ix",
24133            b"PREFIX",
24134            b"1",
24135            b"p:",
24136            b"SKIPINITIALSCAN",
24137            b"SCHEMA",
24138            b"t",
24139            b"TEXT",
24140        ]);
24141        assert_eq!(held(&f, b"ix"), (0, 0));
24142        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24143        assert_eq!(held(&f, b"ix"), (1, 1));
24144    }
24145
24146    /// A command that changed nothing leaves the document where it was, which
24147    /// is not the same as a command that was not a write.
24148    ///
24149    /// All five of these were measured against 8.10.1. Writing the same value
24150    /// again moves the number and a deadline set for later does not, which is
24151    /// the pair that makes the rule "the fields are not what they were" rather
24152    /// than "this was a write".
24153    #[test]
24154    fn only_a_real_change_gives_the_document_a_new_number() {
24155        let mut f = Fixture::new();
24156        f.run(&[
24157            b"FT.CREATE",
24158            b"ix",
24159            b"PREFIX",
24160            b"1",
24161            b"p:",
24162            b"SCHEMA",
24163            b"t",
24164            b"TEXT",
24165        ]);
24166        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24167        assert_eq!(held(&f, b"ix"), (1, 1));
24168
24169        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24170        assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
24171
24172        for quiet in [
24173            vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
24174            vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
24175            vec![b"HGET".as_slice(), b"p:1", b"t"],
24176            vec![b"HGETALL".as_slice(), b"p:1"],
24177            vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
24178            vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
24179            vec![
24180                b"HGETEX".as_slice(),
24181                b"p:1",
24182                b"EX",
24183                b"100",
24184                b"FIELDS",
24185                b"1",
24186                b"t",
24187            ],
24188            vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
24189        ] {
24190            f.run(&quiet);
24191            assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
24192        }
24193
24194        // And the ones that do change something.
24195        f.run(&[b"HSET", b"p:2", b"n", b"1"]);
24196        f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
24197        assert_eq!(held(&f, b"ix"), (2, 4));
24198        // A deadline that has already passed takes the field away, and taking
24199        // the last field away takes the key and the document with it. The
24200        // number still moves on the way past, because the field going and the
24201        // key going are two separate pieces of news and the first of them
24202        // writes the document one last time.
24203        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
24204        assert_eq!(held(&f, b"ix"), (1, 5));
24205    }
24206
24207    /// The two ways of emptying a hash, which do not leave the same thing
24208    /// behind. `HDEL` of the last field spends no number and is counted as a
24209    /// refusal, and a deadline that has already passed spends one on a document
24210    /// nobody sees and is counted as nothing. Measured against 8.10.1 and not
24211    /// something anyone would guess.
24212    #[test]
24213    fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
24214        /// The index's own failure count.
24215        fn refused(f: &Fixture, name: &[u8]) -> u64 {
24216            let search = f.server.search.lock();
24217            let index = search.named(name).expect("the index is there");
24218            index.trouble.whole().failures()
24219        }
24220
24221        let mut f = Fixture::new();
24222        f.run(&[
24223            b"FT.CREATE",
24224            b"ix",
24225            b"PREFIX",
24226            b"1",
24227            b"p:",
24228            b"SCHEMA",
24229            b"t",
24230            b"TEXT",
24231        ]);
24232        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24233        assert_eq!(held(&f, b"ix"), (1, 1));
24234        f.run(&[b"HDEL", b"p:1", b"t"]);
24235        assert_eq!(
24236            held(&f, b"ix"),
24237            (0, 1),
24238            "HDEL of the last field spends none"
24239        );
24240        assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
24241
24242        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24243        assert_eq!(held(&f, b"ix"), (1, 2));
24244        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
24245        assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
24246        assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
24247
24248        f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
24249        assert_eq!(held(&f, b"ix"), (1, 4));
24250        f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
24251        assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
24252
24253        // Two fields and one command is one rewrite and not two, whichever way
24254        // the fields go.
24255        f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
24256        assert_eq!(held(&f, b"ix"), (1, 6));
24257        f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
24258        assert_eq!(held(&f, b"ix"), (0, 7));
24259        assert_eq!(refused(&f, b"ix"), 1);
24260    }
24261
24262    /// `HSETEX` with a deadline that has already passed is two pieces of news
24263    /// from one command, so the number moves twice and the value never reaches
24264    /// the index.
24265    #[test]
24266    fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
24267        let mut f = Fixture::new();
24268        f.run(&[
24269            b"FT.CREATE",
24270            b"ix",
24271            b"PREFIX",
24272            b"1",
24273            b"p:",
24274            b"SCHEMA",
24275            b"t",
24276            b"TEXT",
24277            b"u",
24278            b"TEXT",
24279        ]);
24280        f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
24281        assert_eq!(held(&f, b"ix"), (1, 1));
24282        f.run(&[
24283            b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24284        ]);
24285        assert_eq!(
24286            held(&f, b"ix"),
24287            (1, 3),
24288            "the key lived and the field did not"
24289        );
24290
24291        // And the same when the key does not survive it.
24292        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24293        assert_eq!(held(&f, b"ix"), (2, 4));
24294        f.run(&[
24295            b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24296        ]);
24297        assert_eq!(held(&f, b"ix"), (1, 6));
24298    }
24299
24300    /// The number one key is indexed under, or `None` when it holds no
24301    /// document.
24302    fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
24303        let search = f.server.search.lock();
24304        let index = search.named(name).expect("the index is there");
24305        index.held.docs.id(key)
24306    }
24307
24308    /// An index over `p:` with one document under `p:1`, which is where four of
24309    /// the tests below start.
24310    fn indexed() -> Fixture {
24311        let mut f = Fixture::new();
24312        f.run(&[
24313            b"FT.CREATE",
24314            b"ix",
24315            b"PREFIX",
24316            b"1",
24317            b"p:",
24318            b"SCHEMA",
24319            b"t",
24320            b"TEXT",
24321        ]);
24322        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24323        f
24324    }
24325
24326    /// Every way a keyspace command takes a key away leaves no document behind,
24327    /// and none of them spends a number or is counted as a refusal.
24328    #[test]
24329    fn a_key_a_keyspace_command_takes_away_loses_its_document() {
24330        for take in [
24331            vec![b"DEL".as_slice(), b"p:1"],
24332            vec![b"UNLINK".as_slice(), b"p:1"],
24333            vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
24334            vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
24335        ] {
24336            let mut f = indexed();
24337            assert_eq!(held(&f, b"ix"), (1, 1));
24338            f.run(&take);
24339            assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
24340            let search = f.server.search.lock();
24341            let index = search.named(b"ix").expect("the index is there");
24342            assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
24343        }
24344
24345        // A deadline that has not passed yet is not one of them.
24346        let mut f = indexed();
24347        f.run(&[b"EXPIRE", b"p:1", b"1000"]);
24348        assert_eq!(held(&f, b"ix"), (1, 1));
24349        f.run(&[b"PERSIST", b"p:1"]);
24350        assert_eq!(held(&f, b"ix"), (1, 1));
24351    }
24352
24353    /// A rename inside the prefix keeps the number the document had, which is
24354    /// the one write on a followed key that does not spend one. Out of the
24355    /// prefix is an erase and into it is a fresh reading, both measured.
24356    #[test]
24357    fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
24358        let mut f = indexed();
24359        f.run(&[b"RENAME", b"p:1", b"p:2"]);
24360        assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
24361        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24362        assert_eq!(number(&f, b"ix", b"p:1"), None);
24363
24364        f.run(&[b"RENAME", b"p:2", b"q:1"]);
24365        assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
24366
24367        f.run(&[b"RENAME", b"q:1", b"p:3"]);
24368        assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
24369        assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
24370
24371        // `RENAMENX` goes the same way, and the one that answers zero changes
24372        // nothing.
24373        f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
24374        assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
24375        assert_eq!(held(&f, b"ix"), (2, 3));
24376        f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
24377        assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
24378    }
24379
24380    /// A rename over a key that already had a document leaves one document and
24381    /// not two. A real server leaves both, and D-64 is that difference.
24382    #[test]
24383    fn a_rename_over_a_document_leaves_one_of_them() {
24384        let mut f = indexed();
24385        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24386        assert_eq!(held(&f, b"ix"), (2, 2));
24387        f.run(&[b"RENAME", b"p:1", b"p:2"]);
24388        assert_eq!(held(&f, b"ix"), (1, 2));
24389        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24390    }
24391
24392    /// A key that arrives under the prefix by being copied or restored is read
24393    /// as a new document, and one that is written over by something that is not
24394    /// a hash is erased without a word.
24395    #[test]
24396    fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
24397        let mut f = indexed();
24398        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24399        f.run(&[b"COPY", b"q:1", b"p:2"]);
24400        assert_eq!(held(&f, b"ix"), (2, 2));
24401        assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
24402
24403        // Out of the prefix, where the source keeps the document it had.
24404        f.run(&[b"COPY", b"p:1", b"q:2"]);
24405        assert_eq!(held(&f, b"ix"), (2, 2));
24406
24407        // Over a key that has one, which is a new reading and not a rename.
24408        f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
24409        assert_eq!(held(&f, b"ix"), (2, 3));
24410        assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
24411
24412        // And a string landing on top of a document takes it away, spending no
24413        // number and counting no failure.
24414        f.run(&[b"SET", b"s:1", b"plain"]);
24415        f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
24416        assert_eq!(held(&f, b"ix"), (1, 3));
24417        let dump = f.run(&[b"DUMP", b"q:1"]);
24418        assert!(dump.starts_with('$'), "{dump}");
24419    }
24420
24421    /// The keyspace group reads a key back on database zero whatever database
24422    /// the command ran on, which is measured and is not what the hash commands
24423    /// do. A `COPY` into another database indexes nothing and takes away
24424    /// whatever the destination had, and a `RESTORE` anywhere else is invisible.
24425    #[test]
24426    fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
24427        let mut f = indexed();
24428        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24429        assert_eq!(held(&f, b"ix"), (2, 2));
24430        // Into database one, so the indexes look for `p:2` on database zero,
24431        // find the one that is still there and read it again.
24432        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24433        assert_eq!(held(&f, b"ix"), (2, 3));
24434        // And with nothing under that name on database zero, the copy leaves
24435        // the index one document lighter than it found it.
24436        f.run(&[b"DEL", b"p:2"]);
24437        assert_eq!(held(&f, b"ix"), (1, 3));
24438        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24439        assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
24440
24441        // A restore on another database is the same story.
24442        let dump = f.run(&[b"DUMP", b"p:1"]);
24443        assert!(dump.starts_with('$'), "{dump}");
24444        f.run(&[b"SELECT", b"1"]);
24445        f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
24446        f.run(&[b"RENAME", b"q:1", b"p:3"]);
24447        assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
24448    }
24449
24450    /// `MOVE` is not a change at all, because an index follows a key by name
24451    /// and a write on any database still reaches it.
24452    #[test]
24453    fn a_move_leaves_the_document_where_it_is() {
24454        let mut f = indexed();
24455        f.run(&[b"MOVE", b"p:1", b"1"]);
24456        assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
24457        assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
24458
24459        f.run(&[b"SELECT", b"1"]);
24460        f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
24461        assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
24462        f.run(&[b"DEL", b"p:1"]);
24463        assert_eq!(held(&f, b"ix"), (0, 2));
24464    }
24465
24466    /// A flush takes every index with it, whichever database it flushed.
24467    #[test]
24468    fn a_flush_drops_the_indexes() {
24469        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24470            let mut f = indexed();
24471            f.run(&[flush]);
24472            assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
24473            assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
24474        }
24475
24476        // Even on a database no index ever read, which is what a real server
24477        // does and is not what anyone would guess.
24478        let mut f = indexed();
24479        f.run(&[b"SELECT", b"9"]);
24480        f.run(&[b"FLUSHDB"]);
24481        assert!(f.server.search.lock().is_empty());
24482    }
24483
24484    /// An index whose schema has one tag field of each kind, plus a number so
24485    /// there is something for `FT.TAGVALS` to refuse.
24486    fn tagged() -> Fixture {
24487        let mut f = Fixture::new();
24488        f.run(&[
24489            b"FT.CREATE",
24490            b"tv",
24491            b"PREFIX",
24492            b"1",
24493            b"tv:",
24494            b"SCHEMA",
24495            b"g",
24496            b"AS",
24497            b"gg",
24498            b"TAG",
24499            b"h",
24500            b"TAG",
24501            b"SEPARATOR",
24502            b"|",
24503            b"CASESENSITIVE",
24504            b"n",
24505            b"NUMERIC",
24506        ]);
24507        f.run(&[
24508            b"HSET",
24509            b"tv:1",
24510            b"g",
24511            b"Red, BLUE ",
24512            b"h",
24513            b"Aa|bB",
24514            b"n",
24515            b"1",
24516        ]);
24517        f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
24518        f
24519    }
24520
24521    /// The values come back as they are stored, so an ordinary tag field
24522    /// answers them folded and trimmed and a `CASESENSITIVE` one answers what
24523    /// it was given. Byte order either way, which puts the capital first.
24524    #[test]
24525    fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
24526        let mut f = tagged();
24527        assert_eq!(
24528            f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
24529            "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
24530        );
24531        assert_eq!(
24532            f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
24533            "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
24534        );
24535    }
24536
24537    /// The name asked about is the attribute, so the identifier of a field
24538    /// declared `AS` is not a name this knows.
24539    #[test]
24540    fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
24541        let mut f = tagged();
24542        for (name, want) in [
24543            (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
24544            (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
24545            (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
24546        ] {
24547            assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
24548        }
24549        assert_eq!(
24550            f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
24551            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24552        );
24553    }
24554
24555    /// Looking up the index counts as a use of it on the roads that refuse the
24556    /// field as well as on the one that answers, which is measured.
24557    #[test]
24558    fn asking_for_tag_values_counts_a_use_of_the_index() {
24559        let mut f = tagged();
24560        let uses = |f: &mut Fixture| {
24561            let reply = f.run(&[b"FT.INFO", b"tv"]);
24562            let at = reply.find("number_of_uses").expect("the field is reported");
24563            let value = reply[at..].split("\r\n").nth(1).unwrap();
24564            value.trim_start_matches(':').parse::<i64>().unwrap()
24565        };
24566        let before = uses(&mut f);
24567        f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
24568        f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
24569        // Three more than before: two tag lookups and the second `FT.INFO`.
24570        assert_eq!(uses(&mut f), before + 3);
24571    }
24572
24573    /// A tag field nothing was ever written to has no list at all, which
24574    /// answers the same empty set a list that has been emptied does.
24575    #[test]
24576    fn a_tag_field_with_nothing_in_it_answers_empty() {
24577        let mut f = Fixture::new();
24578        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
24579        assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
24580    }
24581
24582    /// A dictionary is module state and not a key, so nothing in the keyspace
24583    /// can see one.
24584    #[test]
24585    fn a_dictionary_is_not_a_key() {
24586        let mut f = Fixture::new();
24587        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
24588        assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
24589        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
24590        assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
24591    }
24592
24593    /// The count is how many terms were new, an empty term is not a term, and
24594    /// the dump is sorted by bytes rather than folded.
24595    #[test]
24596    fn a_dictionary_counts_the_terms_it_had_not_seen() {
24597        let mut f = Fixture::new();
24598        assert_eq!(
24599            f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
24600            ":3\r\n"
24601        );
24602        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
24603        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
24604        assert_eq!(
24605            f.run(&[b"FT.DICTDUMP", b"d"]),
24606            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
24607        );
24608        assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
24609    }
24610
24611    /// A name nobody ever added to is not an error on either of the two
24612    /// commands that will take one, which is the only place in the group where
24613    /// a missing name is forgiven.
24614    #[test]
24615    fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
24616        let mut f = Fixture::new();
24617        assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
24618        assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
24619    }
24620
24621    /// The dictionaries go when the keyspace does, the same way the indexes do.
24622    #[test]
24623    fn a_flush_drops_the_dictionaries() {
24624        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24625            let mut f = Fixture::new();
24626            f.run(&[b"FT.DICTADD", b"d", b"a"]);
24627            f.run(&[flush]);
24628            assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
24629        }
24630    }
24631
24632    // -------------------------------------------------------------- profile
24633
24634    /// A fixture holding one index over three documents, two of which hold the
24635    /// first word and two the second.
24636    fn profiling() -> Fixture {
24637        let mut f = Fixture::new();
24638        f.run(&[
24639            b"FT.CREATE",
24640            b"ix",
24641            b"PREFIX",
24642            b"1",
24643            b"p:",
24644            b"SCHEMA",
24645            b"t",
24646            b"TEXT",
24647            b"n",
24648            b"NUMERIC",
24649        ]);
24650        f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
24651        f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
24652        f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
24653        f
24654    }
24655
24656    /// The reply with every time taken out of it, since no two runs agree on
24657    /// those and everything else about a profile is exact.
24658    fn timeless(reply: &str) -> String {
24659        const KEYS: &[&str] = &[
24660            "+Total profile time",
24661            "+Parsing time",
24662            "+Workers queue time",
24663            "+Pipeline creation time",
24664            "+Time",
24665        ];
24666        let mut out = String::new();
24667        let mut parts = reply.split("\r\n").peekable();
24668        while let Some(part) = parts.next() {
24669            out.push_str(part);
24670            out.push_str("\r\n");
24671            if !KEYS.contains(&part) {
24672                continue;
24673            }
24674            // A double is one line on RESP3 and a bulk header and its digits on
24675            // RESP2, and both of them stand for the same one value.
24676            match parts.next() {
24677                Some(head) if head.starts_with('$') => {
24678                    parts.next();
24679                }
24680                _ => {}
24681            }
24682            out.push_str("<t>\r\n");
24683        }
24684        // The split leaves an empty piece past the last line ending.
24685        out.truncate(out.len() - 2);
24686        out
24687    }
24688
24689    /// The whole envelope on both protocols, which is a two element array on
24690    /// one and a two key map on the other.
24691    #[test]
24692    fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
24693        let mut f = profiling();
24694        assert_eq!(
24695            timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
24696            "*2\r\n\
24697             *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\
24698             $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\
24699             *4\r\n+Shards\r\n*1\r\n*14\r\n\
24700             +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
24701             +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
24702             +Warning\r\n*1\r\n+None\r\n\
24703             +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24704             +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24705             +Estimated number of matches\r\n:2\r\n\
24706             +Result processors profile\r\n*4\r\n\
24707             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24708             *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24709             *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24710             *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24711             +Coordinator\r\n*0\r\n"
24712        );
24713        let mut g = profiling();
24714        g.run(&[b"HELLO", b"3"]);
24715        let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
24716        assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
24717        assert!(
24718            three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
24719            "{three}"
24720        );
24721        assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
24722        assert!(
24723            three.contains(
24724                "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24725                 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24726                 +Estimated number of matches\r\n:2\r\n"
24727            ),
24728            "{three}"
24729        );
24730    }
24731
24732    /// Every kind of step names itself, and the three that hold other steps say
24733    /// so in the singular or the plural depending on how many they hold.
24734    #[test]
24735    fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
24736        let mut f = profiling();
24737        let tree = |f: &mut Fixture, query: &[u8]| {
24738            let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
24739            let at = reply.find("+Iterators profile").expect("a tree");
24740            let end = reply.find("+Result processors").expect("a list of steps");
24741            reply[at..end].to_string()
24742        };
24743        assert_eq!(
24744            tree(&mut f, b"alpha beta"),
24745            "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
24746             +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
24747             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
24748             +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
24749             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
24750             +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
24751        );
24752        assert!(tree(&mut f, b"alpha|beta").starts_with(
24753            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
24754             +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
24755        ));
24756        // One thing under it, named in the singular, which is a different key
24757        // and not a list holding one.
24758        assert!(tree(&mut f, b"-alpha").starts_with(
24759            "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
24760             +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
24761        ));
24762        assert!(tree(&mut f, b"~alpha").starts_with(
24763            "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
24764             +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
24765        ));
24766        // No guess at how many, which is the one leaf that leaves it off.
24767        assert_eq!(
24768            tree(&mut f, b"*"),
24769            "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
24770             +Number of reading operations\r\n:3\r\n"
24771        );
24772        assert!(tree(&mut f, b"@n:[1 2]").starts_with(
24773            "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
24774             $19\r\n1.000000 - 2.000000\r\n"
24775        ));
24776    }
24777
24778    /// A union an expansion made folds into a count of its branches and a union
24779    /// a client wrote with a bar does not.
24780    #[test]
24781    fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
24782        let mut f = profiling();
24783        f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
24784        let tree = |f: &mut Fixture, words: &[&[u8]]| {
24785            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
24786            argv.extend_from_slice(words);
24787            let reply = timeless(&f.run(&argv));
24788            let at = reply.find("+Iterators profile").expect("a tree");
24789            let end = reply.find("+Result processors").expect("a list of steps");
24790            reply[at..end].to_string()
24791        };
24792        assert_eq!(
24793            tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
24794            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
24795             +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
24796             +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
24797             +The number of iterators in the union is 2\r\n"
24798        );
24799        assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
24800        assert!(
24801            tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
24802                .contains("+Child iterators\r\n*2\r\n")
24803        );
24804        // A union that says nothing but its own name says it as a status, and
24805        // one that says what it stood for says that as a string. Measured, and
24806        // it is the one place in this reply where the two are told apart.
24807        assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
24808        assert!(
24809            tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
24810        );
24811    }
24812
24813    /// Which steps a search runs the rows through, which turns on the window,
24814    /// on whether anything asked for the fields and on what the order is.
24815    #[test]
24816    fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
24817        let mut f = profiling();
24818        let steps = |f: &mut Fixture, words: &[&[u8]]| {
24819            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
24820            argv.extend_from_slice(words);
24821            let reply = timeless(&f.run(&argv));
24822            let at = reply.find("+Result processors").expect("a list of steps");
24823            let end = reply.find("+Coordinator").expect("an end");
24824            let mut out = Vec::new();
24825            let mut parts = reply[at..end].split("\r\n").peekable();
24826            while let Some(part) = parts.next() {
24827                if part == "+Type" {
24828                    out.push(parts.next().unwrap_or_default().to_string());
24829                }
24830            }
24831            out
24832        };
24833        assert_eq!(
24834            steps(&mut f, &[]),
24835            ["+Index", "+Scorer", "+Sorter", "+Loader"]
24836        );
24837        assert_eq!(
24838            steps(&mut f, &[b"NOCONTENT"]),
24839            ["+Index", "+Scorer", "+Sorter"]
24840        );
24841        // A window of nothing is a client asking for the total and nothing
24842        // else, so nothing is scored and nothing is sorted.
24843        assert_eq!(
24844            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
24845            ["+Index", "+Counter"]
24846        );
24847        // A sort by a field does not need a score, and asking for the scores
24848        // puts the step back.
24849        assert_eq!(
24850            steps(&mut f, &[b"SORTBY", b"n"]),
24851            ["+Index", "+Sorter", "+Loader"]
24852        );
24853        assert_eq!(
24854            steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
24855            ["+Index", "+Scorer", "+Sorter", "+Loader"]
24856        );
24857        assert_eq!(
24858            steps(&mut f, &[b"HIGHLIGHT"]),
24859            ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
24860        );
24861        assert_eq!(
24862            steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
24863            ["+Index", "+Scorer", "+Sorter"]
24864        );
24865    }
24866
24867    /// A pipeline names each of its steps after the expression it runs, which
24868    /// is what a real server prints beside them.
24869    #[test]
24870    fn a_pipeline_names_every_step_after_what_it_runs() {
24871        let mut f = profiling();
24872        let steps = |f: &mut Fixture, words: &[&[u8]]| {
24873            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
24874            argv.extend_from_slice(words);
24875            let reply = timeless(&f.run(&argv));
24876            let at = reply.find("+Result processors").expect("a list of steps");
24877            let end = reply.find("+Coordinator").expect("an end");
24878            let mut out = Vec::new();
24879            let mut parts = reply[at..end].split("\r\n").peekable();
24880            while let Some(part) = parts.next() {
24881                if part == "+Type" {
24882                    out.push(parts.next().unwrap_or_default().to_string());
24883                }
24884            }
24885            out
24886        };
24887        assert_eq!(steps(&mut f, &[]), ["+Index"]);
24888        assert_eq!(
24889            steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
24890            ["+Index", "+Projector - Literal 1"]
24891        );
24892        assert_eq!(
24893            steps(
24894                &mut f,
24895                &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
24896            ),
24897            ["+Index", "+Loader", "+Projector - Operator *"]
24898        );
24899        assert_eq!(
24900            steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
24901            ["+Index", "+Loader", "+Filter - Predicate >"]
24902        );
24903        assert_eq!(
24904            steps(
24905                &mut f,
24906                &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
24907            ),
24908            ["+Index", "+Loader", "+Grouper"]
24909        );
24910        assert_eq!(
24911            steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
24912            ["+Index", "+Loader", "+Sorter"]
24913        );
24914        assert_eq!(
24915            steps(&mut f, &[b"LIMIT", b"0", b"2"]),
24916            ["+Index", "+Pager/Limiter"]
24917        );
24918        // Asking for the score by name is a step of its own, and it goes in
24919        // front of the read rather than after it.
24920        assert_eq!(
24921            steps(
24922                &mut f,
24923                &[
24924                    b"ADDSCORES",
24925                    b"LOAD",
24926                    b"1",
24927                    b"@n",
24928                    b"APPLY",
24929                    b"@__score",
24930                    b"AS",
24931                    b"s"
24932                ]
24933            ),
24934            [
24935                "+Index",
24936                "+Scorer",
24937                "+Loader",
24938                "+Projector - Property __score"
24939            ]
24940        );
24941    }
24942
24943    /// A field the schema marked sortable is held beside the document number,
24944    /// so a pipeline that only names those never opens a key and never reports
24945    /// a read.
24946    ///
24947    /// Measured: on a schema of `n NUMERIC SORTABLE g TAG`, `LOAD 1 @n` has no
24948    /// `Loader` step and `LOAD 1 @g` has one. So does `LOAD *`, because what a
24949    /// key turns out to hold is not knowable without opening it.
24950    #[test]
24951    fn a_sortable_field_is_read_without_the_key_being_opened() {
24952        let mut f = Fixture::new();
24953        f.run(&[
24954            b"FT.CREATE",
24955            b"sx",
24956            b"PREFIX",
24957            b"1",
24958            b"s:",
24959            b"SCHEMA",
24960            b"n",
24961            b"NUMERIC",
24962            b"SORTABLE",
24963            b"g",
24964            b"TAG",
24965        ]);
24966        f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
24967        f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
24968        let loads = |f: &mut Fixture, words: &[&[u8]]| {
24969            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
24970            argv.extend_from_slice(words);
24971            f.run(&argv).contains("+Loader")
24972        };
24973        assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
24974        assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
24975        assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
24976        assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
24977        assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
24978        assert!(loads(
24979            &mut f,
24980            &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
24981        ));
24982        assert!(loads(&mut f, &[b"LOAD", b"*"]));
24983    }
24984
24985    /// The four ways the words can be wrong, none of which reaches the search
24986    /// underneath.
24987    #[test]
24988    fn a_profile_checks_its_own_words_before_it_runs_anything() {
24989        let mut f = profiling();
24990        assert_eq!(
24991            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
24992            "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
24993        );
24994        assert_eq!(
24995            f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
24996            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24997        );
24998        // The word goes between the two and nowhere else, so one written in
24999        // front of them is not the word at all.
25000        assert_eq!(
25001            f.run(&[
25002                b"FT.PROFILE",
25003                b"ix",
25004                b"LIMITED",
25005                b"SEARCH",
25006                b"QUERY",
25007                b"alpha"
25008            ]),
25009            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25010        );
25011        assert_eq!(
25012            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
25013            "-The QUERY keyword is expected\r\n"
25014        );
25015        assert_eq!(
25016            f.run(&[
25017                b"FT.PROFILE",
25018                b"ix",
25019                b"AGGREGATE",
25020                b"QUERY",
25021                b"alpha",
25022                b"WITHCURSOR"
25023            ]),
25024            "-FT.PROFILE does not support cursor\r\n"
25025        );
25026        // And what the search itself complains about comes back on its own,
25027        // without an envelope around it saying the command worked.
25028        assert_eq!(
25029            f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
25030            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
25031        );
25032        assert_eq!(
25033            f.run(&[
25034                b"FT.PROFILE",
25035                b"ix",
25036                b"SEARCH",
25037                b"QUERY",
25038                b"alpha",
25039                b"extra"
25040            ]),
25041            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
25042        );
25043    }
25044
25045    /// Every word of the command's own is read without regard to case.
25046    #[test]
25047    fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
25048        let mut f = profiling();
25049        let one = f.run(&[
25050            b"FT.PROFILE",
25051            b"ix",
25052            b"search",
25053            b"limited",
25054            b"query",
25055            b"alpha",
25056        ]);
25057        let two = f.run(&[
25058            b"FT.PROFILE",
25059            b"ix",
25060            b"SEARCH",
25061            b"LIMITED",
25062            b"QUERY",
25063            b"alpha",
25064        ]);
25065        assert_eq!(timeless(&one), timeless(&two));
25066    }
25067
25068    // -------------------------------------------------------------- dropping
25069
25070    /// The two spellings take opposite defaults, which is measured and is the
25071    /// only difference between them that a client can see.
25072    #[test]
25073    fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
25074        let mut f = profiling();
25075        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
25076        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25077
25078        let mut f = profiling();
25079        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25080        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25081
25082        let mut f = profiling();
25083        assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
25084        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25085
25086        let mut f = profiling();
25087        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
25088        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25089    }
25090
25091    /// Each spelling takes its own word and refuses the other one's, which
25092    /// reads as an oversight and is what a real server answers.
25093    #[test]
25094    fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
25095        let mut f = profiling();
25096        let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
25097        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
25098        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
25099        // Refused rather than half done, so the index is still there.
25100        assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
25101    }
25102
25103    /// Only what the index read is deleted, which is not the same as
25104    /// everything under its prefix.
25105    #[test]
25106    fn dropping_the_documents_leaves_a_key_the_index_never_read() {
25107        let mut f = profiling();
25108        f.run(&[b"SET", b"p:4", b"alpha"]);
25109        f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
25110        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25111        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25112        assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
25113    }
25114
25115    /// An index still standing over the same keys hears about them going,
25116    /// rather than answering later with keys that are not there.
25117    #[test]
25118    fn another_index_over_the_same_keys_loses_the_documents_too() {
25119        let mut f = profiling();
25120        f.run(&[
25121            b"FT.CREATE",
25122            b"other",
25123            b"PREFIX",
25124            b"1",
25125            b"p:",
25126            b"SCHEMA",
25127            b"t",
25128            b"TEXT",
25129        ]);
25130        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25131        assert_eq!(
25132            f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
25133            "*1\r\n:0\r\n"
25134        );
25135    }
25136
25137    /// A drop that found nothing to drop deletes nothing either, which is the
25138    /// one case where the shortcut spelling answers `OK` without a sweep.
25139    #[test]
25140    fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
25141        let mut f = profiling();
25142        assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
25143        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25144        assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
25145        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25146    }
25147
25148    // --------------------------------------------------------------- config
25149
25150    /// The two shapes a dump comes back in, which are the one mix of simple
25151    /// strings and bulk strings the group sends.
25152    #[test]
25153    fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
25154        let mut f = Fixture::new();
25155        assert_eq!(
25156            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25157            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25158        );
25159        assert_eq!(
25160            f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25161            "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
25162        );
25163        let mut g = Fixture::new();
25164        g.run(&[b"HELLO", b"3"]);
25165        assert_eq!(
25166            g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25167            "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25168        );
25169        assert_eq!(
25170            g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25171            "%1\r\n+EXTLOAD\r\n_\r\n"
25172        );
25173    }
25174
25175    /// The help text rides along in the middle of the same row, flat on RESP2
25176    /// and as a map of its own on RESP3.
25177    #[test]
25178    fn a_help_row_carries_the_description_and_the_value_together() {
25179        let mut f = Fixture::new();
25180        assert_eq!(
25181            f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25182            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25183             +Value\r\n$3\r\n500\r\n"
25184        );
25185        let mut g = Fixture::new();
25186        g.run(&[b"HELLO", b"3"]);
25187        assert_eq!(
25188            g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25189            "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
25190             +Value\r\n$3\r\n500\r\n"
25191        );
25192    }
25193
25194    /// A name is matched whole, ignoring case, and the single word star is the
25195    /// only thing that means all of them.
25196    #[test]
25197    fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
25198        let mut f = Fixture::new();
25199        assert_eq!(
25200            f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
25201            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25202        );
25203        for name in [
25204            b"TIMEOUT*".as_slice(),
25205            b"?IMEOUT",
25206            b"*TIMEOUT*",
25207            b"TIME",
25208            b"NOSUCH",
25209            b"",
25210        ] {
25211            assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
25212        }
25213        assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
25214        assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
25215    }
25216
25217    /// Words after the name are stepped over rather than refused, on both of
25218    /// the two reads.
25219    #[test]
25220    fn a_read_ignores_whatever_follows_the_name() {
25221        let mut f = Fixture::new();
25222        assert_eq!(
25223            f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
25224            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25225        );
25226        assert_eq!(
25227            f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
25228            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25229             +Value\r\n$3\r\n500\r\n"
25230        );
25231    }
25232
25233    /// The container reports its own name and the subcommand it was given in
25234    /// the two lines the dispatcher writes.
25235    #[test]
25236    fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
25237        let mut f = Fixture::new();
25238        assert_eq!(
25239            f.run(&[b"FT.CONFIG"]),
25240            "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
25241        );
25242        for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
25243            let want = format!(
25244                "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
25245                String::from_utf8_lossy(sub)
25246            );
25247            assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
25248        }
25249        assert_eq!(
25250            f.run(&[b"ft.config", b"get"]),
25251            "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
25252        );
25253        assert_eq!(
25254            f.run(&[b"FT.CONFIG", b"bogus"]),
25255            "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
25256        );
25257    }
25258
25259    /// The name, then whether it can move, then the value, then the count of
25260    /// words, and each of the first three answers before the next is looked at.
25261    #[test]
25262    fn a_write_checks_the_name_then_the_setting_then_the_value() {
25263        let mut f = Fixture::new();
25264        for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
25265            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
25266            cmd.extend(tail);
25267            assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
25268        }
25269        for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
25270            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
25271            cmd.extend(tail);
25272            assert_eq!(
25273                f.run(&cmd),
25274                "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
25275            );
25276        }
25277        assert_eq!(
25278            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
25279            "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
25280        );
25281    }
25282
25283    /// Too many words is a status and not an error, and the value has already
25284    /// been written by the time it goes out.
25285    #[test]
25286    fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
25287        let mut f = Fixture::new();
25288        assert_eq!(
25289            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
25290            "+OK\r\n"
25291        );
25292        assert_eq!(
25293            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
25294            "+EXCESSARGS\r\n"
25295        );
25296        assert_eq!(
25297            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25298            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
25299        );
25300    }
25301
25302    /// Strictly first and loosely second, so a hexadecimal and a leading zero
25303    /// and an exponent all land and a fraction does not.
25304    #[test]
25305    fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
25306        let mut f = Fixture::new();
25307        for (given, want) in [
25308            (b"0x10".as_slice(), "16"),
25309            (b"0X1f", "31"),
25310            (b"+0x10", "16"),
25311            (b"+5", "5"),
25312            (b"010", "10"),
25313            (b"08", "8"),
25314            (b"0777", "777"),
25315            (b"1e3", "1000"),
25316            (b"0.0", "0"),
25317            (b"-0.0", "0"),
25318        ] {
25319            assert_eq!(
25320                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25321                "+OK\r\n",
25322                "{given:?}"
25323            );
25324            let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
25325            assert_eq!(
25326                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25327                want,
25328                "{given:?}"
25329            );
25330        }
25331        for given in [
25332            b" 5".as_slice(),
25333            b"5 ",
25334            b"1.5",
25335            b"1e-3",
25336            b"x",
25337            b"",
25338            b"0b11",
25339            b"0xg",
25340            b"nan",
25341            b"inf",
25342            b"1e100",
25343            b"99999999999999999999",
25344        ] {
25345            assert_eq!(
25346                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25347                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25348                "{given:?}"
25349            );
25350        }
25351    }
25352
25353    /// Which of the two readers found a negative decides what it is told, and
25354    /// on a setting with no range at all neither of them is refused.
25355    #[test]
25356    fn a_negative_is_answered_by_whichever_reader_found_it() {
25357        let mut f = Fixture::new();
25358        for given in [b"-1".as_slice(), b"-16"] {
25359            assert_eq!(
25360                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25361                "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
25362                "{given:?}"
25363            );
25364        }
25365        for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
25366            assert_eq!(
25367                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25368                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25369                "{given:?}"
25370            );
25371        }
25372        let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
25373        for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
25374            assert_eq!(
25375                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25376                "+OK\r\n",
25377                "{given:?}"
25378            );
25379            assert_eq!(
25380                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25381                unlimited,
25382                "{given:?}"
25383            );
25384        }
25385    }
25386
25387    /// The two settings with no range truncate into a signed thirty two bit
25388    /// slot and say so once the number has gone under.
25389    #[test]
25390    fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
25391        let mut f = Fixture::new();
25392        for (given, want) in [
25393            (b"2147483647".as_slice(), "2147483647"),
25394            (b"2147483648", "unlimited"),
25395            (b"4294967295", "unlimited"),
25396            (b"9223372036854775806", "unlimited"),
25397            (b"0", "0"),
25398        ] {
25399            assert_eq!(
25400                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25401                "+OK\r\n",
25402                "{given:?}"
25403            );
25404            let want = format!(
25405                "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
25406                want.len()
25407            );
25408            assert_eq!(
25409                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25410                want,
25411                "{given:?}"
25412            );
25413        }
25414    }
25415
25416    /// A number past what a setting will take says which way it went, and the
25417    /// ones with a softer roof of their own say what that roof is about.
25418    #[test]
25419    fn a_number_out_of_range_names_the_limit_it_crossed() {
25420        let mut f = Fixture::new();
25421        let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
25422        for (name, given) in [
25423            (b"MINPREFIX".as_slice(), b"0".as_slice()),
25424            (b"MAX_AGGREGATE_GROUPS", b"0"),
25425            (b"BM25STD_TANH_FACTOR", b"0"),
25426            (b"DEFAULT_DIALECT", b"0"),
25427            (b"MINSTEMLEN", b"4294967296"),
25428            (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
25429            (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
25430            (b"CONNECT_TIMEOUT", b"2147483648"),
25431        ] {
25432            assert_eq!(
25433                f.run(&[b"FT.CONFIG", b"SET", name, given]),
25434                bounds,
25435                "{name:?}"
25436            );
25437        }
25438        for (name, given, want) in [
25439            (
25440                b"MINSTEMLEN".as_slice(),
25441                b"1".as_slice(),
25442                "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
25443            ),
25444            (
25445                b"MAX_AGGREGATE_GROUPS",
25446                b"67108865",
25447                "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
25448            ),
25449            (
25450                b"WORKERS",
25451                b"17",
25452                "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
25453            ),
25454            (
25455                b"_NUMERIC_RANGES_PARENTS",
25456                b"3",
25457                "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
25458                 depth for balance\r\n",
25459            ),
25460            (
25461                b"DEFAULT_DIALECT",
25462                b"5",
25463                "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
25464            ),
25465            (
25466                b"_BG_INDEX_MEM_PCT_THR",
25467                b"101",
25468                "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
25469                 100%\r\n",
25470            ),
25471            (
25472                b"BM25STD_TANH_FACTOR",
25473                b"10001",
25474                "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
25475                 inclusive\r\n",
25476            ),
25477            (
25478                b"BG_INDEX_SLEEP_DURATION_US",
25479                b"1000000",
25480                "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
25481                 999999 (usleep POSIX limit)\r\n",
25482            ),
25483        ] {
25484            assert_eq!(
25485                f.run(&[b"FT.CONFIG", b"SET", name, given]),
25486                want,
25487                "{name:?}"
25488            );
25489        }
25490    }
25491
25492    /// The two trimming delays are measured against each other, and the answer
25493    /// names both settings and both numbers.
25494    #[test]
25495    fn the_trimming_delays_are_checked_against_one_another() {
25496        let mut f = Fixture::new();
25497        assert_eq!(
25498            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
25499            "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
25500             _MAX_TRIM_DELAY_MS (5000)\r\n"
25501        );
25502        assert_eq!(
25503            f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
25504            "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
25505             _MIN_TRIM_DELAY_MS (2000)\r\n"
25506        );
25507        assert_eq!(
25508            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
25509            "+OK\r\n"
25510        );
25511    }
25512
25513    /// Two of the word settings fold the spelling on the way in and the scorer
25514    /// does not, which is the one place in the table case counts.
25515    #[test]
25516    fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
25517        let mut f = Fixture::new();
25518        assert_eq!(
25519            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
25520            "+OK\r\n"
25521        );
25522        assert_eq!(
25523            f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
25524            "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
25525        );
25526        assert_eq!(
25527            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
25528            "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
25529        );
25530        assert_eq!(
25531            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
25532            "+OK\r\n"
25533        );
25534        assert_eq!(
25535            f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
25536            "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
25537        );
25538        assert_eq!(
25539            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
25540            "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
25541        );
25542        let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
25543        for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
25544            assert_eq!(
25545                f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
25546                bad,
25547                "{given:?}"
25548            );
25549        }
25550        assert_eq!(
25551            f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
25552            "+OK\r\n"
25553        );
25554    }
25555
25556    /// True and false, either case, and none of the other words a client might
25557    /// reach for.
25558    #[test]
25559    fn a_yes_or_no_setting_takes_those_two_words_only() {
25560        let mut f = Fixture::new();
25561        assert_eq!(
25562            f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
25563            "+OK\r\n"
25564        );
25565        assert_eq!(
25566            f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
25567            "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
25568        );
25569        for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
25570            assert_eq!(
25571                f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
25572                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25573                "{given:?}"
25574            );
25575        }
25576    }
25577
25578    /// Two pairs of names sit over one number each, and one of that second pair
25579    /// takes no value at all.
25580    #[test]
25581    fn two_names_for_one_setting_move_together() {
25582        let mut f = Fixture::new();
25583        f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
25584        assert_eq!(
25585            f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
25586            "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
25587        );
25588        f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
25589        assert_eq!(
25590            f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
25591            "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
25592        );
25593        let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25594        let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25595        f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
25596        assert_eq!(
25597            f.run(&[b"FT.CONFIG", b"GET", short]),
25598            "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
25599        );
25600        assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
25601        assert_eq!(
25602            f.run(&[b"FT.CONFIG", b"GET", long]),
25603            "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
25604        );
25605    }
25606
25607    /// The one setting that takes a write and never gives it back.
25608    #[test]
25609    fn a_password_reads_back_as_stars_whatever_was_written() {
25610        let mut f = Fixture::new();
25611        assert_eq!(
25612            f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
25613            "+OK\r\n"
25614        );
25615        assert_eq!(
25616            f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
25617            "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
25618        );
25619    }
25620
25621    /// The settings are not in the keyspace, so unlike the dictionaries and the
25622    /// synonym groups beside them they live through an emptied one.
25623    #[test]
25624    fn a_flush_leaves_the_settings_alone() {
25625        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
25626            let mut f = Fixture::new();
25627            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
25628            f.run(&[flush]);
25629            assert_eq!(
25630                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25631                "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
25632                "{flush:?}"
25633            );
25634        }
25635    }
25636
25637    // ---------------------------------------------------------------- debug
25638
25639    /// A small index with one of everything a dump can read, so the tests below
25640    /// all name the same three documents and the same four fields.
25641    fn debugging() -> Fixture {
25642        let mut f = Fixture::new();
25643        f.run(&[
25644            b"FT.CREATE",
25645            b"dx",
25646            b"PREFIX",
25647            b"1",
25648            b"d:",
25649            b"SCHEMA",
25650            b"t",
25651            b"TEXT",
25652            b"g",
25653            b"TAG",
25654            b"n",
25655            b"NUMERIC",
25656            b"s",
25657            b"TEXT",
25658            b"SORTABLE",
25659        ]);
25660        f.run(&[
25661            b"HSET",
25662            b"d:1",
25663            b"t",
25664            b"running dogs",
25665            b"g",
25666            b"red,blue",
25667            b"n",
25668            b"1",
25669            b"s",
25670            b"Alpha",
25671        ]);
25672        f.run(&[
25673            b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
25674        ]);
25675        f.run(&[
25676            b"HSET",
25677            b"d:3",
25678            b"t",
25679            b"dogs alpha",
25680            b"g",
25681            b"green",
25682            b"n",
25683            b"3",
25684        ]);
25685        f
25686    }
25687
25688    /// The whole dictionary in byte order, with the stems in it as entries of
25689    /// their own rather than hidden behind the words they came from.
25690    #[test]
25691    fn a_term_dump_lists_the_stems_beside_the_words() {
25692        let mut f = debugging();
25693        assert_eq!(
25694            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
25695            "*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\
25696             $4\r\ndogs\r\n$7\r\nrunning\r\n"
25697        );
25698    }
25699
25700    /// A posting list is looked up on the bytes given and nothing folds them, so
25701    /// the term that a query would have found is not the term a dump wants.
25702    #[test]
25703    fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
25704        let mut f = debugging();
25705        assert_eq!(
25706            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25707            "*2\r\n:1\r\n:2\r\n"
25708        );
25709        assert_eq!(
25710            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
25711            "*2\r\n:1\r\n:2\r\n"
25712        );
25713        for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
25714            assert_eq!(
25715                f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
25716                "-Can not find the inverted index\r\n",
25717                "{term:?}"
25718            );
25719        }
25720    }
25721
25722    /// Tag values come back folded and in byte order, each with the documents
25723    /// that hold it, and a document with two values is under both of them.
25724    #[test]
25725    fn a_tag_dump_pairs_every_value_with_its_documents() {
25726        let mut f = debugging();
25727        assert_eq!(
25728            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
25729            "*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\
25730             *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
25731        );
25732    }
25733
25734    /// One list holding every document in the field, which is D-96: a range tree
25735    /// answers one list per range and this answers the one it keeps.
25736    #[test]
25737    fn a_number_dump_answers_a_single_range() {
25738        let mut f = debugging();
25739        assert_eq!(
25740            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
25741            "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
25742        );
25743    }
25744
25745    /// A point is a number underneath, so the field that holds points answers
25746    /// the subcommand that dumps numbers and not the one that dumps tags.
25747    #[test]
25748    fn a_geo_field_is_dumped_as_a_numeric_one() {
25749        let mut f = Fixture::new();
25750        f.run(&[
25751            b"FT.CREATE",
25752            b"gx",
25753            b"PREFIX",
25754            b"1",
25755            b"q:",
25756            b"SCHEMA",
25757            b"loc",
25758            b"GEO",
25759            b"gg",
25760            b"AS",
25761            b"tag",
25762            b"TAG",
25763        ]);
25764        f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
25765        f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
25766        assert_eq!(
25767            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
25768            "*1\r\n*2\r\n:1\r\n:2\r\n"
25769        );
25770        assert_eq!(
25771            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
25772            "-Could not find given field in index spec\r\n"
25773        );
25774    }
25775
25776    /// A field is named the way a query names it, so the attribute is the name
25777    /// and the identifier the value was read from is not one.
25778    #[test]
25779    fn a_dump_takes_the_attribute_and_not_the_identifier() {
25780        let mut f = Fixture::new();
25781        f.run(&[
25782            b"FT.CREATE",
25783            b"zx",
25784            b"PREFIX",
25785            b"1",
25786            b"z:",
25787            b"SCHEMA",
25788            b"gg",
25789            b"AS",
25790            b"tag",
25791            b"TAG",
25792        ]);
25793        f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
25794        assert_eq!(
25795            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
25796            "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
25797        );
25798        assert_eq!(
25799            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
25800            "-Could not find given field in index spec\r\n"
25801        );
25802    }
25803
25804    /// The seven keys, with the score as a bulk string here and a double there,
25805    /// and the whole row flat on one protocol and a map on the other.
25806    #[test]
25807    fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
25808        let mut f = debugging();
25809        assert_eq!(
25810            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25811            "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25812             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
25813             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25814             +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\
25815             $5\r\nvalue\r\n$5\r\nalpha\r\n"
25816        );
25817        let mut g = debugging();
25818        g.run(&[b"HELLO", b"3"]);
25819        assert_eq!(
25820            g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25821            "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25822             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
25823             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25824             +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\
25825             $5\r\nvalue\r\n$5\r\nalpha\r\n"
25826        );
25827    }
25828
25829    /// A document that wrote nothing into a sortable slot has no sortables key
25830    /// at all, so the row is a key shorter rather than carrying an empty list.
25831    #[test]
25832    fn a_document_with_no_sortable_value_drops_the_key() {
25833        let mut f = debugging();
25834        assert_eq!(
25835            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
25836            "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
25837             +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"
25838        );
25839    }
25840
25841    /// The flag word is the number and then the names it stands for, and an
25842    /// index built without offsets has none of the three set.
25843    #[test]
25844    fn the_flag_word_spells_out_the_bits_it_carries() {
25845        let mut f = Fixture::new();
25846        f.run(&[
25847            b"FT.CREATE",
25848            b"nx",
25849            b"NOOFFSETS",
25850            b"PREFIX",
25851            b"1",
25852            b"o:",
25853            b"SCHEMA",
25854            b"t",
25855            b"TEXT",
25856        ]);
25857        f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
25858        assert!(
25859            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
25860                .contains("$6\r\n(0x0):\r\n")
25861        );
25862    }
25863
25864    /// Obfuscation replaces the field name with where the field sits in the
25865    /// whole schema, which is not where its value sits among the sortables.
25866    #[test]
25867    fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
25868        let mut f = debugging();
25869        assert!(
25870            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
25871                .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
25872        );
25873    }
25874
25875    /// The keyword is read where it belongs and anything after it is stepped
25876    /// over, whatever the line that complains about it says.
25877    #[test]
25878    fn a_document_row_reads_its_keyword_at_a_fixed_place() {
25879        let mut f = debugging();
25880        let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
25881        assert_eq!(
25882            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
25883            want
25884        );
25885        assert_eq!(
25886            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
25887            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25888        );
25889        assert_eq!(
25890            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
25891            "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
25892        );
25893    }
25894
25895    /// The key is looked up before the keyword is read, so a key nobody indexed
25896    /// beats a keyword nobody wrote.
25897    #[test]
25898    fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
25899        let mut f = debugging();
25900        assert_eq!(
25901            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
25902            "-Document not found in index\r\n"
25903        );
25904        assert_eq!(
25905            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
25906            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25907        );
25908    }
25909
25910    /// The two directions of the document table, and the number nobody handed
25911    /// out reads as one that was given up rather than as one that never was.
25912    #[test]
25913    fn a_document_number_goes_both_ways() {
25914        let mut f = debugging();
25915        assert_eq!(
25916            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25917            "$3\r\nd:2\r\n"
25918        );
25919        assert_eq!(
25920            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25921            ":2\r\n"
25922        );
25923        assert_eq!(
25924            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
25925            ":0\r\n"
25926        );
25927        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
25928        for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
25929            assert_eq!(
25930                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25931                "-document was removed\r\n",
25932                "{id:?}"
25933            );
25934        }
25935    }
25936
25937    /// A document number is read the strict way Redis reads an integer, so a
25938    /// leading zero, a leading plus and a leading space are all refused.
25939    #[test]
25940    fn a_document_number_is_read_the_strict_way() {
25941        let mut f = debugging();
25942        for id in [
25943            b"x".as_slice(),
25944            b"1.5",
25945            b" 1",
25946            b"+1",
25947            b"01",
25948            b"0x1",
25949            b"",
25950            b"9223372036854775808",
25951            b"18446744073709551615",
25952        ] {
25953            assert_eq!(
25954                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25955                "-bad id given\r\n",
25956                "{id:?}"
25957            );
25958        }
25959    }
25960
25961    /// A number a document has given up is still in every list it was in, so a
25962    /// dump names documents that the table says are gone.
25963    #[test]
25964    fn a_dump_keeps_a_number_the_table_has_given_up() {
25965        let mut f = debugging();
25966        f.run(&[b"DEL", b"d:2"]);
25967        assert_eq!(
25968            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25969            "*2\r\n:1\r\n:2\r\n"
25970        );
25971        assert_eq!(
25972            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25973            "-document was removed\r\n"
25974        );
25975        assert_eq!(
25976            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25977            ":0\r\n"
25978        );
25979    }
25980
25981    /// A rewrite hands out a new number and leaves the old one behind, so the
25982    /// counter climbs past the number of documents there are.
25983    #[test]
25984    fn a_rewrite_takes_a_number_of_its_own() {
25985        let mut f = debugging();
25986        f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
25987        assert_eq!(
25988            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
25989            ":4\r\n"
25990        );
25991        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
25992        assert_eq!(
25993            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
25994            "-document was removed\r\n"
25995        );
25996        assert_eq!(
25997            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25998            "*2\r\n:1\r\n:2\r\n"
25999        );
26000    }
26001
26002    /// An alias reads the index it stands for, the same as a query does.
26003    #[test]
26004    fn a_dump_follows_an_alias() {
26005        let mut f = debugging();
26006        f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
26007        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
26008        assert_eq!(
26009            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
26010            "$3\r\nd:1\r\n"
26011        );
26012    }
26013
26014    /// The index name is matched as written and the subcommand name is not, and
26015    /// an index nobody made is reported as a context that could not be built.
26016    #[test]
26017    fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
26018        let mut f = debugging();
26019        assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
26020        assert_eq!(
26021            f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
26022            "-Can not create a search ctx\r\n"
26023        );
26024        assert_eq!(
26025            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
26026            "-Can not create a search ctx\r\n"
26027        );
26028    }
26029
26030    /// A field with nothing written into it answers an empty dump rather than an
26031    /// error, since the field is in the schema and only the values are missing.
26032    #[test]
26033    fn an_empty_field_dumps_as_nothing_at_all() {
26034        let mut f = Fixture::new();
26035        f.run(&[
26036            b"FT.CREATE",
26037            b"ex",
26038            b"PREFIX",
26039            b"1",
26040            b"e:",
26041            b"SCHEMA",
26042            b"t",
26043            b"TEXT",
26044            b"g",
26045            b"TAG",
26046            b"n",
26047            b"NUMERIC",
26048        ]);
26049        assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
26050        assert_eq!(
26051            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
26052            "*0\r\n"
26053        );
26054        assert_eq!(
26055            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
26056            "*0\r\n"
26057        );
26058        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
26059    }
26060
26061    /// The two lines the dispatcher owns are the two that carry a code word, and
26062    /// every subcommand but `DOCINFO` counts its arguments exactly.
26063    #[test]
26064    fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
26065        let mut f = debugging();
26066        for (sub, extra) in [
26067            (b"DUMP_TERMS".as_slice(), 1),
26068            (b"GET_MAX_DOC_ID", 1),
26069            (b"DUMP_INVIDX", 2),
26070            (b"DUMP_TAGIDX", 2),
26071            (b"DUMP_NUMIDX", 2),
26072            (b"IDTODOCID", 2),
26073            (b"DOCIDTOID", 2),
26074        ] {
26075            let want = format!(
26076                "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
26077                str::from_utf8(sub).unwrap()
26078            );
26079            for given in [extra - 1, extra + 1] {
26080                let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
26081                cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
26082                assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
26083            }
26084            let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
26085            right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
26086            assert_ne!(f.run(&right), want, "{sub:?}");
26087        }
26088        assert_eq!(
26089            f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
26090            "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
26091        );
26092    }
26093
26094    /// The eight names that answer rather than the sixty two a real server
26095    /// registers, which is D-97, and anything after the name is stepped over.
26096    #[test]
26097    fn the_help_names_the_subcommands_that_answer() {
26098        let mut f = Fixture::new();
26099        let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
26100             $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
26101             $14\r\nGET_MAX_DOC_ID\r\n";
26102        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
26103        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
26104    }
26105
26106    // ------------------------------------------------------------- synonyms
26107
26108    /// The terms are folded on the way in and the group ids are not, and one
26109    /// term can be in more than one group.
26110    #[test]
26111    fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
26112        let mut f = Fixture::new();
26113        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26114        assert_eq!(
26115            f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
26116            "+OK\r\n"
26117        );
26118        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
26119        assert_eq!(
26120            f.run(&[b"FT.SYNDUMP", b"e"]),
26121            "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
26122             $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
26123        );
26124    }
26125
26126    /// A group is not a comparison made at query time. It is a term of its
26127    /// own, so a word in a group reads as a union of the word, the groups it
26128    /// is in and its stem.
26129    #[test]
26130    fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
26131        let mut f = Fixture::new();
26132        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26133        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
26134        assert_eq!(
26135            f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
26136            "$69\r\nUNION {\n  jogging\n  ~gr(expanded)\n  +jog(expanded)\n  jog(expanded)\n}\n\r\n"
26137        );
26138    }
26139
26140    /// The lookup on the document side is on the word and never on the stem,
26141    /// and a group written after the documents were still finds them because
26142    /// the index is read again.
26143    ///
26144    /// The group holds `running` and `d2` says `runs`, so a query for another
26145    /// word of the group finds `d1` and leaves `d2` where it is. A query for
26146    /// `running` itself does find `d2`, through the stem branch of the union
26147    /// rather than through the group, which is why the two asserts differ.
26148    #[test]
26149    fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
26150        let mut f = Fixture::new();
26151        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26152        f.run(&[b"HSET", b"d1", b"t", b"boy"]);
26153        f.run(&[b"HSET", b"d2", b"t", b"runs"]);
26154        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
26155        assert_eq!(
26156            f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
26157            "*2\r\n:1\r\n$2\r\nd1\r\n"
26158        );
26159        assert_eq!(
26160            f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
26161            "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
26162        );
26163    }
26164
26165    /// Neither command makes an index and neither forgives a name that is not
26166    /// there, in the same words the rest of the group uses.
26167    #[test]
26168    fn a_synonym_command_on_a_name_that_is_not_there_fails() {
26169        let mut f = Fixture::new();
26170        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
26171        assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
26172        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
26173    }
26174
26175    /// The words after `PARAMS n` are counted before their shape is looked at,
26176    /// so a count that reaches past the end of the command and a count that is
26177    /// merely odd are two different errors.
26178    #[test]
26179    fn params_counts_the_words_before_it_pairs_them_up() {
26180        let mut f = Fixture::new();
26181        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26182        let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
26183                    Expected an argument, but none provided\r\n";
26184        let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
26185        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
26186        assert_eq!(
26187            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
26188            none
26189        );
26190        assert_eq!(
26191            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
26192            odd
26193        );
26194        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
26195        assert_eq!(
26196            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
26197            "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
26198        );
26199    }
26200
26201    // --------------------------------------------------------------- vectors
26202
26203    /// Five documents a unit apart along one axis, written in the opposite
26204    /// order to the one they sit in, so a reply in document order and a reply
26205    /// in distance order are two different replies.
26206    ///
26207    /// `d1` is furthest from the origin and `d5` is on it. The text field
26208    /// splits them so a query can narrow before it measures: `d1`, `d2` and
26209    /// `d4` say `alpha` and the other two say `beta`.
26210    fn vectored(f: &mut Fixture) {
26211        f.run(&[
26212            b"FT.CREATE",
26213            b"h",
26214            b"SCHEMA",
26215            b"t",
26216            b"TEXT",
26217            b"v",
26218            b"VECTOR",
26219            b"FLAT",
26220            b"6",
26221            b"TYPE",
26222            b"FLOAT32",
26223            b"DIM",
26224            b"2",
26225            b"DISTANCE_METRIC",
26226            b"L2",
26227        ]);
26228        let at: [&[u8]; 5] = [
26229            b"\x00\x00\x80\x40\x00\x00\x00\x00",
26230            b"\x00\x00\x40\x40\x00\x00\x00\x00",
26231            b"\x00\x00\x00\x40\x00\x00\x00\x00",
26232            b"\x00\x00\x80\x3f\x00\x00\x00\x00",
26233            b"\x00\x00\x00\x00\x00\x00\x00\x00",
26234        ];
26235        for (n, point) in at.iter().enumerate() {
26236            let key = format!("d{}", n + 1);
26237            let word: &[u8] = match n {
26238                0 | 1 | 3 => b"alpha",
26239                _ => b"beta",
26240            };
26241            f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
26242        }
26243    }
26244
26245    /// The origin, which every query below asks about.
26246    const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
26247
26248    /// A `KNN` picks the k nearest and then answers them in document order,
26249    /// which is measured: asking for three of five that were written furthest
26250    /// first answers the last three written and not the first three.
26251    #[test]
26252    fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
26253        let mut f = Fixture::new();
26254        vectored(&mut f);
26255        assert_eq!(
26256            f.run(&[
26257                b"FT.SEARCH",
26258                b"h",
26259                b"*=>[KNN 5 @v $vec]",
26260                b"PARAMS",
26261                b"2",
26262                b"vec",
26263                ORIGIN,
26264                b"DIALECT",
26265                b"2",
26266                b"NOCONTENT",
26267            ]),
26268            "*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"
26269        );
26270        assert_eq!(
26271            f.run(&[
26272                b"FT.SEARCH",
26273                b"h",
26274                b"*=>[KNN 3 @v $vec]",
26275                b"PARAMS",
26276                b"2",
26277                b"vec",
26278                ORIGIN,
26279                b"DIALECT",
26280                b"2",
26281                b"NOCONTENT",
26282            ]),
26283            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26284        );
26285    }
26286
26287    /// A range takes what is really inside it, where the distances are squared
26288    /// so the five documents sit at 16, 9, 4, 1 and 0.
26289    #[test]
26290    fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
26291        let mut f = Fixture::new();
26292        vectored(&mut f);
26293        for (radius, want) in [
26294            ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
26295            ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
26296            (
26297                "9",
26298                "*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",
26299            ),
26300        ] {
26301            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26302            assert_eq!(
26303                f.run(&[
26304                    b"FT.SEARCH",
26305                    b"h",
26306                    query.as_bytes(),
26307                    b"PARAMS",
26308                    b"2",
26309                    b"vec",
26310                    ORIGIN,
26311                    b"DIALECT",
26312                    b"2",
26313                    b"NOCONTENT",
26314                ]),
26315                want,
26316                "radius {radius}"
26317            );
26318        }
26319    }
26320
26321    /// A `KNN` behind a query is the nearest of what the query matched, so
26322    /// asking for two of the three documents that say `alpha` answers the two
26323    /// of those three that are nearest and not the two nearest overall.
26324    #[test]
26325    fn a_knn_measures_what_the_query_in_front_of_it_matched() {
26326        let mut f = Fixture::new();
26327        vectored(&mut f);
26328        assert_eq!(
26329            f.run(&[
26330                b"FT.SEARCH",
26331                b"h",
26332                b"alpha=>[KNN 2 @v $vec]",
26333                b"PARAMS",
26334                b"2",
26335                b"vec",
26336                ORIGIN,
26337                b"DIALECT",
26338                b"2",
26339                b"NOCONTENT",
26340            ]),
26341            "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
26342        );
26343    }
26344
26345    /// A `KNN` counts in whole numbers and a range measures from zero, and the
26346    /// two are refused in their own words.
26347    ///
26348    /// The count is a token of its own and is checked where it stands, ahead of
26349    /// the field and ahead of the vector. A count that arrives through `PARAMS`
26350    /// is read by looser rules than one written into the query, which is
26351    /// measured: a leading plus is fine in a parameter and a syntax error in
26352    /// the query text.
26353    #[test]
26354    fn a_count_and_a_radius_are_refused_in_their_own_words() {
26355        let mut f = Fixture::new();
26356        vectored(&mut f);
26357        let ask = |f: &mut Fixture, query: &str| {
26358            f.run(&[
26359                b"FT.SEARCH",
26360                b"h",
26361                query.as_bytes(),
26362                b"PARAMS",
26363                b"2",
26364                b"vec",
26365                ORIGIN,
26366                b"DIALECT",
26367                b"2",
26368                b"NOCONTENT",
26369            ])
26370        };
26371        for (query, at, near) in [
26372            ("*=>[KNN -1 @v $vec]", 8, "-1"),
26373            ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
26374            ("*=>[KNN +3 @v $vec]", 8, "+3"),
26375            ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
26376            ("*=>[KNN abc @v $vec]", 8, "abc"),
26377            ("*=>[KNN 3 $vec]", 10, "vec"),
26378            ("*=>[KNN 3 @v vec]", 13, "vec"),
26379            ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
26380        ] {
26381            assert_eq!(
26382                ask(&mut f, query),
26383                format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
26384                "{query}"
26385            );
26386        }
26387
26388        // Read as a double the way a real server reads it, so the bound plus
26389        // thirty two rounds back onto the bound and gets in.
26390        let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26391                     query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
26392        assert_eq!(
26393            ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
26394            "*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"
26395        );
26396        assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
26397        assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
26398
26399        for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
26400            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26401            assert_eq!(
26402                ask(&mut f, &query),
26403                format!(
26404                    "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26405                     negative radius ({printed}) given in a range query\r\n"
26406                ),
26407                "{query}"
26408            );
26409        }
26410        // A radius of minus zero is not below zero and is a radius of zero.
26411        assert_eq!(
26412            ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
26413            "*2\r\n:1\r\n$2\r\nd5\r\n"
26414        );
26415    }
26416
26417    /// A count passed with `PARAMS` is read the way a real server reads one,
26418    /// which is not the way the same digits are read in the query text.
26419    #[test]
26420    fn a_count_that_came_from_params_is_read_by_its_own_rules() {
26421        let mut f = Fixture::new();
26422        vectored(&mut f);
26423        let ask = |f: &mut Fixture, count: &[u8]| {
26424            f.run(&[
26425                b"FT.SEARCH",
26426                b"h",
26427                b"*=>[KNN $k @v $vec]",
26428                b"PARAMS",
26429                b"4",
26430                b"vec",
26431                ORIGIN,
26432                b"k",
26433                count,
26434                b"DIALECT",
26435                b"2",
26436                b"NOCONTENT",
26437            ])
26438        };
26439        let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
26440        assert_eq!(ask(&mut f, b"3"), three);
26441        assert_eq!(ask(&mut f, b"  3"), three);
26442        assert_eq!(ask(&mut f, b"+3"), three);
26443        for bad in [
26444            &b"3.0"[..],
26445            b"0x3",
26446            b"-1",
26447            b"abc",
26448            b"",
26449            b"99999999999999999999",
26450        ] {
26451            let value = String::from_utf8_lossy(bad).into_owned();
26452            assert_eq!(
26453                ask(&mut f, bad),
26454                format!(
26455                    "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
26456                     for parameter `k`\r\n"
26457                ),
26458                "{value}"
26459            );
26460        }
26461        assert_eq!(
26462            ask(&mut f, b"288230376151711777"),
26463            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26464             query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
26465        );
26466    }
26467
26468    /// A vector the wrong size is refused against the field it was passed to,
26469    /// naming both sizes in bytes.
26470    #[test]
26471    fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
26472        let mut f = Fixture::new();
26473        vectored(&mut f);
26474        assert_eq!(
26475            f.run(&[
26476                b"FT.SEARCH",
26477                b"h",
26478                b"*=>[KNN 5 @v $vec]",
26479                b"PARAMS",
26480                b"2",
26481                b"vec",
26482                b"abc",
26483                b"DIALECT",
26484                b"2",
26485                b"NOCONTENT",
26486            ]),
26487            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26488             query vector blob size (3) does not match index's expected size (8).\r\n"
26489        );
26490    }
26491
26492    /// A nearest neighbour clause puts its distance on every row it answers,
26493    /// under `__v_score` unless the query renamed it. A range clause puts
26494    /// nothing there at all unless the query named it, which is what
26495    /// `YIELD_DISTANCE_AS` is for.
26496    #[test]
26497    fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
26498        let mut f = Fixture::new();
26499        vectored(&mut f);
26500        let ask = |f: &mut Fixture, query: &str| {
26501            f.run(&[
26502                b"FT.SEARCH",
26503                b"h",
26504                query.as_bytes(),
26505                b"PARAMS",
26506                b"2",
26507                b"vec",
26508                ORIGIN,
26509                b"DIALECT",
26510                b"2",
26511                b"LIMIT",
26512                b"0",
26513                b"1",
26514            ])
26515        };
26516        assert_eq!(
26517            ask(&mut f, "*=>[KNN 3 @v $vec]"),
26518            "*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"
26519        );
26520        assert_eq!(
26521            ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
26522            "*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"
26523        );
26524        assert_eq!(
26525            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
26526            "*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"
26527        );
26528        assert_eq!(
26529            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
26530            "*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"
26531        );
26532    }
26533
26534    /// What decides whether a `RETURN` answers the distance is the name the row
26535    /// would carry it under and not the field it would have been read from,
26536    /// because it is on the row before any key is read.
26537    ///
26538    /// So naming it answers it, renaming it answers nothing at all, and giving
26539    /// its name to another field answers the distance under that name.
26540    #[test]
26541    fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
26542        let mut f = Fixture::new();
26543        vectored(&mut f);
26544        let ask = |f: &mut Fixture, ret: &[&[u8]]| {
26545            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
26546            args.extend_from_slice(ret);
26547            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26548            f.run(&args)
26549        };
26550        assert_eq!(
26551            ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
26552            "*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"
26553        );
26554        assert_eq!(
26555            ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
26556            "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
26557        );
26558        assert_eq!(
26559            ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
26560            "*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"
26561        );
26562        assert_eq!(
26563            ask(&mut f, &[b"RETURN", b"1", b"t"]),
26564            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
26565        );
26566        // The distance goes in front of the rest whatever order they were
26567        // named in, and `NOCONTENT` takes it away with everything else.
26568        assert_eq!(
26569            ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
26570            "*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"
26571        );
26572        assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
26573    }
26574
26575    /// A `SORTBY` can name a distance the query yielded, which sorts by the
26576    /// number rather than by anything the key holds. A name the query did not
26577    /// yield is refused the way any other unknown property is.
26578    #[test]
26579    fn a_sortby_can_name_a_distance_the_query_yielded() {
26580        let mut f = Fixture::new();
26581        vectored(&mut f);
26582        let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
26583            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
26584            if desc {
26585                args.push(b"DESC");
26586            }
26587            args.extend_from_slice(&[
26588                b"PARAMS",
26589                b"2",
26590                b"vec",
26591                ORIGIN,
26592                b"DIALECT",
26593                b"2",
26594                b"NOCONTENT",
26595            ]);
26596            f.run(&args)
26597        };
26598        assert_eq!(
26599            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
26600            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26601        );
26602        assert_eq!(
26603            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
26604            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26605        );
26606        assert_eq!(
26607            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
26608            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26609        );
26610        // Renaming it takes the old name away, and a query with no vector
26611        // clause in it never had the property at all.
26612        let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
26613                       not loaded nor in schema\r\n";
26614        assert_eq!(
26615            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
26616            missing
26617        );
26618        assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
26619        // The query is read before the property is looked up, which is
26620        // measured: a query that will not parse is answered first.
26621        assert_eq!(
26622            ask(&mut f, "foo(", b"zz", false),
26623            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26624        );
26625    }
26626
26627    /// Two vector clauses in one query answer two distances, outermost first.
26628    #[test]
26629    fn two_vector_clauses_answer_two_distances() {
26630        let mut f = Fixture::new();
26631        vectored(&mut f);
26632        assert_eq!(
26633            f.run(&[
26634                b"FT.SEARCH",
26635                b"h",
26636                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
26637                b"RETURN",
26638                b"2",
26639                b"rr",
26640                b"__v_score",
26641                b"PARAMS",
26642                b"2",
26643                b"vec",
26644                ORIGIN,
26645                b"DIALECT",
26646                b"2",
26647            ]),
26648            "*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"
26649        );
26650    }
26651
26652    /// An aggregation carries the distance on every row whether or not the
26653    /// pipeline ever mentions it, and carries it in front of everything a
26654    /// `LOAD` asked for.
26655    #[test]
26656    fn an_aggregation_answers_a_distance_nothing_asked_for() {
26657        let mut f = Fixture::new();
26658        vectored(&mut f);
26659        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26660            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26661            args.extend_from_slice(rest);
26662            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26663            f.run(&args)
26664        };
26665        assert_eq!(
26666            ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
26667            "*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"
26668        );
26669        assert_eq!(
26670            ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
26671            "*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"
26672        );
26673        assert_eq!(
26674            ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
26675            "*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"
26676        );
26677        // A range shows nothing until the query names it.
26678        assert_eq!(
26679            ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
26680            "*3\r\n:1\r\n*0\r\n*0\r\n"
26681        );
26682        assert_eq!(
26683            ask(
26684                &mut f,
26685                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26686                &[]
26687            ),
26688            "*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"
26689        );
26690    }
26691
26692    /// A nearest neighbour clause hands its documents back nearest first and an
26693    /// aggregation keeps them that way, where a search sorts them into document
26694    /// order. A tie goes to the document written first.
26695    #[test]
26696    fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
26697        let mut f = Fixture::new();
26698        vectored(&mut f);
26699        // Sitting on `d3`, so `d2` and `d4` are the same distance away.
26700        const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
26701        let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
26702            f.run(&[
26703                b"FT.AGGREGATE",
26704                b"h",
26705                query.as_bytes(),
26706                b"LOAD",
26707                b"1",
26708                b"@t",
26709                b"PARAMS",
26710                b"2",
26711                b"vec",
26712                vec,
26713                b"DIALECT",
26714                b"2",
26715            ])
26716        };
26717        assert_eq!(
26718            ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
26719            "*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"
26720        );
26721        // A range does no ordering, so those rows stay in document order.
26722        assert_eq!(
26723            ask(
26724                &mut f,
26725                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26726                MIDDLE
26727            ),
26728            "*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"
26729        );
26730    }
26731
26732    /// Every step of the pipeline can name a distance the query yielded, and a
26733    /// query with no vector clause in it is refused for the name three
26734    /// different ways depending on which step asked.
26735    #[test]
26736    fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
26737        let mut f = Fixture::new();
26738        vectored(&mut f);
26739        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26740            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26741            args.extend_from_slice(rest);
26742            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26743            f.run(&args)
26744        };
26745        let knn = "*=>[KNN 2 @v $vec]";
26746        assert_eq!(
26747            ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
26748            "*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"
26749        );
26750        assert_eq!(
26751            ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
26752            "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
26753        );
26754        assert_eq!(
26755            ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
26756            "*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"
26757        );
26758        assert_eq!(
26759            ask(
26760                &mut f,
26761                knn,
26762                &[
26763                    b"GROUPBY",
26764                    b"1",
26765                    b"@t",
26766                    b"REDUCE",
26767                    b"MAX",
26768                    b"1",
26769                    b"@__v_score",
26770                    b"AS",
26771                    b"m"
26772                ]
26773            ),
26774            "*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"
26775        );
26776        assert_eq!(
26777            ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
26778            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
26779             `__v_score`\r\n"
26780        );
26781        assert_eq!(
26782            ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
26783            "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
26784        );
26785        assert_eq!(
26786            ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
26787            "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
26788             schema\r\n"
26789        );
26790    }
26791
26792    /// An aggregation reads every word before it reads the query, and reads the
26793    /// query before it ties anything on the pipeline to a place on the row.
26794    ///
26795    /// So a command with a fault in all three answers the one about the words,
26796    /// a command with a fault in the last two answers the one about the query,
26797    /// and the pipeline speaks last. That is measured, and it is the whole
26798    /// reason the arguments are read twice.
26799    #[test]
26800    fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
26801        let mut f = Fixture::new();
26802        vectored(&mut f);
26803        let ask = |f: &mut Fixture, rest: &[&[u8]]| {
26804            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
26805            args.extend_from_slice(rest);
26806            f.run(&args)
26807        };
26808        assert_eq!(
26809            ask(
26810                &mut f,
26811                &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
26812            ),
26813            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
26814        );
26815        assert_eq!(
26816            ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
26817            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26818        );
26819        assert_eq!(
26820            ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
26821            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26822        );
26823        // An expression that will not read is the pipeline's fault too, so it
26824        // speaks after the query and after a property named before it.
26825        assert_eq!(
26826            ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
26827            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26828        );
26829        assert_eq!(
26830            ask(
26831                &mut f,
26832                &[
26833                    b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
26834                ]
26835            ),
26836            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26837        );
26838        assert_eq!(
26839            ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
26840            "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
26841        );
26842    }
26843
26844    /// A vector clause says which of the ways of answering one it took, and a
26845    /// range says nothing at all when there is no distance to hand back.
26846    #[test]
26847    fn a_vector_step_says_which_way_it_was_answered() {
26848        let mut f = Fixture::new();
26849        vectored(&mut f);
26850        let tree = |f: &mut Fixture, query: &[u8]| {
26851            let reply = timeless(&f.run(&[
26852                b"FT.PROFILE",
26853                b"h",
26854                b"AGGREGATE",
26855                b"QUERY",
26856                query,
26857                b"PARAMS",
26858                b"2",
26859                b"vec",
26860                ORIGIN,
26861                b"DIALECT",
26862                b"2",
26863            ]));
26864            let at = reply.find("+Iterators profile").expect("a tree");
26865            let end = reply.find("+Result processors").expect("a list of steps");
26866            reply[at..end].to_string()
26867        };
26868        assert_eq!(
26869            tree(&mut f, b"*=>[KNN 3 @v $vec]"),
26870            "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26871             +Number of reading operations\r\n:3\r\n\
26872             +Vector search mode\r\n+STANDARD_KNN\r\n"
26873        );
26874        // Renaming the distance changes nothing about how it was answered.
26875        assert_eq!(
26876            tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
26877            tree(&mut f, b"*=>[KNN 3 @v $vec]")
26878        );
26879        // A range with nothing to yield is not a vector step at all, and one
26880        // that yields names the distance in its own type.
26881        assert_eq!(
26882            tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
26883            "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
26884             +Number of reading operations\r\n:4\r\n"
26885        );
26886        assert_eq!(
26887            tree(
26888                &mut f,
26889                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
26890            ),
26891            "+Iterators profile\r\n*8\r\n\
26892             +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
26893             +Number of reading operations\r\n:4\r\n\
26894             +Vector search mode\r\n+RANGE_QUERY\r\n"
26895        );
26896    }
26897
26898    /// What a vector clause narrowed itself down with hangs under it as a
26899    /// single child, and the step that works the distances out is behind the
26900    /// index whenever the query yields one.
26901    #[test]
26902    fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
26903        let mut f = Fixture::new();
26904        vectored(&mut f);
26905        let ask = |f: &mut Fixture, query: &[u8]| {
26906            timeless(&f.run(&[
26907                b"FT.PROFILE",
26908                b"h",
26909                b"AGGREGATE",
26910                b"QUERY",
26911                query,
26912                b"PARAMS",
26913                b"2",
26914                b"vec",
26915                ORIGIN,
26916                b"DIALECT",
26917                b"2",
26918            ]))
26919        };
26920        let cut = |reply: &str| {
26921            let at = reply.find("+Iterators profile").expect("a tree");
26922            reply[at..].to_string()
26923        };
26924        assert_eq!(
26925            cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
26926            "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26927             +Number of reading operations\r\n:3\r\n\
26928             +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
26929             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
26930             +Number of reading operations\r\n:3\r\n\
26931             +Estimated number of matches\r\n:3\r\n\
26932             +Result processors profile\r\n*2\r\n\
26933             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26934             *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
26935             +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
26936        );
26937        // A range nobody named yields nothing, so nothing works a distance out
26938        // and the step is not there.
26939        assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
26940            "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
26941             +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
26942        ));
26943        // A nearest neighbour clause with nothing in front of it yields all
26944        // the same, so the step is there without a child above it.
26945        assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
26946    }
26947
26948    /// A `LIMIT 0 0` on an aggregation is a client asking for the total and
26949    /// nothing else, so the step that would have paged the rows counts them
26950    /// instead, whether or not a `SORTBY` put an order in front of it.
26951    #[test]
26952    fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
26953        let mut f = profiling();
26954        let steps = |f: &mut Fixture, words: &[&[u8]]| {
26955            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
26956            argv.extend_from_slice(words);
26957            let reply = timeless(&f.run(&argv));
26958            let at = reply.find("+Result processors").expect("a list of steps");
26959            reply[at..].to_string()
26960        };
26961        assert_eq!(
26962            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
26963            "+Result processors profile\r\n*2\r\n\
26964             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26965             *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
26966             +Coordinator\r\n*0\r\n"
26967        );
26968        assert!(
26969            steps(
26970                &mut f,
26971                &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
26972            )
26973            .contains("+Type\r\n+Counter\r\n")
26974        );
26975        // A window that keeps something is still a window.
26976        assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
26977            "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
26978             +Results processed\r\n:2\r\n"
26979        ));
26980    }
26981
26982    // ----------------------------------------------------------- spellcheck
26983
26984    /// The score is how many documents hold the suggestion over how many
26985    /// documents there are, and how close the suggestion is to the word does
26986    /// not come into it at all, so the nearer of the two words here is second.
26987    #[test]
26988    fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
26989        let mut f = Fixture::new();
26990        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26991        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26992        f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
26993        assert_eq!(
26994            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
26995            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
26996             *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"
26997        );
26998    }
26999
27000    /// On RESP3 the whole thing is wrapped in a map under one name, a word
27001    /// carries a list of one pair maps, and the score is a double rather than
27002    /// a string.
27003    #[test]
27004    fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
27005        let mut f = Fixture::new();
27006        f.run(&[b"HELLO", b"3"]);
27007        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27008        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27009        assert_eq!(
27010            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
27011            "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
27012             *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
27013        );
27014    }
27015
27016    /// A word the index already holds is not a mistake and is left out of the
27017    /// answer, and that check never looks at the field the query named, while
27018    /// the search for candidates does.
27019    #[test]
27020    fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
27021        let mut f = Fixture::new();
27022        f.run(&[
27023            b"FT.CREATE",
27024            b"e",
27025            b"SCHEMA",
27026            b"a",
27027            b"TEXT",
27028            b"NOSTEM",
27029            b"b",
27030            b"TEXT",
27031            b"NOSTEM",
27032        ]);
27033        f.run(&[b"HSET", b"d1", b"b", b"world"]);
27034        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
27035        assert_eq!(
27036            f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
27037            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
27038        );
27039    }
27040
27041    /// A dictionary named by `INCLUDE` adds words the index never read, scored
27042    /// zero and reported in the spelling the dictionary was given, and one
27043    /// named by `EXCLUDE` says a word is spelled right after all.
27044    #[test]
27045    fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
27046        let mut f = Fixture::new();
27047        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27048        f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
27049        assert_eq!(
27050            f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
27051            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
27052             *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"
27053        );
27054        assert_eq!(
27055            f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
27056            "*0\r\n"
27057        );
27058        assert_eq!(
27059            f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
27060            "-Dict does not exist: nope\r\n"
27061        );
27062    }
27063
27064    /// The first `DISTANCE` counts and the rest are dropped, an argument
27065    /// nobody recognises is stepped over rather than refused, and a distance
27066    /// outside one to four is the one thing here that does fail.
27067    #[test]
27068    fn a_spellcheck_reads_its_arguments_leniently() {
27069        let mut f = Fixture::new();
27070        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27071        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27072        let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27073                   *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
27074        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
27075        let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
27076        let args: &[&[u8]] = &[
27077            b"FT.SPELLCHECK",
27078            b"e",
27079            b"helqp",
27080            b"DISTANCE",
27081            b"1",
27082            b"DISTANCE",
27083            b"4",
27084        ];
27085        assert_eq!(f.run(args), none);
27086        assert_eq!(
27087            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
27088            "-bad distance given, distance must be a natural number between 1 to 4\r\n"
27089        );
27090        assert_eq!(
27091            f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
27092            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
27093        );
27094    }
27095
27096    // -------------------------------------------------------------- suggest
27097
27098    /// The reply is the size of the dictionary afterwards, which is neither
27099    /// what was added nor whether anything changed.
27100    #[test]
27101    fn an_add_answers_how_many_suggestions_are_in_there_now() {
27102        let mut f = Fixture::new();
27103        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
27104        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
27105        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
27106        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
27107        assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
27108    }
27109
27110    /// A suggestion dictionary is the one thing the search module puts in the
27111    /// keyspace, so every keyspace command reaches it.
27112    #[test]
27113    fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
27114        let mut f = Fixture::new();
27115        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27116        assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
27117        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
27118        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
27119        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
27120        assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
27121        assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
27122        assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
27123        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
27124    }
27125
27126    /// The last suggestion out takes the key with it, which most module types
27127    /// do not do.
27128    #[test]
27129    fn deleting_the_last_suggestion_deletes_the_key() {
27130        let mut f = Fixture::new();
27131        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27132        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
27133        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
27134        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
27135        assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
27136    }
27137
27138    /// A key holding anything else is refused rather than overwritten, on all
27139    /// four of them.
27140    #[test]
27141    fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
27142        let mut f = Fixture::new();
27143        f.run(&[b"SET", b"s", b"x"]);
27144        for cmd in [
27145            vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
27146            vec![&b"FT.SUGGET"[..], b"s", b"t"],
27147            vec![&b"FT.SUGDEL"[..], b"s", b"t"],
27148            vec![&b"FT.SUGLEN"[..], b"s"],
27149        ] {
27150            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
27151        }
27152        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
27153    }
27154
27155    /// The scores in here were read off a real server, single precision and
27156    /// all. An exact match answers a sentinel so it sorts in front.
27157    #[test]
27158    fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
27159        let mut f = Fixture::new();
27160        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27161        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27162        f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
27163        assert_eq!(
27164            f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
27165            "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
27166             $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
27167             $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
27168        );
27169        assert_eq!(
27170            f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
27171            "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
27172        );
27173        assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
27174    }
27175
27176    /// `FUZZY` is one edit, and the edit is a rune rather than a byte.
27177    #[test]
27178    fn fuzzy_allows_one_edit_and_nothing_allows_two() {
27179        let mut f = Fixture::new();
27180        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27181        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
27182        assert_eq!(
27183            f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
27184            "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
27185        );
27186        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
27187    }
27188
27189    /// Five without a `MAX`, and the terms come back in score order.
27190    #[test]
27191    fn a_lookup_answers_five_unless_it_is_told_otherwise() {
27192        let mut f = Fixture::new();
27193        for (term, score) in [
27194            (&b"a1"[..], &b"1"[..]),
27195            (b"a2", b"2"),
27196            (b"a3", b"3"),
27197            (b"a4", b"4"),
27198            (b"a5", b"5"),
27199            (b"a6", b"6"),
27200        ] {
27201            f.run(&[b"FT.SUGADD", b"s", term, score]);
27202        }
27203        assert_eq!(
27204            f.run(&[b"FT.SUGGET", b"s", b"a"]),
27205            "*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"
27206        );
27207        assert_eq!(
27208            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
27209            "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
27210        );
27211        // A `MAX` larger than the dictionary answers what there is.
27212        assert!(
27213            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
27214                .starts_with("*6\r\n")
27215        );
27216    }
27217
27218    /// A payload is replaced only when one is given, and an empty one is no
27219    /// payload at all.
27220    #[test]
27221    fn a_payload_comes_back_beside_the_term_or_a_null_does() {
27222        let mut f = Fixture::new();
27223        f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
27224        assert_eq!(
27225            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27226            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27227        );
27228        f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
27229        assert_eq!(
27230            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27231            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27232        );
27233        // An empty payload is the same as not having given one at all, so it
27234        // leaves the payload where it is rather than clearing it.
27235        f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
27236        assert_eq!(
27237            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27238            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27239        );
27240        // A term that never had one answers a null.
27241        f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
27242        assert_eq!(
27243            f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
27244            "*2\r\n$5\r\nother\r\n$-1\r\n"
27245        );
27246    }
27247
27248    /// `INCR` adds to the score that is there rather than replacing it, and
27249    /// three tenths a tenth at a time is the reading that shows the score is
27250    /// held in single precision.
27251    #[test]
27252    fn incr_adds_to_the_score_that_is_already_there() {
27253        let mut f = Fixture::new();
27254        for _ in 0..3 {
27255            f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
27256        }
27257        assert_eq!(
27258            f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
27259            "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
27260        );
27261    }
27262
27263    /// The five error sentences, none of which are written the same way.
27264    #[test]
27265    fn the_suggestion_errors_are_the_lines_the_module_sends() {
27266        let mut f = Fixture::new();
27267        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27268        assert_eq!(
27269            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
27270            "-ERR invalid score\r\n"
27271        );
27272        // The unknown word is complained about before the score is converted.
27273        assert_eq!(
27274            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
27275            "-Unknown argument `NOPE`\r\n"
27276        );
27277        assert_eq!(
27278            f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
27279            "-Invalid payload: Expected an argument, but none provided\r\n"
27280        );
27281        // Too many words is an arity error and not an unknown argument.
27282        assert!(
27283            f.run(&[
27284                b"FT.SUGADD",
27285                b"s",
27286                b"t",
27287                b"1",
27288                b"PAYLOAD",
27289                b"a",
27290                b"PAYLOAD",
27291                b"b"
27292            ])
27293            .contains("wrong number of arguments")
27294        );
27295        assert_eq!(
27296            f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
27297            "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
27298        );
27299        // A count read as a whole number and then found to be out of range,
27300        // against one that had to be read as a double first, where anything
27301        // under one is a conversion that failed rather than a range that did.
27302        for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
27303            assert_eq!(
27304                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27305                "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
27306                "{}",
27307                String::from_utf8_lossy(max)
27308            );
27309        }
27310        for max in [
27311            &b"abc"[..],
27312            b"0.0",
27313            b"00",
27314            b"-0",
27315            b"+0",
27316            b"0.5",
27317            b"-1.5",
27318            b"1e400",
27319        ] {
27320            assert_eq!(
27321                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27322                "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
27323                "{}",
27324                String::from_utf8_lossy(max)
27325            );
27326        }
27327        for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
27328            assert_eq!(
27329                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27330                "*1\r\n$3\r\none\r\n",
27331                "{}",
27332                String::from_utf8_lossy(max)
27333            );
27334        }
27335        assert_eq!(
27336            f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
27337            "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
27338        );
27339        // A score too large for a double is refused where one spelled out is
27340        // taken, which is the module reading errno after the conversion.
27341        assert_eq!(
27342            f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
27343            "-ERR invalid score\r\n"
27344        );
27345        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
27346    }
27347
27348    /// An empty term is taken and not stored, so the reply is the length that
27349    /// was already there and nothing new comes back. The key is still made,
27350    /// and a delete that finds nothing is what clears it away again.
27351    #[test]
27352    fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
27353        let mut f = Fixture::new();
27354        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27355        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
27356        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
27357        assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
27358        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
27359        assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
27360        assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
27361        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
27362    }
27363
27364    /// A key that will not read is counted against the index and against the
27365    /// field, and `FT.INFO` says so.
27366    #[test]
27367    fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
27368        let mut f = Fixture::new();
27369        f.run(&[
27370            b"FT.CREATE",
27371            b"ix",
27372            b"PREFIX",
27373            b"1",
27374            b"p:",
27375            b"SCHEMA",
27376            b"n",
27377            b"NUMERIC",
27378        ]);
27379        f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
27380        assert_eq!(held(&f, b"ix"), (0, 0));
27381
27382        let reply = f.run(&[b"FT.INFO", b"ix"]);
27383        assert!(
27384            reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
27385            "{reply}"
27386        );
27387        assert!(reply.contains("hash_indexing_failures"), "{reply}");
27388    }
27389
27390    /// An index can only be made on database zero, and the check comes after
27391    /// the `IFNX` shortcut and before everything else.
27392    #[test]
27393    fn an_index_can_only_be_made_on_database_zero() {
27394        let mut f = Fixture::new();
27395        f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
27396        f.run(&[b"SELECT", b"1"]);
27397        let refused = "-Cannot create index on db != 0\r\n";
27398        assert_eq!(
27399            f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
27400            refused
27401        );
27402        // The name is taken, and it still answers about the database.
27403        assert_eq!(
27404            f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27405            refused
27406        );
27407        // And so does one whose arguments are nonsense.
27408        assert_eq!(
27409            f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
27410            refused
27411        );
27412        // `IFNX` over a name that is taken is the one that gets through.
27413        assert_eq!(
27414            f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27415            "+OK\r\n"
27416        );
27417        assert_eq!(f.server.search.lock().len(), 1);
27418    }
27419
27420    /// The scan reads the database the create was run on, and after that the
27421    /// index follows its keys in every database.
27422    ///
27423    /// The asymmetry is a real server's, measured, and it is the sort of thing
27424    /// nobody would arrive at by choosing.
27425    #[test]
27426    fn the_scan_is_one_database_and_the_following_is_all_of_them() {
27427        let mut f = Fixture::new();
27428        f.run(&[b"SELECT", b"1"]);
27429        f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
27430        f.run(&[b"SELECT", b"0"]);
27431        f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
27432        f.run(&[
27433            b"FT.CREATE",
27434            b"ix",
27435            b"PREFIX",
27436            b"1",
27437            b"p:",
27438            b"SCHEMA",
27439            b"t",
27440            b"TEXT",
27441        ]);
27442        assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
27443
27444        f.run(&[b"SELECT", b"1"]);
27445        f.run(&[b"HSET", b"p:8", b"t", b"later"]);
27446        assert_eq!(
27447            held(&f, b"ix"),
27448            (2, 2),
27449            "and then it follows every database"
27450        );
27451    }
27452
27453    /// Four documents over the two kinds of field a query can ask about, which
27454    /// is the corpus the searches below read.
27455    fn corpus(f: &mut Fixture) {
27456        f.run(&[
27457            b"FT.CREATE",
27458            b"sx",
27459            b"PREFIX",
27460            b"1",
27461            b"d:",
27462            b"SCHEMA",
27463            b"t",
27464            b"TEXT",
27465            b"g",
27466            b"TAG",
27467            b"n",
27468            b"NUMERIC",
27469        ]);
27470        for (key, text, tag, number) in [
27471            (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
27472            (b"d:2", "alpha gamma", "bb", "2"),
27473            (b"d:3", "delta", "cc", "3"),
27474            (b"d:4", "alpha beta gamma", "aa,cc", "4"),
27475        ] {
27476            f.run(&[
27477                b"HSET",
27478                key,
27479                b"t",
27480                text.as_bytes(),
27481                b"g",
27482                tag.as_bytes(),
27483                b"n",
27484                number.as_bytes(),
27485            ]);
27486        }
27487    }
27488
27489    /// A corpus with something to sort by: a text field the index keeps a copy
27490    /// of, a number, the same text field under another name, and a text field
27491    /// the index keeps nothing of.
27492    fn sortable(f: &mut Fixture) {
27493        f.run(&[
27494            b"FT.CREATE",
27495            b"sy",
27496            b"PREFIX",
27497            b"1",
27498            b"s:",
27499            b"SCHEMA",
27500            b"t",
27501            b"TEXT",
27502            b"SORTABLE",
27503            b"n",
27504            b"NUMERIC",
27505            b"SORTABLE",
27506            b"body",
27507            b"AS",
27508            b"b",
27509            b"TEXT",
27510            b"SORTABLE",
27511            b"p",
27512            b"TEXT",
27513        ]);
27514        for (key, text, number) in [
27515            (b"s:1".as_slice(), "Banana Split", "2"),
27516            (b"s:2", "apple", "10"),
27517        ] {
27518            f.run(&[
27519                b"HSET",
27520                key,
27521                b"t",
27522                text.as_bytes(),
27523                b"n",
27524                number.as_bytes(),
27525                b"body",
27526                text.as_bytes(),
27527                b"p",
27528                b"alpha",
27529            ]);
27530        }
27531        // A key with nothing under either sortable field, which is what sorts
27532        // last whichever way round the sort runs.
27533        f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
27534    }
27535
27536    /// A sort runs off the copy of the value the index keeps, and a row with no
27537    /// value at all is last both ways round.
27538    #[test]
27539    fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
27540        let mut f = Fixture::new();
27541        sortable(&mut f);
27542        assert_eq!(
27543            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
27544            "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27545        );
27546        assert_eq!(
27547            f.run(&[
27548                b"FT.SEARCH",
27549                b"sy",
27550                b"alpha",
27551                b"SORTBY",
27552                b"n",
27553                b"DESC",
27554                b"NOCONTENT"
27555            ]),
27556            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27557        );
27558        // The copy of a text field is folded, so `apple` sorts before
27559        // `Banana Split` where a comparison of the bytes would not.
27560        assert_eq!(
27561            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
27562            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27563        );
27564    }
27565
27566    /// A field the index keeps no copy of is sorted by the value read off the
27567    /// key, which happens after the walk rather than during it.
27568    #[test]
27569    fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
27570        let mut f = Fixture::new();
27571        sortable(&mut f);
27572        f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
27573        assert_eq!(
27574            f.run(&[
27575                b"FT.SEARCH",
27576                b"sy",
27577                b"alpha",
27578                b"SORTBY",
27579                b"p",
27580                b"NOCONTENT",
27581                b"LIMIT",
27582                b"0",
27583                b"2"
27584            ]),
27585            "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27586        );
27587        // Nothing is folded on this side, because the schema never asked for a
27588        // copy to fold, so the value goes into the sort as it was written.
27589        assert_eq!(
27590            f.run(&[
27591                b"FT.SEARCH",
27592                b"sy",
27593                b"alpha",
27594                b"SORTBY",
27595                b"p",
27596                b"WITHSORTKEYS",
27597                b"NOCONTENT",
27598                b"LIMIT",
27599                b"2",
27600                b"1"
27601            ]),
27602            "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
27603        );
27604    }
27605
27606    /// The value the sort compared goes beside every row, as a number after a
27607    /// hash, as text after a dollar, and as a null on a row that had none.
27608    #[test]
27609    fn a_search_can_send_the_value_it_sorted_by_back() {
27610        let mut f = Fixture::new();
27611        sortable(&mut f);
27612        assert_eq!(
27613            f.run(&[
27614                b"FT.SEARCH",
27615                b"sy",
27616                b"alpha",
27617                b"SORTBY",
27618                b"n",
27619                b"WITHSORTKEYS",
27620                b"NOCONTENT"
27621            ]),
27622            concat!(
27623                "*7\r\n:3\r\n",
27624                "$3\r\ns:1\r\n$2\r\n#2\r\n",
27625                "$3\r\ns:2\r\n$3\r\n#10\r\n",
27626                "$3\r\ns:3\r\n$-1\r\n"
27627            )
27628        );
27629        assert_eq!(
27630            f.run(&[
27631                b"FT.SEARCH",
27632                b"sy",
27633                b"alpha",
27634                b"SORTBY",
27635                b"t",
27636                b"WITHSORTKEYS",
27637                b"NOCONTENT"
27638            ]),
27639            concat!(
27640                "*7\r\n:3\r\n",
27641                "$3\r\ns:2\r\n$6\r\n$apple\r\n",
27642                "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
27643                "$3\r\ns:3\r\n$-1\r\n"
27644            )
27645        );
27646        // Asking for a sort key without sorting is taken and answers a null on
27647        // every row, which is what a real server does.
27648        assert_eq!(
27649            f.run(&[
27650                b"FT.SEARCH",
27651                b"sy",
27652                b"banana",
27653                b"WITHSORTKEYS",
27654                b"NOCONTENT"
27655            ]),
27656            "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
27657        );
27658    }
27659
27660    /// The field a search sorted by is written in front of the fields of the
27661    /// key, and the key's own value for it wins when the two share a name.
27662    #[test]
27663    fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
27664        let mut f = Fixture::new();
27665        sortable(&mut f);
27666        // `b` is what the schema calls the field the key calls `body`, so the
27667        // folded copy comes back under one name and the value as it was written
27668        // comes back under the other.
27669        assert_eq!(
27670            f.run(&[
27671                b"FT.SEARCH",
27672                b"sy",
27673                b"alpha",
27674                b"SORTBY",
27675                b"b",
27676                b"LIMIT",
27677                b"0",
27678                b"1"
27679            ]),
27680            concat!(
27681                "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
27682                "$1\r\nb\r\n$5\r\napple\r\n",
27683                "$1\r\nt\r\n$5\r\napple\r\n",
27684                "$1\r\nn\r\n$2\r\n10\r\n",
27685                "$4\r\nbody\r\n$5\r\napple\r\n",
27686                "$1\r\np\r\n$5\r\nalpha\r\n"
27687            )
27688        );
27689        // With a `RETURN` list there is nothing to put in, so the field is moved
27690        // to the front of the names that were asked for instead.
27691        assert_eq!(
27692            f.run(&[
27693                b"FT.SEARCH",
27694                b"sy",
27695                b"alpha",
27696                b"SORTBY",
27697                b"b",
27698                b"RETURN",
27699                b"2",
27700                b"p",
27701                b"b",
27702                b"LIMIT",
27703                b"0",
27704                b"1"
27705            ]),
27706            concat!(
27707                "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
27708                "$1\r\nb\r\n$5\r\napple\r\n",
27709                "$1\r\np\r\n$5\r\nalpha\r\n"
27710            )
27711        );
27712    }
27713
27714    /// The four ways a `SORTBY` on a search is refused.
27715    #[test]
27716    fn a_search_refuses_the_sorts_it_cannot_run() {
27717        let mut f = Fixture::new();
27718        sortable(&mut f);
27719        assert_eq!(
27720            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
27721            "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
27722        );
27723        assert_eq!(
27724            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
27725            "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
27726        );
27727        assert_eq!(
27728            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
27729            "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
27730        );
27731        assert_eq!(
27732            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
27733            "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
27734        );
27735        // The property is looked up once the whole list has read cleanly, so a
27736        // word after it that nobody knows is the error that comes back.
27737        assert_eq!(
27738            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
27739            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
27740        );
27741    }
27742
27743    /// An index over two text fields, a number and a tag, holding one key whose
27744    /// `a` runs long enough to be worth cutting down and whose `b` and `g` hold
27745    /// nothing the query matches.
27746    fn marking(f: &mut Fixture) {
27747        f.run(&[
27748            b"FT.CREATE",
27749            b"mk",
27750            b"ON",
27751            b"HASH",
27752            b"PREFIX",
27753            b"1",
27754            b"m:",
27755            b"SCHEMA",
27756            b"a",
27757            b"TEXT",
27758            b"b",
27759            b"TEXT",
27760            b"n",
27761            b"NUMERIC",
27762            b"g",
27763            b"TAG",
27764        ]);
27765        f.run(&[
27766            b"HSET",
27767            b"m:1",
27768            b"a",
27769            b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
27770            b"b",
27771            b"t1 t2 t3 t4 t5 t6 t7 t8",
27772            b"n",
27773            b"1",
27774            b"g",
27775            b"red",
27776        ]);
27777    }
27778
27779    /// A field the query matched comes back as fragments and a field it did not
27780    /// comes back as its own front.
27781    #[test]
27782    fn a_summarize_cuts_a_field_down_to_what_matched() {
27783        let mut f = Fixture::new();
27784        marking(&mut f);
27785        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
27786        assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
27787        // `b` holds no match, so it keeps its front and loses its last word.
27788        assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
27789        // And so does the tag, which is a value like any other to this clause.
27790        assert!(got.contains("$1\r\nr\r\n"), "{got}");
27791    }
27792
27793    /// `FRAGS` is applied before the context either side of a fragment is worked
27794    /// out, so the fragment that is left runs over the match of the one that was
27795    /// dropped rather than stopping on it.
27796    #[test]
27797    fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
27798        let mut f = Fixture::new();
27799        marking(&mut f);
27800        let got = f.run(&[
27801            b"FT.SEARCH",
27802            b"mk",
27803            b"fox",
27804            b"SUMMARIZE",
27805            b"FRAGS",
27806            b"1",
27807            b"LEN",
27808            b"20",
27809        ]);
27810        assert!(
27811            got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
27812            "{got}"
27813        );
27814        // Keep both and the first stops on the second rather than running over
27815        // it, on the same query and the same budget.
27816        let two = f.run(&[
27817            b"FT.SEARCH",
27818            b"mk",
27819            b"fox",
27820            b"SUMMARIZE",
27821            b"FRAGS",
27822            b"2",
27823            b"LEN",
27824            b"20",
27825        ]);
27826        assert!(
27827            two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
27828            "{two}"
27829        );
27830    }
27831
27832    /// A `HIGHLIGHT` wraps every match, and on a field with no match in it the
27833    /// clause also calls off the cutting down a `SUMMARIZE` would have done.
27834    #[test]
27835    fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
27836        let mut f = Fixture::new();
27837        marking(&mut f);
27838        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
27839        assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
27840        let both = f.run(&[
27841            b"FT.SEARCH",
27842            b"mk",
27843            b"fox",
27844            b"SUMMARIZE",
27845            b"LEN",
27846            b"2",
27847            b"HIGHLIGHT",
27848        ]);
27849        assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
27850        // `b` still holds no match, and this time it comes back whole.
27851        assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
27852        assert!(both.contains("$3\r\nred\r\n"), "{both}");
27853        // Naming a field one clause does not cover leaves it cut down again.
27854        let split = f.run(&[
27855            b"FT.SEARCH",
27856            b"mk",
27857            b"fox",
27858            b"SUMMARIZE",
27859            b"FIELDS",
27860            b"1",
27861            b"b",
27862            b"LEN",
27863            b"2",
27864            b"HIGHLIGHT",
27865            b"FIELDS",
27866            b"1",
27867            b"a",
27868        ]);
27869        assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
27870    }
27871
27872    /// A tag is never marked, in its own field or in a text field beside it.
27873    #[test]
27874    fn a_highlight_does_not_mark_a_tag() {
27875        let mut f = Fixture::new();
27876        marking(&mut f);
27877        f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
27878        let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
27879        assert!(!got.contains("<b>"), "{got}");
27880        assert!(got.contains("red and blue"), "{got}");
27881    }
27882
27883    /// A search answers a total and then a row for every key in the window,
27884    /// with the fields of that key after it.
27885    #[test]
27886    fn a_search_answers_a_total_and_then_the_rows() {
27887        let mut f = Fixture::new();
27888        corpus(&mut f);
27889        assert_eq!(
27890            f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
27891            "*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"
27892        );
27893        // The fields are what the key holds and not what the schema names, so
27894        // a field nobody indexed comes back too.
27895        f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
27896        assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
27897        // `NOCONTENT` leaves the keys on their own, and `LIMIT 0 0` leaves
27898        // the total on its own.
27899        assert_eq!(
27900            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27901            "*2\r\n:1\r\n$3\r\nd:3\r\n"
27902        );
27903        assert_eq!(
27904            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27905            "*1\r\n:3\r\n"
27906        );
27907    }
27908
27909    /// The window is ten rows when nobody said, and the cap is on how wide it
27910    /// is rather than on where it starts.
27911    #[test]
27912    fn the_window_is_ten_rows_and_a_million_wide_at_most() {
27913        let mut f = Fixture::new();
27914        corpus(&mut f);
27915        assert_eq!(
27916            f.run(&[
27917                b"FT.SEARCH",
27918                b"sx",
27919                b"alpha",
27920                b"NOCONTENT",
27921                b"LIMIT",
27922                b"1",
27923                b"1"
27924            ]),
27925            "*2\r\n:3\r\n$3\r\nd:2\r\n"
27926        );
27927        assert_eq!(
27928            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
27929            "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
27930        );
27931        assert_eq!(
27932            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
27933            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27934        );
27935        assert_eq!(
27936            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
27937            "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
27938        );
27939        assert_eq!(
27940            f.run(&[
27941                b"FT.SEARCH",
27942                b"sx",
27943                b"alpha",
27944                b"NOCONTENT",
27945                b"LIMIT",
27946                b"999999",
27947                b"1000000"
27948            ]),
27949            "*1\r\n:3\r\n"
27950        );
27951    }
27952
27953    /// `RETURN 0` reads on the wire like `NOCONTENT` and is not the same
27954    /// thing, because a later `RETURN` puts the fields back and a later
27955    /// `RETURN` after a `NOCONTENT` does not.
27956    #[test]
27957    fn a_return_of_nothing_is_not_the_same_as_nocontent() {
27958        let mut f = Fixture::new();
27959        corpus(&mut f);
27960        let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
27961        assert_eq!(
27962            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
27963            bare
27964        );
27965        assert_eq!(
27966            f.run(&[
27967                b"FT.SEARCH",
27968                b"sx",
27969                b"delta",
27970                b"NOCONTENT",
27971                b"RETURN",
27972                b"1",
27973                b"t"
27974            ]),
27975            bare
27976        );
27977        assert_eq!(
27978            f.run(&[
27979                b"FT.SEARCH",
27980                b"sx",
27981                b"delta",
27982                b"RETURN",
27983                b"0",
27984                b"RETURN",
27985                b"1",
27986                b"t"
27987            ]),
27988            "*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"
27989        );
27990    }
27991
27992    /// The count after `RETURN` counts words and not fields, so the `AS` and
27993    /// the name after it are two of them.
27994    #[test]
27995    fn the_count_after_return_counts_words() {
27996        let mut f = Fixture::new();
27997        corpus(&mut f);
27998        // Two words is one renamed field, and the name is the one it comes
27999        // back under.
28000        assert_eq!(
28001            f.run(&[
28002                b"FT.SEARCH",
28003                b"sx",
28004                b"delta",
28005                b"RETURN",
28006                b"3",
28007                b"t",
28008                b"AS",
28009                b"x"
28010            ]),
28011            "*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"
28012        );
28013        // A count that stops on the `AS` has nothing to rename to, and one
28014        // that reaches past the last word is short an argument.
28015        assert_eq!(
28016            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
28017            "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
28018        );
28019        assert_eq!(
28020            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
28021            "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
28022        );
28023        // A count that stops before the `AS` asks for a field called `AS`,
28024        // which no key holds, and a field the key does not hold is left out
28025        // rather than sent empty.
28026        assert_eq!(
28027            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
28028            "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
28029        );
28030    }
28031
28032    /// A `FILTER` is a numeric range written outside the query, and it is only
28033    /// the wrong way round on a field the schema holds as a number.
28034    #[test]
28035    fn a_filter_is_a_range_written_outside_the_query() {
28036        let mut f = Fixture::new();
28037        corpus(&mut f);
28038        assert_eq!(
28039            f.run(&[
28040                b"FT.SEARCH",
28041                b"sx",
28042                b"alpha",
28043                b"NOCONTENT",
28044                b"FILTER",
28045                b"n",
28046                b"2",
28047                b"4"
28048            ]),
28049            "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
28050        );
28051        assert_eq!(
28052            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
28053            "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
28054        );
28055        assert_eq!(
28056            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
28057            "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
28058        );
28059        assert_eq!(
28060            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
28061            "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
28062        );
28063        // The same range on a field that is not a number at all, and on a
28064        // field that is not there, answers nothing rather than refusing.
28065        for field in [b"g".as_slice(), b"nope"] {
28066            assert_eq!(
28067                f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
28068                "*1\r\n:0\r\n"
28069            );
28070        }
28071    }
28072
28073    /// The index is resolved before the arguments after it are read, so a name
28074    /// that is not there answers about the name whatever else is wrong.
28075    #[test]
28076    fn the_index_is_found_before_the_arguments_are_read() {
28077        let mut f = Fixture::new();
28078        corpus(&mut f);
28079        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
28080        assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
28081        assert_eq!(
28082            f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
28083            missing
28084        );
28085        // And the arguments are read before the query is, so a query that
28086        // will not parse still answers about the argument.
28087        assert_eq!(
28088            f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
28089            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
28090        );
28091    }
28092
28093    /// `INKEYS` filters the answer before the total is taken, which is not
28094    /// where a client would guess it happens.
28095    #[test]
28096    fn inkeys_comes_off_the_total() {
28097        let mut f = Fixture::new();
28098        corpus(&mut f);
28099        assert_eq!(
28100            f.run(&[
28101                b"FT.SEARCH",
28102                b"sx",
28103                b"alpha",
28104                b"NOCONTENT",
28105                b"INKEYS",
28106                b"1",
28107                b"d:1"
28108            ]),
28109            "*2\r\n:1\r\n$3\r\nd:1\r\n"
28110        );
28111        assert_eq!(
28112            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
28113            "*1\r\n:0\r\n"
28114        );
28115    }
28116
28117    /// The fields come from the database the session is on, and a row whose
28118    /// key will not load there is dropped from the reply and taken off the
28119    /// total.
28120    ///
28121    /// Measured against a real server, which follows a key on every database
28122    /// and then loads it from one.
28123    #[test]
28124    fn the_fields_are_read_from_the_session_database() {
28125        let mut f = Fixture::new();
28126        corpus(&mut f);
28127        f.run(&[b"SELECT", b"1"]);
28128        f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
28129        // Both documents are in the index, and only one of them is in this
28130        // database.
28131        assert_eq!(
28132            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28133            "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
28134        );
28135        assert_eq!(
28136            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28137            "*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"
28138        );
28139    }
28140
28141    /// The deeper protocol answers a map of five rather than an array, with
28142    /// every row a map of its own.
28143    #[test]
28144    fn the_third_protocol_answers_a_map_of_five() {
28145        let mut f = Fixture::new();
28146        corpus(&mut f);
28147        f.out = Out::new(Proto::Resp3);
28148        assert_eq!(
28149            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28150            concat!(
28151                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28152                "%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",
28153                "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28154            )
28155        );
28156    }
28157
28158    /// A window of nothing is a client asking for the count on its own, and a
28159    /// window of nothing that starts somewhere else is a contradiction all
28160    /// three commands refuse in the same words.
28161    #[test]
28162    fn a_window_of_nothing_has_to_start_at_the_top() {
28163        let mut f = Fixture::new();
28164        corpus(&mut f);
28165        let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
28166        assert_eq!(
28167            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28168            refused
28169        );
28170        assert_eq!(
28171            f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28172            refused
28173        );
28174        assert_eq!(
28175            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28176            refused
28177        );
28178        assert_eq!(
28179            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28180            "*1\r\n:3\r\n"
28181        );
28182    }
28183
28184    /// An aggregation answers a count and then a list of properties for every
28185    /// row, which is empty until something asks for a field.
28186    #[test]
28187    fn an_aggregation_answers_a_count_and_then_the_properties() {
28188        let mut f = Fixture::new();
28189        corpus(&mut f);
28190        assert_eq!(
28191            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
28192            "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
28193        );
28194        // Every row, and not the ten a search would have cut it down to. The
28195        // count in front of them is one because that is how far the reply had
28196        // got when it was written, which is measured against a real server.
28197        assert_eq!(
28198            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
28199            concat!(
28200                "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28201                "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
28202                "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
28203            )
28204        );
28205        // Ascending document number, because nothing sorts the answer. The
28206        // second and fourth documents are the ones the window lands on and the
28207        // best scoring one is not among them.
28208        assert_eq!(
28209            f.run(&[
28210                b"FT.AGGREGATE",
28211                b"sx",
28212                b"alpha",
28213                b"LOAD",
28214                b"1",
28215                b"@n",
28216                b"LIMIT",
28217                b"1",
28218                b"2"
28219            ]),
28220            "*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"
28221        );
28222        // A query nothing answers is a count of nothing and no rows at all.
28223        assert_eq!(
28224            f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
28225            "*1\r\n:0\r\n"
28226        );
28227    }
28228
28229    /// `LOAD` counts words rather than fields, names the property after the
28230    /// path unless an `AS` renames it, and reads everything the key holds when
28231    /// it is given a star.
28232    #[test]
28233    fn a_load_counts_words_and_can_rename_what_it_reads() {
28234        let mut f = Fixture::new();
28235        corpus(&mut f);
28236        // Three words, which are the path, the `AS` and the name.
28237        assert_eq!(
28238            f.run(&[
28239                b"FT.AGGREGATE",
28240                b"sx",
28241                b"alpha",
28242                b"LOAD",
28243                b"3",
28244                b"@t",
28245                b"AS",
28246                b"text"
28247            ]),
28248            concat!(
28249                "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
28250                "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
28251                "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
28252            )
28253        );
28254        assert_eq!(
28255            f.run(&[
28256                b"FT.AGGREGATE",
28257                b"sx",
28258                b"alpha",
28259                b"LOAD",
28260                b"*",
28261                b"LIMIT",
28262                b"0",
28263                b"1"
28264            ]),
28265            concat!(
28266                "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28267                "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
28268            )
28269        );
28270        // A field the key does not hold is left out rather than sent empty.
28271        assert_eq!(
28272            f.run(&[
28273                b"FT.AGGREGATE",
28274                b"sx",
28275                b"alpha",
28276                b"LOAD",
28277                b"2",
28278                b"@n",
28279                b"@nope",
28280                b"LIMIT",
28281                b"0",
28282                b"2"
28283            ]),
28284            "*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"
28285        );
28286    }
28287
28288    /// The `LOAD` grammar, which has four ways to go wrong and one of them is
28289    /// only reported once the rest of the argument list has read cleanly.
28290    #[test]
28291    fn a_load_refuses_a_count_it_cannot_use() {
28292        let mut f = Fixture::new();
28293        corpus(&mut f);
28294        let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
28295        assert_eq!(
28296            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
28297            format!("{head}Expected number of fields or `*`\r\n")
28298        );
28299        assert_eq!(
28300            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
28301            format!("{head}Value is outside acceptable bounds\r\n")
28302        );
28303        assert_eq!(
28304            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
28305            format!("{head}Expected an argument, but none provided\r\n")
28306        );
28307        assert_eq!(
28308            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
28309            format!("{head}Expected an argument, but none provided\r\n")
28310        );
28311        // A count that runs out on the `AS` is held back, because the word
28312        // after it is read as an argument of its own and may be worth an error
28313        // of its own. Nothing follows here, so the held back line is the one.
28314        assert_eq!(
28315            f.run(&[
28316                b"FT.AGGREGATE",
28317                b"sx",
28318                b"alpha",
28319                b"LOAD",
28320                b"2",
28321                b"@t",
28322                b"AS"
28323            ]),
28324            "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
28325        );
28326        // And here the word after it is one an aggregation stops taking once a
28327        // step has been read, so that is what the client hears about.
28328        assert_eq!(
28329            f.run(&[
28330                b"FT.AGGREGATE",
28331                b"sx",
28332                b"alpha",
28333                b"LOAD",
28334                b"2",
28335                b"@t",
28336                b"AS",
28337                b"VERBATIM"
28338            ]),
28339            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
28340        );
28341        // A `LOAD 0` is a step that names nothing. It shuts the same door
28342        // without becoming a loader, so the count stays the one a query with no
28343        // `LOAD` gets.
28344        assert_eq!(
28345            f.run(&[
28346                b"FT.AGGREGATE",
28347                b"sx",
28348                b"alpha",
28349                b"LOAD",
28350                b"0",
28351                b"LIMIT",
28352                b"0",
28353                b"1"
28354            ]),
28355            "*2\r\n:1\r\n*0\r\n"
28356        );
28357    }
28358
28359    /// Reading a step of the pipeline stops the words about the search itself
28360    /// being taken, and `LIMIT` and `TIMEOUT` are not steps.
28361    #[test]
28362    fn a_pipeline_step_closes_the_door_on_the_search_words() {
28363        let mut f = Fixture::new();
28364        corpus(&mut f);
28365        assert_eq!(
28366            f.run(&[
28367                b"FT.AGGREGATE",
28368                b"sx",
28369                b"alpha",
28370                b"LOAD",
28371                b"1",
28372                b"@t",
28373                b"VERBATIM"
28374            ]),
28375            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
28376        );
28377        assert_eq!(
28378            f.run(&[
28379                b"FT.AGGREGATE",
28380                b"sx",
28381                b"alpha",
28382                b"LIMIT",
28383                b"0",
28384                b"1",
28385                b"VERBATIM"
28386            ]),
28387            "*2\r\n:1\r\n*0\r\n"
28388        );
28389        // Three words a search takes that this command names in its refusal
28390        // rather than calling them unknown.
28391        for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
28392            let name = core::str::from_utf8(word).expect("the three words are text");
28393            assert_eq!(
28394                f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
28395                format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
28396            );
28397        }
28398    }
28399
28400    /// `ADDSCORES` writes the score as a property to twelve significant digits
28401    /// where `WITHSCORES` writes it beside the row in full.
28402    #[test]
28403    fn addscores_writes_a_shorter_score_than_withscores() {
28404        let mut f = Fixture::new();
28405        corpus(&mut f);
28406        assert_eq!(
28407            f.run(&[
28408                b"FT.AGGREGATE",
28409                b"sx",
28410                b"alpha",
28411                b"ADDSCORES",
28412                b"LOAD",
28413                b"1",
28414                b"@n",
28415                b"LIMIT",
28416                b"0",
28417                b"2"
28418            ]),
28419            concat!(
28420                "*3\r\n:1\r\n",
28421                "*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",
28422                "*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"
28423            )
28424        );
28425        // `NOCONTENT` takes the properties away and leaves whatever was asked
28426        // for beside them, and a sort key is always null because nothing sorts
28427        // by one yet.
28428        assert_eq!(
28429            f.run(&[
28430                b"FT.AGGREGATE",
28431                b"sx",
28432                b"alpha",
28433                b"NOCONTENT",
28434                b"WITHSCORES",
28435                b"LIMIT",
28436                b"0",
28437                b"2"
28438            ]),
28439            "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
28440        );
28441        assert_eq!(
28442            f.run(&[
28443                b"FT.AGGREGATE",
28444                b"sx",
28445                b"alpha",
28446                b"WITHSORTKEYS",
28447                b"LOAD",
28448                b"1",
28449                b"@n",
28450                b"LIMIT",
28451                b"0",
28452                b"1"
28453            ]),
28454            "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
28455        );
28456    }
28457
28458    /// The one scorer that has to see the whole answer first turns the count
28459    /// into the real total and hands the rows back backwards.
28460    #[test]
28461    fn a_normalising_scorer_answers_the_rows_backwards() {
28462        let mut f = Fixture::new();
28463        corpus(&mut f);
28464        assert_eq!(
28465            f.run(&[
28466                b"FT.AGGREGATE",
28467                b"sx",
28468                b"alpha",
28469                b"SCORER",
28470                b"BM25STD.NORM",
28471                b"ADDSCORES",
28472                b"LOAD",
28473                b"1",
28474                b"@n",
28475                b"LIMIT",
28476                b"1",
28477                b"2"
28478            ]),
28479            concat!(
28480                "*3\r\n:3\r\n",
28481                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
28482                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
28483            )
28484        );
28485        // Without `ADDSCORES` nothing on the row needs the score, so the rows
28486        // come back the way every other query answers them.
28487        assert_eq!(
28488            f.run(&[
28489                b"FT.AGGREGATE",
28490                b"sx",
28491                b"alpha",
28492                b"SCORER",
28493                b"BM25STD.NORM",
28494                b"LOAD",
28495                b"1",
28496                b"@n",
28497                b"LIMIT",
28498                b"1",
28499                b"2"
28500            ]),
28501            "*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"
28502        );
28503    }
28504
28505    /// The deeper protocol answers the same map of five a search answers, with
28506    /// the `id` gone because an aggregation is about the properties.
28507    #[test]
28508    fn an_aggregation_answers_a_map_of_five_as_well() {
28509        let mut f = Fixture::new();
28510        corpus(&mut f);
28511        f.out = Out::new(Proto::Resp3);
28512        assert_eq!(
28513            f.run(&[
28514                b"FT.AGGREGATE",
28515                b"sx",
28516                b"alpha",
28517                b"ADDSCORES",
28518                b"WITHSCORES",
28519                b"WITHSORTKEYS",
28520                b"LOAD",
28521                b"1",
28522                b"@n",
28523                b"LIMIT",
28524                b"0",
28525                b"1"
28526            ]),
28527            concat!(
28528                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28529                "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
28530                "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
28531                "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
28532                "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28533            )
28534        );
28535        // The count is worked out from the rows the reply reached under this
28536        // protocol, where under RESP2 it is worked out from the first of them.
28537        assert_eq!(
28538            f.run(&[
28539                b"FT.AGGREGATE",
28540                b"sx",
28541                b"alpha",
28542                b"NOCONTENT",
28543                b"LIMIT",
28544                b"0",
28545                b"1"
28546            ]),
28547            concat!(
28548                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28549                "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28550            )
28551        );
28552    }
28553}